arkaos 4.1.0 → 4.2.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 (144) hide show
  1. package/VERSION +1 -1
  2. package/arka/skills/flow/SKILL.md +15 -0
  3. package/config/constitution.yaml +15 -7
  4. package/config/hooks/post-tool-use.sh +16 -1
  5. package/config/hooks/pre-tool-use.sh +16 -1
  6. package/config/hooks/session-start.sh +22 -0
  7. package/config/hooks/stop.sh +16 -1
  8. package/config/hooks/user-prompt-submit.sh +16 -1
  9. package/config/models.yaml +70 -0
  10. package/core/__pycache__/__init__.cpython-314.pyc +0 -0
  11. package/core/cognition/__pycache__/dreaming.cpython-313.pyc +0 -0
  12. package/core/cognition/scheduler/__pycache__/daemon.cpython-313.pyc +0 -0
  13. package/core/forge/__pycache__/__init__.cpython-313.pyc +0 -0
  14. package/core/forge/__pycache__/orchestrator.cpython-313.pyc +0 -0
  15. package/core/forge/__pycache__/persistence.cpython-313.pyc +0 -0
  16. package/core/forge/__pycache__/renderer.cpython-313.pyc +0 -0
  17. package/core/forge/__pycache__/runtime_dispatcher.cpython-313.pyc +0 -0
  18. package/core/forge/__pycache__/schema.cpython-313.pyc +0 -0
  19. package/core/governance/__pycache__/__init__.cpython-314.pyc +0 -0
  20. package/core/governance/__pycache__/closing_marker_check.cpython-313.pyc +0 -0
  21. package/core/governance/__pycache__/closing_marker_check.cpython-314.pyc +0 -0
  22. package/core/governance/__pycache__/compliance_telemetry.cpython-313.pyc +0 -0
  23. package/core/governance/__pycache__/constitution.cpython-314.pyc +0 -0
  24. package/core/governance/__pycache__/evidence_checks.cpython-313.pyc +0 -0
  25. package/core/governance/__pycache__/kb_cite_check.cpython-314.pyc +0 -0
  26. package/core/governance/__pycache__/meta_tag_check.cpython-313.pyc +0 -0
  27. package/core/governance/__pycache__/meta_tag_check.cpython-314.pyc +0 -0
  28. package/core/governance/__pycache__/qg_verdict.cpython-313.pyc +0 -0
  29. package/core/governance/__pycache__/review_workflow.cpython-313.pyc +0 -0
  30. package/core/governance/__pycache__/skill_proposer.cpython-313.pyc +0 -0
  31. package/core/governance/__pycache__/skill_proposer.cpython-314.pyc +0 -0
  32. package/core/governance/__pycache__/sycophancy_detector.cpython-314.pyc +0 -0
  33. package/core/hooks/__pycache__/__init__.cpython-312.pyc +0 -0
  34. package/core/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  35. package/core/hooks/__pycache__/__init__.cpython-314.pyc +0 -0
  36. package/core/hooks/__pycache__/_shared.cpython-312.pyc +0 -0
  37. package/core/hooks/__pycache__/_shared.cpython-313.pyc +0 -0
  38. package/core/hooks/__pycache__/_shared.cpython-314.pyc +0 -0
  39. package/core/hooks/__pycache__/post_tool_use.cpython-312.pyc +0 -0
  40. package/core/hooks/__pycache__/post_tool_use.cpython-313.pyc +0 -0
  41. package/core/hooks/__pycache__/pre_tool_use.cpython-312.pyc +0 -0
  42. package/core/hooks/__pycache__/pre_tool_use.cpython-313.pyc +0 -0
  43. package/core/hooks/__pycache__/pre_tool_use.cpython-314.pyc +0 -0
  44. package/core/hooks/__pycache__/stop.cpython-312.pyc +0 -0
  45. package/core/hooks/__pycache__/stop.cpython-313.pyc +0 -0
  46. package/core/hooks/__pycache__/stop.cpython-314.pyc +0 -0
  47. package/core/hooks/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
  48. package/core/hooks/pre_tool_use.py +55 -3
  49. package/core/jobs/__pycache__/__init__.cpython-314.pyc +0 -0
  50. package/core/jobs/__pycache__/auto_doc_worker.cpython-314.pyc +0 -0
  51. package/core/jobs/__pycache__/manager.cpython-314.pyc +0 -0
  52. package/core/knowledge/__pycache__/embedder.cpython-312.pyc +0 -0
  53. package/core/knowledge/__pycache__/embedder.cpython-313.pyc +0 -0
  54. package/core/knowledge/__pycache__/vector_store.cpython-312.pyc +0 -0
  55. package/core/knowledge/__pycache__/vector_store.cpython-313.pyc +0 -0
  56. package/core/memory/__pycache__/session_store.cpython-314.pyc +0 -0
  57. package/core/obsidian/__pycache__/relator.cpython-313.pyc +0 -0
  58. package/core/obsidian/relator.py +18 -8
  59. package/core/runtime/__pycache__/__init__.cpython-314.pyc +0 -0
  60. package/core/runtime/__pycache__/base.cpython-312.pyc +0 -0
  61. package/core/runtime/__pycache__/base.cpython-313.pyc +0 -0
  62. package/core/runtime/__pycache__/base.cpython-314.pyc +0 -0
  63. package/core/runtime/__pycache__/capabilities_cli.cpython-313.pyc +0 -0
  64. package/core/runtime/__pycache__/claude_code.cpython-312.pyc +0 -0
  65. package/core/runtime/__pycache__/claude_code.cpython-313.pyc +0 -0
  66. package/core/runtime/__pycache__/claude_code.cpython-314.pyc +0 -0
  67. package/core/runtime/__pycache__/codex_cli.cpython-312.pyc +0 -0
  68. package/core/runtime/__pycache__/codex_cli.cpython-313.pyc +0 -0
  69. package/core/runtime/__pycache__/codex_cli.cpython-314.pyc +0 -0
  70. package/core/runtime/__pycache__/cost_governor.cpython-313.pyc +0 -0
  71. package/core/runtime/__pycache__/cursor.cpython-312.pyc +0 -0
  72. package/core/runtime/__pycache__/cursor.cpython-313.pyc +0 -0
  73. package/core/runtime/__pycache__/cursor.cpython-314.pyc +0 -0
  74. package/core/runtime/__pycache__/gemini_cli.cpython-312.pyc +0 -0
  75. package/core/runtime/__pycache__/gemini_cli.cpython-313.pyc +0 -0
  76. package/core/runtime/__pycache__/gemini_cli.cpython-314.pyc +0 -0
  77. package/core/runtime/__pycache__/llm_cost_telemetry.cpython-314.pyc +0 -0
  78. package/core/runtime/__pycache__/llm_provider.cpython-312.pyc +0 -0
  79. package/core/runtime/__pycache__/llm_provider.cpython-313.pyc +0 -0
  80. package/core/runtime/__pycache__/llm_provider.cpython-314.pyc +0 -0
  81. package/core/runtime/__pycache__/llm_retry.cpython-312.pyc +0 -0
  82. package/core/runtime/__pycache__/llm_retry.cpython-313.pyc +0 -0
  83. package/core/runtime/__pycache__/llm_retry.cpython-314.pyc +0 -0
  84. package/core/runtime/__pycache__/model_router.cpython-313.pyc +0 -0
  85. package/core/runtime/__pycache__/model_router_cli.cpython-313.pyc +0 -0
  86. package/core/runtime/__pycache__/native_usage.cpython-312.pyc +0 -0
  87. package/core/runtime/__pycache__/native_usage.cpython-313.pyc +0 -0
  88. package/core/runtime/__pycache__/native_usage.cpython-314.pyc +0 -0
  89. package/core/runtime/__pycache__/ollama_provider.cpython-314.pyc +0 -0
  90. package/core/runtime/__pycache__/openrouter_provider.cpython-313.pyc +0 -0
  91. package/core/runtime/__pycache__/openrouter_provider.cpython-314.pyc +0 -0
  92. package/core/runtime/__pycache__/pricing.cpython-312.pyc +0 -0
  93. package/core/runtime/__pycache__/pricing.cpython-313.pyc +0 -0
  94. package/core/runtime/__pycache__/pricing.cpython-314.pyc +0 -0
  95. package/core/runtime/__pycache__/registry.cpython-312.pyc +0 -0
  96. package/core/runtime/__pycache__/registry.cpython-313.pyc +0 -0
  97. package/core/runtime/__pycache__/registry.cpython-314.pyc +0 -0
  98. package/core/runtime/llm_provider.py +10 -1
  99. package/core/runtime/model_router.py +204 -0
  100. package/core/runtime/model_router_cli.py +82 -0
  101. package/core/runtime/openrouter_provider.py +172 -0
  102. package/core/shared/__pycache__/__init__.cpython-314.pyc +0 -0
  103. package/core/shared/__pycache__/safe_session_id.cpython-314.pyc +0 -0
  104. package/core/shared/__pycache__/test_evidence.cpython-313-pytest-9.1.1.pyc +0 -0
  105. package/core/shared/__pycache__/test_evidence.cpython-313.pyc +0 -0
  106. package/core/synapse/__pycache__/__init__.cpython-314.pyc +0 -0
  107. package/core/synapse/__pycache__/cache.cpython-314.pyc +0 -0
  108. package/core/synapse/__pycache__/engine.cpython-312.pyc +0 -0
  109. package/core/synapse/__pycache__/engine.cpython-313.pyc +0 -0
  110. package/core/synapse/__pycache__/engine.cpython-314.pyc +0 -0
  111. package/core/synapse/__pycache__/graph_context_layer.cpython-313.pyc +0 -0
  112. package/core/synapse/__pycache__/kb_cache.cpython-312.pyc +0 -0
  113. package/core/synapse/__pycache__/kb_cache.cpython-313.pyc +0 -0
  114. package/core/synapse/__pycache__/kb_cache.cpython-314.pyc +0 -0
  115. package/core/synapse/__pycache__/layers.cpython-312.pyc +0 -0
  116. package/core/synapse/__pycache__/layers.cpython-313.pyc +0 -0
  117. package/core/synapse/__pycache__/layers.cpython-314.pyc +0 -0
  118. package/core/sync/__pycache__/__init__.cpython-312.pyc +0 -0
  119. package/core/sync/__pycache__/engine.cpython-312.pyc +0 -0
  120. package/core/sync/__pycache__/manifest.cpython-312.pyc +0 -0
  121. package/core/workflow/__pycache__/__init__.cpython-314.pyc +0 -0
  122. package/core/workflow/__pycache__/enforcer.cpython-313.pyc +0 -0
  123. package/core/workflow/__pycache__/engine.cpython-314.pyc +0 -0
  124. package/core/workflow/__pycache__/flow_enforcer.cpython-313.pyc +0 -0
  125. package/core/workflow/__pycache__/flow_enforcer.cpython-314.pyc +0 -0
  126. package/core/workflow/__pycache__/frontend_gate.cpython-313.pyc +0 -0
  127. package/core/workflow/__pycache__/gate_checkpoint.cpython-313.pyc +0 -0
  128. package/core/workflow/__pycache__/gate_checkpoint.cpython-314.pyc +0 -0
  129. package/core/workflow/__pycache__/loader.cpython-314.pyc +0 -0
  130. package/core/workflow/__pycache__/marker_cache.cpython-314.pyc +0 -0
  131. package/core/workflow/__pycache__/research_gate.cpython-313.pyc +0 -0
  132. package/core/workflow/__pycache__/research_gate.cpython-314.pyc +0 -0
  133. package/core/workflow/__pycache__/rules_registry.cpython-313.pyc +0 -0
  134. package/core/workflow/__pycache__/schema.cpython-314.pyc +0 -0
  135. package/core/workflow/__pycache__/specialist_enforcer.cpython-313.pyc +0 -0
  136. package/core/workflow/__pycache__/state.cpython-314.pyc +0 -0
  137. package/core/workflow/frontend_gate.py +162 -0
  138. package/installer/autostart.js +16 -2
  139. package/installer/cli.js +23 -0
  140. package/installer/keys.js +1 -0
  141. package/package.json +1 -1
  142. package/pyproject.toml +3 -1
  143. package/scripts/__pycache__/synapse-bridge.cpython-313.pyc +0 -0
  144. package/scripts/start-dashboard.sh +25 -1
