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,800 @@
1
+ #!/usr/bin/env python3
2
+ """Render a stable multi-workflow Web3 operator 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 (
12
+ build_execution_bundle,
13
+ resolve_risk_gate,
14
+ resolve_simulation,
15
+ validate_merged_context,
16
+ )
17
+
18
+
19
+ RESEARCH_CONCLUSIONS = (
20
+ "watch",
21
+ "investigate-more",
22
+ "ready-for-risk-gate",
23
+ "avoid-for-now",
24
+ )
25
+ RESEARCH_FRESHNESS_VALUES = ("fresh", "mixed", "stale")
26
+
27
+
28
+ def build_parser() -> argparse.ArgumentParser:
29
+ parser = argparse.ArgumentParser(description="Render a multi-workflow Web3 operator bundle.")
30
+ subparsers = parser.add_subparsers(dest="workflow_kind", required=True)
31
+
32
+ execution = subparsers.add_parser("execution")
33
+ execution.add_argument("--intent", required=True)
34
+ execution.add_argument("--chain", required=True)
35
+ execution.add_argument("--risk-decision", choices=("ALLOW", "WARN", "BLOCK"))
36
+ execution.add_argument("--risk-coverage", choices=("complete", "partial", "missing"))
37
+ execution.add_argument("--risk-input-file", action="append", default=[])
38
+ execution.add_argument(
39
+ "--simulation-status",
40
+ choices=("PASS", "WARN", "FAIL", "NO_SIMULATION"),
41
+ )
42
+ execution.add_argument(
43
+ "--simulation-readiness",
44
+ choices=("ready", "needs-confirmation", "blocked"),
45
+ )
46
+ execution.add_argument("--simulation-venue")
47
+ execution.add_argument("--simulation-input-file", action="append", default=[])
48
+ execution.add_argument("--reason", action="append", default=[])
49
+ execution.add_argument("--follow-up-skill", action="append", default=[])
50
+
51
+ audit = subparsers.add_parser("audit")
52
+ audit.add_argument(
53
+ "--review-state",
54
+ choices=(
55
+ "scope-locked",
56
+ "context-built",
57
+ "evidence-collected",
58
+ "candidate-findings-triaged",
59
+ "confirmed-findings-written",
60
+ "retest-or-residual-risk",
61
+ ),
62
+ required=True,
63
+ )
64
+ audit.add_argument(
65
+ "--static-status",
66
+ choices=("complete", "partial", "blocked", "not-run"),
67
+ default="not-run",
68
+ )
69
+ audit.add_argument(
70
+ "--fuzz-status",
71
+ choices=("ready", "partial", "blocked", "not-run"),
72
+ default="not-run",
73
+ )
74
+ audit.add_argument("--candidate-count", type=int, default=0)
75
+ audit.add_argument("--confirmed-count", type=int, default=0)
76
+ audit.add_argument("--reason", action="append", default=[])
77
+ audit.add_argument("--follow-up-skill", action="append", default=[])
78
+
79
+ research = subparsers.add_parser("research")
80
+ research.add_argument(
81
+ "--conclusion",
82
+ choices=("watch", "investigate-more", "ready-for-risk-gate", "avoid-for-now"),
83
+ required=True,
84
+ )
85
+ research.add_argument("--freshness", choices=("fresh", "mixed", "stale"), default="mixed")
86
+ research.add_argument("--reason", action="append", default=[])
87
+ research.add_argument("--follow-up-skill", action="append", default=[])
88
+
89
+ watch = subparsers.add_parser("watch")
90
+ watch.add_argument(
91
+ "--watch-status",
92
+ choices=("ACTIVE", "TRIGGERED", "CLEARED", "UNAVAILABLE"),
93
+ required=True,
94
+ )
95
+ watch.add_argument(
96
+ "--watch-kind",
97
+ choices=("research", "portfolio", "posttrade", "trace", "venue", "execution"),
98
+ required=True,
99
+ )
100
+ watch.add_argument("--subject", required=True)
101
+ watch.add_argument("--trigger-source", default="")
102
+ watch.add_argument(
103
+ "--severity",
104
+ choices=("low", "medium", "high", "critical"),
105
+ default="medium",
106
+ )
107
+ watch.add_argument("--next-check-hint", default="")
108
+ watch.add_argument("--schedule-hint", default="")
109
+ watch.add_argument("--recommended-adapter", default="")
110
+ watch.add_argument("--reason", action="append", default=[])
111
+ watch.add_argument("--follow-up-skill", action="append", default=[])
112
+
113
+ pretrade = subparsers.add_parser("pretrade")
114
+ pretrade.add_argument("--intent", required=True)
115
+ pretrade.add_argument("--chain", required=True)
116
+ pretrade.add_argument("--research-input-file")
117
+ pretrade.add_argument(
118
+ "--research-conclusion",
119
+ choices=RESEARCH_CONCLUSIONS,
120
+ )
121
+ pretrade.add_argument("--research-freshness", choices=RESEARCH_FRESHNESS_VALUES)
122
+ pretrade.add_argument("--research-question", default="")
123
+ pretrade.add_argument("--research-source", action="append", default=[])
124
+ pretrade.add_argument("--research-evidence", action="append", default=[])
125
+ pretrade.add_argument("--research-cross-check", action="append", default=[])
126
+ pretrade.add_argument("--research-next-step", action="append", default=[])
127
+ pretrade.add_argument("--risk-decision", choices=("ALLOW", "WARN", "BLOCK"))
128
+ pretrade.add_argument("--risk-coverage", choices=("complete", "partial", "missing"))
129
+ pretrade.add_argument("--risk-input-file", action="append", default=[])
130
+ pretrade.add_argument(
131
+ "--simulation-status",
132
+ choices=("PASS", "WARN", "FAIL", "NO_SIMULATION"),
133
+ )
134
+ pretrade.add_argument(
135
+ "--simulation-readiness",
136
+ choices=("ready", "needs-confirmation", "blocked"),
137
+ )
138
+ pretrade.add_argument("--simulation-venue")
139
+ pretrade.add_argument("--simulation-input-file", action="append", default=[])
140
+ pretrade.add_argument("--reason", action="append", default=[])
141
+ pretrade.add_argument("--follow-up-skill", action="append", default=[])
142
+
143
+ posttrade = subparsers.add_parser("posttrade")
144
+ posttrade.add_argument("--venue", required=True)
145
+ posttrade.add_argument(
146
+ "--receipt-status",
147
+ choices=("SUBMITTED", "PENDING", "CONFIRMED", "FILLED", "FAILED", "REVERTED"),
148
+ required=True,
149
+ )
150
+ posttrade.add_argument("--settlement", choices=("evm", "venue"), required=True)
151
+ posttrade.add_argument("--reference-id", default="")
152
+ posttrade.add_argument("--reason", action="append", default=[])
153
+ posttrade.add_argument("--follow-up-skill", action="append", default=[])
154
+
155
+ portfolio = subparsers.add_parser("portfolio")
156
+ portfolio.add_argument(
157
+ "--portfolio-status",
158
+ choices=("COMPLETE", "PARTIAL", "UNAVAILABLE"),
159
+ required=True,
160
+ )
161
+ portfolio.add_argument("--coverage", choices=("complete", "partial", "missing"), default="partial")
162
+ portfolio.add_argument("--source-class", choices=("wallet", "address", "venue"), required=True)
163
+ portfolio.add_argument("--provider", required=True)
164
+ portfolio.add_argument("--venue", default="none")
165
+ portfolio.add_argument("--reason", action="append", default=[])
166
+ portfolio.add_argument("--follow-up-skill", action="append", default=[])
167
+
168
+ trace = subparsers.add_parser("trace")
169
+ trace.add_argument(
170
+ "--trace-status",
171
+ choices=("COMPLETE", "PARTIAL", "UNAVAILABLE"),
172
+ required=True,
173
+ )
174
+ trace.add_argument(
175
+ "--backend-readiness",
176
+ choices=("ready", "partial", "missing"),
177
+ required=True,
178
+ )
179
+ trace.add_argument(
180
+ "--reentry-target",
181
+ choices=("execution", "audit", "research"),
182
+ required=True,
183
+ )
184
+ trace.add_argument("--reason", action="append", default=[])
185
+ trace.add_argument("--follow-up-skill", action="append", default=[])
186
+
187
+ return parser
188
+
189
+
190
+ def parse_research_evidence(entries: list[str]) -> list[dict[str, str]]:
191
+ parsed = []
192
+ for entry in entries:
193
+ adapter, sep, detail = entry.partition(":")
194
+ if sep:
195
+ parsed.append({"adapter": adapter.strip(), "detail": detail.strip()})
196
+ else:
197
+ parsed.append({"adapter": "unknown", "detail": entry})
198
+ return parsed
199
+
200
+
201
+ def default_research_question(intent: str, chain: str) -> str:
202
+ return f"Should intent '{intent}' on {chain} advance to risk gate?"
203
+
204
+
205
+ def load_json_payload(path: str) -> dict[str, object]:
206
+ if path == "-":
207
+ return json.load(sys.stdin)
208
+ return json.loads(Path(path).read_text(encoding="utf-8"))
209
+
210
+
211
+ def extract_research_brief(payload: dict[str, object]) -> dict[str, object]:
212
+ brief = payload.get("research_brief")
213
+ if isinstance(brief, dict):
214
+ return brief
215
+ if {"conclusion", "freshness"}.issubset(payload):
216
+ return payload
217
+ raise ValueError("input payload does not contain research_brief")
218
+
219
+
220
+ def normalize_research_brief(
221
+ brief: dict[str, object],
222
+ intent: str,
223
+ chain: str,
224
+ ) -> dict[str, object]:
225
+ conclusion = str(brief.get("conclusion", ""))
226
+ freshness = str(brief.get("freshness", "mixed"))
227
+ if conclusion not in RESEARCH_CONCLUSIONS:
228
+ raise ValueError(f"unsupported research conclusion: {conclusion}")
229
+ if freshness not in RESEARCH_FRESHNESS_VALUES:
230
+ raise ValueError(f"unsupported research freshness: {freshness}")
231
+
232
+ sources = brief.get("sources_used", [])
233
+ evidence = brief.get("evidence", [])
234
+ cross_checks = brief.get("cross_checks", [])
235
+ next_steps = brief.get("next_steps", [])
236
+ version = brief.get("version", 1)
237
+ return {
238
+ "version": int(version) if isinstance(version, int) else 1,
239
+ "question": str(brief.get("question", "")) or default_research_question(intent, chain),
240
+ "freshness": freshness,
241
+ "sources_used": [str(item) for item in sources if isinstance(item, str)],
242
+ "evidence": [item for item in evidence if isinstance(item, dict)],
243
+ "cross_checks": [str(item) for item in cross_checks if isinstance(item, str)],
244
+ "conclusion": conclusion,
245
+ "next_steps": [str(item) for item in next_steps if isinstance(item, str)],
246
+ }
247
+
248
+
249
+ def build_research_brief(
250
+ intent: str,
251
+ chain: str,
252
+ conclusion: str,
253
+ freshness: str,
254
+ question: str,
255
+ sources: list[str],
256
+ evidence: list[str],
257
+ cross_checks: list[str],
258
+ next_steps: list[str],
259
+ ) -> dict[str, object]:
260
+ return {
261
+ "version": 1,
262
+ "question": question or default_research_question(intent, chain),
263
+ "freshness": freshness,
264
+ "sources_used": sources,
265
+ "evidence": parse_research_evidence(evidence),
266
+ "cross_checks": cross_checks,
267
+ "conclusion": conclusion,
268
+ "next_steps": next_steps,
269
+ }
270
+
271
+
272
+ def resolve_research_brief(
273
+ intent: str,
274
+ chain: str,
275
+ research_input_file: str | None,
276
+ research_conclusion: str | None,
277
+ research_freshness: str | None,
278
+ research_question: str,
279
+ research_source: list[str],
280
+ research_evidence: list[str],
281
+ research_cross_check: list[str],
282
+ research_next_step: list[str],
283
+ ) -> tuple[str, str, dict[str, object]]:
284
+ if research_input_file:
285
+ research_brief = normalize_research_brief(
286
+ extract_research_brief(load_json_payload(research_input_file)),
287
+ intent,
288
+ chain,
289
+ )
290
+ loaded_conclusion = str(research_brief["conclusion"])
291
+ loaded_freshness = str(research_brief["freshness"])
292
+ if research_conclusion and research_conclusion != loaded_conclusion:
293
+ raise ValueError(
294
+ "explicit research conclusion "
295
+ f"{research_conclusion} conflicts with loaded research_brief {loaded_conclusion}"
296
+ )
297
+ if research_freshness and research_freshness != loaded_freshness:
298
+ raise ValueError(
299
+ "explicit research freshness "
300
+ f"{research_freshness} conflicts with loaded research_brief {loaded_freshness}"
301
+ )
302
+ return loaded_conclusion, loaded_freshness, research_brief
303
+
304
+ if not research_conclusion:
305
+ raise ValueError("research conclusion is required unless --research-input-file is provided")
306
+ resolved_freshness = research_freshness or "mixed"
307
+ return (
308
+ research_conclusion,
309
+ resolved_freshness,
310
+ build_research_brief(
311
+ intent=intent,
312
+ chain=chain,
313
+ conclusion=research_conclusion,
314
+ freshness=resolved_freshness,
315
+ question=research_question,
316
+ sources=research_source,
317
+ evidence=research_evidence,
318
+ cross_checks=research_cross_check,
319
+ next_steps=research_next_step,
320
+ ),
321
+ )
322
+
323
+
324
+ def wants_risk_context(args: argparse.Namespace) -> bool:
325
+ return bool(args.risk_input_file or args.risk_decision or args.risk_coverage)
326
+
327
+
328
+ def wants_simulation_context(args: argparse.Namespace) -> bool:
329
+ return bool(
330
+ args.simulation_input_file
331
+ or args.simulation_status
332
+ or args.simulation_readiness
333
+ or args.simulation_venue
334
+ )
335
+
336
+
337
+ def choose_audit_route(
338
+ review_state: str,
339
+ static_status: str,
340
+ fuzz_status: str,
341
+ follow_up_skills: list[str],
342
+ ) -> tuple[str, str, str]:
343
+ if static_status != "complete":
344
+ return "collect-evidence", "web3-static-analysis-runner", "route"
345
+ if fuzz_status != "ready":
346
+ return "collect-evidence", "web3-fuzzing-and-invariants", "route"
347
+ if review_state in ("candidate-findings-triaged",):
348
+ next_adapter = follow_up_skills[0] if follow_up_skills else "web3-audit-orchestrator"
349
+ return "triage-findings", next_adapter, "investigate"
350
+ if review_state in ("confirmed-findings-written", "retest-or-residual-risk"):
351
+ return "report-and-retest", "web3-audit-reporting", "route"
352
+ next_adapter = follow_up_skills[0] if follow_up_skills else "web3-audit-orchestrator"
353
+ return "collect-evidence", next_adapter, "route"
354
+
355
+
356
+ def build_audit_bundle(args: argparse.Namespace) -> dict[str, object]:
357
+ state, next_adapter, execution_mode = choose_audit_route(
358
+ args.review_state,
359
+ args.static_status,
360
+ args.fuzz_status,
361
+ args.follow_up_skill,
362
+ )
363
+ return {
364
+ "version": 1,
365
+ "workflow_kind": "audit",
366
+ "state": state,
367
+ "next_adapter": next_adapter,
368
+ "execution_mode": execution_mode,
369
+ "requires_user_confirmation": False,
370
+ "inputs": {
371
+ "review_state": args.review_state,
372
+ "static_status": args.static_status,
373
+ "fuzz_status": args.fuzz_status,
374
+ "candidate_count": args.candidate_count,
375
+ "confirmed_count": args.confirmed_count,
376
+ },
377
+ "follow_up_skills": args.follow_up_skill,
378
+ "reasons": args.reason,
379
+ }
380
+
381
+
382
+ def build_research_bundle(args: argparse.Namespace) -> dict[str, object]:
383
+ if args.conclusion == "avoid-for-now":
384
+ state = "avoid"
385
+ next_adapter = "none"
386
+ execution_mode = "stop"
387
+ elif args.conclusion == "ready-for-risk-gate":
388
+ state = "ready-to-gate"
389
+ next_adapter = "web3-risk-gate"
390
+ execution_mode = "route"
391
+ elif args.conclusion == "watch":
392
+ state = "watch"
393
+ next_adapter = args.follow_up_skill[0] if args.follow_up_skill else "none"
394
+ execution_mode = "monitor"
395
+ else:
396
+ state = "investigate-more"
397
+ next_adapter = (
398
+ args.follow_up_skill[0]
399
+ if args.follow_up_skill
400
+ else "web3-research-and-market-intel"
401
+ )
402
+ execution_mode = "investigate"
403
+ return {
404
+ "version": 1,
405
+ "workflow_kind": "research",
406
+ "state": state,
407
+ "next_adapter": next_adapter,
408
+ "execution_mode": execution_mode,
409
+ "requires_user_confirmation": False,
410
+ "inputs": {
411
+ "conclusion": args.conclusion,
412
+ "freshness": args.freshness,
413
+ },
414
+ "follow_up_skills": args.follow_up_skill,
415
+ "reasons": args.reason,
416
+ }
417
+
418
+
419
+ def default_watch_adapter(watch_kind: str) -> str:
420
+ return {
421
+ "research": "web3-research-and-market-intel",
422
+ "portfolio": "web3-research-and-market-intel",
423
+ "posttrade": "web3-trace-and-state-analysis",
424
+ "trace": "web3-trace-and-state-analysis",
425
+ "venue": "hyperliquid",
426
+ "execution": "web3-risk-gate",
427
+ }[watch_kind]
428
+
429
+
430
+ def build_watch_bundle(args: argparse.Namespace) -> dict[str, object]:
431
+ next_adapter = (
432
+ args.recommended_adapter
433
+ or (args.follow_up_skill[0] if args.follow_up_skill else "")
434
+ or default_watch_adapter(args.watch_kind)
435
+ )
436
+
437
+ if args.watch_status == "UNAVAILABLE":
438
+ state = "investigate-watch"
439
+ execution_mode = "investigate"
440
+ elif args.watch_status == "CLEARED":
441
+ state = "watch-cleared"
442
+ next_adapter = "none"
443
+ execution_mode = "stop"
444
+ elif args.severity in ("high", "critical"):
445
+ state = "triggered-high-severity"
446
+ execution_mode = "investigate"
447
+ elif args.watch_status == "TRIGGERED":
448
+ state = "watch-triggered"
449
+ execution_mode = "monitor"
450
+ else:
451
+ state = "monitor-watch"
452
+ execution_mode = "monitor"
453
+
454
+ return {
455
+ "version": 1,
456
+ "workflow_kind": "watch",
457
+ "state": state,
458
+ "next_adapter": next_adapter,
459
+ "execution_mode": execution_mode,
460
+ "requires_user_confirmation": False,
461
+ "inputs": {
462
+ "watch_status": args.watch_status,
463
+ "watch_kind": args.watch_kind,
464
+ "subject": args.subject,
465
+ "trigger_source": args.trigger_source,
466
+ "severity": args.severity,
467
+ "next_check_hint": args.next_check_hint,
468
+ "schedule_hint": args.schedule_hint,
469
+ "recommended_adapter": args.recommended_adapter,
470
+ },
471
+ "follow_up_skills": args.follow_up_skill,
472
+ "reasons": args.reason,
473
+ }
474
+
475
+
476
+ def build_pretrade_bundle(
477
+ args: argparse.Namespace,
478
+ research_conclusion: str | None = None,
479
+ research_freshness: str | None = None,
480
+ risk_decision: str | None = None,
481
+ risk_coverage: str | None = None,
482
+ simulation_status: str | None = None,
483
+ simulation_readiness: str | None = None,
484
+ simulation_venue: str | None = None,
485
+ ) -> dict[str, object]:
486
+ resolved_research_conclusion = research_conclusion or args.research_conclusion
487
+ resolved_research_freshness = research_freshness or args.research_freshness or "mixed"
488
+ resolved_risk_decision = risk_decision or args.risk_decision
489
+ resolved_risk_coverage = risk_coverage or args.risk_coverage
490
+ if resolved_risk_coverage is None and resolved_risk_decision is not None:
491
+ resolved_risk_coverage = "complete"
492
+ resolved_simulation_status = simulation_status or args.simulation_status
493
+ resolved_simulation_readiness = simulation_readiness or args.simulation_readiness
494
+ if resolved_simulation_readiness is None and resolved_simulation_status is not None:
495
+ resolved_simulation_readiness = "needs-confirmation"
496
+ resolved_simulation_venue = simulation_venue if simulation_venue is not None else (args.simulation_venue or "")
497
+ if resolved_research_conclusion == "avoid-for-now":
498
+ state = "blocked-by-research"
499
+ next_adapter = "none"
500
+ execution_mode = "stop"
501
+ requires_confirmation = False
502
+ elif resolved_research_conclusion != "ready-for-risk-gate":
503
+ state = "research-incomplete"
504
+ next_adapter = (
505
+ args.follow_up_skill[0]
506
+ if args.follow_up_skill
507
+ else "web3-research-and-market-intel"
508
+ )
509
+ execution_mode = "investigate"
510
+ requires_confirmation = False
511
+ elif resolved_risk_decision == "BLOCK":
512
+ state = "blocked-by-risk"
513
+ next_adapter = "none"
514
+ execution_mode = "stop"
515
+ requires_confirmation = False
516
+ elif resolved_risk_coverage != "complete" or resolved_simulation_status == "NO_SIMULATION":
517
+ state = "investigate-more"
518
+ next_adapter = (
519
+ args.follow_up_skill[0]
520
+ if args.follow_up_skill
521
+ else "web3-trace-and-state-analysis"
522
+ )
523
+ execution_mode = "investigate"
524
+ requires_confirmation = False
525
+ elif (
526
+ resolved_risk_decision == "WARN"
527
+ or resolved_simulation_status == "WARN"
528
+ or resolved_simulation_readiness == "needs-confirmation"
529
+ ):
530
+ state = "needs-confirmation"
531
+ next_adapter = resolved_simulation_venue or "web3-transaction-simulator"
532
+ execution_mode = "confirm_then_route"
533
+ requires_confirmation = True
534
+ else:
535
+ state = "ready-to-route"
536
+ next_adapter = resolved_simulation_venue or "web3-transaction-simulator"
537
+ execution_mode = "route"
538
+ requires_confirmation = False
539
+ return {
540
+ "version": 1,
541
+ "workflow_kind": "pretrade",
542
+ "intent": args.intent,
543
+ "chain": args.chain,
544
+ "state": state,
545
+ "next_adapter": next_adapter,
546
+ "execution_mode": execution_mode,
547
+ "requires_user_confirmation": requires_confirmation,
548
+ "inputs": {
549
+ "research_conclusion": resolved_research_conclusion,
550
+ "research_freshness": resolved_research_freshness,
551
+ "risk_decision": resolved_risk_decision,
552
+ "risk_coverage": resolved_risk_coverage,
553
+ "simulation_status": resolved_simulation_status,
554
+ "simulation_readiness": resolved_simulation_readiness,
555
+ "simulation_venue": resolved_simulation_venue,
556
+ },
557
+ "follow_up_skills": args.follow_up_skill,
558
+ "reasons": args.reason,
559
+ }
560
+
561
+
562
+ def build_posttrade_bundle(args: argparse.Namespace) -> dict[str, object]:
563
+ if args.receipt_status in ("FAILED", "REVERTED"):
564
+ state = "trade-failed"
565
+ execution_mode = "investigate"
566
+ elif args.receipt_status in ("SUBMITTED", "PENDING"):
567
+ state = "monitor-settlement"
568
+ execution_mode = "monitor"
569
+ else:
570
+ state = "verify-outcome"
571
+ execution_mode = "route"
572
+
573
+ if args.follow_up_skill:
574
+ next_adapter = args.follow_up_skill[0]
575
+ elif args.settlement == "evm":
576
+ next_adapter = "web3-trace-and-state-analysis"
577
+ else:
578
+ next_adapter = args.venue
579
+
580
+ return {
581
+ "version": 1,
582
+ "workflow_kind": "posttrade",
583
+ "state": state,
584
+ "next_adapter": next_adapter,
585
+ "execution_mode": execution_mode,
586
+ "requires_user_confirmation": False,
587
+ "inputs": {
588
+ "receipt_status": args.receipt_status,
589
+ "venue": args.venue,
590
+ "settlement": args.settlement,
591
+ "reference_id": args.reference_id,
592
+ },
593
+ "follow_up_skills": args.follow_up_skill,
594
+ "reasons": args.reason,
595
+ }
596
+
597
+
598
+ def build_portfolio_bundle(args: argparse.Namespace) -> dict[str, object]:
599
+ if args.portfolio_status == "UNAVAILABLE" or args.coverage == "missing":
600
+ state = "investigate-portfolio"
601
+ next_adapter = (
602
+ args.follow_up_skill[0]
603
+ if args.follow_up_skill
604
+ else "web3-research-and-market-intel"
605
+ )
606
+ execution_mode = "investigate"
607
+ elif args.source_class == "venue" and args.venue == "hyperliquid":
608
+ state = "venue-position-check"
609
+ next_adapter = "hyperliquid"
610
+ execution_mode = "route"
611
+ elif args.source_class == "wallet":
612
+ state = "monitor-portfolio"
613
+ next_adapter = "debank"
614
+ execution_mode = "route"
615
+ elif args.source_class == "address":
616
+ state = "address-holdings-check"
617
+ next_adapter = "query-address-info"
618
+ execution_mode = "route"
619
+ else:
620
+ state = "investigate-portfolio"
621
+ next_adapter = (
622
+ args.follow_up_skill[0]
623
+ if args.follow_up_skill
624
+ else "web3-research-and-market-intel"
625
+ )
626
+ execution_mode = "investigate"
627
+ return {
628
+ "version": 1,
629
+ "workflow_kind": "portfolio",
630
+ "state": state,
631
+ "next_adapter": next_adapter,
632
+ "execution_mode": execution_mode,
633
+ "requires_user_confirmation": False,
634
+ "inputs": {
635
+ "portfolio_status": args.portfolio_status,
636
+ "coverage": args.coverage,
637
+ "source_class": args.source_class,
638
+ "provider": args.provider,
639
+ "venue": args.venue,
640
+ },
641
+ "follow_up_skills": args.follow_up_skill,
642
+ "reasons": args.reason,
643
+ }
644
+
645
+
646
+ def build_trace_bundle(args: argparse.Namespace) -> dict[str, object]:
647
+ if args.trace_status == "UNAVAILABLE" or args.backend_readiness == "missing":
648
+ state = "investigate-backend"
649
+ next_adapter = args.follow_up_skill[0] if args.follow_up_skill else "evm-mcp-playbook"
650
+ execution_mode = "investigate"
651
+ elif args.reentry_target == "execution":
652
+ state = "re-enter-execution"
653
+ if args.trace_status == "COMPLETE" and args.backend_readiness == "ready":
654
+ next_adapter = (
655
+ args.follow_up_skill[0]
656
+ if args.follow_up_skill
657
+ else "web3-transaction-simulator"
658
+ )
659
+ execution_mode = "route"
660
+ else:
661
+ next_adapter = args.follow_up_skill[0] if args.follow_up_skill else "web3-risk-gate"
662
+ execution_mode = "investigate"
663
+ elif args.reentry_target == "audit":
664
+ state = "re-enter-audit"
665
+ next_adapter = (
666
+ args.follow_up_skill[0]
667
+ if args.follow_up_skill
668
+ else "web3-audit-orchestrator"
669
+ )
670
+ execution_mode = "route" if args.trace_status == "COMPLETE" else "investigate"
671
+ else:
672
+ state = "re-enter-research"
673
+ next_adapter = (
674
+ args.follow_up_skill[0]
675
+ if args.follow_up_skill
676
+ else "web3-research-and-market-intel"
677
+ )
678
+ execution_mode = "route" if args.trace_status == "COMPLETE" else "investigate"
679
+ return {
680
+ "version": 1,
681
+ "workflow_kind": "trace",
682
+ "state": state,
683
+ "next_adapter": next_adapter,
684
+ "execution_mode": execution_mode,
685
+ "requires_user_confirmation": False,
686
+ "inputs": {
687
+ "trace_status": args.trace_status,
688
+ "backend_readiness": args.backend_readiness,
689
+ "reentry_target": args.reentry_target,
690
+ },
691
+ "follow_up_skills": args.follow_up_skill,
692
+ "reasons": args.reason,
693
+ }
694
+
695
+
696
+ def main() -> int:
697
+ parser = build_parser()
698
+ args = parser.parse_args()
699
+ payload: dict[str, object]
700
+ research_brief: dict[str, object] | None = None
701
+ merged_gate: dict[str, object] | None = None
702
+ merged_simulation: dict[str, object] | None = None
703
+ if args.workflow_kind == "execution":
704
+ try:
705
+ risk_decision, risk_coverage, merged_gate = resolve_risk_gate(
706
+ args.risk_decision,
707
+ args.risk_coverage,
708
+ args.risk_input_file,
709
+ )
710
+ simulation_status, simulation_readiness, simulation_venue, merged_simulation = resolve_simulation(
711
+ args.simulation_status,
712
+ args.simulation_readiness,
713
+ args.simulation_venue,
714
+ args.simulation_input_file,
715
+ )
716
+ validate_merged_context(args.intent, args.chain, merged_gate, merged_simulation)
717
+ except ValueError as exc:
718
+ parser.error(str(exc))
719
+ bundle = build_execution_bundle(
720
+ intent=args.intent,
721
+ chain=args.chain,
722
+ risk_decision=risk_decision,
723
+ risk_coverage=risk_coverage,
724
+ simulation_status=simulation_status,
725
+ simulation_readiness=simulation_readiness,
726
+ simulation_venue=simulation_venue,
727
+ reasons=args.reason,
728
+ follow_up_skills=args.follow_up_skill,
729
+ )
730
+ elif args.workflow_kind == "audit":
731
+ bundle = build_audit_bundle(args)
732
+ elif args.workflow_kind == "watch":
733
+ bundle = build_watch_bundle(args)
734
+ elif args.workflow_kind == "pretrade":
735
+ try:
736
+ research_conclusion, research_freshness, research_brief = resolve_research_brief(
737
+ intent=args.intent,
738
+ chain=args.chain,
739
+ research_input_file=args.research_input_file,
740
+ research_conclusion=args.research_conclusion,
741
+ research_freshness=args.research_freshness,
742
+ research_question=args.research_question,
743
+ research_source=args.research_source,
744
+ research_evidence=args.research_evidence,
745
+ research_cross_check=args.research_cross_check,
746
+ research_next_step=args.research_next_step,
747
+ )
748
+ risk_decision = None
749
+ risk_coverage = None
750
+ simulation_status = None
751
+ simulation_readiness = None
752
+ simulation_venue = None
753
+ if research_conclusion == "ready-for-risk-gate" or wants_risk_context(args):
754
+ risk_decision, risk_coverage, merged_gate = resolve_risk_gate(
755
+ args.risk_decision,
756
+ args.risk_coverage,
757
+ args.risk_input_file,
758
+ )
759
+ if research_conclusion == "ready-for-risk-gate" or wants_simulation_context(args):
760
+ simulation_status, simulation_readiness, simulation_venue, merged_simulation = resolve_simulation(
761
+ args.simulation_status,
762
+ args.simulation_readiness,
763
+ args.simulation_venue,
764
+ args.simulation_input_file,
765
+ )
766
+ validate_merged_context(args.intent, args.chain, merged_gate, merged_simulation)
767
+ except ValueError as exc:
768
+ parser.error(str(exc))
769
+ bundle = build_pretrade_bundle(
770
+ args,
771
+ research_conclusion=research_conclusion,
772
+ research_freshness=research_freshness,
773
+ risk_decision=risk_decision,
774
+ risk_coverage=risk_coverage,
775
+ simulation_status=simulation_status,
776
+ simulation_readiness=simulation_readiness,
777
+ simulation_venue=simulation_venue,
778
+ )
779
+ elif args.workflow_kind == "posttrade":
780
+ bundle = build_posttrade_bundle(args)
781
+ elif args.workflow_kind == "portfolio":
782
+ bundle = build_portfolio_bundle(args)
783
+ elif args.workflow_kind == "trace":
784
+ bundle = build_trace_bundle(args)
785
+ else:
786
+ bundle = build_research_bundle(args)
787
+ payload = {"operator_bundle": bundle}
788
+ if research_brief is not None:
789
+ payload["research_brief"] = research_brief
790
+ if merged_gate is not None:
791
+ payload["risk_gate"] = merged_gate
792
+ if merged_simulation is not None:
793
+ payload["simulation"] = merged_simulation
794
+ json.dump(payload, sys.stdout, ensure_ascii=False, indent=2)
795
+ sys.stdout.write("\n")
796
+ return 0
797
+
798
+
799
+ if __name__ == "__main__":
800
+ raise SystemExit(main())