web3skill 0.1.0

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 (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +127 -0
  3. package/dist/archives/web3-audit-orchestrator.skill +0 -0
  4. package/dist/archives/web3-audit-reporting.skill +0 -0
  5. package/dist/archives/web3-fuzzing-and-invariants.skill +0 -0
  6. package/dist/archives/web3-native-operator.skill +0 -0
  7. package/dist/archives/web3-repo-heuristics.skill +0 -0
  8. package/dist/archives/web3-research-and-market-intel.skill +0 -0
  9. package/dist/archives/web3-risk-gate.skill +0 -0
  10. package/dist/archives/web3-service-orchestrator.skill +0 -0
  11. package/dist/archives/web3-static-analysis-runner.skill +0 -0
  12. package/dist/archives/web3-trace-and-state-analysis.skill +0 -0
  13. package/dist/archives/web3-transaction-simulator.skill +0 -0
  14. package/dist/archives/web3-wallet-operator.skill +0 -0
  15. package/dist/manifest.json +170 -0
  16. package/dist/skills/web3-audit-orchestrator/SKILL.md +79 -0
  17. package/dist/skills/web3-audit-orchestrator/references/ADAPTER_CONSUMPTION_MAP.md +15 -0
  18. package/dist/skills/web3-audit-orchestrator/references/OUTPUT_TEMPLATE.md +52 -0
  19. package/dist/skills/web3-audit-orchestrator/references/REVIEW_STATE_MACHINE.md +25 -0
  20. package/dist/skills/web3-audit-orchestrator/scripts/render_audit_review.py +95 -0
  21. package/dist/skills/web3-audit-reporting/SKILL.md +77 -0
  22. package/dist/skills/web3-audit-reporting/references/FINDING_TEMPLATE.md +54 -0
  23. package/dist/skills/web3-audit-reporting/references/REPORT_TEMPLATE.md +58 -0
  24. package/dist/skills/web3-audit-reporting/references/RETEST_TEMPLATE.md +35 -0
  25. package/dist/skills/web3-audit-reporting/references/SEVERITY_RUBRIC.md +75 -0
  26. package/dist/skills/web3-fuzzing-and-invariants/SKILL.md +68 -0
  27. package/dist/skills/web3-fuzzing-and-invariants/references/ADAPTER_CONSUMPTION_MAP.md +14 -0
  28. package/dist/skills/web3-fuzzing-and-invariants/references/OUTPUT_TEMPLATE.md +40 -0
  29. package/dist/skills/web3-fuzzing-and-invariants/references/READINESS_AND_FAILURES.md +25 -0
  30. package/dist/skills/web3-fuzzing-and-invariants/scripts/render_fuzz_summary.py +64 -0
  31. package/dist/skills/web3-native-operator/SKILL.md +218 -0
  32. package/dist/skills/web3-native-operator/references/EXECUTION_BUNDLE_TEMPLATE.md +47 -0
  33. package/dist/skills/web3-native-operator/references/OPERATOR_BUNDLE_TEMPLATE.md +39 -0
  34. package/dist/skills/web3-native-operator/references/POSTTRADE_FOLLOWUP_BUNDLE_TEMPLATE.md +35 -0
  35. package/dist/skills/web3-native-operator/references/POSTTRADE_WATCH_TEMPLATE.md +34 -0
  36. package/dist/skills/web3-native-operator/references/PRETRADE_PACKET_TEMPLATE.md +34 -0
  37. package/dist/skills/web3-native-operator/references/ROUTE_RECIPES.md +140 -0
  38. package/dist/skills/web3-native-operator/references/ROUTING_STATE_MACHINE.md +73 -0
  39. package/dist/skills/web3-native-operator/references/WATCH_CRON_REQUEST_TEMPLATE.md +26 -0
  40. package/dist/skills/web3-native-operator/references/WATCH_FOLLOWUP_BUNDLE_TEMPLATE.md +35 -0
  41. package/dist/skills/web3-native-operator/references/WATCH_HEARTBEAT_TEMPLATE.md +31 -0
  42. package/dist/skills/web3-native-operator/scripts/apply_followup_bundle_to_heartbeat.py +118 -0
  43. package/dist/skills/web3-native-operator/scripts/render_execution_bundle.py +259 -0
  44. package/dist/skills/web3-native-operator/scripts/render_operator_bundle.py +800 -0
  45. package/dist/skills/web3-native-operator/scripts/render_posttrade_followup_bundle.py +118 -0
  46. package/dist/skills/web3-native-operator/scripts/render_posttrade_watch_status.py +125 -0
  47. package/dist/skills/web3-native-operator/scripts/render_pretrade_packet.py +205 -0
  48. package/dist/skills/web3-native-operator/scripts/render_watch_cron_request.py +88 -0
  49. package/dist/skills/web3-native-operator/scripts/render_watch_followup_bundle.py +118 -0
  50. package/dist/skills/web3-native-operator/scripts/render_watch_heartbeat.py +52 -0
  51. package/dist/skills/web3-repo-heuristics/SKILL.md +37 -0
  52. package/dist/skills/web3-repo-heuristics/references/FOUNDRY.md +49 -0
  53. package/dist/skills/web3-repo-heuristics/references/HARDHAT.md +47 -0
  54. package/dist/skills/web3-repo-heuristics/references/VYPER.md +26 -0
  55. package/dist/skills/web3-research-and-market-intel/SKILL.md +138 -0
  56. package/dist/skills/web3-research-and-market-intel/references/ADAPTER_CONSUMPTION_MAP.md +66 -0
  57. package/dist/skills/web3-research-and-market-intel/references/EVIDENCE_QUALITY.md +27 -0
  58. package/dist/skills/web3-research-and-market-intel/references/OUTPUT_TEMPLATE.md +37 -0
  59. package/dist/skills/web3-research-and-market-intel/references/PORTFOLIO_STATUS_TEMPLATE.md +51 -0
  60. package/dist/skills/web3-research-and-market-intel/references/WATCH_STATUS_TEMPLATE.md +39 -0
  61. package/dist/skills/web3-research-and-market-intel/scripts/render_portfolio_status.py +85 -0
  62. package/dist/skills/web3-research-and-market-intel/scripts/render_research_brief.py +58 -0
  63. package/dist/skills/web3-research-and-market-intel/scripts/render_watch_status.py +70 -0
  64. package/dist/skills/web3-risk-gate/SKILL.md +100 -0
  65. package/dist/skills/web3-risk-gate/references/OUTPUT_TEMPLATE.md +72 -0
  66. package/dist/skills/web3-risk-gate/references/SIGNAL_TAXONOMY.md +34 -0
  67. package/dist/skills/web3-risk-gate/scripts/merge_risk_gate_blocks.py +189 -0
  68. package/dist/skills/web3-service-orchestrator/SKILL.md +34 -0
  69. package/dist/skills/web3-static-analysis-runner/SKILL.md +76 -0
  70. package/dist/skills/web3-static-analysis-runner/references/ADAPTER_CONSUMPTION_MAP.md +13 -0
  71. package/dist/skills/web3-static-analysis-runner/references/OUTPUT_TEMPLATE.md +45 -0
  72. package/dist/skills/web3-static-analysis-runner/references/TRIAGE_BUCKETS.md +16 -0
  73. package/dist/skills/web3-static-analysis-runner/scripts/render_static_analysis_summary.py +64 -0
  74. package/dist/skills/web3-trace-and-state-analysis/SKILL.md +74 -0
  75. package/dist/skills/web3-trace-and-state-analysis/references/ADAPTER_CONSUMPTION_MAP.md +27 -0
  76. package/dist/skills/web3-trace-and-state-analysis/references/OUTPUT_TEMPLATE.md +63 -0
  77. package/dist/skills/web3-trace-and-state-analysis/references/TRACE_BACKEND_PREFLIGHT.md +47 -0
  78. package/dist/skills/web3-trace-and-state-analysis/scripts/render_trace_summary.py +99 -0
  79. package/dist/skills/web3-transaction-simulator/SKILL.md +83 -0
  80. package/dist/skills/web3-transaction-simulator/references/OUTPUT_TEMPLATE.md +86 -0
  81. package/dist/skills/web3-transaction-simulator/references/STATUS_AND_FAILURES.md +49 -0
  82. package/dist/skills/web3-transaction-simulator/scripts/merge_simulation_blocks.py +198 -0
  83. package/dist/skills/web3-wallet-operator/SKILL.md +52 -0
  84. package/dist/skills/web3-wallet-operator/references/ACTION_RECIPES.md +56 -0
  85. package/dist/skills/web3-wallet-operator/references/OUTPUT_TEMPLATE.md +43 -0
  86. package/dist/skills/web3-wallet-operator/scripts/render_wallet_operation_plan.py +101 -0
  87. package/index.js +50 -0
  88. package/package.json +36 -0
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env python3
2
+ """Render the full posttrade follow-up bundle from execution receipt fields."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import sys
9
+
10
+ from render_operator_bundle import build_posttrade_bundle
11
+ from render_posttrade_watch_status import build_posttrade_watch_payload
12
+ from render_watch_cron_request import build_cron_request_payload
13
+ from render_watch_heartbeat import build_heartbeat_task
14
+
15
+
16
+ def build_parser() -> argparse.ArgumentParser:
17
+ parser = argparse.ArgumentParser(description="Render a Web3 posttrade follow-up bundle.")
18
+ parser.add_argument("--venue", required=True)
19
+ parser.add_argument(
20
+ "--receipt-status",
21
+ choices=("SUBMITTED", "PENDING", "CONFIRMED", "FILLED", "FAILED", "REVERTED"),
22
+ required=True,
23
+ )
24
+ parser.add_argument("--settlement", choices=("evm", "venue"), required=True)
25
+ parser.add_argument("--reference-id", default="")
26
+ parser.add_argument("--subject", default="")
27
+ parser.add_argument(
28
+ "--severity",
29
+ choices=("low", "medium", "high", "critical"),
30
+ default="",
31
+ )
32
+ parser.add_argument("--next-check-hint", default="")
33
+ parser.add_argument("--schedule-hint", default="")
34
+ parser.add_argument("--recommended-adapter", default="")
35
+ parser.add_argument("--reason", action="append", default=[])
36
+ parser.add_argument("--follow-up-skill", action="append", default=[])
37
+ parser.add_argument("--evidence", action="append", default=[])
38
+ parser.add_argument("--task-note", default="")
39
+ parser.add_argument("--every-seconds", type=int, default=0)
40
+ parser.add_argument("--cron-expr", default="")
41
+ parser.add_argument("--tz", default="")
42
+ parser.add_argument("--at", default="")
43
+ return parser
44
+
45
+
46
+ def maybe_build_cron_request(
47
+ args: argparse.Namespace,
48
+ watch_args: argparse.Namespace,
49
+ ) -> dict[str, object] | None:
50
+ if not (
51
+ args.every_seconds > 0
52
+ or args.cron_expr
53
+ or args.at
54
+ or (watch_args.schedule_hint and watch_args.schedule_hint != "manual")
55
+ ):
56
+ return None
57
+ cron_expr = args.cron_expr or (
58
+ watch_args.schedule_hint if watch_args.schedule_hint and watch_args.schedule_hint != "manual" else ""
59
+ )
60
+ cron_args = argparse.Namespace(
61
+ subject=watch_args.subject,
62
+ watch_kind="posttrade",
63
+ status=watch_args.status,
64
+ severity=watch_args.severity,
65
+ recommended_adapter=watch_args.recommended_adapter,
66
+ next_check_hint=watch_args.next_check_hint,
67
+ message="",
68
+ every_seconds=args.every_seconds,
69
+ cron_expr=cron_expr,
70
+ tz=args.tz,
71
+ at=args.at,
72
+ )
73
+ return build_cron_request_payload(cron_args)["cron_request"]
74
+
75
+
76
+ def build_receipt_summary(args: argparse.Namespace) -> dict[str, str]:
77
+ return {
78
+ "receipt_status": args.receipt_status,
79
+ "venue": args.venue,
80
+ "settlement": args.settlement,
81
+ "reference_id": args.reference_id,
82
+ }
83
+
84
+
85
+ def main() -> int:
86
+ args = build_parser().parse_args()
87
+ operator_bundle = build_posttrade_bundle(args)
88
+ watch_payload = build_posttrade_watch_payload(args)["watch_status"]
89
+ watch_args = argparse.Namespace(
90
+ subject=str(watch_payload["subject"]),
91
+ watch_kind="posttrade",
92
+ watch_status=str(watch_payload["status"]),
93
+ status=str(watch_payload["status"]),
94
+ severity=str(watch_payload["severity"]),
95
+ recommended_adapter=str(watch_payload["recommended_adapter"]),
96
+ next_check_hint=str(watch_payload["next_check_hint"]),
97
+ schedule_hint=str(watch_payload["schedule_hint"]),
98
+ task_note=args.task_note,
99
+ )
100
+ heartbeat_task = build_heartbeat_task(watch_args)
101
+ cron_request = maybe_build_cron_request(args, watch_args)
102
+ payload = {
103
+ "posttrade_followup_bundle": {
104
+ "version": 1,
105
+ "execution_receipt": build_receipt_summary(args),
106
+ "operator_bundle": operator_bundle,
107
+ "watch_status": watch_payload,
108
+ "heartbeat_task": heartbeat_task,
109
+ "cron_request": cron_request,
110
+ }
111
+ }
112
+ json.dump(payload, sys.stdout, ensure_ascii=False, indent=2)
113
+ sys.stdout.write("\n")
114
+ return 0
115
+
116
+
117
+ if __name__ == "__main__":
118
+ raise SystemExit(main())
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env python3
2
+ """Render a watch_status block from a normalized execution_receipt."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import sys
9
+
10
+
11
+ def build_parser() -> argparse.ArgumentParser:
12
+ parser = argparse.ArgumentParser(description="Render a posttrade watch status block.")
13
+ parser.add_argument(
14
+ "--receipt-status",
15
+ choices=("SUBMITTED", "PENDING", "CONFIRMED", "FILLED", "FAILED", "REVERTED"),
16
+ required=True,
17
+ )
18
+ parser.add_argument("--venue", required=True)
19
+ parser.add_argument("--settlement", choices=("evm", "venue"), required=True)
20
+ parser.add_argument("--reference-id", default="")
21
+ parser.add_argument("--subject", default="")
22
+ parser.add_argument(
23
+ "--severity",
24
+ choices=("low", "medium", "high", "critical"),
25
+ default="",
26
+ )
27
+ parser.add_argument("--next-check-hint", default="")
28
+ parser.add_argument("--schedule-hint", default="")
29
+ parser.add_argument("--recommended-adapter", default="")
30
+ parser.add_argument("--evidence", action="append", default=[])
31
+ return parser
32
+
33
+
34
+ def default_subject(args: argparse.Namespace) -> str:
35
+ if args.subject:
36
+ return args.subject
37
+ if args.reference_id and args.settlement == "evm":
38
+ return f"tx:{args.reference_id}"
39
+ if args.reference_id:
40
+ return f"{args.venue}:{args.reference_id}"
41
+ return f"{args.venue}:posttrade"
42
+
43
+
44
+ def default_severity(receipt_status: str) -> str:
45
+ if receipt_status in ("FAILED", "REVERTED"):
46
+ return "high"
47
+ if receipt_status in ("CONFIRMED", "FILLED"):
48
+ return "medium"
49
+ return "medium"
50
+
51
+
52
+ def default_schedule_hint(receipt_status: str) -> str:
53
+ if receipt_status in ("SUBMITTED", "PENDING"):
54
+ return "*/5 * * * *"
55
+ if receipt_status in ("CONFIRMED", "FILLED"):
56
+ return "manual"
57
+ return "manual"
58
+
59
+
60
+ def default_next_check_hint(args: argparse.Namespace) -> str:
61
+ if args.receipt_status in ("SUBMITTED", "PENDING"):
62
+ if args.settlement == "evm":
63
+ return "check receipt confirmations and final token balances after 5 minutes"
64
+ return "refresh venue fill status and open positions after 5 minutes"
65
+ if args.receipt_status in ("CONFIRMED", "FILLED"):
66
+ if args.settlement == "evm":
67
+ return "verify final settlement state, logs, and output balances"
68
+ return "verify final position, fills, and venue balances"
69
+ return "inspect failure path, revert reason, and settlement evidence immediately"
70
+
71
+
72
+ def default_adapter(args: argparse.Namespace) -> str:
73
+ if args.recommended_adapter:
74
+ return args.recommended_adapter
75
+ if args.settlement == "evm":
76
+ return "web3-trace-and-state-analysis"
77
+ return args.venue
78
+
79
+
80
+ def map_watch_status(receipt_status: str) -> str:
81
+ if receipt_status in ("SUBMITTED", "PENDING"):
82
+ return "ACTIVE"
83
+ return "TRIGGERED"
84
+
85
+
86
+ def parse_evidence(entries: list[str], venue: str, receipt_status: str) -> list[dict[str, str]]:
87
+ if not entries:
88
+ return [{"adapter": venue, "detail": f"execution receipt status is {receipt_status}"}]
89
+ parsed: list[dict[str, str]] = []
90
+ for entry in entries:
91
+ adapter, sep, detail = entry.partition(":")
92
+ if sep:
93
+ parsed.append({"adapter": adapter.strip(), "detail": detail.strip()})
94
+ else:
95
+ parsed.append({"adapter": venue, "detail": entry})
96
+ return parsed
97
+
98
+
99
+ def build_posttrade_watch_payload(args: argparse.Namespace) -> dict[str, object]:
100
+ return {
101
+ "watch_status": {
102
+ "version": 1,
103
+ "status": map_watch_status(args.receipt_status),
104
+ "watch_kind": "posttrade",
105
+ "subject": default_subject(args),
106
+ "trigger_source": args.venue,
107
+ "severity": args.severity or default_severity(args.receipt_status),
108
+ "next_check_hint": args.next_check_hint or default_next_check_hint(args),
109
+ "schedule_hint": args.schedule_hint or default_schedule_hint(args.receipt_status),
110
+ "recommended_adapter": default_adapter(args),
111
+ "evidence": parse_evidence(args.evidence, args.venue, args.receipt_status),
112
+ }
113
+ }
114
+
115
+
116
+ def main() -> int:
117
+ args = build_parser().parse_args()
118
+ payload = build_posttrade_watch_payload(args)
119
+ json.dump(payload, sys.stdout, ensure_ascii=False, indent=2)
120
+ sys.stdout.write("\n")
121
+ return 0
122
+
123
+
124
+ if __name__ == "__main__":
125
+ raise SystemExit(main())
@@ -0,0 +1,205 @@
1
+ #!/usr/bin/env python3
2
+ """Render all normalized pretrade artifacts in one bundle."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ from pathlib import Path
9
+ import sys
10
+
11
+ from render_execution_bundle import resolve_risk_gate, resolve_simulation, validate_merged_context
12
+ from render_operator_bundle import (
13
+ RESEARCH_CONCLUSIONS,
14
+ RESEARCH_FRESHNESS_VALUES,
15
+ build_pretrade_bundle,
16
+ resolve_research_brief,
17
+ wants_risk_context,
18
+ wants_simulation_context,
19
+ )
20
+
21
+
22
+ RISK_GATE_SCRIPTS_DIR = (
23
+ Path(__file__).resolve().parents[2] / "web3-risk-gate" / "scripts"
24
+ )
25
+ if str(RISK_GATE_SCRIPTS_DIR) not in sys.path:
26
+ sys.path.insert(0, str(RISK_GATE_SCRIPTS_DIR))
27
+
28
+ from merge_risk_gate_blocks import default_next_steps as default_risk_next_steps
29
+
30
+ SIMULATION_SCRIPTS_DIR = (
31
+ Path(__file__).resolve().parents[2] / "web3-transaction-simulator" / "scripts"
32
+ )
33
+ if str(SIMULATION_SCRIPTS_DIR) not in sys.path:
34
+ sys.path.insert(0, str(SIMULATION_SCRIPTS_DIR))
35
+
36
+ from merge_simulation_blocks import default_next_steps as default_simulation_next_steps
37
+
38
+
39
+ def build_parser() -> argparse.ArgumentParser:
40
+ parser = argparse.ArgumentParser(description="Render a Web3 pretrade packet.")
41
+ parser.add_argument("--intent", required=True)
42
+ parser.add_argument("--chain", required=True)
43
+ parser.add_argument("--research-input-file")
44
+ parser.add_argument("--research-conclusion", choices=RESEARCH_CONCLUSIONS)
45
+ parser.add_argument("--research-freshness", choices=RESEARCH_FRESHNESS_VALUES)
46
+ parser.add_argument("--research-question", default="")
47
+ parser.add_argument("--research-source", action="append", default=[])
48
+ parser.add_argument("--research-evidence", action="append", default=[])
49
+ parser.add_argument("--research-cross-check", action="append", default=[])
50
+ parser.add_argument("--research-next-step", action="append", default=[])
51
+ parser.add_argument("--risk-decision", choices=("ALLOW", "WARN", "BLOCK"))
52
+ parser.add_argument("--risk-coverage", choices=("complete", "partial", "missing"))
53
+ parser.add_argument("--risk-input-file", action="append", default=[])
54
+ parser.add_argument(
55
+ "--simulation-status",
56
+ choices=("PASS", "WARN", "FAIL", "NO_SIMULATION"),
57
+ )
58
+ parser.add_argument(
59
+ "--simulation-readiness",
60
+ choices=("ready", "needs-confirmation", "blocked"),
61
+ )
62
+ parser.add_argument("--simulation-venue")
63
+ parser.add_argument("--simulation-input-file", action="append", default=[])
64
+ parser.add_argument("--reason", action="append", default=[])
65
+ parser.add_argument("--follow-up-skill", action="append", default=[])
66
+ return parser
67
+
68
+
69
+ def build_minimal_risk_gate(
70
+ intent: str,
71
+ chain: str,
72
+ decision: str,
73
+ coverage: str,
74
+ ) -> dict[str, object]:
75
+ return {
76
+ "version": 1,
77
+ "decision": decision,
78
+ "action": intent,
79
+ "chain": chain,
80
+ "coverage": coverage,
81
+ "subject": {},
82
+ "next_steps": default_risk_next_steps(decision, intent, coverage),
83
+ "evidence": [],
84
+ }
85
+
86
+
87
+ def build_minimal_simulation(
88
+ intent: str,
89
+ chain: str,
90
+ status: str,
91
+ readiness: str,
92
+ venue: str,
93
+ ) -> dict[str, object]:
94
+ return {
95
+ "version": 1,
96
+ "status": status,
97
+ "action": intent,
98
+ "venue": venue,
99
+ "chain": chain,
100
+ "readiness": readiness,
101
+ "route": {},
102
+ "next_step": default_simulation_next_steps(status, readiness),
103
+ "evidence": [],
104
+ }
105
+
106
+
107
+ def main() -> int:
108
+ parser = build_parser()
109
+ args = parser.parse_args()
110
+ try:
111
+ research_conclusion, research_freshness, research_brief = resolve_research_brief(
112
+ intent=args.intent,
113
+ chain=args.chain,
114
+ research_input_file=args.research_input_file,
115
+ research_conclusion=args.research_conclusion,
116
+ research_freshness=args.research_freshness,
117
+ research_question=args.research_question,
118
+ research_source=args.research_source,
119
+ research_evidence=args.research_evidence,
120
+ research_cross_check=args.research_cross_check,
121
+ research_next_step=args.research_next_step,
122
+ )
123
+ risk_decision = None
124
+ risk_coverage = None
125
+ merged_gate = None
126
+ if research_conclusion == "ready-for-risk-gate" or wants_risk_context(args):
127
+ risk_decision, risk_coverage, merged_gate = resolve_risk_gate(
128
+ args.risk_decision,
129
+ args.risk_coverage,
130
+ args.risk_input_file,
131
+ )
132
+
133
+ simulation_status = None
134
+ simulation_readiness = None
135
+ simulation_venue = None
136
+ merged_simulation = None
137
+ if research_conclusion == "ready-for-risk-gate" or wants_simulation_context(args):
138
+ simulation_status, simulation_readiness, simulation_venue, merged_simulation = resolve_simulation(
139
+ args.simulation_status,
140
+ args.simulation_readiness,
141
+ args.simulation_venue,
142
+ args.simulation_input_file,
143
+ )
144
+
145
+ validate_merged_context(args.intent, args.chain, merged_gate, merged_simulation)
146
+ except ValueError as exc:
147
+ parser.error(str(exc))
148
+
149
+ operator_args = argparse.Namespace(
150
+ intent=args.intent,
151
+ chain=args.chain,
152
+ research_conclusion=research_conclusion,
153
+ research_freshness=research_freshness,
154
+ risk_decision=args.risk_decision,
155
+ risk_coverage=args.risk_coverage,
156
+ simulation_status=args.simulation_status,
157
+ simulation_readiness=args.simulation_readiness,
158
+ simulation_venue=args.simulation_venue,
159
+ follow_up_skill=args.follow_up_skill,
160
+ reason=args.reason,
161
+ )
162
+ operator_bundle = build_pretrade_bundle(
163
+ operator_args,
164
+ research_conclusion=research_conclusion,
165
+ research_freshness=research_freshness,
166
+ risk_decision=risk_decision,
167
+ risk_coverage=risk_coverage,
168
+ simulation_status=simulation_status,
169
+ simulation_readiness=simulation_readiness,
170
+ simulation_venue=simulation_venue,
171
+ )
172
+
173
+ packet: dict[str, object] = {
174
+ "version": 1,
175
+ "intent": args.intent,
176
+ "chain": args.chain,
177
+ "research_brief": research_brief,
178
+ "operator_bundle": operator_bundle,
179
+ }
180
+ if risk_decision is not None and risk_coverage is not None:
181
+ packet["risk_gate"] = (
182
+ merged_gate
183
+ if merged_gate is not None
184
+ else build_minimal_risk_gate(args.intent, args.chain, risk_decision, risk_coverage)
185
+ )
186
+ if simulation_status is not None and simulation_readiness is not None:
187
+ packet["simulation"] = (
188
+ merged_simulation
189
+ if merged_simulation is not None
190
+ else build_minimal_simulation(
191
+ args.intent,
192
+ args.chain,
193
+ simulation_status,
194
+ simulation_readiness,
195
+ simulation_venue or "",
196
+ )
197
+ )
198
+
199
+ json.dump({"pretrade_packet": packet}, sys.stdout, ensure_ascii=False, indent=2)
200
+ sys.stdout.write("\n")
201
+ return 0
202
+
203
+
204
+ if __name__ == "__main__":
205
+ raise SystemExit(main())
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env python3
2
+ """Render a structured cron request from watch fields."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import sys
9
+
10
+
11
+ def build_parser() -> argparse.ArgumentParser:
12
+ parser = argparse.ArgumentParser(description="Render a watch-driven cron request.")
13
+ parser.add_argument("--subject", required=True)
14
+ parser.add_argument("--watch-kind", required=True)
15
+ parser.add_argument("--status", required=True)
16
+ parser.add_argument("--severity", required=True)
17
+ parser.add_argument("--recommended-adapter", required=True)
18
+ parser.add_argument("--next-check-hint", default="")
19
+ parser.add_argument("--message", default="")
20
+ parser.add_argument("--every-seconds", type=int, default=0)
21
+ parser.add_argument("--cron-expr", default="")
22
+ parser.add_argument("--tz", default="")
23
+ parser.add_argument("--at", default="")
24
+ return parser
25
+
26
+
27
+ def build_message(args: argparse.Namespace) -> str:
28
+ if args.message:
29
+ return args.message
30
+ next_check = args.next_check_hint or "refresh according to the configured schedule"
31
+ return (
32
+ f"Re-run {args.recommended_adapter} for {args.subject} ({args.watch_kind}) and summarize any new evidence. "
33
+ f"Current status is {args.status} with severity {args.severity}. "
34
+ f"Next check hint: {next_check}. "
35
+ "Escalate immediately if severity becomes high or critical."
36
+ )
37
+
38
+
39
+ def schedule_key_count(args: argparse.Namespace) -> int:
40
+ return sum(
41
+ 1
42
+ for active in (
43
+ args.every_seconds > 0,
44
+ bool(args.cron_expr),
45
+ bool(args.at),
46
+ )
47
+ if active
48
+ )
49
+
50
+
51
+ def validate_schedule_args(args: argparse.Namespace) -> None:
52
+ if schedule_key_count(args) != 1:
53
+ raise SystemExit("exactly one of --every-seconds, --cron-expr, or --at is required")
54
+ if args.tz and not args.cron_expr:
55
+ raise SystemExit("--tz can only be used with --cron-expr")
56
+
57
+
58
+ def build_cron_request_payload(args: argparse.Namespace) -> dict[str, object]:
59
+ validate_schedule_args(args)
60
+ payload: dict[str, object] = {
61
+ "cron_request": {
62
+ "action": "add",
63
+ "message": build_message(args),
64
+ }
65
+ }
66
+ request = payload["cron_request"]
67
+ assert isinstance(request, dict)
68
+ if args.every_seconds > 0:
69
+ request["every_seconds"] = args.every_seconds
70
+ elif args.cron_expr:
71
+ request["cron_expr"] = args.cron_expr
72
+ if args.tz:
73
+ request["tz"] = args.tz
74
+ else:
75
+ request["at"] = args.at
76
+ return payload
77
+
78
+
79
+ def main() -> int:
80
+ args = build_parser().parse_args()
81
+ payload = build_cron_request_payload(args)
82
+ json.dump(payload, sys.stdout, ensure_ascii=False, indent=2)
83
+ sys.stdout.write("\n")
84
+ return 0
85
+
86
+
87
+ if __name__ == "__main__":
88
+ raise SystemExit(main())
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env python3
2
+ """Render all downstream watch artifacts in one bundle."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import sys
9
+
10
+ from render_operator_bundle import build_watch_bundle
11
+ from render_watch_cron_request import build_cron_request_payload
12
+ from render_watch_heartbeat import build_heartbeat_task
13
+
14
+
15
+ def build_parser() -> argparse.ArgumentParser:
16
+ parser = argparse.ArgumentParser(description="Render a Web3 watch follow-up bundle.")
17
+ parser.add_argument(
18
+ "--watch-status",
19
+ choices=("ACTIVE", "TRIGGERED", "CLEARED", "UNAVAILABLE"),
20
+ required=True,
21
+ )
22
+ parser.add_argument(
23
+ "--watch-kind",
24
+ choices=("research", "portfolio", "posttrade", "trace", "venue", "execution"),
25
+ required=True,
26
+ )
27
+ parser.add_argument("--subject", required=True)
28
+ parser.add_argument("--trigger-source", default="")
29
+ parser.add_argument(
30
+ "--severity",
31
+ choices=("low", "medium", "high", "critical"),
32
+ default="medium",
33
+ )
34
+ parser.add_argument("--next-check-hint", default="")
35
+ parser.add_argument("--schedule-hint", default="")
36
+ parser.add_argument("--recommended-adapter", default="")
37
+ parser.add_argument("--reason", action="append", default=[])
38
+ parser.add_argument("--follow-up-skill", action="append", default=[])
39
+ parser.add_argument("--task-note", default="")
40
+ parser.add_argument("--every-seconds", type=int, default=0)
41
+ parser.add_argument("--cron-expr", default="")
42
+ parser.add_argument("--tz", default="")
43
+ parser.add_argument("--at", default="")
44
+ return parser
45
+
46
+
47
+ def maybe_build_cron_request(args: argparse.Namespace) -> dict[str, object] | None:
48
+ explicit_args = argparse.Namespace(
49
+ subject=args.subject,
50
+ watch_kind=args.watch_kind,
51
+ status=args.watch_status,
52
+ severity=args.severity,
53
+ recommended_adapter=args.recommended_adapter,
54
+ next_check_hint=args.next_check_hint,
55
+ message="",
56
+ every_seconds=args.every_seconds,
57
+ cron_expr=args.cron_expr,
58
+ tz=args.tz,
59
+ at=args.at,
60
+ )
61
+ if args.every_seconds > 0 or args.cron_expr or args.at:
62
+ return build_cron_request_payload(explicit_args)["cron_request"]
63
+ if args.schedule_hint and args.schedule_hint != "manual":
64
+ cron_args = argparse.Namespace(
65
+ subject=args.subject,
66
+ watch_kind=args.watch_kind,
67
+ status=args.watch_status,
68
+ severity=args.severity,
69
+ recommended_adapter=args.recommended_adapter,
70
+ next_check_hint=args.next_check_hint,
71
+ message="",
72
+ every_seconds=0,
73
+ cron_expr=args.schedule_hint,
74
+ tz=args.tz,
75
+ at="",
76
+ )
77
+ return build_cron_request_payload(cron_args)["cron_request"]
78
+ return None
79
+
80
+
81
+ def build_watch_status(args: argparse.Namespace) -> dict[str, object]:
82
+ return {
83
+ "version": 1,
84
+ "status": args.watch_status,
85
+ "watch_kind": args.watch_kind,
86
+ "subject": args.subject,
87
+ "trigger_source": args.trigger_source,
88
+ "severity": args.severity,
89
+ "next_check_hint": args.next_check_hint,
90
+ "schedule_hint": args.schedule_hint,
91
+ "recommended_adapter": args.recommended_adapter,
92
+ }
93
+
94
+
95
+ def main() -> int:
96
+ args = build_parser().parse_args()
97
+ operator_bundle = build_watch_bundle(args)
98
+ effective_adapter = args.recommended_adapter or str(operator_bundle["next_adapter"])
99
+ normalized_args = argparse.Namespace(**vars(args))
100
+ normalized_args.recommended_adapter = effective_adapter
101
+ heartbeat_task = build_heartbeat_task(normalized_args)
102
+ cron_request = maybe_build_cron_request(normalized_args)
103
+ payload = {
104
+ "watch_followup_bundle": {
105
+ "version": 1,
106
+ "watch_status": build_watch_status(normalized_args),
107
+ "operator_bundle": operator_bundle,
108
+ "heartbeat_task": heartbeat_task,
109
+ "cron_request": cron_request,
110
+ }
111
+ }
112
+ json.dump(payload, sys.stdout, ensure_ascii=False, indent=2)
113
+ sys.stdout.write("\n")
114
+ return 0
115
+
116
+
117
+ if __name__ == "__main__":
118
+ raise SystemExit(main())