package/VERSION CHANGED
@@ -1 +1 @@
1
- 4.1.0
1
+ 4.2.0
@@ -87,6 +87,21 @@ resumes at the right gate.
87
87
  coverage read from the report file, security grep, spell-check for
88
88
  copy. Reviewers (Quality Gate personas) interpret tool output; they do
89
89
  not replace it. APPROVED/REJECTED derives from evidence.
90
+ - **Excellence check (`excellence-mandate`, mandatory):** before closing,
91
+ answer three questions with evidence, not narration:
92
+ 1. What is **unfinished** in this delivery (trimmed scope, TODO left
93
+ behind, parity skipped)?
94
+ 2. What is **default** (template look, boilerplate copy, unstyled
95
+ component, generic strategy)?
96
+ 3. What would a **top-tier lead reject** here? Name the benchmark
97
+ (`reference_companies.application` — e.g. Linear/Stripe for
98
+ frontend) and judge against it.
99
+ A non-empty answer loops the work back into Gate 3 or is escalated to
100
+ the operator as an explicit open-items decision. Shipping with a
101
+ non-empty list and no operator decision is a constitution breach.
102
+ Time and token cost are not acceptable answers to any of the three.
103
+ - Quality Gate REJECTED loops back at most twice; a third REJECTED
104
+ escalates to the operator with the full verdict.
90
105
  - Close with an honest summary: what changed, where, how it was
