arkaos 2.17.1 → 2.17.4
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.
- package/VERSION +1 -1
- package/arka/SKILL.md +9 -67
- package/arka/skills/comfyui/SKILL.md +50 -12
- package/arka/skills/conclave/SKILL.md +43 -141
- package/arka/skills/conclave/references/advisors.md +36 -0
- package/arka/skills/human-writing/SKILL.md +15 -100
- package/arka/skills/human-writing/references/forbidden-patterns.md +32 -0
- package/config/hooks/post-tool-use.sh +72 -0
- package/config/hooks/session-start.sh +16 -0
- package/config/hooks/user-prompt-submit.ps1 +2 -2
- package/config/hooks/user-prompt-submit.sh +102 -26
- package/core/agents/__pycache__/behavior_enforcer.cpython-313.pyc +0 -0
- package/core/agents/__pycache__/dna_registry.cpython-313.pyc +0 -0
- package/core/agents/adapters/__pycache__/disc_adapter.cpython-313.pyc +0 -0
- package/core/agents/adapters/disc_adapter.py +149 -0
- package/core/agents/behavior_enforcer.py +255 -0
- package/core/agents/dna_registry.py +235 -0
- package/core/forge/__init__.py +36 -0
- package/core/forge/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/forge/__pycache__/orchestrator.cpython-313.pyc +0 -0
- package/core/forge/__pycache__/runtime_dispatcher.cpython-313.pyc +0 -0
- package/core/forge/orchestrator.py +770 -0
- package/core/forge/runtime_dispatcher.py +465 -0
- package/core/governance/__pycache__/quality_api.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/quality_router.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/review_workflow.cpython-313.pyc +0 -0
- package/core/governance/quality_api.py +280 -0
- package/core/governance/quality_router.py +304 -0
- package/core/governance/review_workflow.py +386 -0
- package/core/memory/__pycache__/compressor.cpython-313.pyc +0 -0
- package/core/memory/__pycache__/rehydrator.cpython-313.pyc +0 -0
- package/core/memory/__pycache__/session_store.cpython-313.pyc +0 -0
- package/core/memory/compressor.py +269 -0
- package/core/memory/rehydrator.py +204 -0
- package/core/memory/session_store.py +256 -0
- package/core/runtime/__pycache__/context_compactor.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/subagent.cpython-313.pyc +0 -0
- package/core/synapse/__init__.py +10 -3
- package/core/synapse/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/synapse/__pycache__/engine.cpython-313.pyc +0 -0
- package/core/synapse/__pycache__/kb_cache.cpython-313.pyc +0 -0
- package/core/synapse/__pycache__/layers.cpython-313.pyc +0 -0
- package/core/synapse/engine.py +27 -16
- package/core/synapse/kb_cache.py +382 -0
- package/core/synapse/layers.py +253 -50
- package/core/sync/__pycache__/self_healing.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/announcer.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/dashboard.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/enforcer.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/rules_registry.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/state.cpython-313.pyc +0 -0
- package/core/workflow/announcer.py +246 -0
- package/core/workflow/dashboard.py +194 -0
- package/core/workflow/enforcer.py +234 -0
- package/core/workflow/recovery.py +196 -0
- package/core/workflow/rules_registry.py +484 -0
- package/core/workflow/session_summary.py +204 -0
- package/core/workflow/state.py +12 -2
- package/departments/dev/SKILL.md +10 -42
- package/departments/dev/skills/agent-design/SKILL.md +6 -26
- package/departments/dev/skills/ci-cd-pipeline/SKILL.md +6 -29
- package/departments/dev/skills/db-schema/SKILL.md +6 -24
- package/departments/dev/skills/dependency-audit/SKILL.md +1 -3
- package/departments/dev/skills/incident/SKILL.md +6 -24
- package/departments/dev/skills/mcp-builder/SKILL.md +4 -17
- package/departments/dev/skills/observability/SKILL.md +6 -29
- package/departments/dev/skills/performance-profiler/SKILL.md +5 -26
- package/departments/dev/skills/rag-architect/SKILL.md +5 -23
- package/departments/dev/skills/release/SKILL.md +4 -17
- package/departments/dev/skills/spec/SKILL.md +47 -148
- package/departments/landing/skills/landing-gen/SKILL.md +3 -15
- package/departments/marketing/skills/cold-email/SKILL.md +5 -17
- package/departments/marketing/skills/programmatic-seo/SKILL.md +6 -21
- package/departments/strategy/skills/board-advisor/SKILL.md +7 -21
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/scripts/synapse-bridge.py +27 -3
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
"""Forge Runtime Dispatcher — multi-runtime Task tool abstraction.
|
|
2
|
+
|
|
3
|
+
Provides a unified interface for dispatching explorer and critic subagents
|
|
4
|
+
across all supported runtimes (Claude Code, Codex CLI, Gemini CLI, Cursor).
|
|
5
|
+
Each runtime gets its own dispatcher implementation.
|
|
6
|
+
|
|
7
|
+
Model routing per tier:
|
|
8
|
+
- shallow (≤30): haiku (cost-optimized, inline execution)
|
|
9
|
+
- standard (31-65): sonnet (balanced cost/quality)
|
|
10
|
+
- deep (66-85): opus (highest quality for complex judgment)
|
|
11
|
+
- super (≥86): opus (full synthesis, highest judgment)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import re
|
|
16
|
+
from abc import ABC, abstractmethod
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from datetime import datetime, timezone
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
from core.forge.schema import (
|
|
22
|
+
ExplorerLens,
|
|
23
|
+
ExplorerApproach,
|
|
24
|
+
CriticVerdict,
|
|
25
|
+
ForgeContext,
|
|
26
|
+
ForgeTier,
|
|
27
|
+
PhaseDeliverable,
|
|
28
|
+
KeyDecision,
|
|
29
|
+
RiskSeverity,
|
|
30
|
+
RejectedElement,
|
|
31
|
+
IdentifiedRisk,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
EXPLORER_PREAMBLE = """You are an expert planning agent within ArkaOS. Your task is to produce a structured execution plan for the following prompt."""
|
|
36
|
+
|
|
37
|
+
CRITIC_PREAMBLE = """You are the Plan Critic within ArkaOS. You have received {n} independent planning approaches for the same prompt. Your job is to synthesize the best plan by combining the strongest elements from each approach."""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
CONSTITUTION_RULES = """CONSTITUTION RULES (non-negotiable):
|
|
41
|
+
- branch-isolation: all work on feature branches, never main
|
|
42
|
+
- spec-driven: spec must exist before implementation
|
|
43
|
+
- solid-clean-code: SOLID + Clean Code on all code
|
|
44
|
+
- mandatory-qa: tests must pass, coverage >= 80%
|
|
45
|
+
- quality-gate: Marta + Eduardo + Francisca must APPROVE before done
|
|
46
|
+
- conventional-commits: all commits follow conventional commit format"""
|
|
47
|
+
|
|
48
|
+
AVAILABLE_DEPARTMENTS = "dev, ops, mkt, brand, fin, strat, pm, saas, landing, content, ecom, kb, sales, lead, community, org"
|
|
49
|
+
|
|
50
|
+
AVAILABLE_AGENTS = """dev: Paulo (lead), Gabriel (architect), Andre (backend), Diana (frontend), Bruno (security), Carlos (devops), Rita (qa), Vasco (dba)
|
|
51
|
+
ops: Daniel (lead), Lucas (automation)
|
|
52
|
+
mkt: Luna (lead), Sofia (growth), Pedro (analytics), Carla (email)
|
|
53
|
+
brand: Valentina (lead), Miguel (visual), Ana (copy), Joao (ux)
|
|
54
|
+
quality (cross-cutting): Marta (CQO), Eduardo (copy), Francisca (tech)"""
|
|
55
|
+
|
|
56
|
+
LENS_INSTRUCTIONS = {
|
|
57
|
+
ExplorerLens.PRAGMATIC: """Your lens: PRAGMATIC
|
|
58
|
+
Question to answer: "What is the simplest thing that works?"
|
|
59
|
+
Principles:
|
|
60
|
+
- Minimum viable approach — reuse existing patterns, avoid over-engineering
|
|
61
|
+
- Maximum reuse of existing ArkaOS skills and workflows
|
|
62
|
+
- Fewer phases is better — collapse where safe to do so
|
|
63
|
+
- Prefer known, proven solutions over novel ones
|
|
64
|
+
- Identify what can be skipped without meaningful quality loss
|
|
65
|
+
Be direct. Challenge gold-plating. Propose the leanest plan that still satisfies all Constitution rules.""",
|
|
66
|
+
ExplorerLens.ARCHITECTURAL: """Your lens: ARCHITECTURAL
|
|
67
|
+
Question to answer: "What is the right way to build this for the long term?"
|
|
68
|
+
Principles:
|
|
69
|
+
- Long-term extensibility and maintainability over short-term speed
|
|
70
|
+
- Proper separation of concerns, clean boundaries
|
|
71
|
+
- Identify technical debt that would be created by a simpler approach
|
|
72
|
+
- Ensure observability, testability, and deployability are first-class
|
|
73
|
+
- Reference DDD, SOLID, Clean Architecture where applicable
|
|
74
|
+
Be thorough. Do not cut corners that will create future problems.""",
|
|
75
|
+
ExplorerLens.CONTRARIAN: """Your lens: CONTRARIAN
|
|
76
|
+
Question to answer: "What is everyone missing or assuming wrongly?"
|
|
77
|
+
Principles:
|
|
78
|
+
- Challenge the premise of the prompt — is this the right problem to solve?
|
|
79
|
+
- Surface hidden dependencies, risks, and blockers others would miss
|
|
80
|
+
- Question every assumed constraint — are they real?
|
|
81
|
+
- Propose an alternative framing if the original prompt is misguided
|
|
82
|
+
- Identify the single biggest risk in the other approaches
|
|
83
|
+
Be adversarial but constructive. Your job is to stress-test assumptions, not to be contrarian for its own sake. You must still produce a valid plan.""",
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass
|
|
88
|
+
class DispatchResult:
|
|
89
|
+
"""Result from a dispatcher call."""
|
|
90
|
+
|
|
91
|
+
success: bool
|
|
92
|
+
output: str = ""
|
|
93
|
+
error: str = ""
|
|
94
|
+
raw_response: str = ""
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@dataclass
|
|
98
|
+
class ExplorerDispatchRequest:
|
|
99
|
+
"""Request to dispatch an explorer subagent."""
|
|
100
|
+
|
|
101
|
+
lens: ExplorerLens
|
|
102
|
+
prompt: str
|
|
103
|
+
context: ForgeContext
|
|
104
|
+
similar_plans: list[str] = field(default_factory=list)
|
|
105
|
+
reused_patterns: list[str] = field(default_factory=list)
|
|
106
|
+
model: str = "sonnet"
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@dataclass
|
|
110
|
+
class CriticDispatchRequest:
|
|
111
|
+
"""Request to dispatch a critic subagent."""
|
|
112
|
+
|
|
113
|
+
original_prompt: str
|
|
114
|
+
approaches: list[ExplorerApproach]
|
|
115
|
+
model: str = "sonnet"
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _tier_to_model(tier: ForgeTier) -> str:
|
|
119
|
+
"""Map tier to default model."""
|
|
120
|
+
mapping = {
|
|
121
|
+
ForgeTier.SHALLOW: "haiku",
|
|
122
|
+
ForgeTier.STANDARD: "sonnet",
|
|
123
|
+
ForgeTier.DEEP: "opus",
|
|
124
|
+
}
|
|
125
|
+
return mapping.get(tier, "sonnet")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _build_explorer_prompt(req: ExplorerDispatchRequest) -> str:
|
|
129
|
+
"""Build the full prompt for an explorer subagent."""
|
|
130
|
+
parts = [
|
|
131
|
+
EXPLORER_PREAMBLE,
|
|
132
|
+
"",
|
|
133
|
+
f"PROMPT: {req.prompt}",
|
|
134
|
+
"",
|
|
135
|
+
"REPO CONTEXT:",
|
|
136
|
+
f" Repo: {req.context.repo}",
|
|
137
|
+
f" Branch: {req.context.branch}",
|
|
138
|
+
f" Commit: {req.context.commit_at_forge}",
|
|
139
|
+
f" ArkaOS: {req.context.arkaos_version}",
|
|
140
|
+
"",
|
|
141
|
+
"OBSIDIAN KNOWLEDGE:",
|
|
142
|
+
f" Similar plans found: {', '.join(req.similar_plans) if req.similar_plans else 'none'}",
|
|
143
|
+
f" Reusable patterns: {', '.join(req.reused_patterns) if req.reused_patterns else 'none'}",
|
|
144
|
+
"",
|
|
145
|
+
CONSTITUTION_RULES,
|
|
146
|
+
"",
|
|
147
|
+
f"AVAILABLE DEPARTMENTS: {AVAILABLE_DEPARTMENTS}",
|
|
148
|
+
"",
|
|
149
|
+
f"AVAILABLE AGENTS:\n{AVAILABLE_AGENTS}",
|
|
150
|
+
"",
|
|
151
|
+
LENS_INSTRUCTIONS[req.lens],
|
|
152
|
+
"",
|
|
153
|
+
"Your output MUST follow this exact format:",
|
|
154
|
+
"EXPLORER: <your lens name>",
|
|
155
|
+
"SUMMARY: <2-3 sentences>",
|
|
156
|
+
"KEY_DECISIONS:",
|
|
157
|
+
" - decision: <what>",
|
|
158
|
+
" rationale: <why>",
|
|
159
|
+
"PHASES:",
|
|
160
|
+
" - name: <phase name>",
|
|
161
|
+
" department: <dept>",
|
|
162
|
+
" agents: [<names>]",
|
|
163
|
+
" deliverables: [<items>]",
|
|
164
|
+
" acceptance_criteria: [<items>]",
|
|
165
|
+
" depends_on: [<phase names>]",
|
|
166
|
+
]
|
|
167
|
+
return "\n".join(parts)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _build_critic_prompt(req: CriticDispatchRequest) -> str:
|
|
171
|
+
"""Build the full prompt for a critic subagent."""
|
|
172
|
+
approach_texts = []
|
|
173
|
+
for i, approach in enumerate(req.approaches, 1):
|
|
174
|
+
decisions = "\n".join(
|
|
175
|
+
f" - decision: {d.decision}\n rationale: {d.rationale}"
|
|
176
|
+
for d in approach.key_decisions
|
|
177
|
+
)
|
|
178
|
+
phases = "\n".join(
|
|
179
|
+
f" - name: {p.name}\n department: {p.department}\n agents: {p.agents}\n deliverables: {p.deliverables}\n acceptance_criteria: {p.acceptance_criteria}\n depends_on: {p.depends_on}"
|
|
180
|
+
for p in approach.phases
|
|
181
|
+
)
|
|
182
|
+
text = f"""APPROACH {i} ({approach.explorer.value}):
|
|
183
|
+
Summary: {approach.summary}
|
|
184
|
+
Key Decisions:
|
|
185
|
+
{decisions}
|
|
186
|
+
Phases:
|
|
187
|
+
{phases}"""
|
|
188
|
+
approach_texts.append(text)
|
|
189
|
+
|
|
190
|
+
parts = [
|
|
191
|
+
CRITIC_PREAMBLE.format(n=len(req.approaches)),
|
|
192
|
+
"",
|
|
193
|
+
f"PROMPT: {req.original_prompt}",
|
|
194
|
+
"",
|
|
195
|
+
"APPROACHES:",
|
|
196
|
+
"\n---\n".join(approach_texts),
|
|
197
|
+
"",
|
|
198
|
+
"RULES:",
|
|
199
|
+
" - You MUST adopt the best elements from multiple approaches (do not just pick one)",
|
|
200
|
+
" - You MUST reject at least 1 element with a clear reason",
|
|
201
|
+
" - You MUST identify at least 1 risk in the final plan",
|
|
202
|
+
" - Confidence score must reflect genuine uncertainty (do not always output 0.9)",
|
|
203
|
+
" - The final phase list MUST include a Quality Gate phase as the penultimate step",
|
|
204
|
+
" - The final phase list MUST include an Obsidian persistence phase as the last step",
|
|
205
|
+
" - All Constitution rules apply to the final plan",
|
|
206
|
+
"",
|
|
207
|
+
"Your output MUST follow this exact format:",
|
|
208
|
+
"CONFIDENCE: <0.0-1.0>",
|
|
209
|
+
"SYNTHESIS:",
|
|
210
|
+
" approach_1: [<adopted elements from approach 1>]",
|
|
211
|
+
" approach_2: [<adopted elements from approach 2>]",
|
|
212
|
+
" approach_3: [<adopted elements from approach 3, if applicable>]",
|
|
213
|
+
"REJECTED:",
|
|
214
|
+
" - element: <what>",
|
|
215
|
+
" reason: <why>",
|
|
216
|
+
"RISKS:",
|
|
217
|
+
" - risk: <description>",
|
|
218
|
+
" severity: high|medium|low",
|
|
219
|
+
" mitigation: <how to address>",
|
|
220
|
+
"FINAL_PHASES:",
|
|
221
|
+
" - name: <phase name>",
|
|
222
|
+
" department: <dept>",
|
|
223
|
+
" agents: [<names>]",
|
|
224
|
+
" deliverables: [<items>]",
|
|
225
|
+
" acceptance_criteria: [<items>]",
|
|
226
|
+
" depends_on: [<phase names>]",
|
|
227
|
+
]
|
|
228
|
+
return "\n".join(parts)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _parse_explorer_output(raw: str, lens: ExplorerLens) -> ExplorerApproach:
|
|
232
|
+
"""Parse structured text output from an explorer subagent."""
|
|
233
|
+
explorer_match = re.search(r"EXPLORER:\s*(\w+)", raw)
|
|
234
|
+
summary_match = re.search(r"SUMMARY:\s*(.+?)(?=KEY_DECISIONS:|$)", raw, re.DOTALL)
|
|
235
|
+
key_decisions = []
|
|
236
|
+
for match in re.finditer(
|
|
237
|
+
r"-\s*decision:\s*(.+?)\n\s*rationale:\s*(.+?)(?=\n\s*-\s*decision:|\nPHASES:|$)",
|
|
238
|
+
raw,
|
|
239
|
+
re.DOTALL,
|
|
240
|
+
):
|
|
241
|
+
key_decisions.append(
|
|
242
|
+
KeyDecision(decision=match.group(1).strip(), rationale=match.group(2).strip())
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
phases = []
|
|
246
|
+
phase_blocks = re.split(r"(?=^\s*-\s*name:)", raw, flags=re.MULTILINE)
|
|
247
|
+
for block in phase_blocks[1:]:
|
|
248
|
+
name_match = re.search(r"name:\s*(.+?)(?=\n|$)", block)
|
|
249
|
+
dept_match = re.search(r"department:\s*(\w+)", block)
|
|
250
|
+
agents_match = re.findall(r"agents:\s*\[(.+?)\]", block)
|
|
251
|
+
deliverables_match = re.findall(r"deliverables:\s*\[(.+?)\]", block)
|
|
252
|
+
criteria_match = re.findall(r"acceptance_criteria:\s*\[(.+?)\]", block)
|
|
253
|
+
depends_match = re.findall(r"depends_on:\s*\[(.+?)\]", block)
|
|
254
|
+
if name_match and dept_match:
|
|
255
|
+
phases.append(
|
|
256
|
+
PhaseDeliverable(
|
|
257
|
+
name=name_match.group(1).strip(),
|
|
258
|
+
deliverables=[d.strip() for d in deliverables_match],
|
|
259
|
+
agents=[a.strip() for a in agents_match],
|
|
260
|
+
acceptance_criteria=[c.strip() for c in criteria_match],
|
|
261
|
+
depends_on=[d.strip() for d in depends_match],
|
|
262
|
+
)
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
return ExplorerApproach(
|
|
266
|
+
explorer=lens,
|
|
267
|
+
summary=summary_match.group(1).strip() if summary_match else "",
|
|
268
|
+
key_decisions=key_decisions,
|
|
269
|
+
phases=phases,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _parse_critic_output(raw: str) -> CriticVerdict:
|
|
274
|
+
"""Parse structured text output from a critic subagent."""
|
|
275
|
+
confidence_match = re.search(r"CONFIDENCE:\s*([\d.]+)", raw)
|
|
276
|
+
confidence = float(confidence_match.group(1)) if confidence_match else 0.0
|
|
277
|
+
|
|
278
|
+
synthesis = {}
|
|
279
|
+
synthesis_section = re.search(r"SYNTHESIS:(.+?)(?=REJECTED:|$)", raw, re.DOTALL)
|
|
280
|
+
if synthesis_section:
|
|
281
|
+
for approach_match in re.finditer(
|
|
282
|
+
r"(\w+):\s*\[(.+?)\]", synthesis_section.group(1), re.DOTALL
|
|
283
|
+
):
|
|
284
|
+
items = [i.strip() for i in approach_match.group(2).split(",") if i.strip()]
|
|
285
|
+
synthesis[approach_match.group(1)] = items
|
|
286
|
+
|
|
287
|
+
rejected = []
|
|
288
|
+
for match in re.finditer(
|
|
289
|
+
r"-\s*element:\s*(.+?)\n\s*reason:\s*(.+?)(?=\n\s*-\s*element:|\nRISKS:|$)", raw, re.DOTALL
|
|
290
|
+
):
|
|
291
|
+
rejected.append(
|
|
292
|
+
RejectedElement(element=match.group(1).strip(), reason=match.group(2).strip())
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
risks = []
|
|
296
|
+
for match in re.finditer(
|
|
297
|
+
r"-\s*risk:\s*(.+?)\n\s*severity:\s*(\w+)\n\s*mitigation:\s*(.+?)(?=\n\s*-\s*risk:|$)",
|
|
298
|
+
raw,
|
|
299
|
+
re.DOTALL,
|
|
300
|
+
):
|
|
301
|
+
severity_str = match.group(2).strip().lower()
|
|
302
|
+
severity = (
|
|
303
|
+
RiskSeverity.HIGH
|
|
304
|
+
if severity_str == "high"
|
|
305
|
+
else RiskSeverity.MEDIUM
|
|
306
|
+
if severity_str == "medium"
|
|
307
|
+
else RiskSeverity.LOW
|
|
308
|
+
)
|
|
309
|
+
risks.append(
|
|
310
|
+
IdentifiedRisk(
|
|
311
|
+
risk=match.group(1).strip(), mitigation=match.group(3).strip(), severity=severity
|
|
312
|
+
)
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
phases = []
|
|
316
|
+
phase_section = re.search(r"FINAL_PHASES:(.+?)$", raw, re.DOTALL)
|
|
317
|
+
if phase_section:
|
|
318
|
+
for match in re.finditer(
|
|
319
|
+
r"-\s*name:\s*(.+?)\n\s*department:\s*(\w+)\n\s*agents:\s*\[(.+?)\]\n\s*deliverables:\s*\[(.+?)\]\n\s*acceptance_criteria:\s*\[(.+?)\]\n\s*depends_on:\s*\[(.+?)\]",
|
|
320
|
+
phase_section.group(1),
|
|
321
|
+
re.DOTALL,
|
|
322
|
+
):
|
|
323
|
+
phases.append(
|
|
324
|
+
PhaseDeliverable(
|
|
325
|
+
name=match.group(1).strip(),
|
|
326
|
+
deliverables=[d.strip() for d in match.group(4).split(",") if d.strip()],
|
|
327
|
+
agents=[a.strip() for a in match.group(3).split(",") if a.strip()],
|
|
328
|
+
acceptance_criteria=[c.strip() for c in match.group(5).split(",") if c.strip()],
|
|
329
|
+
depends_on=[d.strip() for d in match.group(6).split(",") if d.strip()],
|
|
330
|
+
)
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
return CriticVerdict(
|
|
334
|
+
synthesis=synthesis,
|
|
335
|
+
rejected_elements=rejected,
|
|
336
|
+
risks=risks,
|
|
337
|
+
confidence=confidence,
|
|
338
|
+
estimated_phases=len(phases),
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
class ForgeTaskDispatcher(ABC):
|
|
343
|
+
"""Abstract base for Forge Task tool dispatchers.
|
|
344
|
+
|
|
345
|
+
Each runtime (Claude Code, Codex, Gemini CLI, Cursor) implements
|
|
346
|
+
this interface to dispatch explorer and critic subagents.
|
|
347
|
+
"""
|
|
348
|
+
|
|
349
|
+
@abstractmethod
|
|
350
|
+
def dispatch_explorer(self, request: ExplorerDispatchRequest) -> DispatchResult:
|
|
351
|
+
"""Dispatch an explorer subagent via Task tool.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
request: Explorer dispatch request with lens, prompt, context
|
|
355
|
+
|
|
356
|
+
Returns:
|
|
357
|
+
DispatchResult with raw response text from the subagent
|
|
358
|
+
"""
|
|
359
|
+
|
|
360
|
+
@abstractmethod
|
|
361
|
+
def dispatch_critic(self, request: CriticDispatchRequest) -> DispatchResult:
|
|
362
|
+
"""Dispatch a critic subagent via Task tool.
|
|
363
|
+
|
|
364
|
+
Args:
|
|
365
|
+
request: Critic dispatch request with approaches and prompt
|
|
366
|
+
|
|
367
|
+
Returns:
|
|
368
|
+
DispatchResult with raw response text from the subagent
|
|
369
|
+
"""
|
|
370
|
+
|
|
371
|
+
def dispatch_explorer_and_parse(self, request: ExplorerDispatchRequest) -> ExplorerApproach:
|
|
372
|
+
"""Dispatch an explorer and parse its structured output."""
|
|
373
|
+
result = self.dispatch_explorer(request)
|
|
374
|
+
if not result.success:
|
|
375
|
+
raise RuntimeError(f"Explorer dispatch failed: {result.error}")
|
|
376
|
+
return _parse_explorer_output(result.raw_response, request.lens)
|
|
377
|
+
|
|
378
|
+
def dispatch_critic_and_parse(self, request: CriticDispatchRequest) -> CriticVerdict:
|
|
379
|
+
"""Dispatch a critic and parse its structured output."""
|
|
380
|
+
result = self.dispatch_critic(request)
|
|
381
|
+
if not result.success:
|
|
382
|
+
raise RuntimeError(f"Critic dispatch failed: {result.error}")
|
|
383
|
+
return _parse_critic_output(result.raw_response)
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
class ClaudeCodeForgeDispatcher(ForgeTaskDispatcher):
|
|
387
|
+
"""Claude Code implementation using the Task tool with model parameter.
|
|
388
|
+
|
|
389
|
+
Claude Code supports the Agent tool with model routing:
|
|
390
|
+
- haiku: fast, cost-optimized
|
|
391
|
+
- sonnet: balanced
|
|
392
|
+
- opus: highest capability
|
|
393
|
+
"""
|
|
394
|
+
|
|
395
|
+
def dispatch_explorer(self, request: ExplorerDispatchRequest) -> DispatchResult:
|
|
396
|
+
"""Dispatch explorer using Claude Code's Agent tool."""
|
|
397
|
+
prompt = _build_explorer_prompt(request)
|
|
398
|
+
raw_response = self._call_agent(prompt, model=request.model, agent_type="explorer")
|
|
399
|
+
return DispatchResult(success=True, raw_response=raw_response, output=raw_response)
|
|
400
|
+
|
|
401
|
+
def dispatch_critic(self, request: CriticDispatchRequest) -> DispatchResult:
|
|
402
|
+
"""Dispatch critic using Claude Code's Agent tool."""
|
|
403
|
+
prompt = _build_critic_prompt(request)
|
|
404
|
+
raw_response = self._call_agent(prompt, model=request.model, agent_type="critic")
|
|
405
|
+
return DispatchResult(success=True, raw_response=raw_response, output=raw_response)
|
|
406
|
+
|
|
407
|
+
def _call_agent(
|
|
408
|
+
self,
|
|
409
|
+
prompt: str,
|
|
410
|
+
model: str,
|
|
411
|
+
agent_type: str,
|
|
412
|
+
timeout: int = 120,
|
|
413
|
+
) -> str:
|
|
414
|
+
"""Call the Claude Code Agent tool.
|
|
415
|
+
|
|
416
|
+
This method should be overridden in tests to mock the actual
|
|
417
|
+
Task tool call. The real implementation uses the Agent tool
|
|
418
|
+
from Claude Code's toolkit.
|
|
419
|
+
"""
|
|
420
|
+
from agentTool import Agent # type: ignore
|
|
421
|
+
|
|
422
|
+
task_result = Agent(
|
|
423
|
+
prompt=prompt,
|
|
424
|
+
model=model,
|
|
425
|
+
task_type=agent_type,
|
|
426
|
+
)
|
|
427
|
+
return task_result.output
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def create_dispatcher(runtime: str | None = None) -> ForgeTaskDispatcher:
|
|
431
|
+
"""Create the appropriate dispatcher for the current runtime.
|
|
432
|
+
|
|
433
|
+
Args:
|
|
434
|
+
runtime: Runtime identifier. If None, auto-detected.
|
|
435
|
+
|
|
436
|
+
Returns:
|
|
437
|
+
ForgeTaskDispatcher instance for the runtime
|
|
438
|
+
"""
|
|
439
|
+
if runtime is None:
|
|
440
|
+
runtime = _detect_runtime()
|
|
441
|
+
|
|
442
|
+
dispatchers = {
|
|
443
|
+
"claude-code": ClaudeCodeForgeDispatcher,
|
|
444
|
+
"claude": ClaudeCodeForgeDispatcher,
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
dispatcher_class = dispatchers.get(runtime, ClaudeCodeForgeDispatcher)
|
|
448
|
+
return dispatcher_class()
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def _detect_runtime() -> str:
|
|
452
|
+
"""Auto-detect the current runtime environment."""
|
|
453
|
+
import os
|
|
454
|
+
|
|
455
|
+
if os.environ.get("ARKAOS_RUNTIME"):
|
|
456
|
+
return os.environ["ARKAOS_RUNTIME"]
|
|
457
|
+
|
|
458
|
+
try:
|
|
459
|
+
import claude
|
|
460
|
+
|
|
461
|
+
return "claude-code"
|
|
462
|
+
except ImportError:
|
|
463
|
+
pass
|
|
464
|
+
|
|
465
|
+
return "claude-code"
|
|
Binary file
|
|
Binary file
|
|
Binary file
|