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.
- apps/__init__.py +1 -0
- apps/audit/__init__.py +1 -0
- apps/audit/admin.py +6 -0
- apps/audit/apps.py +8 -0
- apps/audit/migrations/0001_initial.py +35 -0
- apps/audit/migrations/__init__.py +0 -0
- apps/audit/models.py +22 -0
- apps/harness/__init__.py +1 -0
- apps/harness/admin.py +6 -0
- apps/harness/apps.py +8 -0
- apps/harness/migrations/0001_initial.py +35 -0
- apps/harness/migrations/__init__.py +0 -0
- apps/harness/models.py +22 -0
- apps/harness/templatetags/__init__.py +1 -0
- apps/integrations/__init__.py +1 -0
- apps/integrations/admin.py +6 -0
- apps/integrations/apps.py +8 -0
- apps/integrations/migrations/0001_initial.py +29 -0
- apps/integrations/migrations/__init__.py +0 -0
- apps/integrations/models.py +16 -0
- apps/integrations/services.py +31 -0
- apps/mcp/__init__.py +1 -0
- apps/mcp/admin.py +20 -0
- apps/mcp/apps.py +8 -0
- apps/mcp/migrations/0001_initial.py +168 -0
- apps/mcp/migrations/__init__.py +0 -0
- apps/mcp/models.py +154 -0
- apps/mcp/services.py +327 -0
- apps/orders/__init__.py +1 -0
- apps/orders/admin.py +6 -0
- apps/orders/apps.py +8 -0
- apps/orders/migrations/0001_initial.py +79 -0
- apps/orders/migrations/__init__.py +0 -0
- apps/orders/models.py +66 -0
- apps/orders/services.py +107 -0
- apps/policy/__init__.py +1 -0
- apps/policy/admin.py +6 -0
- apps/policy/apps.py +8 -0
- apps/policy/migrations/0001_initial.py +75 -0
- apps/policy/migrations/__init__.py +0 -0
- apps/policy/models.py +61 -0
- apps/policy/services.py +110 -0
- apps/portfolio/__init__.py +1 -0
- apps/portfolio/admin.py +6 -0
- apps/portfolio/apps.py +8 -0
- apps/portfolio/migrations/0001_initial.py +67 -0
- apps/portfolio/migrations/__init__.py +0 -0
- apps/portfolio/models.py +53 -0
- apps/research/__init__.py +1 -0
- apps/research/admin.py +1 -0
- apps/research/apps.py +8 -0
- apps/research/migrations/__init__.py +0 -0
- apps/research/models.py +1 -0
- apps/workflows/__init__.py +1 -0
- apps/workflows/admin.py +6 -0
- apps/workflows/apps.py +8 -0
- apps/workflows/migrations/0001_initial.py +51 -0
- apps/workflows/migrations/__init__.py +0 -0
- apps/workflows/models.py +44 -0
- tradingcodex-0.1.0.dist-info/METADATA +337 -0
- tradingcodex-0.1.0.dist-info/RECORD +254 -0
- tradingcodex-0.1.0.dist-info/WHEEL +5 -0
- tradingcodex-0.1.0.dist-info/entry_points.txt +2 -0
- tradingcodex-0.1.0.dist-info/licenses/LICENSE +202 -0
- tradingcodex-0.1.0.dist-info/licenses/NOTICE +24 -0
- tradingcodex-0.1.0.dist-info/top_level.txt +4 -0
- tradingcodex_cli/__init__.py +1 -0
- tradingcodex_cli/__main__.py +124 -0
- tradingcodex_cli/commands/__init__.py +2 -0
- tradingcodex_cli/commands/bootstrap.py +157 -0
- tradingcodex_cli/commands/db.py +36 -0
- tradingcodex_cli/commands/doctor.py +230 -0
- tradingcodex_cli/commands/mcp.py +186 -0
- tradingcodex_cli/commands/orders.py +89 -0
- tradingcodex_cli/commands/policy.py +21 -0
- tradingcodex_cli/commands/profile.py +110 -0
- tradingcodex_cli/commands/research.py +76 -0
- tradingcodex_cli/commands/skills.py +93 -0
- tradingcodex_cli/commands/strategies.py +67 -0
- tradingcodex_cli/commands/subagents.py +106 -0
- tradingcodex_cli/commands/utils.py +134 -0
- tradingcodex_cli/commands/workspaces.py +53 -0
- tradingcodex_cli/generator.py +234 -0
- tradingcodex_cli/mcp_stdio.py +26 -0
- tradingcodex_cli/service_autostart.py +127 -0
- tradingcodex_service/__init__.py +5 -0
- tradingcodex_service/admin.py +1 -0
- tradingcodex_service/api.py +486 -0
- tradingcodex_service/application/__init__.py +2 -0
- tradingcodex_service/application/agents.py +1470 -0
- tradingcodex_service/application/audit.py +71 -0
- tradingcodex_service/application/common.py +88 -0
- tradingcodex_service/application/components.py +299 -0
- tradingcodex_service/application/harness.py +747 -0
- tradingcodex_service/application/markdown_preview.py +179 -0
- tradingcodex_service/application/orders.py +404 -0
- tradingcodex_service/application/policy.py +150 -0
- tradingcodex_service/application/portfolio.py +166 -0
- tradingcodex_service/application/research.py +356 -0
- tradingcodex_service/application/runtime.py +321 -0
- tradingcodex_service/asgi.py +6 -0
- tradingcodex_service/mcp_http.py +59 -0
- tradingcodex_service/mcp_runtime.py +565 -0
- tradingcodex_service/settings.py +91 -0
- tradingcodex_service/templates/web/activity.html +24 -0
- tradingcodex_service/templates/web/agent_skills.html +111 -0
- tradingcodex_service/templates/web/agents.html +121 -0
- tradingcodex_service/templates/web/base.html +150 -0
- tradingcodex_service/templates/web/dashboard.html +109 -0
- tradingcodex_service/templates/web/fragments/role_inspector.html +81 -0
- tradingcodex_service/templates/web/fragments/starter_prompt.html +24 -0
- tradingcodex_service/templates/web/fragments/topology_canvas.html +85 -0
- tradingcodex_service/templates/web/harness.html +87 -0
- tradingcodex_service/templates/web/mcp_router.html +250 -0
- tradingcodex_service/templates/web/orders.html +68 -0
- tradingcodex_service/templates/web/policy.html +81 -0
- tradingcodex_service/templates/web/portfolio.html +52 -0
- tradingcodex_service/templates/web/research.html +73 -0
- tradingcodex_service/templates/web/starter_prompt.html +40 -0
- tradingcodex_service/templates/web/strategies.html +74 -0
- tradingcodex_service/urls.py +42 -0
- tradingcodex_service/version.py +1 -0
- tradingcodex_service/web.py +885 -0
- tradingcodex_service/wsgi.py +6 -0
- workspace_templates/__init__.py +1 -0
- workspace_templates/modules/audit/files/.tradingcodex/audit/README.md +5 -0
- workspace_templates/modules/audit/files/trading/audit/.gitkeep +1 -0
- workspace_templates/modules/audit/module.json +16 -0
- workspace_templates/modules/codex-base/files/.codex/config.toml +272 -0
- workspace_templates/modules/codex-base/files/.codex/hooks/tradingcodex_hook.py +173 -0
- workspace_templates/modules/codex-base/files/.codex/hooks.json +105 -0
- workspace_templates/modules/codex-base/files/.codex/prompts/base_instructions/head-manager.md +129 -0
- workspace_templates/modules/codex-base/files/.codex/rules/tradingcodex.rules +50 -0
- workspace_templates/modules/codex-base/files/.tradingcodex/capabilities.yaml +56 -0
- workspace_templates/modules/codex-base/files/.tradingcodex/cli.py +16 -0
- workspace_templates/modules/codex-base/files/.tradingcodex/config.yaml +53 -0
- workspace_templates/modules/codex-base/files/.tradingcodex/policies/policy-bindings.yaml +16 -0
- workspace_templates/modules/codex-base/files/.tradingcodex/policies/principals.yaml +17 -0
- workspace_templates/modules/codex-base/files/.tradingcodex/policies/roles.yaml +27 -0
- workspace_templates/modules/codex-base/files/AGENTS.md +56 -0
- workspace_templates/modules/codex-base/files/pyproject.toml +13 -0
- workspace_templates/modules/codex-base/files/tcx +35 -0
- workspace_templates/modules/codex-base/module.json +17 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/policies/access-policies.yaml +39 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/policies/restricted-list.yaml +6 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/approval_receipt.schema.json +14 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/audit_event.schema.json +11 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/evidence_pack.schema.json +16 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/execution_result.schema.json +12 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/fundamental_report.schema.json +15 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/news_report.schema.json +15 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/order_intent.schema.json +30 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/portfolio_review.schema.json +15 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/postmortem_report.schema.json +14 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/risk_report.schema.json +15 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/technical_report.schema.json +15 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/thesis.schema.json +15 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/schemas/valuation.schema.json +15 -0
- workspace_templates/modules/enforcement-guardrails/files/.tradingcodex/scripts/validate-order-intent.py +15 -0
- workspace_templates/modules/enforcement-guardrails/module.json +19 -0
- workspace_templates/modules/fixed-subagents/files/.codex/agents/execution-operator.toml +71 -0
- workspace_templates/modules/fixed-subagents/files/.codex/agents/fundamental-analyst.toml +62 -0
- workspace_templates/modules/fixed-subagents/files/.codex/agents/instrument-analyst.toml +64 -0
- workspace_templates/modules/fixed-subagents/files/.codex/agents/macro-analyst.toml +64 -0
- workspace_templates/modules/fixed-subagents/files/.codex/agents/news-analyst.toml +63 -0
- workspace_templates/modules/fixed-subagents/files/.codex/agents/portfolio-manager.toml +62 -0
- workspace_templates/modules/fixed-subagents/files/.codex/agents/risk-manager.toml +65 -0
- workspace_templates/modules/fixed-subagents/files/.codex/agents/technical-analyst.toml +63 -0
- workspace_templates/modules/fixed-subagents/files/.codex/agents/valuation-analyst.toml +59 -0
- workspace_templates/modules/fixed-subagents/files/.tradingcodex/mainagent/head-manager.yaml +66 -0
- workspace_templates/modules/fixed-subagents/files/.tradingcodex/mainagent/skill-change-proposals/.gitkeep +1 -0
- workspace_templates/modules/fixed-subagents/files/.tradingcodex/mainagent/subagent-registry.yaml +56 -0
- workspace_templates/modules/fixed-subagents/module.json +23 -0
- workspace_templates/modules/guidance-guardrails/files/.tradingcodex/guidance/guardrails.md +17 -0
- workspace_templates/modules/guidance-guardrails/files/.tradingcodex/guidance/task-quality-checklist.md +37 -0
- workspace_templates/modules/guidance-guardrails/module.json +18 -0
- workspace_templates/modules/information-barriers/files/.tradingcodex/policies/information-barriers.yaml +211 -0
- workspace_templates/modules/information-barriers/files/.tradingcodex/secrets.md +9 -0
- workspace_templates/modules/information-barriers/files/trading/approvals/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/market-data/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/orders/approved/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/orders/draft/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/orders/executed/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/orders/rejected/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/portfolio/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/reports/fundamental/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/reports/instrument/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/reports/macro/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/reports/news/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/reports/policy/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/reports/portfolio/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/reports/postmortem/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/reports/risk/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/reports/technical/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/reports/valuation/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/files/trading/research/.gitkeep +1 -0
- workspace_templates/modules/information-barriers/module.json +19 -0
- workspace_templates/modules/paper-trading/files/.tradingcodex/mcp/adapters/paper-trading.py +4 -0
- workspace_templates/modules/paper-trading/module.json +16 -0
- workspace_templates/modules/postmortem/files/.tradingcodex/workflows/postmortem.yaml +12 -0
- workspace_templates/modules/postmortem/module.json +16 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/investment-workflow-map/SKILL.md +106 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/investment-workflow-map/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/manage-optional-skills/SKILL.md +101 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/manage-optional-skills/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/manage-subagents/SKILL.md +140 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/manage-subagents/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/orchestrate-workflow/SKILL.md +140 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/orchestrate-workflow/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/postmortem/SKILL.md +31 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/postmortem/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/scenario-quality-gates/SKILL.md +138 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/scenario-quality-gates/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/strategy-creator/SKILL.md +109 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/strategy-creator/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/synthesize-decision/SKILL.md +54 -0
- workspace_templates/modules/repo-skills/files/.agents/skills/synthesize-decision/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/execution-operator/execute-paper-order/SKILL.md +35 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/execution-operator/execute-paper-order/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/fundamental-analyst/fundamental-analysis/SKILL.md +46 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/fundamental-analyst/fundamental-analysis/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/instrument-analyst/instrument-analysis/SKILL.md +40 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/instrument-analyst/instrument-analysis/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/macro-analyst/macro-analysis/SKILL.md +40 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/macro-analyst/macro-analysis/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/news-analyst/news-analysis/SKILL.md +43 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/news-analyst/news-analysis/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/portfolio-manager/create-order-intent/SKILL.md +46 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/portfolio-manager/create-order-intent/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/portfolio-manager/portfolio-review/SKILL.md +44 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/portfolio-manager/portfolio-review/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/risk-manager/approve-order/SKILL.md +38 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/risk-manager/approve-order/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/risk-manager/policy-review/SKILL.md +43 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/risk-manager/policy-review/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/risk-manager/review-risk/SKILL.md +45 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/risk-manager/review-risk/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/shared/collect-evidence/SKILL.md +46 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/shared/collect-evidence/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/shared/external-data-source-gate/SKILL.md +66 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/shared/external-data-source-gate/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/technical-analyst/technical-analysis/SKILL.md +43 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/technical-analyst/technical-analysis/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/valuation-analyst/valuation-review/SKILL.md +47 -0
- workspace_templates/modules/repo-skills/files/.tradingcodex/subagents/skills/valuation-analyst/valuation-review/agents/openai.yaml +6 -0
- workspace_templates/modules/repo-skills/module.json +37 -0
- workspace_templates/modules/stub-execution/files/.tradingcodex/mcp/adapters/stub-execution.py +4 -0
- workspace_templates/modules/stub-execution/module.json +15 -0
- workspace_templates/modules/tradingcodex-mcp/files/.tradingcodex/mcp/adapters/live-adapter.contract.md +25 -0
- workspace_templates/modules/tradingcodex-mcp/files/.tradingcodex/mcp/enforcer/README.md +5 -0
- workspace_templates/modules/tradingcodex-mcp/files/.tradingcodex/mcp/gateway/README.md +8 -0
- workspace_templates/modules/tradingcodex-mcp/files/.tradingcodex/mcp/server.py +27 -0
- workspace_templates/modules/tradingcodex-mcp/files/.tradingcodex/mcp/smoke-call.py +18 -0
- 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)]
|