91
106
  verified (real commands + results), what remains open.
92
107
 
@@ -117,14 +117,18 @@ enforcement_levels:
117
117
  rule: "When a squad lead dispatches a specialist via the Agent tool, the lead MUST emit `[arka:dispatch] <from> -> <to>` on a line of its own immediately before the dispatch call. The marker identifies the specialist to the PreToolUse specialist-enforcer so writes from the specialist pass `owner-match` instead of falling through as `no-routing-tag` or blocking the lead. The format is identical to `[arka:routing]` but uses the verb `dispatch` and points from the calling persona to the receiving specialist (e.g., `[arka:dispatch] paulo -> frontend-dev`)."
118
118
  enforcement: "PreToolUse hook (config/hooks/pre-tool-use.sh) reads the dispatch marker via core.workflow.specialist_enforcer._resolve_persona. Dispatch tag overrides routing tag because it is more specific. Without it, lead writes to specialist-owned files are blocked when hooks.specialistEnforcement=true. See ADR docs/adr/2026-05-28-specialist-dispatch-subagent-blindspot.md for the architectural constraint this rule mitigates."
119
119
 
120
+ # ─── Rule added in Excellence Reform (2026-07-05, operator mandate) ──
121
+ - id: excellence-mandate
122
+ rule: "Every deliverable targets excellence, not acceptance. Supervisors (leads, C-Suite, Quality Gate) and specialists are REQUIRED to be maximally critical and meticulous: no default-looking output, no unfinished edges, no lazy implementations, no delivery-for-the-sake-of-delivering. Latency and token cost are NEVER arguments against quality work — the CostGovernor hard budget is the only ceiling. Before any gate closes, the closing agent answers with evidence: 'what is unfinished, what is default, what would a top-tier lead (see reference_companies.application) reject here?' — a non-empty answer loops the work back or escalates to the operator; it is never silently shipped. UI work MUST load the frontend design skills (frontend-design, ui-ux-pro-max, project design system) at maximum effort and pass visual review against a named benchmark before reaching the Quality Gate."
123
+ enforcement: "G4 excellence check in arka/skills/flow/SKILL.md; frontend gate core/workflow/frontend_gate.py (flag hooks.frontendGate); QG reviewers must cite the named benchmark and enumerate concrete rejections; REJECTED redo loop capped at 2 cycles then operator escalation. Operator mandate 2026-07-05: 'não interessa se demora — o goal é excelência'."
124
+
120
125
  quality_gate:
