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,97 @@
1
+ """Shared safe-write primitives for installer commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+
8
+
9
+ class InstallWriteError(RuntimeError):
10
+ """Raised when planned installer writes are unsafe or refused."""
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class PlannedWrite:
15
+ """A fully rendered file write planned by an installer."""
16
+
17
+ destination: Path
18
+ content: str
19
+
20
+
21
+ def apply_planned_writes(
22
+ *,
23
+ writes: list[PlannedWrite],
24
+ root: Path,
25
+ force: bool = False,
26
+ dry_run: bool = False,
27
+ generated_markers: tuple[str, ...] = (),
28
+ ) -> list[Path]:
29
+ """Validate and optionally apply planned writes under root."""
30
+ root = root.expanduser().resolve()
31
+ _validate_destinations(root=root, writes=writes)
32
+ _check_overwrites(
33
+ writes=writes,
34
+ force=force,
35
+ generated_markers=generated_markers,
36
+ )
37
+
38
+ written: list[Path] = []
39
+ if dry_run:
40
+ return [write.destination for write in writes]
41
+
42
+ for write in writes:
43
+ write.destination.parent.mkdir(parents=True, exist_ok=True)
44
+ write.destination.write_text(write.content, encoding="utf-8")
45
+ written.append(write.destination)
46
+ return written
47
+
48
+
49
+ def _validate_destinations(*, root: Path, writes: list[PlannedWrite]) -> None:
50
+ for write in writes:
51
+ resolved = write.destination.resolve()
52
+ try:
53
+ resolved.relative_to(root)
54
+ except ValueError as exc:
55
+ raise InstallWriteError(
56
+ f"Installer write resolves outside target root: {write.destination}"
57
+ ) from exc
58
+ if write.destination.is_symlink():
59
+ raise InstallWriteError(
60
+ f"Refusing to overwrite symlink during install: {write.destination}"
61
+ )
62
+
63
+
64
+ def _check_overwrites(
65
+ *,
66
+ writes: list[PlannedWrite],
67
+ force: bool,
68
+ generated_markers: tuple[str, ...],
69
+ ) -> None:
70
+ for write in writes:
71
+ dst = write.destination
72
+ if not dst.exists():
73
+ continue
74
+ existing_text = _read_existing_text(dst)
75
+ if force:
76
+ continue
77
+ if existing_text is not None and existing_text == write.content:
78
+ continue
79
+ if existing_text is not None and _text_has_generated_marker(
80
+ existing_text,
81
+ generated_markers,
82
+ ):
83
+ continue
84
+ raise InstallWriteError(
85
+ f"Refusing to overwrite existing non-generated file without --force: {dst}"
86
+ )
87
+
88
+
89
+ def _read_existing_text(path: Path) -> str | None:
90
+ try:
91
+ return path.read_text(encoding="utf-8")
92
+ except (OSError, UnicodeDecodeError):
93
+ return None
94
+
95
+
96
+ def _text_has_generated_marker(text: str, markers: tuple[str, ...]) -> bool:
97
+ return any(marker and marker in text for marker in markers)
@@ -0,0 +1,11 @@
1
+ """Conversational onboarding protocol for multi-agent-brief-workflow."""
2
+ from multi_agent_brief.onboarding.schema import OnboardingResult
3
+ from multi_agent_brief.onboarding.io import load_onboarding_result, save_onboarding_result
4
+ from multi_agent_brief.onboarding.mapper import map_onboarding_to_profile
5
+
6
+ __all__ = [
7
+ "OnboardingResult",
8
+ "load_onboarding_result",
9
+ "save_onboarding_result",
10
+ "map_onboarding_to_profile",
11
+ ]
@@ -0,0 +1,103 @@
1
+ """OnboardingResult I/O: JSON load/save with tolerance for unknown or missing fields."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ from pathlib import Path
6
+ from typing import Any
7
+ from dataclasses import fields
8
+
9
+ from multi_agent_brief.onboarding.schema import OnboardingResult
10
+
11
+ _DATACLASS_FIELDS = {f.name for f in fields(OnboardingResult)}
12
+ _LIST_FIELDS = {"forbidden_sources", "must_watch", "missing"}
13
+ _INT_FIELDS = {"max_items_per_brief", "source_age_days"}
14
+ _BOOL_FIELDS = {"tavily_enabled"}
15
+ _DICT_FIELDS = {"market_scope", "competitor_preferences"}
16
+
17
+ # Agent-generated onboarding.json often uses simplified field names.
18
+ # Map them to canonical OnboardingResult field names.
19
+ _FIELD_ALIASES: dict[str, str] = {
20
+ "company": "company_or_org",
21
+ "industry": "industry_or_theme",
22
+ "title": "brief_title",
23
+ "audience": "audience_plain",
24
+ "language": "language_plain",
25
+ "output_language": "language_plain",
26
+ "cadence": "cadence_plain",
27
+ "source_style": "source_style_plain",
28
+ "output_style": "output_style_plain",
29
+ "focus_areas": "must_watch",
30
+ "monitoring_scope": "must_watch",
31
+ }
32
+
33
+
34
+ def load_onboarding_result(path: str | Path) -> OnboardingResult:
35
+ """Load an OnboardingResult from a JSON file.
36
+
37
+ Unknown fields are silently ignored.
38
+ Missing fields use dataclass defaults.
39
+ """
40
+ text = Path(path).read_text(encoding="utf-8")
41
+ data = json.loads(text)
42
+ if not isinstance(data, dict):
43
+ raise ValueError(
44
+ f"Onboarding JSON must be a JSON object, got {type(data).__name__}."
45
+ )
46
+ # Apply field aliases (agent-generated JSON often uses short names)
47
+ aliased: dict[str, Any] = {}
48
+ for k, v in data.items():
49
+ canonical = _FIELD_ALIASES.get(k, k)
50
+ if canonical in aliased:
51
+ if k == canonical:
52
+ aliased[canonical] = v
53
+ continue # canonical keys override aliases; aliases otherwise keep first write
54
+ aliased[canonical] = v
55
+
56
+ # Keep only known fields; ignore unknown keys.
57
+ known = {k: _normalize_field(k, v) for k, v in aliased.items() if k in _DATACLASS_FIELDS}
58
+ return OnboardingResult(**known)
59
+
60
+
61
+ def _normalize_field(name: str, value: Any) -> Any:
62
+ if name in _LIST_FIELDS:
63
+ if value is None:
64
+ return []
65
+ if isinstance(value, list):
66
+ return [str(item).strip() for item in value if str(item).strip()]
67
+ if isinstance(value, str):
68
+ return [value.strip()] if value.strip() else []
69
+ raise ValueError(f"onboarding.{name} must be a string or list of strings")
70
+ if name in _INT_FIELDS:
71
+ if value in (None, ""):
72
+ return 0
73
+ try:
74
+ return int(value)
75
+ except (TypeError, ValueError) as exc:
76
+ raise ValueError(f"onboarding.{name} must be an integer") from exc
77
+ if name in _BOOL_FIELDS:
78
+ if isinstance(value, bool):
79
+ return value
80
+ if isinstance(value, str):
81
+ return value.strip().lower() in {"1", "true", "yes", "y", "on"}
82
+ return bool(value)
83
+ if name in _DICT_FIELDS:
84
+ if value is None:
85
+ return {}
86
+ if isinstance(value, dict):
87
+ return value
88
+ raise ValueError(f"onboarding.{name} must be a JSON object")
89
+ if isinstance(value, (str, int, float, bool)) or value is None:
90
+ return "" if value is None else str(value) if not isinstance(value, bool) else value
91
+ raise ValueError(f"onboarding.{name} must be a scalar value")
92
+
93
+
94
+ def save_onboarding_result(result: OnboardingResult, path: str | Path) -> None:
95
+ """Save an OnboardingResult to a JSON file (UTF-8, pretty-printed)."""
96
+ from dataclasses import asdict
97
+
98
+ dest = Path(path)
99
+ dest.parent.mkdir(parents=True, exist_ok=True)
100
+ dest.write_text(
101
+ json.dumps(asdict(result), ensure_ascii=False, indent=2) + "\n",
102
+ encoding="utf-8",
103
+ )
@@ -0,0 +1,484 @@
1
+ """Map OnboardingResult to InitProfile.
2
+
3
+ All mapping is business-language → internal fields.
4
+ Users never see source_profile, selector_max_items, etc.
5
+
6
+ Architecture principle:
7
+ - user.md = rich user profile and task context (primary semantic layer)
8
+ - config.yaml = operational run settings only
9
+ - sources.yaml = approved or discovered sources only
10
+ - source.mode = llm_decide by default
11
+ - industry packs = optional seed packs, not the source of truth
12
+ """
13
+ from __future__ import annotations
14
+
15
+ from multi_agent_brief.onboarding.schema import OnboardingResult
16
+ from multi_agent_brief.sources.industry_packs import INDUSTRY_PACKS
17
+ from multi_agent_brief.audience.profiles import map_audience_to_profile
18
+ from multi_agent_brief.workspace.init_profile import InitProfile
19
+
20
+
21
+ # ── language mapping ────────────────────────────────────────────────
22
+
23
+ _LANG_MAP: dict[str, str] = {
24
+ "zh": "zh-CN",
25
+ "zh-cn": "zh-CN",
26
+ "zh_cn": "zh-CN",
27
+ "中文": "zh-CN",
28
+ "chinese": "zh-CN",
29
+ "en": "en-US",
30
+ "en-us": "en-US",
31
+ "en_us": "en-US",
32
+ "english": "en-US",
33
+ "英文": "en-US",
34
+ "ja": "ja-JP",
35
+ "ja-jp": "ja-JP",
36
+ "ja_jp": "ja-JP",
37
+ "japanese": "ja-JP",
38
+ "日文": "ja-JP",
39
+ "bilingual": "bilingual",
40
+ "dual language": "bilingual",
41
+ "中英": "bilingual",
42
+ "双语": "bilingual",
43
+ }
44
+
45
+ _DEFAULT_LANG = "en-US"
46
+
47
+
48
+ def normalize_language(text: str) -> str:
49
+ t = text.strip().lower()
50
+ if not t:
51
+ return _DEFAULT_LANG
52
+ return _LANG_MAP.get(t, t)
53
+
54
+
55
+ # ── cadence mapping ────────────────────────────────────────────────
56
+
57
+ _CADENCE_MAP: dict[str, str] = {
58
+ "daily": "daily",
59
+ "day": "daily",
60
+ "每日": "daily",
61
+ "weekly": "weekly",
62
+ "week": "weekly",
63
+ "weekly brief": "weekly",
64
+ "周报": "weekly",
65
+ "每周": "weekly",
66
+ "monthly": "monthly",
67
+ "month": "monthly",
68
+ "monthly brief": "monthly",
69
+ "月报": "monthly",
70
+ "每月": "monthly",
71
+ }
72
+
73
+ _DEFAULT_CADENCE = "weekly"
74
+
75
+
76
+ def normalize_cadence(text: str) -> str:
77
+ t = text.strip().lower()
78
+ if not t:
79
+ return _DEFAULT_CADENCE
80
+ if t in _CADENCE_MAP:
81
+ return _CADENCE_MAP[t]
82
+ if any(k in t for k in ("daily", "每日", "every day")):
83
+ return "daily"
84
+ if any(k in t for k in ("monthly", "月报", "每月", "every month")):
85
+ return "monthly"
86
+ if any(k in t for k in ("weekly", "week", "周报", "每周", "every week")):
87
+ return "weekly"
88
+ return t
89
+
90
+
91
+ # ── audience mapping ───────────────────────────────────────────────
92
+
93
+ _AUDIENCE_MAP: dict[str, str] = {
94
+ "management": "management",
95
+ "executive": "management",
96
+ "ceo office": "management",
97
+ "ceo": "management",
98
+ "leadership": "management",
99
+ "管理层": "management",
100
+ "management team": "management",
101
+ "总裁办": "management",
102
+ "boss": "management",
103
+ "investment": "investment",
104
+ "portfolio": "investment",
105
+ "fund": "investment",
106
+ "investor": "investment",
107
+ "投资": "investment",
108
+ "持仓": "investment",
109
+ "基金": "investment",
110
+ "ir": "investor_relations",
111
+ "investor relations": "investor_relations",
112
+ "disclosure": "investor_relations",
113
+ "投关": "investor_relations",
114
+ "披露": "investor_relations",
115
+ "research": "research",
116
+ "analyst": "research",
117
+ "研究员": "research",
118
+ "legal": "compliance",
119
+ "compliance": "compliance",
120
+ "法务": "compliance",
121
+ "合规": "compliance",
122
+ "business": "business",
123
+ "operations": "business",
124
+ "sales": "business",
125
+ "业务": "business",
126
+ }
127
+
128
+ _DEFAULT_AUDIENCE = "management"
129
+
130
+
131
+ def normalize_audience(text: str) -> str:
132
+ t = text.strip().lower()
133
+ if not t:
134
+ return _DEFAULT_AUDIENCE
135
+ if t in _AUDIENCE_MAP:
136
+ return _AUDIENCE_MAP[t]
137
+ if any(k in t for k in ("investor relations", "ir", "投关", "披露")):
138
+ return "investor_relations"
139
+ if any(k in t for k in ("investment", "portfolio", "fund", "investor", "投资", "持仓", "基金")):
140
+ return "investment"
141
+ if any(k in t for k in ("management", "executive", "ceo", "leadership", "管理层", "总裁", "boss")):
142
+ return "management"
143
+ if any(k in t for k in ("research", "analyst", "研究员")):
144
+ return "research"
145
+ if any(k in t for k in ("legal", "compliance", "法务", "合规")):
146
+ return "compliance"
147
+ if any(k in t for k in ("business", "operations", "sales", "业务")):
148
+ return "business"
149
+ return t
150
+
151
+
152
+ # ── source_profile mapping ─────────────────────────────────────────
153
+
154
+ _SOURCE_STYLE_MAP: dict[str, str] = {
155
+ "conservative": "conservative",
156
+ "official": "conservative",
157
+ "filing": "conservative",
158
+ "announcement": "conservative",
159
+ "官方": "conservative",
160
+ "公告": "conservative",
161
+ "research": "research",
162
+ "reliable research": "research",
163
+ "industry media": "research",
164
+ "研究": "research",
165
+ "行业媒体": "research",
166
+ "aggressive_signal": "aggressive_signal",
167
+ "radar": "aggressive_signal",
168
+ "broad scan": "aggressive_signal",
169
+ "social signals": "aggressive_signal",
170
+ "社媒": "aggressive_signal",
171
+ "信号": "aggressive_signal",
172
+ "llm_decide": "llm_decide",
173
+ "ai decide": "llm_decide",
174
+ "ai决定": "llm_decide",
175
+ "让ai决定": "llm_decide",
176
+ }
177
+
178
+ _DEFAULT_SOURCE_PROFILE = "llm_decide"
179
+
180
+
181
+ def normalize_source_profile(text: str) -> str:
182
+ t = text.strip().lower()
183
+ if not t:
184
+ return _DEFAULT_SOURCE_PROFILE
185
+ if t in _SOURCE_STYLE_MAP:
186
+ return _SOURCE_STYLE_MAP[t]
187
+ if "official" in t or "filing" in t or "announcement" in t or "公告" in t:
188
+ return "conservative"
189
+ if "social" in t or "github" in t or "radar" in t or "broad" in t or "社媒" in t or "信号" in t:
190
+ return "aggressive_signal"
191
+ # Default to llm_decide for vague or unknown source preferences
192
+ return "llm_decide"
193
+
194
+
195
+ # ── search backend mapping ─────────────────────────────────────────
196
+
197
+ _SEARCH_BACKEND_MAP: dict[str, str] = {
198
+ "tavily": "tavily",
199
+ "exa": "exa",
200
+ "brave": "brave",
201
+ "firecrawl": "firecrawl",
202
+ "serper": "serper",
203
+ "runtime_websearch": "runtime_websearch",
204
+ "configure_later": "configure_later",
205
+ "稍后配置": "configure_later",
206
+ "稍后": "configure_later",
207
+ "none": "none",
208
+ "无": "none",
209
+ "不启用": "none",
210
+ }
211
+
212
+ _DEFAULT_SEARCH_BACKEND = ""
213
+
214
+
215
+ def normalize_search_backend(text: str) -> str:
216
+ t = text.strip().lower()
217
+ if not t:
218
+ return _DEFAULT_SEARCH_BACKEND
219
+ if t in _SEARCH_BACKEND_MAP:
220
+ return _SEARCH_BACKEND_MAP[t]
221
+ # Check for partial matches
222
+ for key, value in _SEARCH_BACKEND_MAP.items():
223
+ if key in t:
224
+ return value
225
+ return _DEFAULT_SEARCH_BACKEND
226
+
227
+
228
+ # ── role mapping ───────────────────────────────────────────────────
229
+
230
+ _ROLE_MAP: dict[str, str] = {
231
+ "strategy": "strategy_office",
232
+ "strategy office": "strategy_office",
233
+ "president office": "strategy_office",
234
+ "总裁办": "strategy_office",
235
+ "战略": "strategy_office",
236
+ "战略研究": "strategy_office",
237
+ "research": "research_analyst",
238
+ "research analyst": "research_analyst",
239
+ "行业研究": "research_analyst",
240
+ "研究员": "research_analyst",
241
+ "ir": "investor_relations",
242
+ "investor relations": "investor_relations",
243
+ "投资者关系": "investor_relations",
244
+ "投关": "investor_relations",
245
+ "policy": "policy_analyst",
246
+ "policy analyst": "policy_analyst",
247
+ "政策研究": "policy_analyst",
248
+ "政策": "policy_analyst",
249
+ "management": "management_support",
250
+ "management support": "management_support",
251
+ "管理层支持": "management_support",
252
+ "管理": "management_support",
253
+ "marketing": "management_support",
254
+ "市场": "management_support",
255
+ "市场部": "management_support",
256
+ }
257
+
258
+ _DEFAULT_ROLE = "strategy_office"
259
+
260
+
261
+ def normalize_role(text: str) -> str:
262
+ t = text.strip().lower()
263
+ if not t:
264
+ return _DEFAULT_ROLE
265
+ if t in _ROLE_MAP:
266
+ return _ROLE_MAP[t]
267
+ # Check for partial matches
268
+ for key, value in _ROLE_MAP.items():
269
+ if key in t:
270
+ return value
271
+ return _DEFAULT_ROLE
272
+
273
+
274
+ # ── audit strictness mapping ───────────────────────────────────────
275
+
276
+ _AUDIT_STRICTNESS_MAP: dict[str, str] = {
277
+ "standard": "standard",
278
+ "strict": "strict",
279
+ "lenient": "lenient",
280
+ "标准": "standard",
281
+ "严格": "strict",
282
+ "宽松": "lenient",
283
+ }
284
+
285
+ _DEFAULT_AUDIT_STRICTNESS = "standard"
286
+
287
+
288
+ def normalize_audit_strictness(text: str) -> str:
289
+ t = text.strip().lower()
290
+ if not t:
291
+ return _DEFAULT_AUDIT_STRICTNESS
292
+ if t in _AUDIT_STRICTNESS_MAP:
293
+ return _AUDIT_STRICTNESS_MAP[t]
294
+ if "strict" in t or "严格" in t:
295
+ return "strict"
296
+ if "lenient" in t or "宽松" in t:
297
+ return "lenient"
298
+ return _DEFAULT_AUDIT_STRICTNESS
299
+
300
+
301
+ # ── industry matching (optional seed pack only) ────────────────────
302
+
303
+ _REGISTERED_PACK_KEYS = set(INDUSTRY_PACKS.keys())
304
+
305
+
306
+ def _try_match_seed_pack(text: str) -> str:
307
+ """Try to match user text to a registered industry pack key.
308
+
309
+ Returns the pack key if clearly matched, empty string otherwise.
310
+ Does NOT guess or invent unsupported slugs.
311
+ """
312
+ t = text.strip().lower()
313
+ if not t:
314
+ return ""
315
+ # Exact match against registered pack keys
316
+ if t in _REGISTERED_PACK_KEYS:
317
+ return t
318
+ # Substring match against registered pack keys
319
+ for key in _REGISTERED_PACK_KEYS:
320
+ if key in t:
321
+ return key
322
+ return ""
323
+
324
+
325
+ def normalize_industry(text: str) -> str:
326
+ """Normalize industry text to a registered pack key or empty string.
327
+
328
+ Returns a registered pack key if clearly matched, empty string otherwise.
329
+ Does NOT guess or invent unsupported slugs.
330
+ """
331
+ return _try_match_seed_pack(text)
332
+
333
+
334
+ # ── selector_max_items ─────────────────────────────────────────────
335
+
336
+ _SELECTOR_MAP: dict[str, int] = {
337
+ "conservative": 20,
338
+ "research": 20,
339
+ "aggressive_signal": 28,
340
+ "llm_decide": 20,
341
+ }
342
+
343
+
344
+ # ── main mapper ────────────────────────────────────────────────────
345
+
346
+ def map_onboarding_to_profile(result: OnboardingResult) -> InitProfile:
347
+ """Convert business-language OnboardingResult into an InitProfile.
348
+
349
+ Preserves raw user text in user.md-facing fields.
350
+ Only maps to registered pack keys when clearly matched.
351
+ Defaults to llm_decide mode for source discovery.
352
+ """
353
+ profile = InitProfile()
354
+
355
+ language = normalize_language(result.language_plain)
356
+ profile.interface_language = language
357
+ profile.output_language = language
358
+
359
+ profile.company = result.company_or_org.strip()
360
+
361
+ # Role mapping
362
+ role_raw = getattr(result, "role_plain", "").strip()
363
+ if role_raw:
364
+ profile.role = normalize_role(role_raw)
365
+
366
+ # Preserve raw industry text for user.md
367
+ industry_raw = result.industry_or_theme.strip()
368
+ profile.industry_text = industry_raw
369
+ # Only set industry slug if clearly matches a registered pack
370
+ profile.industry = _try_match_seed_pack(industry_raw)
371
+ profile.optional_seed_pack = profile.industry # same as industry if matched
372
+
373
+ profile.audience = normalize_audience(result.audience_plain)
374
+ profile.audience_profile = map_audience_to_profile(result.audience_plain)
375
+ profile.cadence = normalize_cadence(result.cadence_plain)
376
+ profile.source_profile = normalize_source_profile(result.source_style_plain)
377
+ profile.selector_max_items = _SELECTOR_MAP.get(profile.source_profile, 20)
378
+
379
+ # Brief title: user-specified overrides auto-generation.
380
+ # task_objective is descriptive context, not a title or filename stem.
381
+ user_title = ""
382
+ if hasattr(result, "brief_title") and result.brief_title.strip():
383
+ user_title = result.brief_title.strip()
384
+
385
+ if user_title:
386
+ profile.brief_title = user_title
387
+ else:
388
+ industry_display = industry_raw or "Industry"
389
+ company = profile.company
390
+ cadence_word = profile.cadence.capitalize()
391
+ if language == "zh-CN" and company and company != "Sample Company":
392
+ profile.brief_title = f"{company} {industry_display}周报"
393
+ elif company and company != "Sample Company":
394
+ profile.brief_title = f"{company} {cadence_word} Brief"
395
+ else:
396
+ profile.brief_title = "Multi-Agent Brief"
397
+
398
+ # Focus areas: preserve raw must_watch items
399
+ base_focus = ["company", "industry", "policy", "competitors", "risk_events"]
400
+ seen: set[str] = set()
401
+ focus: list[str] = []
402
+ for item in base_focus:
403
+ if item not in seen:
404
+ focus.append(item)
405
+ seen.add(item)
406
+ for item in result.must_watch:
407
+ key = item.strip()
408
+ if key and key.lower() not in seen:
409
+ focus.append(key)
410
+ seen.add(key.lower())
411
+ profile.focus_areas = focus
412
+
413
+ # Store task objective and forbidden sources
414
+ if hasattr(result, "task_objective") and result.task_objective:
415
+ profile.task_objective = result.task_objective
416
+ if hasattr(result, "forbidden_sources") and result.forbidden_sources:
417
+ profile.forbidden_sources = list(result.forbidden_sources)
418
+
419
+ # Output formats: standard artifact set
420
+ profile.output_formats = ["markdown", "docx", "claim_ledger", "audit_report", "source_appendix"]
421
+
422
+ # If output_style_plain requests docx/word, include it
423
+ style_lower = (getattr(result, "output_style_plain", "") or "").lower()
424
+ if "docx" not in profile.output_formats and any(
425
+ k in style_lower for k in ("docx", "word", "docx格式", "word格式")
426
+ ):
427
+ profile.output_formats.append("docx")
428
+
429
+ # New fields from extended onboarding
430
+ if hasattr(result, "max_items_per_brief") and result.max_items_per_brief:
431
+ profile.selector_max_items = result.max_items_per_brief
432
+
433
+ if hasattr(result, "source_age_days") and result.source_age_days:
434
+ profile.max_source_age_days = result.source_age_days
435
+
436
+ # Web search: handle search backend selection
437
+ search_backend = getattr(result, "search_backend_plain", "").strip()
438
+ if search_backend:
439
+ search_backend = normalize_search_backend(search_backend)
440
+ if search_backend == "tavily":
441
+ profile.tavily_enabled = True
442
+ profile.web_search_enabled = True
443
+ profile.web_search_mode = "external_api"
444
+ profile.search_backend = "tavily"
445
+ elif search_backend == "none":
446
+ profile.tavily_enabled = False
447
+ profile.web_search_enabled = False
448
+ profile.web_search_mode = "disabled"
449
+ profile.search_backend = ""
450
+ elif search_backend == "runtime_websearch":
451
+ profile.tavily_enabled = False
452
+ profile.web_search_enabled = True
453
+ profile.web_search_mode = "runtime_tool"
454
+ profile.search_backend = ""
455
+ elif search_backend == "configure_later":
456
+ profile.tavily_enabled = False
457
+ profile.web_search_enabled = True
458
+ profile.web_search_mode = "configure_later"
459
+ profile.search_backend = ""
460
+ else:
461
+ # Other backends (exa, brave, firecrawl, serper)
462
+ profile.tavily_enabled = False
463
+ profile.web_search_enabled = True
464
+ profile.web_search_mode = "external_api"
465
+ profile.search_backend = search_backend
466
+
467
+ # Also check the legacy tavily_enabled field
468
+ if getattr(result, "tavily_enabled", False):
469
+ profile.tavily_enabled = True
470
+ profile.web_search_enabled = True
471
+ if not profile.web_search_mode or profile.web_search_mode == "disabled":
472
+ profile.web_search_mode = "external_api"
473
+ if not profile.search_backend:
474
+ profile.search_backend = "tavily"
475
+
476
+ # Competitor monitoring
477
+ comp_prefs = getattr(result, "competitor_preferences", None) or {}
478
+ if comp_prefs.get("enabled"):
479
+ profile.competitor_module_enabled = True
480
+ names = comp_prefs.get("names", [])
481
+ if isinstance(names, list):
482
+ profile.competitor_names = [n.strip() for n in names if isinstance(n, str) and n.strip()]
483
+
484
+ return profile