briefloop 0.11.12__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 (424) hide show
  1. briefloop-0.11.12.dist-info/METADATA +473 -0
  2. briefloop-0.11.12.dist-info/RECORD +424 -0
  3. briefloop-0.11.12.dist-info/WHEEL +5 -0
  4. briefloop-0.11.12.dist-info/entry_points.txt +3 -0
  5. briefloop-0.11.12.dist-info/licenses/LICENSE +22 -0
  6. briefloop-0.11.12.dist-info/top_level.txt +1 -0
  7. multi_agent_brief/__init__.py +36 -0
  8. multi_agent_brief/analysis_blocks/__init__.py +11 -0
  9. multi_agent_brief/analysis_blocks/builder.py +206 -0
  10. multi_agent_brief/analysis_blocks/renderer.py +237 -0
  11. multi_agent_brief/analysis_blocks/schemas.py +52 -0
  12. multi_agent_brief/analysis_modules/__init__.py +8 -0
  13. multi_agent_brief/analysis_modules/base.py +87 -0
  14. multi_agent_brief/analysis_modules/market_competitor/__init__.py +68 -0
  15. multi_agent_brief/analysis_modules/market_competitor/auditor.py +290 -0
  16. multi_agent_brief/analysis_modules/market_competitor/config.py +187 -0
  17. multi_agent_brief/analysis_modules/market_competitor/event_builder.py +226 -0
  18. multi_agent_brief/analysis_modules/market_competitor/renderer.py +199 -0
  19. multi_agent_brief/analysis_modules/market_competitor/schemas.py +518 -0
  20. multi_agent_brief/analysis_modules/policy_regulatory/__init__.py +7 -0
  21. multi_agent_brief/analysis_modules/policy_regulatory/audit.py +195 -0
  22. multi_agent_brief/analysis_modules/policy_regulatory/module.py +387 -0
  23. multi_agent_brief/analysis_modules/policy_regulatory/schemas.py +129 -0
  24. multi_agent_brief/analysis_modules/registry.py +73 -0
  25. multi_agent_brief/audience/__init__.py +19 -0
  26. multi_agent_brief/audience/profiles.py +246 -0
  27. multi_agent_brief/audience_memory/__init__.py +23 -0
  28. multi_agent_brief/audience_memory/profile.py +358 -0
  29. multi_agent_brief/audit/__init__.py +36 -0
  30. multi_agent_brief/audit/case_applicability.py +181 -0
  31. multi_agent_brief/audit/deterministic.py +471 -0
  32. multi_agent_brief/audit/editorial_governance.py +404 -0
  33. multi_agent_brief/audit/final_quality.py +764 -0
  34. multi_agent_brief/audit/harness.py +242 -0
  35. multi_agent_brief/audit/interfaces.py +96 -0
  36. multi_agent_brief/audit/limitation_hygiene.py +238 -0
  37. multi_agent_brief/audit/proposal_boundary.py +34 -0
  38. multi_agent_brief/audit/redaction.py +27 -0
  39. multi_agent_brief/audit/rule_packs.py +96 -0
  40. multi_agent_brief/audit/semantic.py +435 -0
  41. multi_agent_brief/capabilities/__init__.py +22 -0
  42. multi_agent_brief/capabilities/catalog.py +215 -0
  43. multi_agent_brief/capabilities/detect.py +199 -0
  44. multi_agent_brief/capabilities/models.py +60 -0
  45. multi_agent_brief/capabilities/recommend.py +206 -0
  46. multi_agent_brief/cli/__init__.py +2 -0
  47. multi_agent_brief/cli/analysis_commands.py +140 -0
  48. multi_agent_brief/cli/approval_commands.py +107 -0
  49. multi_agent_brief/cli/audit_commands.py +59 -0
  50. multi_agent_brief/cli/capability_commands.py +548 -0
  51. multi_agent_brief/cli/claude_commands.py +159 -0
  52. multi_agent_brief/cli/competitors_commands.py +182 -0
  53. multi_agent_brief/cli/controls_commands.py +137 -0
  54. multi_agent_brief/cli/deliver_commands.py +942 -0
  55. multi_agent_brief/cli/eval_cases_commands.py +99 -0
  56. multi_agent_brief/cli/experiments_commands.py +395 -0
  57. multi_agent_brief/cli/feedback_commands.py +192 -0
  58. multi_agent_brief/cli/finalize_commands.py +153 -0
  59. multi_agent_brief/cli/gates_commands.py +174 -0
  60. multi_agent_brief/cli/hermes_commands.py +356 -0
  61. multi_agent_brief/cli/improve_commands.py +224 -0
  62. multi_agent_brief/cli/init_commands.py +564 -0
  63. multi_agent_brief/cli/init_wizard.py +1473 -0
  64. multi_agent_brief/cli/input_commands.py +237 -0
  65. multi_agent_brief/cli/main.py +288 -0
  66. multi_agent_brief/cli/onboard_commands.py +193 -0
  67. multi_agent_brief/cli/product_commands.py +1449 -0
  68. multi_agent_brief/cli/provenance_commands.py +100 -0
  69. multi_agent_brief/cli/release_commands.py +68 -0
  70. multi_agent_brief/cli/repair_commands.py +169 -0
  71. multi_agent_brief/cli/run_commands.py +292 -0
  72. multi_agent_brief/cli/runtime_commands.py +81 -0
  73. multi_agent_brief/cli/secrets_commands.py +232 -0
  74. multi_agent_brief/cli/semantic_support_commands.py +80 -0
  75. multi_agent_brief/cli/sources_commands.py +647 -0
  76. multi_agent_brief/cli/start_commands.py +8 -0
  77. multi_agent_brief/cli/state_commands.py +446 -0
  78. multi_agent_brief/cli/status_commands.py +26 -0
  79. multi_agent_brief/cli/workbuddy_commands.py +103 -0
  80. multi_agent_brief/configs/artifact_contracts.yaml +514 -0
  81. multi_agent_brief/configs/orchestrator_contract.yaml +181 -0
  82. multi_agent_brief/configs/policy_packs/default.yaml +65 -0
  83. multi_agent_brief/configs/policy_profiles/evidence_extract_default.yaml +58 -0
  84. multi_agent_brief/configs/policy_profiles/finance_default.yaml +44 -0
  85. multi_agent_brief/configs/policy_profiles/internet_default.yaml +44 -0
  86. multi_agent_brief/configs/policy_profiles/manufacturing_default.yaml +33 -0
  87. multi_agent_brief/configs/policy_profiles/solar_manufacturing_default.yaml +64 -0
  88. multi_agent_brief/configs/report_packs/evidence_extract.yaml +48 -0
  89. multi_agent_brief/configs/report_packs/management_monthly.yaml +36 -0
  90. multi_agent_brief/configs/report_packs/market_weekly.yaml +36 -0
  91. multi_agent_brief/configs/report_packs/solar_industry_periodic.yaml +50 -0
  92. multi_agent_brief/configs/report_templates/evidence_extract.yaml +55 -0
  93. multi_agent_brief/configs/report_templates/management_monthly.yaml +48 -0
  94. multi_agent_brief/configs/report_templates/market_weekly.yaml +49 -0
  95. multi_agent_brief/configs/report_templates/solar_industry_periodic.yaml +75 -0
  96. multi_agent_brief/configs/stage_specs.yaml +180 -0
  97. multi_agent_brief/contracts/__init__.py +41 -0
  98. multi_agent_brief/contracts/base.py +99 -0
  99. multi_agent_brief/contracts/errors.py +41 -0
  100. multi_agent_brief/contracts/migrations/__init__.py +5 -0
  101. multi_agent_brief/contracts/migrations/claim_v1_to_v2.py +33 -0
  102. multi_agent_brief/contracts/registry.py +159 -0
  103. multi_agent_brief/contracts/role_topology.py +25 -0
  104. multi_agent_brief/contracts/schemas/__init__.py +35 -0
  105. multi_agent_brief/contracts/schemas/analysis_pack.py +135 -0
  106. multi_agent_brief/contracts/schemas/atomic_claim_graph.py +304 -0
  107. multi_agent_brief/contracts/schemas/audit_report.py +80 -0
  108. multi_agent_brief/contracts/schemas/candidate_item.py +51 -0
  109. multi_agent_brief/contracts/schemas/claim.py +197 -0
  110. multi_agent_brief/contracts/schemas/claim_draft.py +386 -0
  111. multi_agent_brief/contracts/schemas/claim_support_matrix.py +334 -0
  112. multi_agent_brief/contracts/schemas/evidence_span_registry.py +270 -0
  113. multi_agent_brief/contracts/schemas/policy_profile.py +247 -0
  114. multi_agent_brief/contracts/schemas/report_spec.py +257 -0
  115. multi_agent_brief/contracts/schemas/semantic_assessment_report.py +480 -0
  116. multi_agent_brief/contracts/schemas/source_evidence_pack_manifest.py +233 -0
  117. multi_agent_brief/contracts/schemas/source_item.py +96 -0
  118. multi_agent_brief/contracts/semantic_assessment_status.py +12 -0
  119. multi_agent_brief/contracts/source_metadata.py +252 -0
  120. multi_agent_brief/contracts/target_contract.py +460 -0
  121. multi_agent_brief/contracts/validator.py +225 -0
  122. multi_agent_brief/controls/__init__.py +23 -0
  123. multi_agent_brief/controls/contract.py +106 -0
  124. multi_agent_brief/controls/switchboard.py +1076 -0
  125. multi_agent_brief/core/__init__.py +2 -0
  126. multi_agent_brief/core/citations.py +14 -0
  127. multi_agent_brief/core/claim_ledger.py +94 -0
  128. multi_agent_brief/core/config.py +191 -0
  129. multi_agent_brief/core/env.py +69 -0
  130. multi_agent_brief/core/previous.py +83 -0
  131. multi_agent_brief/core/schemas.py +181 -0
  132. multi_agent_brief/core/selection.py +189 -0
  133. multi_agent_brief/delivery/__init__.py +6 -0
  134. multi_agent_brief/delivery/base.py +36 -0
  135. multi_agent_brief/delivery/feishu.py +204 -0
  136. multi_agent_brief/delivery/gws.py +237 -0
  137. multi_agent_brief/evaluation_cases/__init__.py +15 -0
  138. multi_agent_brief/evaluation_cases/contract.py +429 -0
  139. multi_agent_brief/evaluation_cases/fixtures/cases/approved_guidance_materialized/workspace/config.yaml +18 -0
  140. multi_agent_brief/evaluation_cases/fixtures/cases/approved_guidance_materialized/workspace/improvement/ledger.jsonl +2 -0
  141. multi_agent_brief/evaluation_cases/fixtures/cases/approved_guidance_materialized/workspace/sources.yaml +2 -0
  142. multi_agent_brief/evaluation_cases/fixtures/cases/approved_guidance_materialized/workspace/user.md +4 -0
  143. multi_agent_brief/evaluation_cases/fixtures/cases/company_event_missing_latest_official_check/workspace/config.yaml +13 -0
  144. multi_agent_brief/evaluation_cases/fixtures/cases/company_event_missing_latest_official_check/workspace/sources.yaml +9 -0
  145. multi_agent_brief/evaluation_cases/fixtures/cases/company_event_missing_latest_official_check/workspace/user.md +4 -0
  146. multi_agent_brief/evaluation_cases/fixtures/cases/control_switchboard_selection_is_not_execution/workspace/config.yaml +12 -0
  147. multi_agent_brief/evaluation_cases/fixtures/cases/control_switchboard_selection_is_not_execution/workspace/sources.yaml +6 -0
  148. multi_agent_brief/evaluation_cases/fixtures/cases/control_switchboard_selection_is_not_execution/workspace/user.md +4 -0
  149. multi_agent_brief/evaluation_cases/fixtures/cases/feedback_triage_required/workspace/config.yaml +6 -0
  150. multi_agent_brief/evaluation_cases/fixtures/cases/feedback_triage_required/workspace/input/human_feedback.md +1 -0
  151. multi_agent_brief/evaluation_cases/fixtures/cases/feedback_triage_required/workspace/sources.yaml +2 -0
  152. multi_agent_brief/evaluation_cases/fixtures/cases/feedback_triage_required/workspace/user.md +3 -0
  153. multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/config.yaml +8 -0
  154. multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/output/brief.md +14 -0
  155. multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/output/intermediate/audit_report.json +1 -0
  156. multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/output/intermediate/audited_brief.md +5 -0
  157. multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/output/intermediate/candidate_claims.json +1 -0
  158. multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/output/intermediate/claim_ledger.json +1 -0
  159. multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/output/intermediate/screened_candidates.json +1 -0
  160. multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/sources.yaml +2 -0
  161. multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/user.md +3 -0
  162. multi_agent_brief/evaluation_cases/fixtures/cases/formal_release_missing_human_approval/workspace/config.yaml +13 -0
  163. multi_agent_brief/evaluation_cases/fixtures/cases/formal_release_missing_human_approval/workspace/sources.yaml +9 -0
  164. multi_agent_brief/evaluation_cases/fixtures/cases/formal_release_missing_human_approval/workspace/user.md +4 -0
  165. multi_agent_brief/evaluation_cases/fixtures/cases/guidance_manifestation_not_observable/workspace/config.yaml +4 -0
  166. multi_agent_brief/evaluation_cases/fixtures/cases/guidance_manifestation_not_observable/workspace/sources.yaml +2 -0
  167. multi_agent_brief/evaluation_cases/fixtures/cases/guidance_manifestation_not_observable/workspace/user.md +3 -0
  168. multi_agent_brief/evaluation_cases/fixtures/cases/media_only_legal_policy_blocks_research_review/workspace/config.yaml +13 -0
  169. multi_agent_brief/evaluation_cases/fixtures/cases/media_only_legal_policy_blocks_research_review/workspace/sources.yaml +9 -0
  170. multi_agent_brief/evaluation_cases/fixtures/cases/media_only_legal_policy_blocks_research_review/workspace/user.md +4 -0
  171. multi_agent_brief/evaluation_cases/fixtures/cases/mixed_metric_scope_support_blocker/workspace/config.yaml +13 -0
  172. multi_agent_brief/evaluation_cases/fixtures/cases/mixed_metric_scope_support_blocker/workspace/sources.yaml +9 -0
  173. multi_agent_brief/evaluation_cases/fixtures/cases/mixed_metric_scope_support_blocker/workspace/user.md +5 -0
  174. multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/config.yaml +6 -0
  175. multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/output/intermediate/audited_brief.md +3 -0
  176. multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/output/intermediate/candidate_claims.json +1 -0
  177. multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/output/intermediate/claim_ledger.json +1 -0
  178. multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/output/intermediate/feedback_issues.json +25 -0
  179. multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/output/intermediate/repair_plan.json +27 -0
  180. multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/output/intermediate/screened_candidates.json +1 -0
  181. multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/sources.yaml +2 -0
  182. multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/user.md +3 -0
  183. multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/config.yaml +8 -0
  184. multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/output/intermediate/audit_report.json +1 -0
  185. multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/output/intermediate/audited_brief.md +3 -0
  186. multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/output/intermediate/candidate_claims.json +1 -0
  187. multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/output/intermediate/claim_ledger.json +11 -0
  188. multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/output/intermediate/quality_gate_report.json +22 -0
  189. multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/output/intermediate/screened_candidates.json +1 -0
  190. multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/sources.yaml +2 -0
  191. multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/user.md +3 -0
  192. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/config.yaml +13 -0
  193. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/output/intermediate/audit_report.json +1 -0
  194. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/output/intermediate/audited_brief.md +5 -0
  195. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/output/intermediate/candidate_claims.json +1 -0
  196. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/output/intermediate/claim_ledger.json +40 -0
  197. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/output/intermediate/screened_candidates.json +1 -0
  198. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/sources.yaml +3 -0
  199. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/user.md +3 -0
  200. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/config.yaml +8 -0
  201. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/output/brief.md +3 -0
  202. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/output/intermediate/audit_report.json +1 -0
  203. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/output/intermediate/audited_brief.md +3 -0
  204. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/output/intermediate/candidate_claims.json +1 -0
  205. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/output/intermediate/claim_ledger.json +13 -0
  206. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/output/intermediate/screened_candidates.json +1 -0
  207. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/sources.yaml +2 -0
  208. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/user.md +3 -0
  209. multi_agent_brief/evaluation_cases/fixtures/cases/release_readiness_forged_event_blocker/workspace/config.yaml +13 -0
  210. multi_agent_brief/evaluation_cases/fixtures/cases/release_readiness_forged_event_blocker/workspace/output/intermediate/release_readiness_report.json +31 -0
  211. multi_agent_brief/evaluation_cases/fixtures/cases/release_readiness_forged_event_blocker/workspace/sources.yaml +9 -0
  212. multi_agent_brief/evaluation_cases/fixtures/cases/release_readiness_forged_event_blocker/workspace/user.md +4 -0
  213. multi_agent_brief/evaluation_cases/fixtures/cases/reverted_entry_removed_from_next_snapshot/workspace/config.yaml +18 -0
  214. multi_agent_brief/evaluation_cases/fixtures/cases/reverted_entry_removed_from_next_snapshot/workspace/improvement/ledger.jsonl +3 -0
  215. multi_agent_brief/evaluation_cases/fixtures/cases/reverted_entry_removed_from_next_snapshot/workspace/improvement/memory.md +3 -0
  216. multi_agent_brief/evaluation_cases/fixtures/cases/reverted_entry_removed_from_next_snapshot/workspace/output/intermediate/improvement_memory_snapshot.md +3 -0
  217. multi_agent_brief/evaluation_cases/fixtures/cases/reverted_entry_removed_from_next_snapshot/workspace/sources.yaml +2 -0
  218. multi_agent_brief/evaluation_cases/fixtures/cases/reverted_entry_removed_from_next_snapshot/workspace/user.md +4 -0
  219. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/config.yaml +12 -0
  220. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/input/sources/source-001.txt +1 -0
  221. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/brief.md +37 -0
  222. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/delivery/brief.md +37 -0
  223. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/atomic_claim_graph.json +17 -0
  224. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/audit_report.json +5 -0
  225. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/audited_brief.md +37 -0
  226. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/claim_ledger.json +20 -0
  227. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/evidence_span_registry.json +20 -0
  228. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/finalize_report.json +40 -0
  229. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/gates/auditor_quality_gate_report.json +42 -0
  230. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/gates/finalize_quality_gate_report.json +42 -0
  231. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/screened_candidates.json +33 -0
  232. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/source_appendix.md +12 -0
  233. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/source_appendix_trace.md +10 -0
  234. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/report_spec.yaml +24 -0
  235. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/sources.yaml +4 -0
  236. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/user.md +3 -0
  237. multi_agent_brief/evaluation_cases/fixtures/cases/source_evidence_pack_blocks_non_evidence_file/workspace/config.yaml +13 -0
  238. multi_agent_brief/evaluation_cases/fixtures/cases/source_evidence_pack_blocks_non_evidence_file/workspace/input/sources/README.md +4 -0
  239. multi_agent_brief/evaluation_cases/fixtures/cases/source_evidence_pack_blocks_non_evidence_file/workspace/output/intermediate/source_evidence_pack_manifest.json +26 -0
  240. multi_agent_brief/evaluation_cases/fixtures/cases/source_evidence_pack_blocks_non_evidence_file/workspace/sources.yaml +9 -0
  241. multi_agent_brief/evaluation_cases/fixtures/cases/source_evidence_pack_blocks_non_evidence_file/workspace/user.md +4 -0
  242. multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/config.yaml +9 -0
  243. multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/output/intermediate/audit_report.json +1 -0
  244. multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/output/intermediate/audited_brief.md +3 -0
  245. multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/output/intermediate/candidate_claims.json +1 -0
  246. multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/output/intermediate/claim_ledger.json +13 -0
  247. multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/output/intermediate/screened_candidates.json +1 -0
  248. multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/sources.yaml +2 -0
  249. multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/user.md +3 -0
  250. multi_agent_brief/evaluation_cases/fixtures/cases/third_party_price_snapshot_formal_block/workspace/config.yaml +13 -0
  251. multi_agent_brief/evaluation_cases/fixtures/cases/third_party_price_snapshot_formal_block/workspace/sources.yaml +9 -0
  252. multi_agent_brief/evaluation_cases/fixtures/cases/third_party_price_snapshot_formal_block/workspace/user.md +4 -0
  253. multi_agent_brief/evaluation_cases/fixtures/cases/trajectory_retry_budget_exhausted/workspace/config.yaml +4 -0
  254. multi_agent_brief/evaluation_cases/fixtures/cases/trajectory_retry_budget_exhausted/workspace/sources.yaml +2 -0
  255. multi_agent_brief/evaluation_cases/fixtures/cases/trajectory_retry_budget_exhausted/workspace/user.md +3 -0
  256. multi_agent_brief/evaluation_cases/fixtures/cases/unapproved_entry_not_materialized/workspace/config.yaml +18 -0
  257. multi_agent_brief/evaluation_cases/fixtures/cases/unapproved_entry_not_materialized/workspace/improvement/ledger.jsonl +1 -0
  258. multi_agent_brief/evaluation_cases/fixtures/cases/unapproved_entry_not_materialized/workspace/sources.yaml +2 -0
  259. multi_agent_brief/evaluation_cases/fixtures/cases/unapproved_entry_not_materialized/workspace/user.md +4 -0
  260. multi_agent_brief/evaluation_cases/fixtures/cases/unauthorized_institution_branding_blocks_release/workspace/config.yaml +18 -0
  261. multi_agent_brief/evaluation_cases/fixtures/cases/unauthorized_institution_branding_blocks_release/workspace/sources.yaml +9 -0
  262. multi_agent_brief/evaluation_cases/fixtures/cases/unauthorized_institution_branding_blocks_release/workspace/user.md +5 -0
  263. multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/config.yaml +8 -0
  264. multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/output/intermediate/audit_report.json +1 -0
  265. multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/output/intermediate/audited_brief.md +8 -0
  266. multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/output/intermediate/candidate_claims.json +1 -0
  267. multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/output/intermediate/claim_ledger.json +1 -0
  268. multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/output/intermediate/screened_candidates.json +1 -0
  269. multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/sources.yaml +2 -0
  270. multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/user.md +3 -0
  271. multi_agent_brief/evaluation_cases/fixtures/manifest.yaml +861 -0
  272. multi_agent_brief/evaluation_cases/fixtures.py +20 -0
  273. multi_agent_brief/evaluation_cases/runner.py +1603 -0
  274. multi_agent_brief/experiments/__init__.py +45 -0
  275. multi_agent_brief/experiments/experiment_080.py +5835 -0
  276. multi_agent_brief/experiments/schemas.py +63 -0
  277. multi_agent_brief/experiments/target_contract.py +10 -0
  278. multi_agent_brief/feedback/__init__.py +17 -0
  279. multi_agent_brief/feedback/feedback_contract.py +482 -0
  280. multi_agent_brief/feedback/feedback_state.py +898 -0
  281. multi_agent_brief/hermes/__init__.py +25 -0
  282. multi_agent_brief/hermes/adapter.py +832 -0
  283. multi_agent_brief/improvement/__init__.py +83 -0
  284. multi_agent_brief/improvement/contract.py +1114 -0
  285. multi_agent_brief/improvement/memory.py +451 -0
  286. multi_agent_brief/improvement/product_definition.py +228 -0
  287. multi_agent_brief/improvement/state.py +961 -0
  288. multi_agent_brief/inputs/__init__.py +2 -0
  289. multi_agent_brief/inputs/classifier.py +322 -0
  290. multi_agent_brief/inputs/contracts.py +59 -0
  291. multi_agent_brief/inputs/extractor.py +310 -0
  292. multi_agent_brief/install/__init__.py +2 -0
  293. multi_agent_brief/install/writer.py +97 -0
  294. multi_agent_brief/onboarding/__init__.py +11 -0
  295. multi_agent_brief/onboarding/io.py +103 -0
  296. multi_agent_brief/onboarding/mapper.py +484 -0
  297. multi_agent_brief/onboarding/schema.py +46 -0
  298. multi_agent_brief/orchestrator/__init__.py +2 -0
  299. multi_agent_brief/orchestrator/fact_layer_import.py +352 -0
  300. multi_agent_brief/orchestrator/handoff.py +2209 -0
  301. multi_agent_brief/orchestrator/role_topology.py +42 -0
  302. multi_agent_brief/orchestrator/run_archive.py +888 -0
  303. multi_agent_brief/orchestrator/run_integrity.py +346 -0
  304. multi_agent_brief/orchestrator/runtime_state/__init__.py +70 -0
  305. multi_agent_brief/orchestrator/runtime_state/_io.py +167 -0
  306. multi_agent_brief/orchestrator/runtime_state/_transactions.py +298 -0
  307. multi_agent_brief/orchestrator/runtime_state/artifact_registry.py +1825 -0
  308. multi_agent_brief/orchestrator/runtime_state/atomic_claim_graph.py +96 -0
  309. multi_agent_brief/orchestrator/runtime_state/claim_ledger_freeze.py +510 -0
  310. multi_agent_brief/orchestrator/runtime_state/claim_metadata_enrichment.py +836 -0
  311. multi_agent_brief/orchestrator/runtime_state/claim_support_matrix.py +582 -0
  312. multi_agent_brief/orchestrator/runtime_state/completion_gates.py +672 -0
  313. multi_agent_brief/orchestrator/runtime_state/contracts_loader.py +66 -0
  314. multi_agent_brief/orchestrator/runtime_state/decisions.py +239 -0
  315. multi_agent_brief/orchestrator/runtime_state/errors.py +61 -0
  316. multi_agent_brief/orchestrator/runtime_state/event_log.py +256 -0
  317. multi_agent_brief/orchestrator/runtime_state/evidence_span_registry.py +233 -0
  318. multi_agent_brief/orchestrator/runtime_state/fact_layer.py +755 -0
  319. multi_agent_brief/orchestrator/runtime_state/identity.py +90 -0
  320. multi_agent_brief/orchestrator/runtime_state/lifecycle.py +675 -0
  321. multi_agent_brief/orchestrator/runtime_state/manifest.py +82 -0
  322. multi_agent_brief/orchestrator/runtime_state/operations.py +224 -0
  323. multi_agent_brief/orchestrator/runtime_state/paths.py +38 -0
  324. multi_agent_brief/orchestrator/runtime_state/repair.py +984 -0
  325. multi_agent_brief/orchestrator/runtime_state/safety_surfaces.py +147 -0
  326. multi_agent_brief/orchestrator/runtime_state/semantic_assessment_report.py +692 -0
  327. multi_agent_brief/orchestrator/runtime_state/semantic_support_acceptance.py +683 -0
  328. multi_agent_brief/orchestrator/runtime_state/source_evidence_pack.py +131 -0
  329. multi_agent_brief/orchestrator/runtime_state/stage_completion.py +1320 -0
  330. multi_agent_brief/orchestrator/runtime_state/trajectory.py +200 -0
  331. multi_agent_brief/orchestrator/runtime_state/workflow.py +350 -0
  332. multi_agent_brief/orchestrator/source_evidence.py +43 -0
  333. multi_agent_brief/orchestrator/timing.py +410 -0
  334. multi_agent_brief/orchestrator_contract.py +96 -0
  335. multi_agent_brief/outputs/__init__.py +2 -0
  336. multi_agent_brief/outputs/atomic_reader_projection.py +288 -0
  337. multi_agent_brief/outputs/docx.py +32 -0
  338. multi_agent_brief/outputs/finalize.py +1036 -0
  339. multi_agent_brief/outputs/ib_docx.py +983 -0
  340. multi_agent_brief/outputs/naming.py +38 -0
  341. multi_agent_brief/outputs/pdf.py +20 -0
  342. multi_agent_brief/outputs/reader_final_gate.py +509 -0
  343. multi_agent_brief/outputs/reader_projection.py +397 -0
  344. multi_agent_brief/outputs/source_appendix.py +753 -0
  345. multi_agent_brief/outputs/templates/__init__.py +112 -0
  346. multi_agent_brief/product/__init__.py +49 -0
  347. multi_agent_brief/product/bundle_projection.py +686 -0
  348. multi_agent_brief/product/citation_profile.py +145 -0
  349. multi_agent_brief/product/guidance_manifestation.py +421 -0
  350. multi_agent_brief/product/materiality_selection.py +472 -0
  351. multi_agent_brief/product/policy_gate_adapter.py +116 -0
  352. multi_agent_brief/product/policy_profile.py +39 -0
  353. multi_agent_brief/product/policy_projection.py +161 -0
  354. multi_agent_brief/product/policy_registry.py +83 -0
  355. multi_agent_brief/product/policy_resolver.py +225 -0
  356. multi_agent_brief/product/quality_closeout.py +188 -0
  357. multi_agent_brief/product/quality_panel.py +2217 -0
  358. multi_agent_brief/product/release_approval.py +1034 -0
  359. multi_agent_brief/product/report_pack.py +130 -0
  360. multi_agent_brief/product/report_pack_aliases.py +68 -0
  361. multi_agent_brief/product/report_registry.py +101 -0
  362. multi_agent_brief/product/report_spec.py +181 -0
  363. multi_agent_brief/product/support_wording.py +525 -0
  364. multi_agent_brief/product/template_conformance.py +516 -0
  365. multi_agent_brief/product/template_projection.py +123 -0
  366. multi_agent_brief/product/template_registry.py +244 -0
  367. multi_agent_brief/product/template_render_plan.py +315 -0
  368. multi_agent_brief/product/template_renderer.py +224 -0
  369. multi_agent_brief/product/trajectory_regulation.py +441 -0
  370. multi_agent_brief/provenance/__init__.py +5 -0
  371. multi_agent_brief/provenance/builder.py +841 -0
  372. multi_agent_brief/provenance/contract.py +21 -0
  373. multi_agent_brief/provenance/io.py +144 -0
  374. multi_agent_brief/provenance/model.py +110 -0
  375. multi_agent_brief/provenance/references.py +43 -0
  376. multi_agent_brief/provenance/validator.py +135 -0
  377. multi_agent_brief/quality_gates/__init__.py +19 -0
  378. multi_agent_brief/quality_gates/contract.py +581 -0
  379. multi_agent_brief/quality_gates/state.py +2498 -0
  380. multi_agent_brief/repair/__init__.py +5 -0
  381. multi_agent_brief/repair/router.py +842 -0
  382. multi_agent_brief/runtime_assets.py +431 -0
  383. multi_agent_brief/sources/__init__.py +16 -0
  384. multi_agent_brief/sources/api_filings.py +236 -0
  385. multi_agent_brief/sources/api_news.py +174 -0
  386. multi_agent_brief/sources/base.py +142 -0
  387. multi_agent_brief/sources/cached_package.py +119 -0
  388. multi_agent_brief/sources/cli_provider.py +263 -0
  389. multi_agent_brief/sources/coverage.py +336 -0
  390. multi_agent_brief/sources/decider.py +756 -0
  391. multi_agent_brief/sources/doctor.py +373 -0
  392. multi_agent_brief/sources/evidence_pack.py +364 -0
  393. multi_agent_brief/sources/feishu_provider.py +326 -0
  394. multi_agent_brief/sources/filing_resolver.py +400 -0
  395. multi_agent_brief/sources/industry_packs.py +130 -0
  396. multi_agent_brief/sources/join.py +206 -0
  397. multi_agent_brief/sources/local_signal.py +81 -0
  398. multi_agent_brief/sources/local_signal_planner.py +636 -0
  399. multi_agent_brief/sources/manual.py +259 -0
  400. multi_agent_brief/sources/mcp_provider.py +309 -0
  401. multi_agent_brief/sources/mineru_provider.py +601 -0
  402. multi_agent_brief/sources/normalizer.py +95 -0
  403. multi_agent_brief/sources/opencli_provider.py +282 -0
  404. multi_agent_brief/sources/planner.py +178 -0
  405. multi_agent_brief/sources/registry.py +367 -0
  406. multi_agent_brief/sources/rss.py +190 -0
  407. multi_agent_brief/sources/search_backends/__init__.py +31 -0
  408. multi_agent_brief/sources/search_backends/base.py +51 -0
  409. multi_agent_brief/sources/search_backends/brave.py +192 -0
  410. multi_agent_brief/sources/search_backends/capabilities.py +94 -0
  411. multi_agent_brief/sources/search_backends/exa.py +180 -0
  412. multi_agent_brief/sources/search_backends/firecrawl.py +164 -0
  413. multi_agent_brief/sources/search_backends/serper.py +241 -0
  414. multi_agent_brief/sources/search_backends/tavily.py +124 -0
  415. multi_agent_brief/sources/sourcehub.py +505 -0
  416. multi_agent_brief/sources/web_search.py +267 -0
  417. multi_agent_brief/status.py +966 -0
  418. multi_agent_brief/tools/__init__.py +3 -0
  419. multi_agent_brief/tools/draft_cleanup.py +198 -0
  420. multi_agent_brief/workbuddy/__init__.py +1 -0
  421. multi_agent_brief/workbuddy/diagnose.py +569 -0
  422. multi_agent_brief/workbuddy/skill_pack.py +309 -0
  423. multi_agent_brief/workspace/__init__.py +1 -0
  424. multi_agent_brief/workspace/init_profile.py +45 -0