121
126
  description: "Mandatory pre-delivery review. Nothing ships without APPROVED verdict."
122
127
  trigger: "After the last execution phase, before delivery to user"
123
128
  frequency: "Once per workflow execution, not per phase"
124
129
  model_policy:
125
- default: sonnet
126
- opus_scope: "Tier 0 diffs only: constitution, security-relevant changes, release pipeline, installer author deliverables explicitly security-flagged"
127
- note: "Marta's veto is model-independent: the verdict derives from the evidence report (core.governance.evidence_checks), not from model size."
130
+ default: best-available
131
+ note: "Quality Gate reviews run the best model available (frontier tier) at maximum effort by default the Excellence Reform (2026-07-05) inverted the former sonnet-default cost posture; review quality is never the place to save tokens. User overrides per role live in ~/.arkaos/models.yaml (Model Fabric). Marta's veto is model-independent: the verdict derives from the evidence report (core.governance.evidence_checks), not from model size."
128
132
  agents:
129
133
  orchestrator:
130
134
  id: cqo-marta
@@ -140,9 +144,10 @@ enforcement_levels:
140
144
  process:
141
145
  - "Evidence engine runs first: python -m core.governance.evidence_checks — the verdict derives from its report"
142
146
  - "Marta receives all output from execution phase plus the evidence report"
143
- - "Marta dispatches Eduardo (text) and Francisca (technical) in parallel to interpret the report"
144
- - "Each reviewer returns a structured QGVerdict (APPROVED or REJECTED) with specific issues; evidence overall=fail forces REJECTED"
147
+ - "Marta dispatches Eduardo (text) and Francisca (technical) in parallel, each in an INDEPENDENT subagent context (clean context, no stake in the work being approved)"
148
+ - "Each reviewer returns a structured QGVerdict (APPROVED or REJECTED) with specific issues; the verdict MUST name the benchmark used (reference_companies.application) and enumerate the concrete rejections a top-tier lead would raise; evidence overall=fail forces REJECTED"
145
149
  - "If ANY reviewer rejects: work loops back to execution with issue list"
150
+ - "Redo loop is capped at 2 cycles: a third REJECTED escalates to the operator with the full verdict instead of another silent retry (excellence-mandate)"
146
151
  - "If ALL approve: Marta issues final APPROVED verdict"
147
152
  - "Nothing reaches the user without APPROVED from all three"
148
153
 
@@ -182,11 +187,11 @@ enforcement_levels:
182
187
  enforcement: "persistence.py exports on status change; audit detects missing exports"
183
188
 
184
189
  - id: model-routing
185
- rule: "Per-tier model assignment enforced: Tier 0 opus, Tier 1/2 sonnet, mechanical haiku. Quality Gate reviewers run sonnet by default; opus ONLY for Tier 0/security-scope diffs (constitution, security, release pipeline, installer auth). Marta keeps her veto regardless of model tier."
190
+ rule: "Quality-first model routing: design, review, architecture, strategy, and Quality Gate phases run the BEST model available for the task (frontier tier) at maximum effort BY DEFAULT. Cheaper/smaller models (haiku, local) are reserved for genuinely mechanical work: commit messages, keyword extraction, data fetching, formatting. Cost optimisation NEVER downgrades a quality-critical phase — the CostGovernor hard budget is the only ceiling. User-owned overrides live in ~/.arkaos/models.yaml (Model Fabric): the user decides which provider/model serves each role; this rule sets the default posture, not a fixed vendor table. Replaced the cost-optimised per-tier table in the Excellence Reform (2026-07-05)."
186
191
  enforcement: "warning"
