tradingcodex 0.1.0__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 (254) hide show
  1. apps/__init__.py +1 -0
  2. apps/audit/__init__.py +1 -0
  3. apps/audit/admin.py +6 -0
  4. apps/audit/apps.py +8 -0
  5. apps/audit/migrations/0001_initial.py +35 -0
  6. apps/audit/migrations/__init__.py +0 -0
  7. apps/audit/models.py +22 -0
  8. apps/harness/__init__.py +1 -0
  9. apps/harness/admin.py +6 -0
  10. apps/harness/apps.py +8 -0
  11. apps/harness/migrations/0001_initial.py +35 -0
  12. apps/harness/migrations/__init__.py +0 -0
  13. apps/harness/models.py +22 -0
  14. apps/harness/templatetags/__init__.py +1 -0
  15. apps/integrations/__init__.py +1 -0
  16. apps/integrations/admin.py +6 -0
  17. apps/integrations/apps.py +8 -0
  18. apps/integrations/migrations/0001_initial.py +29 -0
  19. apps/integrations/migrations/__init__.py +0 -0
  20. apps/integrations/models.py +16 -0
  21. apps/integrations/services.py +31 -0
  22. apps/mcp/__init__.py +1 -0
  23. apps/mcp/admin.py +20 -0
  24. apps/mcp/apps.py +8 -0
  25. apps/mcp/migrations/0001_initial.py +168 -0
  26. apps/mcp/migrations/__init__.py +0 -0
  27. apps/mcp/models.py +154 -0
  28. apps/mcp/services.py +327 -0
  29. apps/orders/__init__.py +1 -0
  30. apps/orders/admin.py +6 -0
  31. apps/orders/apps.py +8 -0
  32. apps/orders/migrations/0001_initial.py +79 -0
  33. apps/orders/migrations/__init__.py +0 -0
  34. apps/orders/models.py +66 -0
  35. apps/orders/services.py +107 -0
  36. apps/policy/__init__.py +1 -0
  37. apps/policy/admin.py +6 -0
  38. apps/policy/apps.py +8 -0
  39. apps/policy/migrations/0001_initial.py +75 -0
  40. apps/policy/migrations/__init__.py +0 -0
  41. apps/policy/models.py +61 -0
  42. apps/policy/services.py +110 -0
  43. apps/portfolio/__init__.py +1 -0
  44. apps/portfolio/admin.py +6 -0
  45. apps/portfolio/apps.py +8 -0
  46. apps/portfolio/migrations/0001_initial.py +67 -0
  47. apps/portfolio/migrations/__init__.py +0 -0
  48. apps/portfolio/models.py +53 -0
  49. apps/research/__init__.py +1 -0
  50. apps/research/admin.py +1 -0
  51. apps/research/apps.py +8 -0
  52. apps/research/migrations/__init__.py +0 -0
  53. apps/research/models.py +1 -0
  54. apps/workflows/__init__.py +1 -0
  55. apps/workflows/admin.py +6 -0
  56. apps/workflows/apps.py +8 -0
  57. apps/workflows/migrations/0001_initial.py +51 -0
  58. apps/workflows/migrations/__init__.py +0 -0
  59. apps/workflows/models.py +44 -0
  60. tradingcodex-0.1.0.dist-info/METADATA +337 -0
  61. tradingcodex-0.1.0.dist-info/RECORD +254 -0
  62. tradingcodex-0.1.0.dist-info/WHEEL +5 -0
  63. tradingcodex-0.1.0.dist-info/entry_points.txt +2 -0
  64. tradingcodex-0.1.0.dist-info/licenses/LICENSE +202 -0
  65. tradingcodex-0.1.0.dist-info/licenses/NOTICE +24 -0
  66. tradingcodex-0.1.0.dist-info/top_level.txt +4 -0
  67. tradingcodex_cli/__init__.py +1 -0
  68. tradingcodex_cli/__main__.py +124 -0
  69. tradingcodex_cli/commands/__init__.py +2 -0
  70. tradingcodex_cli/commands/bootstrap.py +157 -0
  71. tradingcodex_cli/commands/db.py +36 -0
  72. tradingcodex_cli/commands/doctor.py +230 -0
  73. tradingcodex_cli/commands/mcp.py +186 -0
  74. tradingcodex_cli/commands/orders.py +89 -0
  75. tradingcodex_cli/commands/policy.py +21 -0
  76. tradingcodex_cli/commands/profile.py +110 -0
  77. tradingcodex_cli/commands/research.py +76 -0
  78. tradingcodex_cli/commands/skills.py +93 -0
  79. tradingcodex_cli/commands/strategies.py +67 -0
  80. tradingcodex_cli/commands/subagents.py +106 -0
  81. tradingcodex_cli/commands/utils.py +134 -0
  82. tradingcodex_cli/commands/workspaces.py +53 -0
  83. tradingcodex_cli/generator.py +234 -0
  84. tradingcodex_cli/mcp_stdio.py +26 -0
  85. tradingcodex_cli/service_autostart.py +127 -0
  86. tradingcodex_service/__init__.py +5 -0
  87. tradingcodex_service/admin.py +1 -0
  88. tradingcodex_service/api.py +486 -0
  89. tradingcodex_service/application/__init__.py +2 -0
  90. tradingcodex_service/application/agents.py +1470 -0
  91. tradingcodex_service/application/audit.py +71 -0
  92. tradingcodex_service/application/common.py +88 -0
  93. tradingcodex_service/application/components.py +299 -0
  94. tradingcodex_service/application/harness.py +747 -0
  95. tradingcodex_service/application/markdown_preview.py +179 -0
  96. tradingcodex_service/application/orders.py +404 -0
  97. tradingcodex_service/application/policy.py +150 -0
  98. tradingcodex_service/application/portfolio.py +166 -0
  99. tradingcodex_service/application/research.py +356 -0
  100. tradingcodex_service/application/runtime.py +321 -0
  101. tradingcodex_service/asgi.py +6 -0
  102. tradingcodex_service/mcp_http.py +59 -0
  103. tradingcodex_service/mcp_runtime.py +565 -0
  104. tradingcodex_service/settings.py +91 -0
  105. tradingcodex_service/templates/web/activity.html +24 -0
  106. tradingcodex_service/templates/web/agent_skills.html +111 -0
  107. tradingcodex_service/templates/web/agents.html +121 -0
  108. tradingcodex_service/templates/web/base.html +150 -0
  109. tradingcodex_service/templates/web/dashboard.html +109 -0
  110. tradingcodex_service/templates/web/fragments/role_inspector.html +81 -0
  111. tradingcodex_service/templates/web/fragments/starter_prompt.html +24 -0
  112. tradingcodex_service/templates/web/fragments/topology_canvas.html +85 -0
  113. tradingcodex_service/templates/web/harness.html +87 -0
  114. tradingcodex_service/templates/web/mcp_router.html +250 -0
  115. tradingcodex_service/templates/web/orders.html +68 -0
  116. tradingcodex_service/templates/web/policy.html +81 -0
  117. tradingcodex_service/templates/web/portfolio.html +52 -0
  118. tradingcodex_service/templates/web/research.html +73 -0
  119. tradingcodex_service/templates/web/starter_prompt.html +40 -0
  120. tradingcodex_service/templates/web/strategies.html +74 -0
  121. tradingcodex_service/urls.py +42 -0
  122. tradingcodex_service/version.py +1 -0
  123. tradingcodex_service/web.py +885 -0
  124. tradingcodex_service/wsgi.py +6 -0
  125. workspace_templates/__init__.py +1 -0
  126. workspace_templates/modules/audit/files/.tradingcodex/audit/README.md +5 -0
  127. workspace_templates/modules/audit/files/trading/audit/.gitkeep +1 -0
  128. workspace_templates/modules/audit/module.json +16 -0
  129. workspace_templates/modules/codex-base/files/.codex/config.toml +272 -0
  130. workspace_templates/modules/codex-base/files/.codex/hooks/tradingcodex_hook.py +173 -0
  131. workspace_templates/modules/codex-base/files/.codex/hooks.json +105 -0
  132. workspace_templates/modules/codex-base/files/.codex/prompts/base_instructions/head-manager.md +129 -0
  133. workspace_templates/modules/codex-base/files/.codex/rules/tradingcodex.rules +50 -0
  134. workspace_templates/modules/codex-base/files/.tradingcodex/capabilities.yaml +56 -0
  135. workspace_templates/modules/codex-base/files/.tradingcodex/cli.py +16 -0
  136. workspace_templates/modules/codex-base/files/.tradingcodex/config.yaml +53 -0
  137. workspace_templates/modules/codex-base/files/.tradingcodex/policies/policy-bindings.yaml +16 -0
  138. workspace_templates/modules/codex-base/files/.tradingcodex/policies/principals.yaml +17 -0
  139. workspace_templates/modules/codex-base/files/.tradingcodex/policies/roles.yaml +27 -0
  140. workspace_templates/modules/codex-base/files/AGENTS.md +56 -0
  141. workspace_templates/modules/codex-base/files/pyproject.toml +13 -0
  142. workspace_templates/modules/codex-base/files/tcx +35 -0
  143. workspace_templates/modules/codex-base/module.json +17 -0
  144. workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/policies/access-policies.yaml +39 -0
  145. workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/policies/restricted-list.yaml +6 -0
  146. workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/approval_receipt.schema.json +14 -0
  147. workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/audit_event.schema.json +11 -0
  148. workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/evidence_pack.schema.json +16 -0
  149. workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/execution_result.schema.json +12 -0
  150. workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/fundamental_report.schema.json +15 -0
  151. workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/news_report.schema.json +15 -0
  152. workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/order_intent.schema.json +30 -0
  153. workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/portfolio_review.schema.json +15 -0
  154. workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/postmortem_report.schema.json +14 -0
  155. workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/risk_report.schema.json +15 -0
  156. workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/technical_report.schema.json +15 -0
  157. workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/thesis.schema.json +15 -0
  158. workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/valuation.schema.json +15 -0
  159. workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/scripts/validate-order-intent.py +15 -0
  160. workspace_templates/modules/enforcement-guardrails/module.json +19 -0
  161. workspace_templates/modules/fixed-subagents/files/.codex/agents/execution-operator.toml +71 -0
  162. workspace_templates/modules/fixed-subagents/files/.codex/agents/fundamental-analyst.toml +62 -0
  163. workspace_templates/modules/fixed-subagents/files/.codex/agents/instrument-analyst.toml +64 -0
  164. workspace_templates/modules/fixed-subagents/files/.codex/agents/macro-analyst.toml +64 -0
  165. workspace_templates/modules/fixed-subagents/files/.codex/agents/news-analyst.toml +63 -0
  166. workspace_templates/modules/fixed-subagents/files/.codex/agents/portfolio-manager.toml +62 -0
  167. workspace_templates/modules/fixed-subagents/files/.codex/agents/risk-manager.toml +65 -0
  168. workspace_templates/modules/fixed-subagents/files/.codex/agents/technical-analyst.toml +63 -0
  169. workspace_templates/modules/fixed-subagents/files/.codex/agents/valuation-analyst.toml +59 -0
  170. workspace_templates/modules/fixed-subagents/files/.tradingcodex/mainagent/head-manager.yaml +66 -0
  171. workspace_templates/modules/fixed-subagents/files/.tradingcodex/mainagent/skill-change-proposals/.gitkeep +1 -0
  172. workspace_templates/modules/fixed-subagents/files/.tradingcodex/mainagent/subagent-registry.yaml +56 -0
  173. workspace_templates/modules/fixed-subagents/module.json +23 -0
  174. workspace_templates/modules/guidance-guardrails/files/.tradingcodex/guidance/guardrails.md +17 -0
  175. workspace_templates/modules/guidance-guardrails/files/.tradingcodex/guidance/task-quality-checklist.md +37 -0
  176. workspace_templates/modules/guidance-guardrails/module.json +18 -0
  177. workspace_templates/modules/information-barriers/files/.tradingcodex/policies/information-barriers.yaml +211 -0
  178. workspace_templates/modules/information-barriers/files/.tradingcodex/secrets.md +9 -0
  179. workspace_templates/modules/information-barriers/files/trading/approvals/.gitkeep +1 -0
  180. workspace_templates/modules/information-barriers/files/trading/market-data/.gitkeep +1 -0
  181. workspace_templates/modules/information-barriers/files/trading/orders/approved/.gitkeep +1 -0
  182. workspace_templates/modules/information-barriers/files/trading/orders/draft/.gitkeep +1 -0
  183. workspace_templates/modules/information-barriers/files/trading/orders/executed/.gitkeep +1 -0
  184. workspace_templates/modules/information-barriers/files/trading/orders/rejected/.gitkeep +1 -0
  185. workspace_templates/modules/information-barriers/files/trading/portfolio/.gitkeep +1 -0
  186. workspace_templates/modules/information-barriers/files/trading/reports/fundamental/.gitkeep +1 -0
  187. workspace_templates/modules/information-barriers/files/trading/reports/instrument/.gitkeep +1 -0
  188. workspace_templates/modules/information-barriers/files/trading/reports/macro/.gitkeep +1 -0
  189. workspace_templates/modules/information-barriers/files/trading/reports/news/.gitkeep +1 -0
  190. workspace_templates/modules/information-barriers/files/trading/reports/policy/.gitkeep +1 -0
  191. workspace_templates/modules/information-barriers/files/trading/reports/portfolio/.gitkeep +1 -0
  192. workspace_templates/modules/information-barriers/files/trading/reports/postmortem/.gitkeep +1 -0
  193. workspace_templates/modules/information-barriers/files/trading/reports/risk/.gitkeep +1 -0
  194. workspace_templates/modules/information-barriers/files/trading/reports/technical/.gitkeep +1 -0
  195. workspace_templates/modules/information-barriers/files/trading/reports/valuation/.gitkeep +1 -0
  196. workspace_templates/modules/information-barriers/files/trading/research/.gitkeep +1 -0
  197. workspace_templates/modules/information-barriers/module.json +19 -0
  198. workspace_templates/modules/paper-trading/files/.tradingcodex/mcp/adapters/paper-trading.py +4 -0
  199. workspace_templates/modules/paper-trading/module.json +16 -0
  200. workspace_templates/modules/postmortem/files/.tradingcodex/workflows/postmortem.yaml +12 -0
  201. workspace_templates/modules/postmortem/module.json +16 -0
  202. workspace_templates/modules/repo-skills/files/.agents/skills/investment-workflow-map/SKILL.md +106 -0
  203. workspace_templates/modules/repo-skills/files/.agents/skills/investment-workflow-map/agents/openai.yaml +6 -0
  204. workspace_templates/modules/repo-skills/files/.agents/skills/manage-optional-skills/SKILL.md +101 -0
  205. workspace_templates/modules/repo-skills/files/.agents/skills/manage-optional-skills/agents/openai.yaml +6 -0
  206. workspace_templates/modules/repo-skills/files/.agents/skills/manage-subagents/SKILL.md +140 -0
  207. workspace_templates/modules/repo-skills/files/.agents/skills/manage-subagents/agents/openai.yaml +6 -0
  208. workspace_templates/modules/repo-skills/files/.agents/skills/orchestrate-workflow/SKILL.md +140 -0
  209. workspace_templates/modules/repo-skills/files/.agents/skills/orchestrate-workflow/agents/openai.yaml +6 -0
  210. workspace_templates/modules/repo-skills/files/.agents/skills/postmortem/SKILL.md +31 -0
  211. workspace_templates/modules/repo-skills/files/.agents/skills/postmortem/agents/openai.yaml +6 -0
  212. workspace_templates/modules/repo-skills/files/.agents/skills/scenario-quality-gates/SKILL.md +138 -0
  213. workspace_templates/modules/repo-skills/files/.agents/skills/scenario-quality-gates/agents/openai.yaml +6 -0
  214. workspace_templates/modules/repo-skills/files/.agents/skills/strategy-creator/SKILL.md +109 -0
  215. workspace_templates/modules/repo-skills/files/.agents/skills/strategy-creator/agents/openai.yaml +6 -0
  216. workspace_templates/modules/repo-skills/files/.agents/skills/synthesize-decision/SKILL.md +54 -0
  217. workspace_templates/modules/repo-skills/files/.agents/skills/synthesize-decision/agents/openai.yaml +6 -0
  218. workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/execution-operator/execute-paper-order/SKILL.md +35 -0
  219. workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/execution-operator/execute-paper-order/agents/openai.yaml +6 -0
  220. workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/fundamental-analyst/fundamental-analysis/SKILL.md +46 -0
  221. workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/fundamental-analyst/fundamental-analysis/agents/openai.yaml +6 -0
  222. workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/instrument-analyst/instrument-analysis/SKILL.md +40 -0
  223. workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/instrument-analyst/instrument-analysis/agents/openai.yaml +6 -0
  224. workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/macro-analyst/macro-analysis/SKILL.md +40 -0
  225. workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/macro-analyst/macro-analysis/agents/openai.yaml +6 -0
  226. workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/news-analyst/news-analysis/SKILL.md +43 -0
  227. workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/news-analyst/news-analysis/agents/openai.yaml +6 -0
  228. workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/portfolio-manager/create-order-intent/SKILL.md +46 -0
  229. workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/portfolio-manager/create-order-intent/agents/openai.yaml +6 -0
  230. workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/portfolio-manager/portfolio-review/SKILL.md +44 -0
  231. workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/portfolio-manager/portfolio-review/agents/openai.yaml +6 -0
  232. workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/risk-manager/approve-order/SKILL.md +38 -0
  233. workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/risk-manager/approve-order/agents/openai.yaml +6 -0
  234. workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/risk-manager/policy-review/SKILL.md +43 -0
  235. workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/risk-manager/policy-review/agents/openai.yaml +6 -0
  236. workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/risk-manager/review-risk/SKILL.md +45 -0
  237. workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/risk-manager/review-risk/agents/openai.yaml +6 -0
  238. workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/shared/collect-evidence/SKILL.md +46 -0
  239. workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/shared/collect-evidence/agents/openai.yaml +6 -0
  240. workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/shared/external-data-source-gate/SKILL.md +66 -0
  241. workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/shared/external-data-source-gate/agents/openai.yaml +6 -0
  242. workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/technical-analyst/technical-analysis/SKILL.md +43 -0
  243. workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/technical-analyst/technical-analysis/agents/openai.yaml +6 -0
  244. workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/valuation-analyst/valuation-review/SKILL.md +47 -0
  245. workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/valuation-analyst/valuation-review/agents/openai.yaml +6 -0
  246. workspace_templates/modules/repo-skills/module.json +37 -0
  247. workspace_templates/modules/stub-execution/files/.tradingcodex/mcp/adapters/stub-execution.py +4 -0
  248. workspace_templates/modules/stub-execution/module.json +15 -0
  249. workspace_templates/modules/tradingcodex-mcp/files/.tradingcodex/mcp/adapters/live-adapter.contract.md +25 -0
  250. workspace_templates/modules/tradingcodex-mcp/files/.tradingcodex/mcp/enforcer/README.md +5 -0
  251. workspace_templates/modules/tradingcodex-mcp/files/.tradingcodex/mcp/gateway/README.md +8 -0
  252. workspace_templates/modules/tradingcodex-mcp/files/.tradingcodex/mcp/server.py +27 -0
  253. workspace_templates/modules/tradingcodex-mcp/files/.tradingcodex/mcp/smoke-call.py +18 -0
  254. workspace_templates/modules/tradingcodex-mcp/module.json +24 -0
