arkaos 4.0.1 → 4.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.
- package/README.md +2 -2
- package/VERSION +1 -1
- package/arka/SKILL.md +16 -22
- package/arka/skills/flow/SKILL.md +108 -234
- package/config/claude-agents/eduardo-copy.md +53 -0
- package/config/claude-agents/francisca-tech.md +52 -0
- package/config/claude-agents/marta-cqo.md +52 -0
- package/config/claude-agents/paulo-tech-lead.md +50 -0
- package/config/cognition/schedules.yaml +15 -0
- package/config/constitution.yaml +17 -21
- package/config/hooks/_lib/workflow-classifier.sh +1 -1
- package/config/hooks/post-tool-use.sh +40 -539
- package/config/hooks/pre-tool-use.sh +32 -281
- package/config/hooks/session-start.sh +8 -10
- package/config/hooks/stop.sh +30 -425
- package/config/hooks/user-prompt-submit.sh +42 -459
- package/core/__pycache__/__init__.cpython-312.pyc +0 -0
- package/core/cognition/__pycache__/__init__.cpython-312.pyc +0 -0
- package/core/cognition/__pycache__/dreaming.cpython-313.pyc +0 -0
- package/core/cognition/__pycache__/retrieval.cpython-312.pyc +0 -0
- package/core/cognition/capture/__pycache__/__init__.cpython-312.pyc +0 -0
- package/core/cognition/capture/__pycache__/store.cpython-312.pyc +0 -0
- package/core/cognition/dreaming.py +5 -0
- package/core/cognition/insights/__pycache__/__init__.cpython-312.pyc +0 -0
- package/core/cognition/insights/__pycache__/store.cpython-312.pyc +0 -0
- package/core/cognition/memory/__pycache__/__init__.cpython-312.pyc +0 -0
- package/core/cognition/memory/__pycache__/obsidian.cpython-312.pyc +0 -0
- package/core/cognition/memory/__pycache__/schemas.cpython-312.pyc +0 -0
- package/core/cognition/memory/__pycache__/vector.cpython-312.pyc +0 -0
- package/core/cognition/memory/__pycache__/writer.cpython-312.pyc +0 -0
- package/core/cognition/scheduler/__pycache__/daemon.cpython-313.pyc +0 -0
- package/core/cognition/scheduler/daemon.py +10 -0
- package/core/forge/__init__.py +2 -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__/persistence.cpython-313.pyc +0 -0
- package/core/forge/__pycache__/renderer.cpython-313.pyc +0 -0
- package/core/forge/__pycache__/runtime_dispatcher.cpython-313.pyc +0 -0
- package/core/forge/__pycache__/schema.cpython-313.pyc +0 -0
- package/core/forge/orchestrator.py +50 -27
- package/core/forge/persistence.py +16 -0
- package/core/forge/renderer.py +16 -0
- package/core/forge/runtime_dispatcher.py +34 -19
- package/core/forge/schema.py +12 -0
- package/core/governance/__pycache__/__init__.cpython-312.pyc +0 -0
- package/core/governance/__pycache__/closing_marker_check.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/compliance_telemetry.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/constitution.cpython-312.pyc +0 -0
- package/core/governance/__pycache__/evidence_checks.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/meta_tag_check.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/qg_verdict.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/review_workflow.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/skill_proposer.cpython-313.pyc +0 -0
- package/core/governance/closing_marker_check.py +9 -5
- package/core/governance/compliance_telemetry.py +1 -1
- package/core/governance/compliance_telemetry_cli.py +1 -1
- package/core/governance/evidence_checks.py +421 -0
- package/core/governance/meta_tag_check.py +36 -0
- package/core/governance/qg_verdict.py +78 -0
- package/core/governance/review_workflow.py +34 -1
- package/core/governance/skill_proposer.py +2 -1
- package/core/hooks/__init__.py +12 -0
- package/core/hooks/__pycache__/__init__.cpython-312.pyc +0 -0
- package/core/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/hooks/__pycache__/_shared.cpython-312.pyc +0 -0
- package/core/hooks/__pycache__/_shared.cpython-313.pyc +0 -0
- package/core/hooks/__pycache__/post_tool_use.cpython-312.pyc +0 -0
- package/core/hooks/__pycache__/post_tool_use.cpython-313.pyc +0 -0
- package/core/hooks/__pycache__/pre_tool_use.cpython-312.pyc +0 -0
- package/core/hooks/__pycache__/pre_tool_use.cpython-313.pyc +0 -0
- package/core/hooks/__pycache__/stop.cpython-312.pyc +0 -0
- package/core/hooks/__pycache__/stop.cpython-313.pyc +0 -0
- package/core/hooks/__pycache__/user_prompt_submit.cpython-312.pyc +0 -0
- package/core/hooks/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
- package/core/hooks/_shared.py +110 -0
- package/core/hooks/post_tool_use.py +689 -0
- package/core/hooks/pre_tool_use.py +267 -0
- package/core/hooks/stop.py +395 -0
- package/core/hooks/user_prompt_submit.py +600 -0
- package/core/jobs/__pycache__/__init__.cpython-312.pyc +0 -0
- package/core/jobs/__pycache__/auto_doc_worker.cpython-312.pyc +0 -0
- package/core/jobs/__pycache__/manager.cpython-312.pyc +0 -0
- package/core/knowledge/__pycache__/__init__.cpython-312.pyc +0 -0
- package/core/knowledge/__pycache__/chunker.cpython-312.pyc +0 -0
- package/core/knowledge/__pycache__/embedder.cpython-312.pyc +0 -0
- package/core/knowledge/__pycache__/embedder.cpython-313.pyc +0 -0
- package/core/knowledge/__pycache__/vector_store.cpython-312.pyc +0 -0
- package/core/knowledge/__pycache__/vector_store.cpython-313.pyc +0 -0
- package/core/knowledge/embedder.py +118 -11
- package/core/knowledge/vector_store.py +111 -14
- package/core/runtime/__pycache__/__init__.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/base.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/base.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/capabilities_cli.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/capabilities_cli.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/claude_code.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/claude_code.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/codex_cli.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/codex_cli.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/cost_governor.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/cost_governor.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/cursor.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/cursor.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/gemini_cli.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/gemini_cli.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/llm_cost_telemetry.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/llm_provider.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/llm_provider.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/llm_retry.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/llm_retry.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/native_usage.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/native_usage.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/ollama_provider.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/path_resolver.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/pricing.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/pricing.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/registry.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/registry.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/squad_orchestrator.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/user_paths.cpython-312.pyc +0 -0
- package/core/runtime/base.py +29 -0
- package/core/runtime/capabilities_cli.py +74 -0
- package/core/runtime/claude_code.py +8 -0
- package/core/runtime/codex_cli.py +205 -37
- package/core/runtime/cost_governor.py +184 -0
- package/core/runtime/cursor.py +8 -0
- package/core/runtime/gemini_cli.py +8 -0
- package/core/runtime/llm_provider.py +32 -13
- package/core/runtime/llm_retry.py +233 -0
- package/core/runtime/native_usage.py +186 -0
- package/core/runtime/pricing.py +9 -0
- package/core/runtime/registry.py +3 -0
- package/core/shared/__pycache__/__init__.cpython-312.pyc +0 -0
- package/core/shared/__pycache__/safe_session_id.cpython-312.pyc +0 -0
- package/core/shared/__pycache__/test_evidence.cpython-313-pytest-9.1.1.pyc +0 -0
- package/core/shared/__pycache__/test_evidence.cpython-313.pyc +0 -0
- package/core/shared/test_evidence.py +34 -0
- package/core/synapse/__pycache__/__init__.cpython-312.pyc +0 -0
- package/core/synapse/__pycache__/agent_experiences_layer.cpython-312.pyc +0 -0
- package/core/synapse/__pycache__/cache.cpython-312.pyc +0 -0
- package/core/synapse/__pycache__/engine.cpython-312.pyc +0 -0
- package/core/synapse/__pycache__/engine.cpython-313.pyc +0 -0
- package/core/synapse/__pycache__/graph_context_layer.cpython-313.pyc +0 -0
- package/core/synapse/__pycache__/kb_cache.cpython-312.pyc +0 -0
- package/core/synapse/__pycache__/kb_cache.cpython-313.pyc +0 -0
- package/core/synapse/__pycache__/layers.cpython-312.pyc +0 -0
- package/core/synapse/__pycache__/layers.cpython-313.pyc +0 -0
- package/core/synapse/engine.py +7 -0
- package/core/synapse/graph_context_layer.py +212 -0
- package/core/synapse/kb_cache.py +3 -0
- package/core/synapse/layers.py +104 -20
- package/core/workflow/__pycache__/__init__.cpython-312.pyc +0 -0
- package/core/workflow/__pycache__/enforcer.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/engine.cpython-312.pyc +0 -0
- package/core/workflow/__pycache__/flow_enforcer.cpython-312.pyc +0 -0
- package/core/workflow/__pycache__/flow_enforcer.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/gate_checkpoint.cpython-312.pyc +0 -0
- package/core/workflow/__pycache__/gate_checkpoint.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/loader.cpython-312.pyc +0 -0
- package/core/workflow/__pycache__/research_gate.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/rules_registry.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/schema.cpython-312.pyc +0 -0
- package/core/workflow/__pycache__/specialist_enforcer.cpython-312.pyc +0 -0
- package/core/workflow/__pycache__/specialist_enforcer.cpython-313.pyc +0 -0
- package/core/workflow/enforcer.py +5 -16
- package/core/workflow/flow_enforcer.py +36 -11
- package/core/workflow/gate_checkpoint.py +149 -0
- package/core/workflow/research_gate.py +15 -3
- package/core/workflow/rules_registry.py +137 -389
- package/core/workflow/specialist_enforcer.py +21 -4
- package/dashboard/app/pages/knowledge/index.vue +4 -1
- package/departments/brand/agents/ux-designer.yaml +1 -0
- package/departments/brand/agents/visual-designer.yaml +2 -0
- package/departments/dev/agents/frontend-dev.yaml +2 -0
- package/departments/ops/skills/n8n-flow/SKILL.md +13 -0
- package/departments/ops/skills/workflow-automate/SKILL.md +16 -0
- package/departments/quality/SKILL.md +45 -5
- package/departments/quality/agents/copy-director.yaml +3 -1
- package/departments/quality/agents/tech-director.yaml +3 -1
- package/installer/adapters/claude-code.js +46 -2
- package/installer/config-seed.js +39 -14
- package/installer/doctor.js +8 -0
- package/installer/graphify.js +153 -0
- package/installer/index.js +24 -0
- package/installer/init.js +11 -0
- package/installer/update.js +23 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/scripts/__pycache__/dashboard-api.cpython-313.pyc +0 -0
- package/scripts/__pycache__/knowledge-index.cpython-313.pyc +0 -0
- package/scripts/__pycache__/synapse-bridge.cpython-312.pyc +0 -0
- package/scripts/__pycache__/synapse-bridge.cpython-313.pyc +0 -0
- package/scripts/bench/__pycache__/__init__.cpython-313.pyc +0 -0
- package/scripts/bench/__pycache__/harness.cpython-313.pyc +0 -0
- package/scripts/bench/__pycache__/run.cpython-313.pyc +0 -0
- package/scripts/knowledge-index.py +8 -2
- package/scripts/synapse-bridge.py +102 -26
- package/scripts/tools/__pycache__/sync_wiki.cpython-313.pyc +0 -0
- package/scripts/tools/sync_wiki.py +163 -0
|
@@ -157,6 +157,8 @@ class ForgeOrchestrator:
|
|
|
157
157
|
self._dispatcher = dispatcher or ClaudeCodeForgeDispatcher()
|
|
158
158
|
self._current_plan: Optional[ForgePlan] = None
|
|
159
159
|
self._current_step: Optional[ForgeStep] = None
|
|
160
|
+
self._critic_verdict: Optional[CriticVerdict] = None
|
|
161
|
+
self._dispatch_errors: list[str] = []
|
|
160
162
|
|
|
161
163
|
# -------------------------------------------------------------------------
|
|
162
164
|
# Main Commands
|
|
@@ -554,12 +556,15 @@ class ForgeOrchestrator:
|
|
|
554
556
|
for lens in lenses
|
|
555
557
|
]
|
|
556
558
|
|
|
559
|
+
self._dispatch_errors = []
|
|
557
560
|
for req in requests:
|
|
558
561
|
try:
|
|
559
562
|
approach = self._dispatcher.dispatch_explorer_and_parse(req)
|
|
560
563
|
self._approaches.append(approach)
|
|
561
564
|
except Exception as e:
|
|
562
|
-
|
|
565
|
+
self._dispatch_errors.append(
|
|
566
|
+
f"explorer[{req.lens.value}]: {type(e).__name__}: {e}"
|
|
567
|
+
)
|
|
563
568
|
|
|
564
569
|
def _step6_critic_synthesis(self, revision_request: Optional[str] = None) -> None:
|
|
565
570
|
"""Step 6: Launch critic subagent for synthesis.
|
|
@@ -576,16 +581,13 @@ class ForgeOrchestrator:
|
|
|
576
581
|
|
|
577
582
|
try:
|
|
578
583
|
self._critic_verdict = self._dispatcher.dispatch_critic_and_parse(critic_req)
|
|
579
|
-
except Exception:
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
confidence=0.5,
|
|
585
|
-
estimated_phases=len(self._approaches[0].phases) if self._approaches else 3,
|
|
586
|
-
)
|
|
584
|
+
except Exception as e:
|
|
585
|
+
# Honest failure: NEVER fabricate a synthetic verdict. A missing
|
|
586
|
+
# critic means the plan is degraded and must say so.
|
|
587
|
+
self._critic_verdict = None
|
|
588
|
+
self._dispatch_errors.append(f"critic: {type(e).__name__}: {e}")
|
|
587
589
|
|
|
588
|
-
if revision_request:
|
|
590
|
+
if revision_request and self._critic_verdict is not None:
|
|
589
591
|
self._critic_verdict.synthesis["revision_request"] = [revision_request]
|
|
590
592
|
|
|
591
593
|
def _step7_enforce_constitution(self) -> None:
|
|
@@ -596,7 +598,7 @@ class ForgeOrchestrator:
|
|
|
596
598
|
has_branch = any("branch" in n or "feature" in n for n in phase_names)
|
|
597
599
|
has_spec = any("spec" in n or "specification" in n for n in phase_names)
|
|
598
600
|
has_qg = any("quality" in n or "gate" in n or "qg" in n for n in phase_names)
|
|
599
|
-
has_obsidian = any("obsidian" in n or "persist" in n or "
|
|
601
|
+
has_obsidian = any("obsidian" in n or "persist" in n or "document" in n for n in phase_names)
|
|
600
602
|
|
|
601
603
|
enforced = []
|
|
602
604
|
if not has_branch:
|
|
@@ -643,9 +645,16 @@ class ForgeOrchestrator:
|
|
|
643
645
|
self._enforced_phases = enforced
|
|
644
646
|
|
|
645
647
|
def _step8_build_plan(self) -> None:
|
|
646
|
-
"""Step 8: Build the ForgePlan object.
|
|
648
|
+
"""Step 8: Build the ForgePlan object.
|
|
649
|
+
|
|
650
|
+
When explorer/critic dispatch failed, the plan is marked
|
|
651
|
+
``degraded`` with confidence 0.0 and carries the dispatch errors —
|
|
652
|
+
never a fabricated verdict.
|
|
653
|
+
"""
|
|
647
654
|
plan_id = self._generate_plan_id(self._forge_context.prompt)
|
|
648
|
-
|
|
655
|
+
phases = self._final_phases_from_critic()
|
|
656
|
+
execution_path = select_execution_path(phases)
|
|
657
|
+
degraded = self._critic_verdict is None
|
|
649
658
|
|
|
650
659
|
self._current_plan = ForgePlan(
|
|
651
660
|
id=plan_id,
|
|
@@ -656,8 +665,8 @@ class ForgeOrchestrator:
|
|
|
656
665
|
context=self._forge_context,
|
|
657
666
|
complexity=self._complexity,
|
|
658
667
|
approaches=self._approaches,
|
|
659
|
-
critic=self._critic_verdict,
|
|
660
|
-
plan_phases=
|
|
668
|
+
critic=self._critic_verdict or CriticVerdict(confidence=0.0),
|
|
669
|
+
plan_phases=phases,
|
|
661
670
|
goal=self._forge_context.prompt,
|
|
662
671
|
execution_path=execution_path,
|
|
663
672
|
governance=ForgeGovernance(
|
|
@@ -665,6 +674,8 @@ class ForgeOrchestrator:
|
|
|
665
674
|
quality_gate_required=True,
|
|
666
675
|
branch_strategy=f"feature/{plan_id}",
|
|
667
676
|
),
|
|
677
|
+
degraded=degraded,
|
|
678
|
+
dispatch_errors=list(self._dispatch_errors),
|
|
668
679
|
status=ForgeStatus.REVIEWING,
|
|
669
680
|
)
|
|
670
681
|
|
|
@@ -700,21 +711,33 @@ class ForgeOrchestrator:
|
|
|
700
711
|
pass
|
|
701
712
|
|
|
702
713
|
def _final_phases_from_critic(self) -> list[PlanPhase]:
|
|
703
|
-
"""Build final phases
|
|
704
|
-
phases = []
|
|
705
|
-
for i, p in enumerate(range(self._critic_verdict.estimated_phases or 3)):
|
|
706
|
-
phases.append(
|
|
707
|
-
PlanPhase(
|
|
708
|
-
name=f"Phase {i + 1}",
|
|
709
|
-
department="dev",
|
|
710
|
-
)
|
|
711
|
-
)
|
|
712
|
-
|
|
713
|
-
for ep in self._enforced_phases:
|
|
714
|
-
phases.append(ep)
|
|
714
|
+
"""Build final phases: critic-derived base + constitution enforcement.
|
|
715
715
|
|
|
716
|
+
When the critic verdict is missing (dispatch failed) or carries no
|
|
717
|
+
real content, the base list is EMPTY — the plan then contains only
|
|
718
|
+
the constitution-enforced phases and is marked degraded downstream.
|
|
719
|
+
"""
|
|
720
|
+
phases = self._critic_base_phases()
|
|
721
|
+
phases.extend(self._enforced_phases)
|
|
716
722
|
return phases
|
|
717
723
|
|
|
724
|
+
def _critic_base_phases(self) -> list[PlanPhase]:
|
|
725
|
+
"""Phases derived from the critic verdict; [] when absent/empty."""
|
|
726
|
+
verdict = self._critic_verdict
|
|
727
|
+
if verdict is None or not self._verdict_has_content(verdict):
|
|
728
|
+
return []
|
|
729
|
+
return [
|
|
730
|
+
PlanPhase(name=f"Phase {i + 1}", department="dev")
|
|
731
|
+
for i in range(verdict.estimated_phases)
|
|
732
|
+
]
|
|
733
|
+
|
|
734
|
+
@staticmethod
|
|
735
|
+
def _verdict_has_content(verdict: CriticVerdict) -> bool:
|
|
736
|
+
"""A verdict counts as real only when it estimated phases AND synthesized something."""
|
|
737
|
+
return verdict.estimated_phases > 0 and bool(
|
|
738
|
+
verdict.synthesis or verdict.rejected_elements or verdict.risks
|
|
739
|
+
)
|
|
740
|
+
|
|
718
741
|
def _generate_plan_id(self, prompt: str) -> str:
|
|
719
742
|
"""Generate a forge plan ID."""
|
|
720
743
|
date = datetime.now(timezone.utc).strftime("%Y%m%d")
|
|
@@ -123,9 +123,25 @@ def _render_obsidian_frontmatter(plan: ForgePlan) -> list[str]:
|
|
|
123
123
|
f"complexity: {plan.complexity.score}",
|
|
124
124
|
f"created: {plan.created_at or ''}",
|
|
125
125
|
]
|
|
126
|
+
if plan.degraded:
|
|
127
|
+
lines.append("degraded: true")
|
|
126
128
|
if plan.executed_at:
|
|
127
129
|
lines.append(f"executed: {plan.executed_at}")
|
|
128
130
|
lines += ["---", "", f"# {plan.name}", ""]
|
|
131
|
+
if plan.degraded:
|
|
132
|
+
lines += _render_obsidian_degraded(plan)
|
|
133
|
+
return lines
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _render_obsidian_degraded(plan: ForgePlan) -> list[str]:
|
|
137
|
+
"""Render the degraded-plan warning block."""
|
|
138
|
+
lines = [
|
|
139
|
+
"> [!warning] DEGRADED: explorer/critic dispatch unavailable — "
|
|
140
|
+
"plan contains only constitution-enforced phases",
|
|
141
|
+
]
|
|
142
|
+
for err in plan.dispatch_errors:
|
|
143
|
+
lines.append(f"> - {err}")
|
|
144
|
+
lines.append("")
|
|
129
145
|
return lines
|
|
130
146
|
|
|
131
147
|
|
package/core/forge/renderer.py
CHANGED
|
@@ -60,6 +60,12 @@ def render_plan_overview(phases: list[PlanPhase], execution_path: ExecutionPath)
|
|
|
60
60
|
return "\n".join(lines)
|
|
61
61
|
|
|
62
62
|
|
|
63
|
+
DEGRADED_NOTICE = (
|
|
64
|
+
"DEGRADED: explorer/critic dispatch unavailable — "
|
|
65
|
+
"plan contains only constitution-enforced phases"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
63
69
|
def render_terminal(plan: ForgePlan) -> str:
|
|
64
70
|
"""Render a complete forge plan for terminal display."""
|
|
65
71
|
lines = [
|
|
@@ -70,6 +76,10 @@ def render_terminal(plan: ForgePlan) -> str:
|
|
|
70
76
|
"▸ Complexity Analysis",
|
|
71
77
|
render_complexity(plan.complexity), "",
|
|
72
78
|
]
|
|
79
|
+
if plan.degraded:
|
|
80
|
+
lines.extend([f"⚠ {DEGRADED_NOTICE}"])
|
|
81
|
+
lines.extend(f" - {err}" for err in plan.dispatch_errors)
|
|
82
|
+
lines.append("")
|
|
73
83
|
if plan.critic.confidence > 0:
|
|
74
84
|
lines.extend(["▸ Critic Verdict", render_critic_summary(plan.critic), ""])
|
|
75
85
|
if plan.plan_phases:
|
|
@@ -115,6 +125,11 @@ def render_html(plan: ForgePlan) -> str:
|
|
|
115
125
|
phases_html = _render_phases_html(plan.plan_phases)
|
|
116
126
|
critic_html = _render_critic_html(plan.critic)
|
|
117
127
|
approaches_html = _render_approaches_html(plan.approaches)
|
|
128
|
+
degraded_html = (
|
|
129
|
+
f'<p><span class="badge badge-red">⚠ {_esc(DEGRADED_NOTICE)}</span></p>'
|
|
130
|
+
if plan.degraded
|
|
131
|
+
else ""
|
|
132
|
+
)
|
|
118
133
|
ctx = plan.context
|
|
119
134
|
meta = (
|
|
120
135
|
f"{_esc(ctx.repo)} @ {_esc(ctx.commit_at_forge)} | Branch: {_esc(ctx.branch)} "
|
|
@@ -128,6 +143,7 @@ def render_html(plan: ForgePlan) -> str:
|
|
|
128
143
|
f"<style>\n{_COMPANION_CSS}\n</style></head><body>"
|
|
129
144
|
f"<h1>\u2692 {_esc(plan.name)}</h1>"
|
|
130
145
|
f'<div class="meta">{meta}</div>'
|
|
146
|
+
f"{degraded_html}"
|
|
131
147
|
f'<div class="grid">'
|
|
132
148
|
f'<div class="card"><h2>Complexity Radar</h2>{radar_svg}</div>'
|
|
133
149
|
f'<div class="card"><h2>Critic Verdict</h2>{critic_html}</div>'
|
|
@@ -84,6 +84,29 @@ Be adversarial but constructive. Your job is to stress-test assumptions, not to
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
|
|
87
|
+
class ForgeDispatchUnavailableError(RuntimeError):
|
|
88
|
+
"""Raised when Python-side subagent dispatch is attempted.
|
|
89
|
+
|
|
90
|
+
Python-side agent dispatch does not exist: there is no in-process
|
|
91
|
+
bridge to the runtime's Agent/Task tool. The runtime executes the
|
|
92
|
+
explorer and critic prompts (built by ``_build_explorer_prompt`` /
|
|
93
|
+
``_build_critic_prompt``) via the arka-forge SKILL, not via this
|
|
94
|
+
module. Callers that invoke the dispatcher directly MUST handle
|
|
95
|
+
degraded planning: no explorer approaches, no critic verdict, and
|
|
96
|
+
a plan that only carries constitution-enforced phases.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
def __init__(self, agent_type: str, model: str) -> None:
|
|
100
|
+
super().__init__(
|
|
101
|
+
f"Cannot dispatch '{agent_type}' subagent (model={model}): "
|
|
102
|
+
"Python-side agent dispatch does not exist. The runtime "
|
|
103
|
+
"executes explorer/critic prompts via the arka-forge SKILL. "
|
|
104
|
+
"Callers must handle degraded planning."
|
|
105
|
+
)
|
|
106
|
+
self.agent_type = agent_type
|
|
107
|
+
self.model = model
|
|
108
|
+
|
|
109
|
+
|
|
87
110
|
@dataclass
|
|
88
111
|
class DispatchResult:
|
|
89
112
|
"""Result from a dispatcher call."""
|
|
@@ -384,22 +407,23 @@ class ForgeTaskDispatcher(ABC):
|
|
|
384
407
|
|
|
385
408
|
|
|
386
409
|
class ClaudeCodeForgeDispatcher(ForgeTaskDispatcher):
|
|
387
|
-
"""Claude Code
|
|
410
|
+
"""Claude Code dispatcher — prompt builder only, no in-process dispatch.
|
|
388
411
|
|
|
389
|
-
|
|
390
|
-
-
|
|
391
|
-
|
|
392
|
-
|
|
412
|
+
This class builds the full explorer/critic prompts. Actual subagent
|
|
413
|
+
execution happens in the runtime via the arka-forge SKILL, never in
|
|
414
|
+
Python. ``_call_agent`` therefore raises
|
|
415
|
+
``ForgeDispatchUnavailableError`` — callers must catch it and degrade
|
|
416
|
+
honestly instead of fabricating results.
|
|
393
417
|
"""
|
|
394
418
|
|
|
395
419
|
def dispatch_explorer(self, request: ExplorerDispatchRequest) -> DispatchResult:
|
|
396
|
-
"""
|
|
420
|
+
"""Build the explorer prompt and attempt dispatch (raises)."""
|
|
397
421
|
prompt = _build_explorer_prompt(request)
|
|
398
422
|
raw_response = self._call_agent(prompt, model=request.model, agent_type="explorer")
|
|
399
423
|
return DispatchResult(success=True, raw_response=raw_response, output=raw_response)
|
|
400
424
|
|
|
401
425
|
def dispatch_critic(self, request: CriticDispatchRequest) -> DispatchResult:
|
|
402
|
-
"""
|
|
426
|
+
"""Build the critic prompt and attempt dispatch (raises)."""
|
|
403
427
|
prompt = _build_critic_prompt(request)
|
|
404
428
|
raw_response = self._call_agent(prompt, model=request.model, agent_type="critic")
|
|
405
429
|
return DispatchResult(success=True, raw_response=raw_response, output=raw_response)
|
|
@@ -411,20 +435,11 @@ class ClaudeCodeForgeDispatcher(ForgeTaskDispatcher):
|
|
|
411
435
|
agent_type: str,
|
|
412
436
|
timeout: int = 120,
|
|
413
437
|
) -> str:
|
|
414
|
-
"""
|
|
438
|
+
"""Always raises — Python cannot call the runtime's Agent tool.
|
|
415
439
|
|
|
416
|
-
|
|
417
|
-
Task tool call. The real implementation uses the Agent tool
|
|
418
|
-
from Claude Code's toolkit.
|
|
440
|
+
Tests may override this method to simulate a working runtime.
|
|
419
441
|
"""
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
task_result = Agent(
|
|
423
|
-
prompt=prompt,
|
|
424
|
-
model=model,
|
|
425
|
-
task_type=agent_type,
|
|
426
|
-
)
|
|
427
|
-
return task_result.output
|
|
442
|
+
raise ForgeDispatchUnavailableError(agent_type=agent_type, model=model)
|
|
428
443
|
|
|
429
444
|
|
|
430
445
|
def create_dispatcher(runtime: str | None = None) -> ForgeTaskDispatcher:
|
package/core/forge/schema.py
CHANGED
|
@@ -206,6 +206,18 @@ class ForgePlan(BaseModel):
|
|
|
206
206
|
|
|
207
207
|
governance: ForgeGovernance = Field(default_factory=ForgeGovernance)
|
|
208
208
|
|
|
209
|
+
degraded: bool = Field(
|
|
210
|
+
default=False,
|
|
211
|
+
description=(
|
|
212
|
+
"True when explorer/critic dispatch was unavailable — the plan "
|
|
213
|
+
"contains only constitution-enforced phases and confidence is 0.0."
|
|
214
|
+
),
|
|
215
|
+
)
|
|
216
|
+
dispatch_errors: List[str] = Field(
|
|
217
|
+
default_factory=list,
|
|
218
|
+
description="Dispatch failures collected during forge (lens: error class: message).",
|
|
219
|
+
)
|
|
220
|
+
|
|
209
221
|
status: ForgeStatus = ForgeStatus.DRAFT
|
|
210
222
|
approved_at: Optional[str] = None
|
|
211
223
|
approved_by: Optional[str] = None
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Closing-marker soft-block (PR59 v2.76.0; evidence flow v4.1.0).
|
|
2
2
|
|
|
3
3
|
Response-side classifier. Inspects the closing assistant message of a
|
|
4
|
-
flow-required turn for the mandatory closure marker —
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
flow-required turn for the mandatory closure marker — ``[arka:gate:4]``
|
|
5
|
+
(evidence flow review gate), the legacy ``[arka:phase:13]`` (accepted
|
|
6
|
+
during the v4.1 deprecation window), or ``[arka:trivial]`` (trivial
|
|
7
|
+
bypass). Mirrors the contract of
|
|
7
8
|
``core.governance.meta_tag_check`` (PR30 v2.49.0) and
|
|
8
9
|
``core.governance.kb_cite_check`` (PR18 v2.40.0).
|
|
9
10
|
|
|
@@ -23,12 +24,13 @@ import re
|
|
|
23
24
|
from dataclasses import dataclass
|
|
24
25
|
|
|
25
26
|
|
|
27
|
+
_GATE4_RE: re.Pattern[str] = re.compile(r"\[arka:gate:4\]", re.IGNORECASE)
|
|
26
28
|
_PHASE13_RE: re.Pattern[str] = re.compile(r"\[arka:phase:13\]", re.IGNORECASE)
|
|
27
29
|
_TRIVIAL_RE: re.Pattern[str] = re.compile(r"\[arka:trivial\]", re.IGNORECASE)
|
|
28
30
|
_TRIVIAL_WORD_THRESHOLD: int = 15
|
|
29
31
|
_SUGGESTION_TEXT: str = (
|
|
30
32
|
"Closing marker missing — end every flow-required turn with "
|
|
31
|
-
"`[arka:
|
|
33
|
+
"`[arka:gate:4] <label>` (evidence flow review gate) or "
|
|
32
34
|
"`[arka:trivial] <reason>` (single-file edit < 10 lines, "
|
|
33
35
|
"imperative verb). Without the marker, telemetry can't confirm "
|
|
34
36
|
"the turn closed cleanly."
|
|
@@ -52,6 +54,8 @@ def check_closing_marker(response_text: str) -> ClosingMarkerResult:
|
|
|
52
54
|
marker is found.
|
|
53
55
|
"""
|
|
54
56
|
text = response_text or ""
|
|
57
|
+
if _GATE4_RE.search(text):
|
|
58
|
+
return ClosingMarkerResult(True, "gate4", None)
|
|
55
59
|
if _PHASE13_RE.search(text):
|
|
56
60
|
return ClosingMarkerResult(True, "phase13", None)
|
|
57
61
|
if _TRIVIAL_RE.search(text):
|
|
@@ -4,7 +4,7 @@ Reads the stop-hook entries in ``~/.arkaos/telemetry/enforcement.jsonl``
|
|
|
4
4
|
and reports compliance with the four contracts the session-start hook
|
|
5
5
|
establishes:
|
|
6
6
|
|
|
7
|
-
- closing_marker_found — [arka:phase:13] or [arka:trivial] present
|
|
7
|
+
- closing_marker_found — [arka:gate:4] (or legacy [arka:phase:13]) or [arka:trivial] present
|
|
8
8
|
- meta_tag_found — [arka:meta] one-liner present (PR12)
|
|
9
9
|
- kb_cite_passed — KB citation soft block result (PR18)
|
|
10
10
|
- sycophancy clean — inverse of sycophancy_is_flagged (PR13)
|
|
@@ -27,7 +27,7 @@ def _render(summary: ComplianceSummary) -> str:
|
|
|
27
27
|
"",
|
|
28
28
|
"| Contract | Compliance rate |",
|
|
29
29
|
"|---|---|",
|
|
30
|
-
f"| `[arka:
|
|
30
|
+
f"| `[arka:gate:4]` / `[arka:trivial]` closing marker "
|
|
31
31
|
f"| {_fmt_rate(summary.closing_marker_rate)} |",
|
|
32
32
|
f"| `[arka:meta]` one-liner (PR12 v2.34.0) "
|
|
33
33
|
f"| {_fmt_rate(summary.meta_tag_rate)} |",
|