187
192
 
188
193
  - id: subagent-discipline
189
- rule: "Dispatch subagents only when task requires >3 Reads or >5 Greps or isolated context. Never parallel subagents sharing state. Prefer main thread for trivial tasks."
194
+ rule: "Dispatch subagents only when task requires >3 Reads or >5 Greps or isolated context. Never parallel subagents sharing state. Prefer main thread for trivial tasks. EXEMPT: quality dispatches (Quality Gate reviewers, adversarial verification, design review) are never suppressed by this rule — independent review context is a correctness requirement under excellence-mandate, not an optimisation choice."
190
195
  enforcement: "warning"
191
196
 
192
197
  # ─── Rule added in PR3 Squad Intelligence Upgrade (2026-05-28) ───────
@@ -436,3 +441,6 @@ amendments:
436
441
  - version: "2.32.0"
437
442
  date: "2026-05-13"
438
443
  changes: "PR10 Conclave Phase 5 — added 7 NON-NEGOTIABLE rules (quality-over-speed, always-research, project-design-system-prerequisite, definition-of-done-per-domain, arkaos-not-yes-man, inter-agent-checkpoints, hybrid-learning) and 3 new top-level sections (definition_of_done per domain, reference_companies, tone_guide)."
444
+ - version: "4.2.0"
445
+ date: "2026-07-05"
446
+ changes: "Excellence Reform (operator mandate) — added excellence-mandate NON-NEGOTIABLE rule; inverted model-routing from cost-optimised tiers to quality-first (best model for design/review/architecture/strategy, user overrides in ~/.arkaos/models.yaml); QG model_policy sonnet-default → best-available; QG reviewers independent + benchmark-cited verdicts + redo loop capped at 2; subagent-discipline exempts quality dispatches."
@@ -49,5 +49,20 @@ if [ ! -f "$ARKAOS_ROOT/core/hooks/post_tool_use.py" ]; then
49
49
  fi
50
50
  fi
51
51
 
52
+ # Interpreter resolution: prefer the ArkaOS venv (has pyyaml/pydantic);
53
+ # otherwise probe — an unrelated project venv first on PATH would give a
54
+ # python3 without yaml and silently degrade every gate.
55
+ _PY=""
56
+ for _cand in "$HOME/.arkaos/venv/bin/python3" "$HOME/.arkaos/.venv/bin/python3"; do
57
+ if [ -x "$_cand" ]; then _PY="$_cand"; break; fi
58
+ done
59
+ if [ -z "$_PY" ]; then
60
+ _PY="python3"
61
+ if ! "$_PY" -c "import yaml" 2>/dev/null; then
62
+ for _cand in /opt/homebrew/bin/python3 /usr/local/bin/python3 /usr/bin/python3; do
63
+ if [ -x "$_cand" ] && "$_cand" -c "import yaml" 2>/dev/null; then _PY="$_cand"; break; fi
64
+ done
65
+ fi
66
+ fi
52
67
  PYTHONPATH="$ARKAOS_ROOT${PYTHONPATH:+:$PYTHONPATH}" \
53
- exec python3 -m core.hooks.post_tool_use
68
+ exec "$_PY" -m core.hooks.post_tool_use
@@ -52,5 +52,20 @@ if [ ! -f "$ARKAOS_ROOT/core/hooks/pre_tool_use.py" ]; then
52
52
  fi
53
53
 
54
54
  # ─── Single python process; stdin/stdout/stderr/exit-code pass through ──
55
+ # Interpreter resolution: prefer the ArkaOS venv (has pyyaml/pydantic);
56
+ # otherwise probe — an unrelated project venv first on PATH would give a
57
+ # python3 without yaml and silently degrade every gate.
58
+ _PY=""
59
+ for _cand in "$HOME/.arkaos/venv/bin/python3" "$HOME/.arkaos/.venv/bin/python3"; do
60
+ if [ -x "$_cand" ]; then _PY="$_cand"; break; fi
61
+ done
62
+ if [ -z "$_PY" ]; then
63
+ _PY="python3"
64
+ if ! "$_PY" -c "import yaml" 2>/dev/null; then
65
+ for _cand in /opt/homebrew/bin/python3 /usr/local/bin/python3 /usr/bin/python3; do
66
+ if [ -x "$_cand" ] && "$_cand" -c "import yaml" 2>/dev/null; then _PY="$_cand"; break; fi
67
+ done
68
+ fi
69
+ fi
55
70
  PYTHONPATH="$ARKAOS_ROOT${PYTHONPATH:+:$PYTHONPATH}" \
56
- exec python3 -m core.hooks.pre_tool_use
71
+ exec "$_PY" -m core.hooks.pre_tool_use
@@ -120,6 +120,28 @@ if [ -n "$REPO" ] && command -v python3 &>/dev/null; then
120
120
  fi
121
121
  fi
122
122
 
