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,234 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import shutil
|
|
6
|
+
import sys
|
|
7
|
+
import uuid
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from tradingcodex_service.version import TRADINGCODEX_VERSION
|
|
14
|
+
from tradingcodex_service.application.agents import project_agent_configuration
|
|
15
|
+
from tradingcodex_service.application.components import list_harness_components
|
|
16
|
+
from tradingcodex_service.application.runtime import ensure_workspace_manifest, read_workspace_manifest
|
|
17
|
+
|
|
18
|
+
DEFAULT_MODULE_IDS = [
|
|
19
|
+
"codex-base",
|
|
20
|
+
"fixed-subagents",
|
|
21
|
+
"repo-skills",
|
|
22
|
+
"guidance-guardrails",
|
|
23
|
+
"enforcement-guardrails",
|
|
24
|
+
"information-barriers",
|
|
25
|
+
"audit",
|
|
26
|
+
"tradingcodex-mcp",
|
|
27
|
+
"stub-execution",
|
|
28
|
+
"paper-trading",
|
|
29
|
+
"postmortem",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class Module:
|
|
35
|
+
id: str
|
|
36
|
+
description: str
|
|
37
|
+
dir: Path
|
|
38
|
+
manifest: dict[str, Any]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def repo_root() -> Path:
|
|
42
|
+
return Path(__file__).resolve().parent.parent
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def templates_dir() -> Path:
|
|
46
|
+
return repo_root() / "workspace_templates"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def bootstrap_workspace(project_dir: Path | str, force: bool = False, dry_run: bool = False, module_ids: list[str] | None = None) -> dict[str, Any]:
|
|
50
|
+
target = Path(project_dir).resolve()
|
|
51
|
+
registry = load_module_registry(templates_dir())
|
|
52
|
+
modules = resolve_module_graph(registry, module_ids or DEFAULT_MODULE_IDS)
|
|
53
|
+
subagents = collect_template_subagent_names(modules)
|
|
54
|
+
existing_manifest = read_workspace_manifest(target)
|
|
55
|
+
workspace_id = str(existing_manifest.get("workspace_id") or f"tcxw_{uuid.uuid4().hex}")
|
|
56
|
+
context = {
|
|
57
|
+
"PROJECT_NAME": sanitize_project_name(target.name or "tradingcodex-workspace"),
|
|
58
|
+
"PROJECT_DIR": str(target),
|
|
59
|
+
"WORKSPACE_ID": workspace_id,
|
|
60
|
+
"SOURCE_ROOT": str(repo_root()),
|
|
61
|
+
"PYTHON_EXECUTABLE": sys.executable,
|
|
62
|
+
"GENERATED_AT": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
|
|
63
|
+
"TRADINGCODEX_VERSION": TRADINGCODEX_VERSION,
|
|
64
|
+
"TRADINGCODEX_MCP_PACKAGE_SPEC": os.environ.get("TRADINGCODEX_MCP_PACKAGE_SPEC", "tradingcodex"),
|
|
65
|
+
"SUBAGENT_COUNT": str(len(subagents)),
|
|
66
|
+
}
|
|
67
|
+
result = {
|
|
68
|
+
"targetDir": str(target),
|
|
69
|
+
"workspaceId": workspace_id,
|
|
70
|
+
"modules": [module.id for module in modules],
|
|
71
|
+
"capabilities": collect_capabilities(modules),
|
|
72
|
+
}
|
|
73
|
+
if dry_run:
|
|
74
|
+
return result
|
|
75
|
+
ensure_target_dir(target, force)
|
|
76
|
+
for module in modules:
|
|
77
|
+
files_dir = module.dir / "files"
|
|
78
|
+
if files_dir.exists():
|
|
79
|
+
copy_template_tree(files_dir, target, context)
|
|
80
|
+
ensure_workspace_manifest(target, project_name=context["PROJECT_NAME"], generated_at=context["GENERATED_AT"])
|
|
81
|
+
write_generated_indexes(target, modules, context)
|
|
82
|
+
project_agent_configuration(target, applied_by="bootstrap", generated_at=context["GENERATED_AT"])
|
|
83
|
+
return result
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def load_module_registry(base_templates_dir: Path) -> dict[str, Module]:
|
|
87
|
+
modules_dir = base_templates_dir / "modules"
|
|
88
|
+
registry: dict[str, Module] = {}
|
|
89
|
+
for module_dir in sorted(path for path in modules_dir.iterdir() if path.is_dir()):
|
|
90
|
+
manifest_path = module_dir / "module.json"
|
|
91
|
+
if not manifest_path.exists():
|
|
92
|
+
continue
|
|
93
|
+
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
94
|
+
module_id = manifest["id"]
|
|
95
|
+
if module_id != module_dir.name:
|
|
96
|
+
raise ValueError(f'Module id "{module_id}" does not match directory "{module_dir.name}"')
|
|
97
|
+
registry[module_id] = Module(module_id, manifest.get("description", ""), module_dir, manifest)
|
|
98
|
+
return registry
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def resolve_module_graph(registry: dict[str, Module], requested_ids: list[str]) -> list[Module]:
|
|
102
|
+
resolved: list[Module] = []
|
|
103
|
+
seen: set[str] = set()
|
|
104
|
+
visiting: set[str] = set()
|
|
105
|
+
|
|
106
|
+
def visit(module_id: str, parent_id: str | None = None) -> None:
|
|
107
|
+
if module_id in seen:
|
|
108
|
+
return
|
|
109
|
+
if module_id in visiting:
|
|
110
|
+
raise ValueError(f'Circular module dependency detected at "{module_id}"')
|
|
111
|
+
if module_id not in registry:
|
|
112
|
+
suffix = f' required by "{parent_id}"' if parent_id else ""
|
|
113
|
+
raise ValueError(f'Unknown module "{module_id}"{suffix}')
|
|
114
|
+
visiting.add(module_id)
|
|
115
|
+
module = registry[module_id]
|
|
116
|
+
for dependency in module.manifest.get("requires", {}).get("modules", []):
|
|
117
|
+
visit(dependency, module_id)
|
|
118
|
+
visiting.remove(module_id)
|
|
119
|
+
seen.add(module_id)
|
|
120
|
+
resolved.append(module)
|
|
121
|
+
|
|
122
|
+
for module_id in requested_ids:
|
|
123
|
+
visit(module_id)
|
|
124
|
+
assert_no_conflicts(resolved)
|
|
125
|
+
return resolved
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def collect_capabilities(modules: list[Module]) -> list[str]:
|
|
129
|
+
capabilities: set[str] = set()
|
|
130
|
+
for module in modules:
|
|
131
|
+
capabilities.update(module.manifest.get("provides", {}).get("capabilities", []))
|
|
132
|
+
return sorted(capabilities)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def ensure_target_dir(target: Path, force: bool) -> None:
|
|
136
|
+
target.mkdir(parents=True, exist_ok=True)
|
|
137
|
+
if not force and not target_has_only_bootstrap_files(target):
|
|
138
|
+
raise ValueError(f"Target directory already has files: {target}. Use an empty directory, a git-initialized empty directory, or pass --overwrite to update matching generated workspace paths.")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def target_has_only_bootstrap_files(target: Path) -> bool:
|
|
142
|
+
allowed_names = {".git", ".gitignore", ".gitattributes"}
|
|
143
|
+
return all(child.name in allowed_names for child in target.iterdir())
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def copy_template_tree(source: Path, target: Path, context: dict[str, str]) -> None:
|
|
147
|
+
for item in source.iterdir():
|
|
148
|
+
if item.name in {"__pycache__", ".DS_Store"} or item.suffix in {".pyc", ".pyo"}:
|
|
149
|
+
continue
|
|
150
|
+
destination = target / item.name
|
|
151
|
+
if item.is_dir():
|
|
152
|
+
destination.mkdir(parents=True, exist_ok=True)
|
|
153
|
+
copy_template_tree(item, destination, context)
|
|
154
|
+
continue
|
|
155
|
+
if not item.is_file():
|
|
156
|
+
continue
|
|
157
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
158
|
+
text = item.read_text(encoding="utf-8")
|
|
159
|
+
rendered = render_template(text, context)
|
|
160
|
+
destination.write_text(rendered, encoding="utf-8")
|
|
161
|
+
if rendered.startswith("#!"):
|
|
162
|
+
destination.chmod(0o755)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def write_generated_indexes(target: Path, modules: list[Module], context: dict[str, str]) -> None:
|
|
166
|
+
generated_dir = target / ".tradingcodex" / "generated"
|
|
167
|
+
generated_dir.mkdir(parents=True, exist_ok=True)
|
|
168
|
+
lock = {
|
|
169
|
+
"generated_at": context["GENERATED_AT"],
|
|
170
|
+
"tradingcodex_version": context["TRADINGCODEX_VERSION"],
|
|
171
|
+
"modules": [
|
|
172
|
+
{
|
|
173
|
+
"id": module.id,
|
|
174
|
+
"description": module.description,
|
|
175
|
+
"capabilities": module.manifest.get("provides", {}).get("capabilities", []),
|
|
176
|
+
}
|
|
177
|
+
for module in modules
|
|
178
|
+
],
|
|
179
|
+
}
|
|
180
|
+
capability_index = {
|
|
181
|
+
"generated_at": context["GENERATED_AT"],
|
|
182
|
+
"capabilities": collect_capabilities(modules),
|
|
183
|
+
}
|
|
184
|
+
component_index = {
|
|
185
|
+
"generated_at": context["GENERATED_AT"],
|
|
186
|
+
"source": "tradingcodex_service.application.components",
|
|
187
|
+
"components": list_harness_components(),
|
|
188
|
+
}
|
|
189
|
+
(generated_dir / "module-lock.json").write_text(json.dumps(lock, indent=2) + "\n", encoding="utf-8")
|
|
190
|
+
(generated_dir / "capability-index.json").write_text(json.dumps(capability_index, indent=2) + "\n", encoding="utf-8")
|
|
191
|
+
(generated_dir / "component-index.json").write_text(json.dumps(component_index, indent=2) + "\n", encoding="utf-8")
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def collect_template_subagent_names(modules: list[Module]) -> list[str]:
|
|
195
|
+
names: list[str] = []
|
|
196
|
+
for module in modules:
|
|
197
|
+
agents_dir = module.dir / "files" / ".codex" / "agents"
|
|
198
|
+
if not agents_dir.exists():
|
|
199
|
+
continue
|
|
200
|
+
for file_path in sorted(agents_dir.glob("*.toml")):
|
|
201
|
+
text = file_path.read_text(encoding="utf-8")
|
|
202
|
+
name = file_path.stem
|
|
203
|
+
for line in text.splitlines():
|
|
204
|
+
if line.startswith("name = "):
|
|
205
|
+
name = line.split('"')[1]
|
|
206
|
+
break
|
|
207
|
+
names.append(name)
|
|
208
|
+
return sorted(names)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def assert_no_conflicts(modules: list[Module]) -> None:
|
|
212
|
+
ids = {module.id for module in modules}
|
|
213
|
+
for module in modules:
|
|
214
|
+
for conflict in module.manifest.get("conflicts", []):
|
|
215
|
+
if conflict in ids:
|
|
216
|
+
raise ValueError(f'Module "{module.id}" conflicts with "{conflict}"')
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def render_template(source: str, context: dict[str, str]) -> str:
|
|
220
|
+
rendered = source
|
|
221
|
+
for key, value in context.items():
|
|
222
|
+
rendered = rendered.replace(f"{{{{{key}}}}}", value)
|
|
223
|
+
return rendered
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def sanitize_project_name(name: str) -> str:
|
|
227
|
+
cleaned = "".join(ch.lower() if ch.isalnum() or ch in "._-" else "-" for ch in name)
|
|
228
|
+
return cleaned.strip("-") or "tradingcodex-workspace"
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def reset_tmp_generated_workspaces(repo: Path | None = None) -> None:
|
|
232
|
+
tmp = (repo or repo_root()) / "tmp"
|
|
233
|
+
for child in ["smoke", "dry-run-smoke", "non-empty-smoke", "scenario-quality", "external-data", "quality-scenarios-20"]:
|
|
234
|
+
shutil.rmtree(tmp / child, ignore_errors=True)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from tradingcodex_service.mcp_runtime import handle_mcp_rpc
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def run_stdio(workspace_root: Path) -> None:
|
|
11
|
+
for line in sys.stdin:
|
|
12
|
+
if not line.strip():
|
|
13
|
+
continue
|
|
14
|
+
try:
|
|
15
|
+
message = json.loads(line)
|
|
16
|
+
except Exception as exc:
|
|
17
|
+
_write({"jsonrpc": "2.0", "id": None, "error": {"code": -32700, "message": str(exc)}})
|
|
18
|
+
continue
|
|
19
|
+
response = handle_mcp_rpc(workspace_root, message)
|
|
20
|
+
if response is not None:
|
|
21
|
+
_write(response)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _write(message: dict) -> None:
|
|
25
|
+
sys.stdout.write(json.dumps(message, ensure_ascii=False) + "\n")
|
|
26
|
+
sys.stdout.flush()
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import json
|
|
5
|
+
import socket
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
import time
|
|
9
|
+
import urllib.request
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from tradingcodex_service.application.runtime import tradingcodex_db_path, tradingcodex_file_lock, tradingcodex_state_dir
|
|
13
|
+
from tradingcodex_service.version import TRADINGCODEX_VERSION
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
DEFAULT_SERVICE_HOST = "127.0.0.1"
|
|
17
|
+
DEFAULT_SERVICE_PORT = 48267
|
|
18
|
+
DEFAULT_SERVICE_ADDR = f"{DEFAULT_SERVICE_HOST}:{DEFAULT_SERVICE_PORT}"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def maybe_autostart_service(workspace_root: Path, source_root: Path | None = None) -> bool:
|
|
22
|
+
if os.environ.get("TRADINGCODEX_MCP_AUTOSTART_SERVICE", "").lower() not in {"1", "true", "yes", "on"}:
|
|
23
|
+
return False
|
|
24
|
+
addr = os.environ.get("TRADINGCODEX_SERVICE_ADDR", DEFAULT_SERVICE_ADDR)
|
|
25
|
+
timeout = float(os.environ.get("TRADINGCODEX_MCP_AUTOSTART_TIMEOUT", "8"))
|
|
26
|
+
return ensure_service_up(workspace_root, addr=addr, source_root=source_root, timeout=timeout)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def ensure_service_up(workspace_root: Path, addr: str = DEFAULT_SERVICE_ADDR, source_root: Path | None = None, timeout: float = 8.0) -> bool:
|
|
30
|
+
host, port = _parse_addr(addr)
|
|
31
|
+
if _tcp_open(host, port):
|
|
32
|
+
_assert_compatible_service(host, port)
|
|
33
|
+
return False
|
|
34
|
+
with tradingcodex_file_lock(f"service-{host}-{port}"):
|
|
35
|
+
if _tcp_open(host, port):
|
|
36
|
+
_assert_compatible_service(host, port)
|
|
37
|
+
return False
|
|
38
|
+
_start_service(workspace_root, addr, source_root)
|
|
39
|
+
deadline = time.monotonic() + timeout
|
|
40
|
+
while time.monotonic() < deadline:
|
|
41
|
+
if _tcp_open(host, port) and _compatible_service(host, port):
|
|
42
|
+
return True
|
|
43
|
+
time.sleep(0.2)
|
|
44
|
+
_assert_compatible_service(host, port)
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def compatible_service_running(addr: str = DEFAULT_SERVICE_ADDR) -> bool:
|
|
49
|
+
host, port = _parse_addr(addr)
|
|
50
|
+
if not _tcp_open(host, port):
|
|
51
|
+
return False
|
|
52
|
+
_assert_compatible_service(host, port)
|
|
53
|
+
return True
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def service_http_url(addr: str = DEFAULT_SERVICE_ADDR) -> str:
|
|
57
|
+
host, port = _parse_addr(addr)
|
|
58
|
+
return f"http://{host}:{port}/"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _start_service(workspace_root: Path, addr: str, source_root: Path | None) -> None:
|
|
62
|
+
run_dir = tradingcodex_state_dir() / "run"
|
|
63
|
+
run_dir.mkdir(parents=True, exist_ok=True)
|
|
64
|
+
log_path = run_dir / "service.log"
|
|
65
|
+
env = os.environ.copy()
|
|
66
|
+
env.setdefault("DJANGO_SETTINGS_MODULE", "tradingcodex_service.settings")
|
|
67
|
+
env.setdefault("TRADINGCODEX_WORKSPACE_ROOT", str(workspace_root.resolve()))
|
|
68
|
+
if source_root:
|
|
69
|
+
current = env.get("PYTHONPATH")
|
|
70
|
+
env["PYTHONPATH"] = str(source_root.resolve()) + (f":{current}" if current else "")
|
|
71
|
+
with log_path.open("ab") as log_handle:
|
|
72
|
+
subprocess.Popen(
|
|
73
|
+
[sys.executable, "-m", "tradingcodex_cli", "service", "runserver", addr, "--noreload"],
|
|
74
|
+
cwd=workspace_root,
|
|
75
|
+
env=env,
|
|
76
|
+
stdin=subprocess.DEVNULL,
|
|
77
|
+
stdout=log_handle,
|
|
78
|
+
stderr=subprocess.STDOUT,
|
|
79
|
+
close_fds=True,
|
|
80
|
+
start_new_session=True,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _parse_addr(addr: str) -> tuple[str, int]:
|
|
85
|
+
if ":" in addr:
|
|
86
|
+
host, port_text = addr.rsplit(":", 1)
|
|
87
|
+
return host or "127.0.0.1", int(port_text)
|
|
88
|
+
return "127.0.0.1", int(addr)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _tcp_open(host: str, port: int) -> bool:
|
|
92
|
+
try:
|
|
93
|
+
with socket.create_connection((host, port), timeout=0.25):
|
|
94
|
+
return True
|
|
95
|
+
except OSError:
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _compatible_service(host: str, port: int) -> bool:
|
|
100
|
+
try:
|
|
101
|
+
_assert_compatible_service(host, port)
|
|
102
|
+
return True
|
|
103
|
+
except Exception:
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _assert_compatible_service(host: str, port: int) -> None:
|
|
108
|
+
health = _service_health(host, port)
|
|
109
|
+
if not health or health.get("service") != "tradingcodex":
|
|
110
|
+
raise RuntimeError(f"{host}:{port} is already in use by a non-TradingCodex service")
|
|
111
|
+
if health.get("version") != TRADINGCODEX_VERSION:
|
|
112
|
+
raise RuntimeError(f"TradingCodex service version mismatch: service={health.get('version')} package={TRADINGCODEX_VERSION}")
|
|
113
|
+
service_db = str(health.get("db_path") or "")
|
|
114
|
+
current_db = str(tradingcodex_db_path())
|
|
115
|
+
if service_db and service_db != current_db:
|
|
116
|
+
raise RuntimeError(f"TradingCodex service DB mismatch: service={service_db} package={current_db}")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _service_health(host: str, port: int) -> dict:
|
|
120
|
+
url = f"http://{host}:{port}/api/health"
|
|
121
|
+
try:
|
|
122
|
+
with urllib.request.urlopen(url, timeout=0.5) as response:
|
|
123
|
+
payload = response.read().decode("utf-8")
|
|
124
|
+
data = json.loads(payload)
|
|
125
|
+
return data if isinstance(data, dict) else {}
|
|
126
|
+
except Exception:
|
|
127
|
+
return {}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Use Django's default admin site without TradingCodex UI overrides."""
|