@@ -0,0 +1,2 @@
1
+ """Core pipeline primitives."""
2
+
@@ -0,0 +1,14 @@
1
+ """Shared helpers for internal claim citation markers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+
7
+
8
+ CLAIM_ID_RE_FRAGMENT = r"[A-Za-z0-9][A-Za-z0-9_-]{1,127}"
9
+ SRC_REF_PATTERN = re.compile(rf"\[src:({CLAIM_ID_RE_FRAGMENT})\]")
10
+ VALID_SRC_REF_PATTERN = re.compile(rf"\[src:{CLAIM_ID_RE_FRAGMENT}\]")
11
+
12
+
13
+ def extract_src_ref_ids(markdown: str) -> list[str]:
14
+ return SRC_REF_PATTERN.findall(markdown)
@@ -0,0 +1,94 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import json
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from multi_agent_brief.core.schemas import Claim
9
+
10
+
11
+ class ClaimLedger:
12
+ """In-memory claim store with validation helpers."""
13
+
14
+ def __init__(self, claims: list[Claim] | None = None) -> None:
15
+ self._claims: dict[str, Claim] = {}
16
+ for claim in claims or []:
17
+ self.add_claim(claim)
18
+
19
+ def __len__(self) -> int:
20
+ return len(self._claims)
21
+
22
+ def __iter__(self):
23
+ return iter(self._claims.values())
24
+
25
+ @staticmethod
26
+ def build_claim_id(statement: str, source_id: str) -> str:
27
+ digest = hashlib.sha1(f"{source_id}|{statement}".encode("utf-8")).hexdigest()[:10]
28
+ prefix = "".join(ch for ch in source_id.upper() if ch.isalnum())[:8] or "CLAIM"
29
+ return f"{prefix}_{digest.upper()}"
30
+
31
+ def add_claim(self, claim: Claim) -> Claim:
32
+ if not claim.claim_id:
33
+ claim.claim_id = self.build_claim_id(claim.statement, claim.source_id)
34
+ self._claims[claim.claim_id] = claim
35
+ return claim
36
+
37
+ def get_claim(self, claim_id: str) -> Claim | None:
38
+ return self._claims.get(claim_id)
39
+
40
+ def to_list(self) -> list[dict]:
41
+ return [claim.to_dict() for claim in self._claims.values()]
42
+
43
+ def export_json(self, path: str | Path) -> None:
44
+ Path(path).write_text(json.dumps(self.to_list(), ensure_ascii=False, indent=2), encoding="utf-8")
45
+
46
+ @classmethod
47
+ def import_json(cls, path: str | Path) -> "ClaimLedger":
48
+ data = json.loads(Path(path).read_text(encoding="utf-8"))
49
+ claims_data = cls._claim_items_from_json(data)
50
+ return cls([Claim.from_dict(item) for item in claims_data])
51
+
52
+ @staticmethod
53
+ def _claim_items_from_json(data: Any) -> list[dict[str, Any]]:
54
+ if isinstance(data, list):
55
+ claims = data
56
+ elif isinstance(data, dict):
57
+ claims = None
58
+ for key in ("claims", "claim_ledger", "items"):
59
+ value = data.get(key)
60
+ if isinstance(value, list):
61
+ claims = value
62
+ break
63
+ if claims is None:
64
+ raise ValueError("Claim Ledger JSON object must contain a claims list.")
65
+ else:
66
+ raise ValueError("Claim Ledger JSON must be a list or an object containing a claims list.")
67
+ if not all(isinstance(item, dict) for item in claims):
68
+ raise ValueError("Claim Ledger claims must be JSON objects.")
69
+ return claims
70
+
71
+ def detect_missing_sources(self) -> list[Claim]:
72
+ return [
73
+ claim
74
+ for claim in self._claims.values()
75
+ if not claim.source_id or not claim.evidence_text.strip()
76
+ ]
77
+
78
+ def detect_duplicate_claims(self) -> list[list[Claim]]:
79
+ buckets: dict[str, list[Claim]] = {}
80
+ for claim in self._claims.values():
81
+ normalized = " ".join(claim.statement.lower().split())
82
+ buckets.setdefault(normalized, []).append(claim)
83
+ return [claims for claims in buckets.values() if len(claims) > 1]
84
+
85
+ def validate_claims(self) -> list[str]:
86
+ errors: list[str] = []
87
+ for claim in self._claims.values():
88
+ if not claim.statement.strip():
89
+ errors.append(f"{claim.claim_id}: missing statement")
90
+ if claim.requires_audit and not claim.evidence_text.strip():
91
+ errors.append(f"{claim.claim_id}: missing evidence_text")
92
+ if not claim.source_id.strip():
93
+ errors.append(f"{claim.claim_id}: missing source_id")
94
+ return errors
@@ -0,0 +1,191 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+ from multi_agent_brief.outputs.naming import DEFAULT_FILENAME_TEMPLATE
7
+
8
+ try:
9
+ import yaml
10
+ except ModuleNotFoundError: # pragma: no cover - exercised when PyYAML is not installed
11
+ yaml = None
12
+
13
+
14
+ def _parse_scalar(value: str) -> Any:
15
+ value = value.strip()
16
+ if value in {"true", "false"}:
17
+ return value == "true"
18
+ if (value.startswith('"') and value.endswith('"')) or (value.startswith("'") and value.endswith("'")):
19
+ return value[1:-1]
20
+ return value
21
+
22
+
23
+ def _minimal_yaml_load(text: str) -> dict[str, Any]:
24
+ """Small fallback parser for the simple config files used by the MVP."""
25
+ data: dict[str, Any] = {}
26
+ current_section: dict[str, Any] | None = None
27
+
28
+ for raw_line in text.splitlines():
29
+ if not raw_line.strip() or raw_line.lstrip().startswith("#"):
30
+ continue
31
+ indent = len(raw_line) - len(raw_line.lstrip(" "))
32
+ line = raw_line.strip()
33
+ if indent == 0 and line.endswith(":"):
34
+ section_name = line[:-1]
35
+ current_section = {}
36
+ data[section_name] = current_section
37
+ continue
38
+ if ":" not in line:
39
+ continue
40
+ key, value = line.split(":", 1)
41
+ target = current_section if indent > 0 and current_section is not None else data
42
+ target[key.strip()] = _parse_scalar(value)
43
+ return data
44
+
45
+
46
+ def load_config(path: str | Path) -> dict[str, Any]:
47
+ config_path = Path(path)
48
+ if not config_path.exists():
49
+ raise FileNotFoundError(f"Config file not found: {config_path}")
50
+ config_text = config_path.read_text(encoding="utf-8")
51
+ data = yaml.safe_load(config_text) if yaml is not None else _minimal_yaml_load(config_text)
52
+ data = data or {}
53
+ if not isinstance(data, dict):
54
+ raise ValueError(f"Config must be a mapping: {config_path}")
55
+ data = normalize_language_config(data)
56
+ data["_config_dir"] = str(config_path.parent)
57
+ return data
58
+
59
+
60
+ def normalize_language_config(config: dict[str, Any]) -> dict[str, Any]:
61
+ project = config.get("project", {}) or {}
62
+ language_config = config.get("language", {}) or {}
63
+ if not isinstance(language_config, dict):
64
+ language_config = {}
65
+
66
+ output_language = language_config.get("output") or project.get("language") or "zh-CN"
67
+ interface_language = language_config.get("interface") or output_language
68
+ source_handling = language_config.get("source_handling") or "preserve_original"
69
+
70
+ config["language"] = {
71
+ **language_config,
72
+ "interface": interface_language,
73
+ "output": output_language,
74
+ "source_handling": source_handling,
75
+ }
76
+ return config
77
+
78
+
79
+ def get_output_config(config: dict[str, Any] | None) -> dict[str, Any]:
80
+ """Return canonical output config, with legacy outputs alias support."""
81
+ config = config or {}
82
+ output_config = config.get("output")
83
+ if isinstance(output_config, dict):
84
+ return output_config
85
+ legacy_output_config = config.get("outputs")
86
+ if isinstance(legacy_output_config, dict):
87
+ return legacy_output_config
88
+ return {}
89
+
90
+
91
+ def _as_bool(value: Any, default: bool = False) -> bool:
92
+ if value is None:
93
+ return default
94
+ if isinstance(value, bool):
95
+ return value
96
+ if isinstance(value, str):
97
+ return value.strip().lower() in {"1", "true", "yes", "y", "on"}
98
+ return bool(value)
99
+
100
+
101
+ def build_run_settings(
102
+ *,
103
+ config: dict[str, Any] | None,
104
+ input_dir: str | None,
105
+ output_dir: str | None,
106
+ name: str | None,
107
+ language: str | None,
108
+ audience: str | None,
109
+ ) -> dict[str, Any]:
110
+ config = config or {}
111
+ project = config.get("project", {}) or {}
112
+ language_config = config.get("language", {}) or {}
113
+ report = config.get("report", {}) or {}
114
+ previous = config.get("previous", {}) or {}
115
+ selection = config.get("selection", {}) or {}
116
+
117
+ # Resolve report.date: "auto" → today's date
118
+ raw_date = report.get("date", "")
119
+ if raw_date == "auto":
120
+ from datetime import date
121
+ raw_date = date.today().isoformat()
122
+ selector = config.get("selector", {}) or {}
123
+ input_config = config.get("input", {}) or {}
124
+ output_config = get_output_config(config)
125
+ config_dir = Path(config.get("_config_dir", "."))
126
+
127
+ resolved_input = input_dir or input_config.get("path")
128
+ if not resolved_input:
129
+ raise ValueError("Input directory is required. Pass input_dir or set input.path in config.")
130
+
131
+ input_path = Path(str(resolved_input))
132
+ if input_dir is None and not input_path.is_absolute():
133
+ input_path = config_dir / input_path
134
+
135
+ resolved_output = output_dir or output_config.get("path") or "output/demo"
136
+ output_path = Path(str(resolved_output))
137
+ if output_dir is None and output_config.get("path") and not output_path.is_absolute():
138
+ output_path = config_dir / output_path
139
+
140
+ project_name = name or project.get("name") or "Weekly Intelligence Brief"
141
+ resolved_language = language or language_config.get("output") or project.get("language") or "zh-CN"
142
+ resolved_audience = audience or project.get("audience") or "management"
143
+
144
+ # Resolve audience profile from config or map from audience text
145
+ audience_profile_config = config.get("audience_profile", {}) or {}
146
+ resolved_audience_profile = audience_profile_config.get("id", "")
147
+ if not resolved_audience_profile:
148
+ from multi_agent_brief.audience.profiles import map_audience_to_profile
149
+ resolved_audience_profile = map_audience_to_profile(resolved_audience)
150
+
151
+ output_filename_template = (
152
+ output_config.get("filename_template")
153
+ or output_config.get("file_name_template")
154
+ or output_config.get("name_template")
155
+ or output_config.get("filename")
156
+ or DEFAULT_FILENAME_TEMPLATE
157
+ )
158
+ output_filename_tokens = {
159
+ "project_name": str(project_name),
160
+ "title": str(project_name),
161
+ "company": str(project.get("company", "")),
162
+ "industry": str(project.get("industry", "")),
163
+ "audience": str(resolved_audience),
164
+ "cadence": str(report.get("cadence", "")),
165
+ "report_date": str(raw_date),
166
+ "date": str(raw_date),
167
+ "language": str(resolved_language),
168
+ }
169
+
170
+ return {
171
+ "project_name": project_name,
172
+ "input_dir": str(input_path),
173
+ "output_dir": str(output_path),
174
+ "language": resolved_language,
175
+ "audience": resolved_audience,
176
+ "audience_profile": resolved_audience_profile,
177
+ "report_date": str(raw_date),
178
+ "max_source_age_days": int(report["max_source_age_days"]) if "max_source_age_days" in report else None,
179
+ "fail_on_stale_source": _as_bool(report.get("fail_on_stale_source"), False),
180
+ "previous_report_dir": str(previous.get("path", "")),
181
+ "max_claims": (
182
+ int(selector["max_items"]) if selector.get("max_items") is not None
183
+ else int(selection.get("max_claims", 160))
184
+ ),
185
+ "quiet_week_min_claims": int(selection.get("quiet_week_min_claims", 5)),
186
+ "output_formats": output_config.get("formats", ["markdown"]),
187
+ "output_footer": output_config.get("footer", ""),
188
+ "output_filename_template": str(output_filename_template),
189
+ "output_filename_tokens": output_filename_tokens,
190
+ "output_named_outputs": _as_bool(output_config.get("named_outputs"), True),
191
+ }
@@ -0,0 +1,69 @@
1
+ """Safe workspace environment helpers."""
2
+ from __future__ import annotations
3
+
4
+ import os
5
+ from pathlib import Path
6
+
7
+
8
+ KNOWN_WORKSPACE_ENV_KEYS = frozenset(
9
+ {
10
+ "TAVILY_API_KEY",
11
+ "EXA_API_KEY",
12
+ "BRAVE_SEARCH_API_KEY",
13
+ "FIRECRAWL_API_KEY",
14
+ "SERPER_API_KEY",
15
+ "NEWSAPI_API_KEY",
16
+ "MINERU_API_TOKEN",
17
+ }
18
+ )
19
+
20
+
21
+ def _parse_env_line(line: str) -> tuple[str, str] | None:
22
+ text = line.strip()
23
+ if not text or text.startswith("#") or "=" not in text:
24
+ return None
25
+ if text.startswith("export "):
26
+ text = text[len("export "):].lstrip()
27
+ key, value = text.split("=", 1)
28
+ key = key.strip()
29
+ if key not in KNOWN_WORKSPACE_ENV_KEYS:
30
+ return None
31
+ value = value.strip()
32
+ if (
33
+ len(value) >= 2
34
+ and value[0] == value[-1]
35
+ and value[0] in {"'", '"'}
36
+ ):
37
+ value = value[1:-1]
38
+ return key, value
39
+
40
+
41
+ def read_workspace_env_key(workspace_dir: str | Path | None, key: str) -> str:
42
+ """Return a known env key from workspace .env without exporting it."""
43
+ if key not in KNOWN_WORKSPACE_ENV_KEYS or not workspace_dir:
44
+ return ""
45
+ path = Path(workspace_dir) / ".env"
46
+ try:
47
+ lines = path.read_text(encoding="utf-8").splitlines()
48
+ except OSError:
49
+ return ""
50
+ for line in lines:
51
+ parsed = _parse_env_line(line)
52
+ if not parsed:
53
+ continue
54
+ parsed_key, value = parsed
55
+ if parsed_key == key and value:
56
+ return value
57
+ return ""
58
+
59
+
60
+ def get_known_env_value(key: str, workspace_dir: str | Path | None = None) -> str:
61
+ """Return shell env value first, then known workspace .env value."""
62
+ if key not in KNOWN_WORKSPACE_ENV_KEYS:
63
+ return ""
64
+ return os.environ.get(key, "") or read_workspace_env_key(workspace_dir, key)
65
+
66
+
67
+ def known_env_key_is_set(key: str, workspace_dir: str | Path | None = None) -> bool:
68
+ """Return whether a known key is set without exposing its value."""
69
+ return bool(get_known_env_value(key, workspace_dir))
@@ -0,0 +1,83 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ import zipfile
5
+ import xml.etree.ElementTree as ET
6
+ from datetime import datetime
7
+ from pathlib import Path
8
+
9
+
10
+ def load_previous_report_baseline(directory: str | Path, report_date: str = "", limit: int = 5) -> tuple[str, list[str]]:
11
+ """Load previous md/txt/docx reports as a deduplication baseline."""
12
+ root = Path(directory)
13
+ if not directory or not root.exists():
14
+ return "", []
15
+
16
+ current = _parse_report_date(report_date)
17
+ candidates = [
18
+ path
19
+ for path in root.iterdir()
20
+ if path.is_file() and path.suffix.lower() in {".md", ".txt", ".docx"}
21
+ ]
22
+ eligible = []
23
+ for path in candidates:
24
+ file_date = _date_from_name(path)
25
+ if current and file_date and file_date >= current:
26
+ continue
27
+ eligible.append(path)
28
+
29
+ eligible.sort(key=lambda path: (_date_from_name(path) or datetime.min.date(), path.stat().st_mtime), reverse=True)
30
+
31
+ parts: list[str] = []
32
+ names: list[str] = []
33
+ for path in eligible[:limit]:
34
+ text = _read_report_text(path).strip()
35
+ if not text:
36
+ continue
37
+ names.append(path.name)
38
+ parts.append(f"===== {path.name} =====\n{text}")
39
+ return "\n\n".join(parts), names
40
+
41
+
42
+ def _parse_report_date(value: str):
43
+ if not value:
44
+ return None
45
+ for fmt in ("%Y%m%d", "%Y-%m-%d"):
46
+ try:
47
+ return datetime.strptime(value, fmt).date()
48
+ except ValueError:
49
+ continue
50
+ return None
51
+
52
+
53
+ def _date_from_name(path: Path):
54
+ match = re.search(r"(20\d{6})", path.name)
55
+ if not match:
56
+ return None
57
+ return _parse_report_date(match.group(1))
58
+
59
+
60
+ def _read_report_text(path: Path) -> str:
61
+ if path.suffix.lower() == ".docx":
62
+ return _extract_docx_text(path)
63
+ try:
64
+ return path.read_text(encoding="utf-8")
65
+ except UnicodeDecodeError:
66
+ return path.read_text(errors="ignore")
67
+
68
+
69
+ def _extract_docx_text(path: Path) -> str:
70
+ try:
71
+ with zipfile.ZipFile(path) as zf:
72
+ xml = zf.read("word/document.xml")
73
+ except Exception:
74
+ return ""
75
+ root = ET.fromstring(xml)
76
+ ns = {"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"}
77
+ lines = []
78
+ for paragraph in root.findall(".//w:p", ns):
79
+ texts = [node.text for node in paragraph.findall(".//w:t", ns) if node.text]
80
+ if texts:
81
+ lines.append("".join(texts))
82
+ return "\n".join(lines)
83
+
@@ -0,0 +1,181 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import asdict, dataclass, field
4
+ from datetime import datetime, timezone
5
+ from typing import Any, Literal
6
+
7
+ # Re-export SourceItem from the canonical location (sources.base)
8
+ from multi_agent_brief.sources.base import SourceItem # noqa: F401
9
+
10
+
11
+ ClaimType = Literal["fact", "number", "date", "interpretation", "forecast", "risk"]
12
+ Confidence = Literal["low", "medium", "high"]
13
+ AuditSeverity = Literal["low", "medium", "high"]
14
+ AuditStatus = Literal["pass", "warning", "fail"]
15
+
16
+ # v2 epistemic types
17
+ SchemaVersion = Literal["v1", "v2"]
18
+ EpistemicType = Literal["observed", "interpreted", "hypothesis", "action", "analogy"]
19
+ EvidenceRelation = Literal["direct", "indirect", "inferred", "analogous"]
20
+
21
+
22
+ def utc_now_iso() -> str:
23
+ return datetime.now(timezone.utc).replace(microsecond=0).isoformat()
24
+
25
+
26
+ @dataclass
27
+ class CandidateItem:
28
+ item_id: str
29
+ title: str
30
+ summary: str
31
+ source_id: str
32
+ topic: str = "general"
33
+ importance: str = "medium"
34
+ reason_for_inclusion: str = ""
35
+
36
+ def to_dict(self) -> dict[str, Any]:
37
+ return asdict(self)
38
+
39
+
40
+ @dataclass
41
+ class Claim:
42
+ claim_id: str
43
+ statement: str
44
+ source_id: str
45
+ evidence_text: str
46
+ source_url: str = ""
47
+ source_type: str = "local_file"
48
+ claim_type: ClaimType = "fact"
49
+ confidence: Confidence = "medium"
50
+ requires_audit: bool = True
51
+ created_by: str = "scout"
52
+ used_in_sections: list[str] = field(default_factory=list)
53
+ metadata: dict[str, Any] = field(default_factory=dict)
54
+ # v2 epistemic fields — new Claims default to v2
55
+ schema_version: SchemaVersion = "v2"
56
+ epistemic_type: EpistemicType = "observed"
57
+ evidence_relation: EvidenceRelation = "direct"
58
+ applicability_reason: str = ""
59
+ limitations: list[str] = field(default_factory=list)
60
+
61
+ def to_dict(self) -> dict[str, Any]:
62
+ return asdict(self)
63
+
64
+ @classmethod
65
+ def from_dict(cls, data: dict[str, Any]) -> "Claim":
66
+ known = {f.name for f in cls.__dataclass_fields__.values()}
67
+ filtered = {k: v for k, v in data.items() if k in known}
68
+ claim = cls(**filtered)
69
+ # v1→v2 migration: fill missing epistemic fields from claim_type
70
+ if "epistemic_type" not in data:
71
+ claim.epistemic_type = cls._migrate_epistemic(claim.claim_type)
72
+ if "schema_version" not in data:
73
+ claim.schema_version = "v1"
74
+ return claim
75
+
76
+ @staticmethod
77
+ def _migrate_epistemic(claim_type: str) -> EpistemicType:
78
+ mapping: dict[str, EpistemicType] = {
79
+ "interpretation": "interpreted",
80
+ "forecast": "hypothesis",
81
+ "risk": "hypothesis",
82
+ }
83
+ return mapping.get(claim_type, "observed")
84
+
85
+
86
+ @dataclass
87
+ class BriefSection:
88
+ title: str
89
+ body: str
90
+ claim_ids: list[str] = field(default_factory=list)
91
+
92
+ def to_dict(self) -> dict[str, Any]:
93
+ return asdict(self)
94
+
95
+
96
+ BlockingLevel = Literal[
97
+ "editor_fixable", # editor can fix in-place (residue, filler, labels)
98
+ "analyst_blocking", # must return to analyst (unsupported certainty, hypothesis misuse)
99
+ "source_blocking", # must return to source collection (missing sources, stale, needs_recrawl)
100
+ "configuration_error", # user must fix config (no API key, missing search_tasks)
101
+ "rendering_error", # docx/markdown rendering issue
102
+ "safety_blocking", # investment advice, redaction risk — cannot ship
103
+ ]
104
+
105
+ RepairOwner = Literal["editor", "analyst", "source", "configuration", "rendering", "safety"]
106
+
107
+
108
+ @dataclass
109
+ class AuditFinding:
110
+ finding_id: str
111
+ severity: AuditSeverity
112
+ finding_type: str
113
+ description: str
114
+ recommendation: str = ""
115
+ related_claim_id: str = ""
116
+ line_number: int | None = None
117
+ evidence: str = ""
118
+ blocking_level: BlockingLevel = "editor_fixable"
119
+ repair_owner: RepairOwner = "editor"
120
+
121
+ def to_dict(self) -> dict[str, Any]:
122
+ return asdict(self)
123
+
124
+
125
+ @dataclass
126
+ class AuditReport:
127
+ audit_status: AuditStatus
128
+ audit_score: int
129
+ findings: list[AuditFinding] = field(default_factory=list)
130
+ metadata: dict[str, Any] = field(default_factory=dict)
131
+
132
+ def to_dict(self) -> dict[str, Any]:
133
+ data = asdict(self)
134
+ data["findings"] = [finding.to_dict() for finding in self.findings]
135
+ return data
136
+
137
+
138
+ @dataclass
139
+ class AgentOutput:
140
+ agent_name: str
141
+ summary: str
142
+ artifacts: dict[str, Any] = field(default_factory=dict)
143
+ created_at: str = field(default_factory=utc_now_iso)
144
+
145
+ def to_dict(self) -> dict[str, Any]:
146
+ return asdict(self)
147
+
148
+
149
+ @dataclass
150
+ class ReportState:
151
+ draft_markdown: str = ""
152
+ prepared_markdown: str = ""
153
+ sections: list[BriefSection] = field(default_factory=list)
154
+ audit_report: AuditReport | None = None
155
+ final_clean_report: dict[str, Any] | None = None
156
+
157
+
158
+ @dataclass
159
+ class PipelineContext:
160
+ project_name: str
161
+ input_dir: str
162
+ output_dir: str
163
+ language: str = "en-US"
164
+ audience: str = "management"
165
+ audience_profile: str = "" # mapped profile ID (management, research, ir, legal_compliance, default)
166
+ report_date: str = ""
167
+ max_source_age_days: int | None = None
168
+ fail_on_stale_source: bool = False
169
+ previous_report_dir: str = ""
170
+ previous_report_text: str = ""
171
+ max_claims: int = 160
172
+ quiet_week_min_claims: int = 5
173
+ sources: list[SourceItem] = field(default_factory=list)
174
+ candidates: list[CandidateItem] = field(default_factory=list)
175
+ report_state: ReportState = field(default_factory=ReportState)
176
+ output_formats: list[str] = field(default_factory=lambda: ["markdown"])
177
+ output_footer: str = ""
178
+ output_filename_template: str = ""
179
+ output_filename_tokens: dict[str, str] = field(default_factory=dict)
180
+ output_named_outputs: bool = True
181
+ metadata: dict[str, Any] = field(default_factory=dict)