123
+ # ─── Dashboard ensure (v4.1.2) ──────────────────────────────────────────
124
+ # Safety net for the login autostart (`npx arkaos autostart enable`): if the
125
+ # dashboard is healthy this is two curl health checks (~50ms); if not, it
126
+ # starts API+UI. Background + disown so it never touches the 5s budget.
127
+ # Toggle off: ~/.arkaos/config.json -> {"dashboard": {"ensure_on_session": false}}
128
+ if [ -n "$REPO" ] && [ -f "$REPO/scripts/start-dashboard.sh" ]; then
129
+ _DASH_ENSURE=$(python3 -c "import json; print(json.load(open('$HOME/.arkaos/config.json')).get('dashboard',{}).get('ensure_on_session', True))" 2>/dev/null || echo "True")
130
+ if [ "$_DASH_ENSURE" = "True" ] || [ "$_DASH_ENSURE" = "true" ]; then
131
+ mkdir -p "$HOME/.arkaos/logs"
132
+ (
133
+ # GNU `timeout` is absent on stock macOS — degrade to an unbounded
134
+ # run; the launcher itself is bounded (health wait + start).
135
+ if command -v timeout >/dev/null 2>&1; then
136
+ ARKAOS_NO_BROWSER=1 timeout 90s bash "$REPO/scripts/start-dashboard.sh" ensure >> "$HOME/.arkaos/logs/dashboard-ensure.log" 2>&1
137
+ else
138
+ ARKAOS_NO_BROWSER=1 bash "$REPO/scripts/start-dashboard.sh" ensure >> "$HOME/.arkaos/logs/dashboard-ensure.log" 2>&1
139
+ fi
140
+ ) &
141
+ disown 2>/dev/null || true
142
+ fi
143
+ fi
144
+
123
145
  # --- Session Memory Resume Context ---
124
146
  if command -v python3 &>/dev/null && [ -n "$REPO" ]; then