@@ -0,0 +1,230 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import sys
5
+ import tomllib
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ from tradingcodex_service.application.agents import (
10
+ EXPECTED_SKILLS,
11
+ EXPECTED_SUBAGENTS,
12
+ ROLE_PERMISSION_PROFILES,
13
+ SKILL_SPECS,
14
+ inspect_agent_configuration,
15
+ )
16
+ from tradingcodex_service.application.runtime import (
17
+ read_workspace_manifest,
18
+ ensure_runtime_database,
19
+ tradingcodex_db_path,
20
+ )
21
+ from tradingcodex_cli.commands.utils import (
22
+ _safe_read,
23
+ list_subagents,
24
+ path_check,
25
+ read_thread_policy,
26
+ text_check,
27
+ )
28
+
29
+ def doctor(root: Path, layer: str) -> None:
30
+ allowed = {"all", "codex-native", "guidance", "enforcement", "information-barrier", "improvement", "mcp", "service"}
31
+ if layer not in allowed:
32
+ raise ValueError(f'unknown layer "{layer}"')
33
+ checks = []
34
+ checks.extend(_central_service_checks(root))
35
+ checks.extend(_guidance_checks(root))
36
+ checks.extend(_enforcement_checks(root))
37
+ checks.extend(_information_barrier_checks(root))
38
+ checks.extend(_improvement_checks(root))
39
+ checks.extend(_mcp_checks(root))
40
+ checks = [check for check in checks if layer == "all" or check["layer"] == layer or (layer == "codex-native" and check.get("codexNative"))]
41
+ failed = 0
42
+ print("TradingCodex Harness\n")
43
+ for check in checks:
44
+ status = "WARN" if check.get("warn") else "PASS" if check["ok"] else "FAIL"
45
+ if not check["ok"] and not check.get("warn"):
46
+ failed += 1
47
+ print(f"{status.ljust(4)} {check['layer'].ljust(20)} {check['name']} - {check['detail']}")
48
+ if failed:
49
+ print(f"TradingCodex doctor failed: {failed} check(s) failed", file=sys.stderr)
50
+ sys.exit(1)
51
+ print("\nTradingCodex doctor passed")
52
+
53
+ def _guidance_checks(root: Path) -> list[dict[str, Any]]:
54
+ return [
55
+ path_check(root, "guidance", "AGENTS.md installed", "AGENTS.md", True),
56
+ text_check(root, "guidance", "head-manager model instructions file configured", ".codex/config.toml", 'model_instructions_file = "prompts/base_instructions/head-manager.md"', True),
57
+ text_check(root, "guidance", "head-manager instructions installed", ".codex/prompts/base_instructions/head-manager.md", "You are the `head-manager` agent", True),
58
+ path_check(root, "guidance", "local CLI wrapper installed", "tcx", False),
59
+ text_check(root, "guidance", "hooks configured", ".codex/hooks.json", "\"PreToolUse\"", True),
60
+ text_check(root, "guidance", "scenario quality gates configured", ".codex/prompts/base_instructions/head-manager.md", "scenario-quality-gates", True),
61
+ text_check(root, "guidance", "investment workflow map configured", ".codex/prompts/base_instructions/head-manager.md", "investment-workflow-map", True),
62
+ text_check(root, "guidance", "handoff quality gate configured", ".codex/prompts/base_instructions/head-manager.md", "## Handoff quality", True),
63
+ {"layer": "guidance", "name": "subagent max_threads matches roster", "ok": read_thread_policy(root)["max_threads"] == len(list_subagents(root)), "codexNative": True, "detail": f"max_threads={read_thread_policy(root)['max_threads']}, subagents={len(list_subagents(root))}"},
64
+ ]
65
+
66
+
67
+ def _central_service_checks(root: Path) -> list[dict[str, Any]]:
68
+ checks: list[dict[str, Any]] = []
69
+ try:
70
+ ensure_runtime_database(root)
71
+ db_path = tradingcodex_db_path()
72
+ checks.append({"layer": "service", "name": "central DB reachable", "ok": db_path.exists(), "codexNative": False, "detail": str(db_path)})
73
+ checks.append({
74
+ "layer": "service",
75
+ "name": "workspace root is provenance only",
76
+ "ok": db_path != (root / ".tradingcodex" / "state" / "tradingcodex.sqlite3").resolve(),
77
+ "codexNative": False,
78
+ "detail": f"workspace={root}",
79
+ })
80
+ manifest = read_workspace_manifest(root)
81
+ has_workspace_id = bool(str(manifest.get("workspace_id", "")).startswith("tcxw_"))
82
+ checks.append({
83
+ "layer": "service",
84
+ "name": "workspace identity manifest installed",
85
+ "ok": has_workspace_id,
86
+ "warn": not has_workspace_id,
87
+ "codexNative": False,
88
+ "detail": str(manifest.get("workspace_id") or "missing .tradingcodex/workspace.json"),
89
+ })
90
+ has_profile = bool((manifest.get("active_profile") or {}).get("portfolio_id"))
91
+ checks.append({
92
+ "layer": "service",
93
+ "name": "active profile configured",
94
+ "ok": has_profile,
95
+ "warn": not has_profile,
96
+ "codexNative": False,
97
+ "detail": (manifest.get("active_profile") or {}).get("label", "missing active profile"),
98
+ })
99
+ from apps.mcp.models import McpToolCall
100
+
101
+ McpToolCall.objects.count()
102
+ checks.append({"layer": "service", "name": "central MCP ledger reachable", "ok": True, "codexNative": False, "detail": "McpToolCall table available"})
103
+ except Exception as exc:
104
+ checks.append({"layer": "service", "name": "central DB reachable", "ok": False, "codexNative": False, "detail": str(exc)})
105
+ export_dirs = ["trading/research", "trading/reports", "trading/audit", "trading/orders", "trading/approvals"]
106
+ for rel in export_dirs:
107
+ path = root / rel
108
+ checks.append({"layer": "service", "name": f"workspace export/cache writable: {rel}", "ok": path.exists() and os.access(path, os.W_OK), "codexNative": False, "detail": "writable" if path.exists() and os.access(path, os.W_OK) else "missing or not writable"})
109
+ return checks
110
+
111
+
112
+ def _enforcement_checks(root: Path) -> list[dict[str, Any]]:
113
+ schemas = ["evidence_pack.schema.json", "fundamental_report.schema.json", "technical_report.schema.json", "news_report.schema.json", "thesis.schema.json", "valuation.schema.json", "portfolio_review.schema.json", "risk_report.schema.json", "order_intent.schema.json", "approval_receipt.schema.json", "execution_result.schema.json", "postmortem_report.schema.json", "audit_event.schema.json"]
114
+ return [
115
+ text_check(root, "enforcement", "command rules configured", ".codex/rules/tradingcodex.rules", "prefix_rule(", True),
116
+ *_codex_mcp_config_checks(root),
117
+ path_check(root, "enforcement", "TradingCodex MCP installed", ".tradingcodex/mcp/server.py", False),
118
+ {"layer": "enforcement", "name": "live broker disabled by default", "ok": not (root / ".tradingcodex" / "mcp" / "adapters" / "live.py").exists(), "detail": "live.py adapter absent"},
119
+ *[path_check(root, "enforcement", f"schema installed: {schema}", f".tradingcodex/schemas/{schema}", False) for schema in schemas],
120
+ ]
121
+
122
+
123
+ def _codex_mcp_config_checks(root: Path) -> list[dict[str, Any]]:
124
+ root_mcp = _read_codex_mcp_config(root / ".codex" / "config.toml")
125
+ execution_mcp = _read_codex_mcp_config(root / ".codex" / "agents" / "execution-operator.toml")
126
+ risk_mcp = _read_codex_mcp_config(root / ".codex" / "agents" / "risk-manager.toml")
127
+ root_tools = set(root_mcp.get("enabled_tools") or [])
128
+ execution_tools = set(execution_mcp.get("enabled_tools") or [])
129
+ risk_tools = set(risk_mcp.get("enabled_tools") or [])
130
+ return [
131
+ {
132
+ "layer": "enforcement",
133
+ "name": "TradingCodex MCP root server configured",
134
+ "ok": bool(root_mcp.get("enabled") is True and root_mcp.get("command") and root_mcp.get("args")),
135
+ "codexNative": True,
136
+ "detail": "enabled with command/args" if root_mcp else "missing mcp_servers.tradingcodex",
137
+ },
138
+ {
139
+ "layer": "enforcement",
140
+ "name": "TradingCodex MCP autostarts local service",
141
+ "ok": root_mcp.get("env", {}).get("TRADINGCODEX_MCP_AUTOSTART_SERVICE") == "1",
142
+ "codexNative": True,
143
+ "detail": "MCP env enables dashboard/service autostart" if root_mcp.get("env", {}).get("TRADINGCODEX_MCP_AUTOSTART_SERVICE") == "1" else "missing TRADINGCODEX_MCP_AUTOSTART_SERVICE=1",
144
+ },
145
+ {
146
+ "layer": "enforcement",
147
+ "name": "head-manager MCP execution submit excluded",
148
+ "ok": "submit_approved_order" not in root_tools,
149
+ "codexNative": True,
150
+ "detail": "root allowlist excludes submit_approved_order" if "submit_approved_order" not in root_tools else "root allowlist includes submit_approved_order",
151
+ },
152
+ {
153
+ "layer": "enforcement",
154
+ "name": "execution-operator MCP execution allowlist configured",
155
+ "ok": "submit_approved_order" in execution_tools,
156
+ "codexNative": True,
157
+ "detail": "execution-operator allowlist includes submit_approved_order" if "submit_approved_order" in execution_tools else "missing submit_approved_order",
158
+ },
159
+ {
160
+ "layer": "enforcement",
161
+ "name": "risk-manager MCP approval allowlist configured",
162
+ "ok": "create_approval_receipt" in risk_tools and "submit_approved_order" not in risk_tools,
163
+ "codexNative": True,
164
+ "detail": "risk-manager can approve but not submit" if "create_approval_receipt" in risk_tools and "submit_approved_order" not in risk_tools else "risk-manager approval/submit allowlist mismatch",
165
+ },
166
+ ]
167
+
168
+
169
+ def _read_codex_mcp_config(path: Path) -> dict[str, Any]:
170
+ try:
171
+ parsed = tomllib.loads(path.read_text(encoding="utf-8"))
172
+ except Exception:
173
+ return {}
174
+ return parsed.get("mcp_servers", {}).get("tradingcodex", {})
175
+
176
+
177
+ def _information_barrier_checks(root: Path) -> list[dict[str, Any]]:
178
+ return [
179
+ path_check(root, "information-barrier", "capabilities installed", ".tradingcodex/capabilities.yaml", False),
180
+ path_check(root, "information-barrier", "information barriers installed", ".tradingcodex/policies/information-barriers.yaml", False),
181
+ path_check(root, "information-barrier", "restricted list installed", ".tradingcodex/policies/restricted-list.yaml", False),
182
+ path_check(root, "information-barrier", "approvals directory installed", "trading/approvals", False),
183
+ ]
184
+
185
+
186
+ def _improvement_checks(root: Path) -> list[dict[str, Any]]:
187
+ checks = []
188
+ for subagent in EXPECTED_SUBAGENTS:
189
+ checks.append(path_check(root, "improvement", f"subagent installed: {subagent}", f".codex/agents/{subagent}.toml", True))
190
+ checks.append(text_check(root, "improvement", f"subagent permissions profile: {subagent}", f".codex/agents/{subagent}.toml", f'default_permissions = "{ROLE_PERMISSION_PROFILES[subagent]}"', True))
191
+ try:
192
+ projected = set(inspect_agent_configuration(root, subagent)["projected_skills"])
193
+ effective = set(inspect_agent_configuration(root, subagent)["effective_skills"])
194
+ checks.append({
195
+ "layer": "improvement",
196
+ "name": f"subagent projected skills current: {subagent}",
197
+ "ok": effective.issubset(projected),
198
+ "codexNative": True,
199
+ "detail": "projected TOML matches effective skills" if effective.issubset(projected) else f"missing {sorted(effective - projected)}",
200
+ })
201
+ except Exception as exc:
202
+ checks.append({"layer": "improvement", "name": f"subagent projected skills current: {subagent}", "ok": False, "codexNative": True, "detail": str(exc)})
203
+ for skill in EXPECTED_SKILLS:
204
+ checks.append(path_check(root, "improvement", f"skill installed: {skill}", _skill_check_path(skill), False))
205
+ checks.append(path_check(root, "improvement", "agent index projected", ".tradingcodex/generated/agent-index.json", False))
206
+ checks.append(path_check(root, "improvement", "skill index projected", ".tradingcodex/generated/skill-index.json", False))
207
+ checks.append(path_check(root, "improvement", "projection manifest projected", ".tradingcodex/generated/projection-manifest.json", False))
208
+ checks.append(text_check(root, "improvement", "no-overlap handoff contract installed", ".agents/skills/manage-subagents/SKILL.md", "Downstream roles consume accepted upstream artifacts", False))
209
+ checks.append(text_check(root, "improvement", "strategy root skill config installed", ".codex/config.toml", "# BEGIN TradingCodex strategy skills", True))
210
+ checks.append(path_check(root, "improvement", "postmortem workflow installed", ".tradingcodex/workflows/postmortem.yaml", False))
211
+ return checks
212
+
213
+
214
+ def _skill_check_path(skill: str) -> str:
215
+ spec = SKILL_SPECS[skill]
216
+ if spec.scope == "subagent_shared":
217
+ return f".tradingcodex/subagents/skills/shared/{skill}/SKILL.md"
218
+ if spec.scope == "subagent_role":
219
+ role = spec.owner_roles[0]
220
+ return f".tradingcodex/subagents/skills/{role}/{skill}/SKILL.md"
221
+ return f".agents/skills/{skill}/SKILL.md"
222
+
223
+
224
+ def _mcp_checks(root: Path) -> list[dict[str, Any]]:
225
+ return [
226
+ path_check(root, "mcp", "stub execution adapter installed", ".tradingcodex/mcp/adapters/stub-execution.py", False),
227
+ path_check(root, "mcp", "paper trading adapter installed", ".tradingcodex/mcp/adapters/paper-trading.py", False),
228
+ path_check(root, "mcp", "live adapter contract installed", ".tradingcodex/mcp/adapters/live-adapter.contract.md", False),
229
+ text_check(root, "mcp", "MCP server instructions installed", ".tradingcodex/mcp/server.py", "approved action gateway", False),
230
+ ]
@@ -0,0 +1,186 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import json
5
+ import sys
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ from tradingcodex_service.application.runtime import ensure_runtime_database, tradingcodex_db_path
10
+ from tradingcodex_service.mcp_runtime import call_mcp_tool
11
+ from tradingcodex_service.mcp_runtime import SAFE_HOME_TOOL_NAMES
12
+ from tradingcodex_cli.commands.utils import _option_value, print_json
13
+
14
+ def mcp(root: Path, argv: list[str]) -> None:
15
+ if not argv or argv[0] in {"--help", "-h", "help"}:
16
+ print_mcp_help()
17
+ return
18
+ if argv and argv[0] == "stdio":
19
+ from tradingcodex_cli.mcp_stdio import run_stdio
20
+ from tradingcodex_cli.service_autostart import maybe_autostart_service
21
+
22
+ maybe_autostart_service(root)
23
+ run_stdio(root)
24
+ return
25
+ if argv and argv[0] in {"ledger", "calls"}:
26
+ mcp_ledger(root, argv[1:])
27
+ return
28
+ if argv and argv[0] == "install-global":
29
+ install_global_mcp(argv[1:])
30
+ return
31
+ if not argv or argv[0] != "call":
32
+ raise ValueError("Usage: tcx mcp call <tool> [--order-intent file] [--approval-receipt file] [--order-id id] | tcx mcp ledger [--tool name] | tcx mcp stdio")
33
+ tool = argv[1] if len(argv) > 1 else ""
34
+ args = argv[2:]
35
+ order_path = _option_value(args, "--order-intent")
36
+ receipt_path = _option_value(args, "--approval-receipt")
37
+ principal_id = _option_value(args, "--principal")
38
+ payload: dict[str, Any] = {}
39
+ if principal_id:
40
+ payload["principal_id"] = principal_id
41
+ payload.update({
42
+ "order_intent_id": _option_value(args, "--order-intent-id"),
43
+ "order_id": _option_value(args, "--order-id"),
44
+ "artifact_id": _option_value(args, "--artifact-id") or _option_value(args, "--id"),
45
+ "artifact_type": _option_value(args, "--artifact-type") or _option_value(args, "--type"),
46
+ "universe": _option_value(args, "--universe"),
47
+ "workflow_type": _option_value(args, "--workflow-type"),
48
+ "symbol": _option_value(args, "--symbol"),
49
+ "title": _option_value(args, "--title"),
50
+ "markdown": _option_value(args, "--markdown"),
51
+ "markdown_path": _option_value(args, "--markdown-file") or _option_value(args, "--file"),
52
+ "source_as_of": _option_value(args, "--source-as-of"),
53
+ "readiness_label": _option_value(args, "--readiness"),
54
+ "query": _option_value(args, "--query") or _option_value(args, "--q"),
55
+ "limit": _option_value(args, "--limit"),
56
+ "provider": _option_value(args, "--provider"),
57
+ "source_category": _option_value(args, "--source-category") or _option_value(args, "--category"),
58
+ "as_of": _option_value(args, "--as-of"),
59
+ })
60
+ payload_json = _option_value(args, "--payload")
61
+ if payload_json:
62
+ parsed_payload = json.loads(payload_json)
63
+ if not isinstance(parsed_payload, dict):
64
+ raise ValueError("--payload must be a JSON object")
65
+ payload["payload"] = parsed_payload
66
+ warnings_json = _option_value(args, "--warnings")
67
+ if warnings_json:
68
+ parsed_warnings = json.loads(warnings_json)
69
+ if not isinstance(parsed_warnings, list):
70
+ raise ValueError("--warnings must be a JSON array")
71
+ payload["warnings"] = parsed_warnings
72
+ payload = {key: value for key, value in payload.items() if value not in (None, "")}
73
+ for raw in args:
74
+ if raw.startswith("{"):
75
+ parsed = json.loads(raw)
76
+ if not isinstance(parsed, dict):
77
+ raise ValueError("positional JSON MCP payload must be an object")
78
+ payload.update(parsed)
79
+ if order_path:
80
+ payload["order_intent"] = json.loads((root / order_path).read_text(encoding="utf-8"))
81
+ if receipt_path:
82
+ payload["approval_receipt"] = json.loads((root / receipt_path).read_text(encoding="utf-8"))
83
+ result = call_mcp_tool(root, tool, payload)
84
+ print_json(result)
85
+ if result.get("status") in {"rejected", "not_supported"} or result.get("decision") == "deny" or result.get("valid") is False:
86
+ sys.exit(1)
87
+
88
+
89
+ def mcp_ledger(root: Path, args: list[str]) -> None:
90
+ ensure_runtime_database(root)
91
+ from apps.mcp.models import McpToolCall
92
+
93
+ queryset = McpToolCall.objects.all()
94
+ tool = _option_value(args, "--tool")
95
+ principal = _option_value(args, "--principal")
96
+ status = _option_value(args, "--status")
97
+ if tool:
98
+ queryset = queryset.filter(tool_name=tool)
99
+ if principal:
100
+ queryset = queryset.filter(principal_id=principal)
101
+ if status:
102
+ queryset = queryset.filter(status=status)
103
+ limit = max(1, min(int(_option_value(args, "--limit") or 20), 200))
104
+ print_json({
105
+ "count": queryset.count(),
106
+ "db_path": str(tradingcodex_db_path()),
107
+ "central_ledger": True,
108
+ "calls": [
109
+ {
110
+ "created_at": call.created_at.isoformat(),
111
+ "tool_name": call.tool_name,
112
+ "principal_id": call.principal_id,
113
+ "status": call.status,
114
+ "workspace_context": call.workspace_context,
115
+ "request_hash": call.request_hash,
116
+ "result_hash": call.result_hash,
117
+ "error": call.error,
118
+ "duration_ms": call.duration_ms,
119
+ }
120
+ for call in queryset[:limit]
121
+ ],
122
+ })
123
+
124
+
125
+ def install_global_mcp(args: list[str]) -> None:
126
+ if "--safe" not in args:
127
+ raise ValueError("Usage: tcx mcp install-global --safe [--config <path>] [--print]")
128
+ config_path = Path(_option_value(args, "--config") or Path.home() / ".codex" / "config.toml").expanduser().resolve()
129
+ block = global_home_mcp_config_block()
130
+ if "--print" in args:
131
+ print(block)
132
+ return
133
+ config_path.parent.mkdir(parents=True, exist_ok=True)
134
+ existing = config_path.read_text(encoding="utf-8") if config_path.exists() else ""
135
+ updated = replace_managed_block(existing, block)
136
+ config_path.write_text(updated, encoding="utf-8")
137
+ print_json({
138
+ "status": "installed",
139
+ "server_name": "tradingcodex-home",
140
+ "config_path": str(config_path),
141
+ "safe_tools": sorted(SAFE_HOME_TOOL_NAMES),
142
+ })
143
+
144
+
145
+ def global_home_mcp_config_block() -> str:
146
+ tools = ",\n ".join(json.dumps(tool) for tool in sorted(SAFE_HOME_TOOL_NAMES))
147
+ return f"""# BEGIN TradingCodex home MCP
148
+ [mcp_servers.tradingcodex-home]
149
+ command = "uvx"
150
+ args = ["--refresh", "--python", "3.14", "--from", "{os.environ.get("TRADINGCODEX_MCP_PACKAGE_SPEC", "tradingcodex")}", "python", "-m", "tradingcodex_cli", "mcp", "stdio"]
151
+ enabled = true
152
+ env = {{ TRADINGCODEX_MCP_SAFE_TOOLS = "1", TRADINGCODEX_MCP_SCOPE = "global-home" }}
153
+ enabled_tools = [
154
+ {tools}
155
+ ]
156
+ default_tools_approval_mode = "prompt"
157
+ startup_timeout_sec = 20
158
+ # END TradingCodex home MCP
159
+ """
160
+
161
+
162
+ def replace_managed_block(existing: str, block: str) -> str:
163
+ start = "# BEGIN TradingCodex home MCP"
164
+ end = "# END TradingCodex home MCP"
165
+ if start in existing and end in existing:
166
+ before, rest = existing.split(start, 1)
167
+ _, after = rest.split(end, 1)
168
+ return before.rstrip() + "\n\n" + block.rstrip() + "\n" + after
169
+ prefix = existing.rstrip() + "\n\n" if existing.strip() else ""
170
+ return prefix + block
171
+
172
+
173
+ def print_mcp_help() -> None:
174
+ print("""TradingCodex MCP
175
+
176
+ Usage:
177
+ ./tcx mcp call <tool> [--principal <role>] [tool args]
178
+ ./tcx mcp ledger [--tool <name>] [--principal <role>] [--status ok]
179
+ ./tcx mcp install-global --safe
180
+ ./tcx mcp stdio
181
+
182
+ Examples:
183
+ ./tcx mcp call create_research_artifact --principal fundamental-analyst --artifact-id note-1 --title "Note" --markdown "# Note" --symbol MSFT
184
+ ./tcx mcp call submit_approved_order --order-intent-id approved-order-id
185
+ ./tcx mcp ledger --tool create_research_artifact --status ok
186
+ """)
@@ -0,0 +1,89 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import sys
5
+ from datetime import datetime, timezone
6
+ from pathlib import Path
7
+
8
+ from tradingcodex_service.application.audit import write_audit_event
9
+ from tradingcodex_service.application.common import sanitize_id, write_json
10
+ from tradingcodex_service.application.orders import create_approval_receipt, validate_order_intent
11
+ from tradingcodex_cli.commands.utils import _option_value, classify_artifact_path, print_json
12
+
13
+ def validate(root: Path, argv: list[str]) -> None:
14
+ if len(argv) < 2 or argv[0] != "order":
15
+ raise ValueError("Usage: tcx validate order <order-intent.json>")
16
+ order = json.loads((root / argv[1]).read_text(encoding="utf-8"))
17
+ result = validate_order_intent(root, {"principal_id": "portfolio-manager", "order_intent": order})
18
+ write_audit_event(root, {"type": "order_intent.validated" if result["valid"] else "order_intent.validation_failed", "payload": result}, "portfolio-manager", "cli")
19
+ print_json(result)
20
+ if not result["valid"]:
21
+ sys.exit(1)
22
+
23
+
24
+ def risk_check(root: Path, argv: list[str]) -> None:
25
+ file_path = argv[0] if argv else _option_value(argv, "--order-intent")
26
+ if not file_path:
27
+ raise ValueError("Usage: tcx risk-check <order-intent.json>")
28
+ order = json.loads((root / file_path).read_text(encoding="utf-8"))
29
+ validation = validate_order_intent(root, {"principal_id": "risk-manager", "order_intent": order})
30
+ result = {"decision": "go" if validation["valid"] else "revise", "order_intent_id": order.get("id"), "reasons": validation["reasons"], "checks": {"schema": not any(reason.startswith("missing ") for reason in validation["reasons"]), "policy": validation["policy"]["decision"]}}
31
+ write_audit_event(root, {"type": "risk_check", "payload": result}, "risk-manager", "cli")
32
+ print_json(result)
33
+ if result["decision"] != "go":
34
+ sys.exit(1)
35
+
36
+
37
+ def approve(root: Path, argv: list[str]) -> None:
38
+ file_path = argv[0] if argv else _option_value(argv, "--order-intent")
39
+ if not file_path:
40
+ raise ValueError("Usage: tcx approve <draft-order-intent.json> [--approved-by risk-manager]")
41
+ order = json.loads((root / file_path).read_text(encoding="utf-8"))
42
+ result = create_approval_receipt(root, order, _option_value(argv, "--approved-by") or "risk-manager", int(_option_value(argv, "--expires-hours") or 24))
43
+ print_json(result)
44
+ if result.get("status") == "rejected":
45
+ sys.exit(1)
46
+
47
+
48
+ def quality_check(root: Path, argv: list[str]) -> None:
49
+ if not argv or argv[0] in {"--help", "-h", "help"}:
50
+ print("Canonical research paths: trading/research/*.evidence.md; trading/reports/<role>/*")
51
+ return
52
+ path = root / argv[0]
53
+ text = path.read_text(encoding="utf-8")
54
+ rel = path.relative_to(root).as_posix()
55
+ result = {"path": rel, "exists": True, "bytes": len(text.encode()), "non_empty": bool(text), "artifact_type": classify_artifact_path(rel), "json_valid": None, "required_fields_missing": [], "warnings": []}
56
+ if rel.endswith(".json"):
57
+ try:
58
+ data = json.loads(text)
59
+ result["json_valid"] = True
60
+ if "order_intent" in rel:
61
+ result["required_fields_missing"] = [field for field in ["id", "symbol", "side", "quantity", "broker", "created_by"] if data.get(field) in (None, "")]
62
+ except Exception:
63
+ result["json_valid"] = False
64
+ result["status"] = "fail" if not result["non_empty"] or result["json_valid"] is False or result["required_fields_missing"] else "pass"
65
+ print_json(result)
66
+ if result["status"] != "pass":
67
+ sys.exit(1)
68
+
69
+
70
+ def audit(root: Path, argv: list[str]) -> None:
71
+ tail = int(_option_value(argv, "--tail") or 20)
72
+ entries = []
73
+ for path in sorted((root / "trading" / "audit").glob("*.jsonl")):
74
+ for line in path.read_text(encoding="utf-8").splitlines():
75
+ if line.strip():
76
+ entries.append((path.name, line))
77
+ for file, line in entries[-tail:]:
78
+ print(f"{file}\t{line}")
79
+
80
+
81
+ def postmortem(root: Path, argv: list[str]) -> None:
82
+ if not argv or argv[0] != "create":
83
+ raise ValueError("Usage: tcx postmortem create --trigger <trigger> [--tail n]")
84
+ trigger = _option_value(argv, "--trigger") or "manual"
85
+ report = {"id": f"postmortem-{datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%S%fZ')}", "created_by": _option_value(argv, "--created-by") or "head-manager", "created_at": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"), "trigger": trigger, "findings": [{"category": "audit-summary", "summary": "Reviewed recent audit events.", "evidence_count": 0}], "next_actions": ["Review rejected or adapter_error events before the next execution-sensitive workflow."]}
86
+ path = root / "trading" / "reports" / "postmortem" / f"{sanitize_id(report['id'])}.postmortem_report.json"
87
+ write_json(path, report)
88
+ write_audit_event(root, {"type": "postmortem.created", "payload": {"id": report["id"], "path": path.relative_to(root).as_posix()}}, "head-manager", "cli")
89
+ print_json({"status": "created", "id": report["id"], "path": path.relative_to(root).as_posix()})
@@ -0,0 +1,21 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ from tradingcodex_service.mcp_runtime import call_mcp_tool
7
+ from tradingcodex_cli.commands.utils import _option_value, print_json
8
+
9
+ def policy(root: Path, argv: list[str]) -> None:
10
+ if not argv or argv[0] != "simulate":
11
+ raise ValueError("Usage: tcx policy simulate --principal <id> --action <action> --resource <resource>")
12
+ args = argv[1:]
13
+ result = call_mcp_tool(root, "simulate_policy", {
14
+ "principal_id": _option_value(args, "--principal") or "unknown",
15
+ "action": _option_value(args, "--action") or "unknown",
16
+ "resource": _option_value(args, "--resource") or "*",
17
+ "require_approval_check": (_option_value(args, "--action") == "mcp.tradingcodex.submit_approved_order"),
18
+ })
19
+ print_json(result)
20
+ if result.get("decision") != "allow":
21
+ sys.exit(1)
@@ -0,0 +1,110 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ from tradingcodex_cli.commands.utils import print_json
8
+ from tradingcodex_service.application.runtime import (
9
+ active_profile_for_workspace,
10
+ default_active_profile,
11
+ ensure_workspace_manifest,
12
+ normalize_active_profile,
13
+ set_active_profile_for_workspace,
14
+ )
15
+ from tradingcodex_service.application.common import sanitize_id
16
+
17
+
18
+ PROFILES_REL = ".tradingcodex/profiles.json"
19
+
20
+
21
+ def profile(root: Path, argv: list[str]) -> None:
22
+ sub = argv[0] if argv else "status"
23
+ args = argv[1:]
24
+ if sub == "status":
25
+ manifest = ensure_workspace_manifest(root)
26
+ print_json({
27
+ "status": "ok",
28
+ "workspace_id": manifest["workspace_id"],
29
+ "active_profile": manifest["active_profile"],
30
+ "execution_mode": manifest["execution_mode"],
31
+ })
32
+ return
33
+ if sub == "list":
34
+ active = active_profile_for_workspace(root)
35
+ print_json({
36
+ "active_profile_id": active["profile_id"],
37
+ "profiles": [
38
+ {**item, "active": item["profile_id"] == active["profile_id"]}
39
+ for item in _list_profiles(root)
40
+ ],
41
+ })
42
+ return
43
+ if sub == "create":
44
+ profile_id = args[0] if args else ""
45
+ if not profile_id:
46
+ raise ValueError("Usage: tcx profile create <profile-id>")
47
+ created = _profile_from_id(profile_id)
48
+ registry = _read_profiles(root)
49
+ registry[created["profile_id"]] = created
50
+ _write_profiles(root, registry)
51
+ print_json({"status": "created", "profile": created})
52
+ return
53
+ if sub == "select":
54
+ profile_id = args[0] if args else ""
55
+ if not profile_id:
56
+ raise ValueError("Usage: tcx profile select <profile-id>")
57
+ registry = _read_profiles(root)
58
+ if profile_id in {"default", "default-paper", "shared"}:
59
+ selected = default_active_profile()
60
+ else:
61
+ selected = registry.get(sanitize_id(profile_id))
62
+ if selected is None:
63
+ raise ValueError(f"unknown profile: {profile_id}. Run `tcx profile create {profile_id}` first.")
64
+ manifest = set_active_profile_for_workspace(root, selected)
65
+ print_json({"status": "selected", "workspace_id": manifest["workspace_id"], "active_profile": manifest["active_profile"]})
66
+ return
67
+ raise ValueError("Usage: tcx profile status|list|create|select")
68
+
69
+
70
+ def _profile_from_id(raw_id: str) -> dict[str, Any]:
71
+ profile_id = sanitize_id(raw_id)
72
+ return normalize_active_profile({
73
+ "profile_id": profile_id,
74
+ "portfolio_id": profile_id,
75
+ "account_id": "local-paper",
76
+ "strategy_id": "default-strategy",
77
+ "label": profile_id,
78
+ "shared": False,
79
+ })
80
+
81
+
82
+ def _profiles_path(root: Path) -> Path:
83
+ return root / PROFILES_REL
84
+
85
+
86
+ def _read_profiles(root: Path) -> dict[str, dict[str, Any]]:
87
+ try:
88
+ raw = json.loads(_profiles_path(root).read_text(encoding="utf-8"))
89
+ except Exception:
90
+ raw = {}
91
+ profiles = raw.get("profiles") if isinstance(raw, dict) else {}
92
+ result: dict[str, dict[str, Any]] = {}
93
+ if isinstance(profiles, dict):
94
+ for key, value in profiles.items():
95
+ if isinstance(value, dict):
96
+ normalized = normalize_active_profile(value)
97
+ result[normalized["profile_id"] or sanitize_id(key)] = normalized
98
+ return result
99
+
100
+
101
+ def _write_profiles(root: Path, profiles: dict[str, dict[str, Any]]) -> None:
102
+ path = _profiles_path(root)
103
+ path.parent.mkdir(parents=True, exist_ok=True)
104
+ path.write_text(json.dumps({"profiles": profiles}, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
105
+
106
+
107
+ def _list_profiles(root: Path) -> list[dict[str, Any]]:
108
+ profiles = {default_active_profile()["profile_id"]: default_active_profile()}
109
+ profiles.update(_read_profiles(root))
110
+ return [profiles[key] for key in sorted(profiles)]