125
147
  _SESSION_CTX=$(cd "$REPO" && python3 -c "
@@ -49,6 +49,21 @@ if [ ! -f "$ARKAOS_ROOT/core/hooks/stop.py" ]; then
49
49
  fi
50
50
  fi
51
51
 
52
+ # Interpreter resolution: prefer the ArkaOS venv (has pyyaml/pydantic);
53
+ # otherwise probe — an unrelated project venv first on PATH would give a
54
+ # python3 without yaml and silently degrade every gate.
55
+ _PY=""
56
+ for _cand in "$HOME/.arkaos/venv/bin/python3" "$HOME/.arkaos/.venv/bin/python3"; do
57
+ if [ -x "$_cand" ]; then _PY="$_cand"; break; fi
58
+ done
59
+ if [ -z "$_PY" ]; then
60
+ _PY="python3"
61
+ if ! "$_PY" -c "import yaml" 2>/dev/null; then
62
+ for _cand in /opt/homebrew/bin/python3 /usr/local/bin/python3 /usr/bin/python3; do
63
+ if [ -x "$_cand" ] && "$_cand" -c "import yaml" 2>/dev/null; then _PY="$_cand"; break; fi
64
+ done
65
+ fi
66
+ fi
52
67
  PYTHONPATH="$ARKAOS_ROOT${PYTHONPATH:+:$PYTHONPATH}" \
53
- python3 -m core.hooks.stop
68
+ "$_PY" -m core.hooks.stop
54
69
  exit 0
@@ -54,5 +54,20 @@ if [ ! -f "$ARKAOS_ROOT/core/hooks/user_prompt_submit.py" ]; then
54
54
  fi
55
55
  fi
56
56
 
57
+ # Interpreter resolution: prefer the ArkaOS venv (has pyyaml/pydantic);
58
+ # otherwise probe — an unrelated project venv first on PATH would give a
59
+ # python3 without yaml and silently degrade every gate.
60
+ _PY=""
61
+ for _cand in "$HOME/.arkaos/venv/bin/python3" "$HOME/.arkaos/.venv/bin/python3"; do
62
+ if [ -x "$_cand" ]; then _PY="$_cand"; break; fi
63
+ done
64
+ if [ -z "$_PY" ]; then
65
+ _PY="python3"
66
+ if ! "$_PY" -c "import yaml" 2>/dev/null; then
67
+ for _cand in /opt/homebrew/bin/python3 /usr/local/bin/python3 /usr/bin/python3; do
68
+ if [ -x "$_cand" ] && "$_cand" -c "import yaml" 2>/dev/null; then _PY="$_cand"; break; fi
69
+ done
70
+ fi
71
+ fi
57
72
  PYTHONPATH="$ARKAOS_ROOT${PYTHONPATH:+:$PYTHONPATH}" \
58
- exec python3 -m core.hooks.user_prompt_submit
73
+ exec "$_PY" -m core.hooks.user_prompt_submit
@@ -0,0 +1,70 @@
1
+ # ═══════════════════════════════════════════════════════════════════════
2
+ # ArkaOS Model Fabric — who runs what.
3
+ #
4
+ # Your copy lives at ~/.arkaos/models.yaml (created by `npx arkaos models
5
+ # init`; this file is the packaged default). Edit it directly or use:
6
+ #
7
+ # npx arkaos models show every role and what it resolves to
8
+ # npx arkaos models set review anthropic/best
9
+ # npx arkaos models set mechanical ollama/qwen3:8b
10
+ #
11
+ # The interactive runtime (Claude Code, Codex, Gemini, Cursor) keeps its
12
+ # own main-loop model — roles govern everything ArkaOS itself dispatches:
13
+ # subagents, Quality Gate, Forge, fusion, cognition, daemons.
14
+ #
15
+ # Constitution `model-routing` (Excellence Reform 2026-07-05): quality-
16
+ # critical roles run the BEST model available by default; only genuinely
17
+ # mechanical work economises. Change the mapping, not the posture.
18
+ # ═══════════════════════════════════════════════════════════════════════
19
+ version: 1
20
+
21
+ # ─── Providers ──────────────────────────────────────────────────────────
22
+ # type must match a provider in core/runtime/llm_provider.py.
23
+ providers:
24
+ runtime:
25
+ type: subagent # shell out to the active runtime CLI
26
+ anthropic:
27
+ type: anthropic-direct # SDK; needs ANTHROPIC_API_KEY
28
+ ollama:
29
+ type: ollama # local models, http://localhost:11434
30
+ # openrouter: # one key -> hundreds of models (Model Fabric PR-B)
31
+ # type: openrouter
32
+
33
+ # ─── Aliases ────────────────────────────────────────────────────────────
34
+ # `best` / `default` / `fast` resolve per provider, so roles stay readable.
35
+ aliases:
36
+ runtime:
37
+ best: opus
38
+ default: sonnet
39
+ fast: haiku
40
+ anthropic:
41
+ best: claude-opus-4-8
42
+ default: claude-sonnet-5
43
+ fast: claude-haiku-4-5-20251001
44
+ ollama:
45
+ best: "" # set your strongest local model, e.g. kimi-k2.6
46
+ default: ""
47
+ fast: ""
48
+
49
+ # ─── Roles ──────────────────────────────────────────────────────────────
50
+ # provider/model per kind of work. `model` is an alias above or a literal
51
+ # model id. `effort`: low | medium | high | max.
52
+ roles:
53
+ design: {provider: runtime, model: best, effort: max}
54
+ review: {provider: runtime, model: best, effort: max}
55
+ architecture: {provider: runtime, model: best, effort: max}
56
+ strategy: {provider: runtime, model: best, effort: max}
57
+ quality_gate: {provider: runtime, model: best, effort: max}
58
+ execution: {provider: runtime, model: default, effort: high}
59
+ mechanical: {provider: runtime, model: fast, effort: low}
60
+
61
+ # ─── Fusion (Model Fabric PR-D) ─────────────────────────────────────────
62
+ # Panel answers in parallel; the judge analyses consensus, contradictions
63
+ # and blind spots, then synthesises. Empty panel = fusion disabled.
64
+ fusion:
65
+ judge: {provider: runtime, model: best, effort: max}
66
+ panel: []
67
+ # panel:
68
+ # - {provider: ollama, model: kimi-k2.6}
69
+ # - {provider: openrouter, model: deepseek/deepseek-v4-pro}
70
+ # - {provider: anthropic, model: best}
@@ -5,10 +5,12 @@ with ONE python process. Gate order is preserved exactly:
5
5
 
6
6
  1. KB-first research gate (core/workflow/research_gate.py)
7
7
  2. Specialist-dispatch gate (core/workflow/specialist_enforcer.py)
8
- 3. Fast allow for non-flow-gated tools
9
- 4. CostGovernor budget check (stdlib-only MUST run even when the
8
+ 3. Frontend excellence gate (core/workflow/frontend_gate.py — v4.2.0
9
+ excellence-mandate: UI edits require [arka:design] evidence)
10
+ 4. Fast allow for non-flow-gated tools
11
+ 5. CostGovernor budget check (stdlib-only — MUST run even when the
10
12
  yaml-needing enforcers degrade; see ADR 2026-07-04-cost-governor)
11
- 5. Evidence-flow gate (core/workflow/flow_enforcer.py)
13
+ 6. Evidence-flow gate (core/workflow/flow_enforcer.py)
12
14
 
13
15
  Behavior contract (unchanged from the bash version):
14
16
  - allow → no stdout, exit 0 (nudges/warnings on stderr)
@@ -165,6 +167,50 @@ def _specialist_gate(
165
167
  return None
166
168
 
167
169
 
170
+ def _frontend_gate(
171
+ root: str,
172
+ tool_name: str,
173
+ transcript_path: str,
174
+ session_id: str,
175
+ cwd: str,
176
+ tool_input: dict,
177
+ messages: _MessagesOnce,
178
+ ) -> int | None:
179
+ """Frontend excellence gate (excellence-mandate). 2 on deny, None to continue."""
180
+ if tool_name not in _SPECIALIST_TOOLS:
181
+ return None
182
+ if not (Path(root) / "core" / "workflow" / "frontend_gate.py").is_file():
183
+ return None
184
+ try:
185
+ from core.workflow.frontend_gate import (
186
+ evaluate,
187
+ is_ui_file,
188
+ record_telemetry,
189
+ )
190
+ except Exception:
191
+ return None # frontend-gate-import-failed → allow
192
+ # Zero-read fast path: only UI files ever need the transcript.
193
+ if not is_ui_file(str(tool_input.get("file_path", ""))):
194
+ return None
195
+ decision = evaluate(
196
+ tool_name=tool_name,
197
+ transcript_path=transcript_path,
198
+ session_id=session_id,
199
+ cwd=cwd,
200
+ tool_input=tool_input,
201
+ messages=messages.load(),
202
+ )
203
+ try:
204
+ record_telemetry(session_id=session_id, tool=tool_name, decision=decision)
205
+ except Exception:
206
+ pass
207
+ if not decision.allow:
208
+ return _deny(decision.to_stderr_message())
209
+ if decision.to_stderr_message():
210
+ print(decision.to_stderr_message(), file=sys.stderr)
211
+ return None
212
+
213
+
168
214
  def _budget_check(session_id: str) -> tuple[bool, str]:
169
215
  """CostGovernor gate (stdlib-only). Returns (allow, warning)."""
170
216
  try:
@@ -244,6 +290,12 @@ def main(stdin_json: dict | None = None) -> int:
244
290
  if code is not None:
245
291
  return code
246
292
 
293
+ code = _frontend_gate(
294
+ root, tool_name, transcript_path, session_id, cwd, tool_input, messages
295
+ )
296
+ if code is not None:
297
+ return code
298
+
247
299
  # Fast allow: not a flow-gated tool (Bash stays — classified per-command
248
300
  # by the enforcer via bash_is_effect()).
249
301
  if tool_name not in _FLOW_GATED_TOOLS:
@@ -36,6 +36,13 @@ _STOPWORDS = frozenset({
36
36
  })
37
37
 
38
38
 
39
+ # Jaccard between full documents rarely exceeds ~0.3 even for clearly
40
+ # related notes, while cosine similarity of embeddings sits comfortably
41
+ # above 0.5. A single threshold calibrated for cosine silently disabled
42
+ # the relator on every machine without fastembed (v4.1.1 fix).
43
+ _KEYWORD_MIN_SIMILARITY = 0.15
44
+
45
+
39
46
  def find_related(
40
47
  content: str,
41
48
  vault: Path,
@@ -48,8 +55,9 @@ def find_related(
48
55
  candidates = _collect_notes(vault, exclude)
49
56
  if not candidates:
50
57
  return []
51
- scored = _score_candidates(content, candidates)
52
- filtered = [r for r in scored if r.score >= min_similarity]
58
+ scored, method = _score_candidates(content, candidates)
59
+ threshold = min_similarity if method == "semantic" else _KEYWORD_MIN_SIMILARITY
60
+ filtered = [r for r in scored if r.score >= threshold]
53
61
  filtered.sort(key=lambda r: r.score, reverse=True)
54
62
  return filtered[:top_n]
55
63
 
@@ -70,22 +78,24 @@ def _collect_notes(vault: Path, exclude: Optional[Path]) -> list[tuple[Path, str
70
78
 
71
79
  def _score_candidates(
72
80
  content: str, candidates: list[tuple[Path, str]]
73
- ) -> list[RelatedNote]:
81
+ ) -> tuple[list[RelatedNote], str]:
82
+ """Score candidates, returning (results, method) so the caller can
83
+ apply a threshold appropriate to the scoring space."""
74
84
  try:
75
85
  return _semantic_score(content, candidates)
76
86
  except Exception:
77
- return _keyword_score(content, candidates)
87
+ return _keyword_score(content, candidates), "keyword"
78
88
 
79
89
 
80
90
  def _semantic_score(
81
91
  content: str, candidates: list[tuple[Path, str]]
82
- ) -> list[RelatedNote]:
92
+ ) -> tuple[list[RelatedNote], str]:
83
93
  from core.knowledge.embedder import embed, is_available
84
94
  if not is_available():
85
- return _keyword_score(content, candidates)
95
+ return _keyword_score(content, candidates), "keyword"
86
96
  query_emb = embed(content)
87
97
  if query_emb is None:
88
- return _keyword_score(content, candidates)
98
+ return _keyword_score(content, candidates), "keyword"
89
99
  results = []
90
100
  for path, text in candidates:
91
101
  note_emb = embed(text[:2000])
@@ -96,7 +106,7 @@ def _semantic_score(
96
106
  path=path, title=_title_from_path(path),
97
107
  score=score, excerpt=text[:160],
98
108
  ))
99
- return results
109
+ return results, "semantic"
100
110
 
101
111
 
102
112
  def _keyword_score(