arkaos 4.14.0 → 4.14.1
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/core/governance/__pycache__/evidence_checks.cpython-313.pyc +0 -0
- package/knowledge/skills-manifest.json +1 -1
- package/mcps/arka-prompts/commands.py +451 -0
- package/mcps/arka-prompts/pyproject.toml +15 -0
- package/mcps/arka-prompts/server.py +156 -0
- package/mcps/arka-tools/__pycache__/server.cpython-313.pyc +0 -0
- package/mcps/arka-tools/pyproject.toml +10 -0
- package/mcps/arka-tools/server.py +319 -0
- package/mcps/profiles/base.json +19 -0
- package/mcps/profiles/brand.json +19 -0
- package/mcps/profiles/comms.json +13 -0
- package/mcps/profiles/content.json +20 -0
- package/mcps/profiles/ecommerce.json +13 -0
- package/mcps/profiles/full-stack.json +15 -0
- package/mcps/profiles/laravel.json +12 -0
- package/mcps/profiles/nextjs.json +13 -0
- package/mcps/profiles/nuxt.json +13 -0
- package/mcps/profiles/react.json +12 -0
- package/mcps/profiles/vue.json +12 -0
- package/mcps/registry.json +423 -0
- package/mcps/scripts/apply-mcps.sh +282 -0
- package/mcps/stacks/laravel-packages.json +49 -0
- package/mcps/stacks/react-packages.json +58 -0
- package/package.json +2 -1
- package/pyproject.toml +1 -1
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
"""ArkaOS — programmatic MCP tools server (F2-3, Claude Code reform).
|
|
2
|
+
|
|
3
|
+
Where ``arka-prompts`` exposes department commands as PROMPTS, this
|
|
4
|
+
server exposes the core engine as TOOLS: KB vector search, workflow
|
|
5
|
+
state, Quality Gate queue, validated recipes, session memory and
|
|
6
|
+
telemetry summaries — grounded in the real ``core/`` APIs, never
|
|
7
|
+
re-implemented here.
|
|
8
|
+
|
|
9
|
+
Honesty contract (the explicit anti-pattern is claude-flow's fabricated
|
|
10
|
+
fallbacks): a tool whose backing store is absent returns
|
|
11
|
+
``{"available": false, "reason": ...}`` — real emptiness, never invented
|
|
12
|
+
data. Retrieval results carry their provenance labels
|
|
13
|
+
(``semantic`` / ``keyword-degraded``) untouched.
|
|
14
|
+
|
|
15
|
+
Write tools (workflow_update_phase, qg_submit) are DISABLED unless
|
|
16
|
+
``ARKA_TOOLS_WRITE=1`` — v1 ships read-first; mutation is an explicit
|
|
17
|
+
operator opt-in. ``qg_verdict`` is deliberately NOT exposed: a Quality
|
|
18
|
+
Gate verdict emitted by the same model doing the work defeats the whole
|
|
19
|
+
point of an INDEPENDENT gate (arkaos-not-yes-man). Verdicts stay with
|
|
20
|
+
the human/CQO reviewer path, never a self-service tool.
|
|
21
|
+
|
|
22
|
+
Usage: ``uv run server.py`` (deployed to ~/.claude/skills/arka/mcp-tools).
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import os
|
|
28
|
+
import sys
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import Any
|
|
31
|
+
|
|
32
|
+
from mcp.server.fastmcp import FastMCP
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _resolve_repo() -> str:
|
|
36
|
+
env = os.environ.get("ARKA_OS", "")
|
|
37
|
+
if env and (Path(env) / "core").is_dir():
|
|
38
|
+
return env
|
|
39
|
+
repo_file = Path.home() / ".arkaos" / ".repo-path"
|
|
40
|
+
try:
|
|
41
|
+
candidate = repo_file.read_text(encoding="utf-8").strip()
|
|
42
|
+
if candidate and (Path(candidate) / "core").is_dir():
|
|
43
|
+
return candidate
|
|
44
|
+
except OSError:
|
|
45
|
+
pass
|
|
46
|
+
return ""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
_REPO = _resolve_repo()
|
|
50
|
+
if _REPO:
|
|
51
|
+
sys.path.insert(0, _REPO)
|
|
52
|
+
|
|
53
|
+
mcp = FastMCP(
|
|
54
|
+
"arka-tools",
|
|
55
|
+
instructions=(
|
|
56
|
+
"ArkaOS programmatic tools: kb_search (vector KB, honest degraded"
|
|
57
|
+
" labels), workflow_get/update_phase, Quality Gate queue"
|
|
58
|
+
" (qg_pending/status/submit — NO verdict tool: gate independence),"
|
|
59
|
+
" recipes_search/recipe_get, session_memory_search (project scope"
|
|
60
|
+
" required), telemetry_summary, forge_plan, routing_scores. Write"
|
|
61
|
+
" tools require ARKA_TOOLS_WRITE=1."
|
|
62
|
+
),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
_UNAVAILABLE = {"available": False}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _unavailable(reason: str) -> dict[str, Any]:
|
|
69
|
+
"""Honest emptiness — never fabricated data (anti-claude-flow)."""
|
|
70
|
+
return {**_UNAVAILABLE, "reason": reason}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _writes_enabled() -> bool:
|
|
74
|
+
return os.environ.get("ARKA_TOOLS_WRITE", "").strip() == "1"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _writes_disabled_error() -> dict[str, Any]:
|
|
78
|
+
return _unavailable(
|
|
79
|
+
"write tools are disabled — set ARKA_TOOLS_WRITE=1 in the MCP"
|
|
80
|
+
" server env to enable (v1 ships read-first)"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@mcp.tool()
|
|
85
|
+
def kb_search(query: str, top_k: int = 5) -> dict[str, Any]:
|
|
86
|
+
"""Search the ArkaOS knowledge base (vector store at ~/.arkaos/knowledge.db).
|
|
87
|
+
|
|
88
|
+
Results carry their retrieval provenance: 'semantic' (real similarity
|
|
89
|
+
score) or 'keyword-degraded' (substring match, score=None) — never
|
|
90
|
+
faked.
|
|
91
|
+
"""
|
|
92
|
+
if not _REPO:
|
|
93
|
+
return _unavailable("ArkaOS repo not resolvable (~/.arkaos/.repo-path)")
|
|
94
|
+
db_path = Path.home() / ".arkaos" / "knowledge.db"
|
|
95
|
+
if not db_path.is_file():
|
|
96
|
+
return _unavailable("knowledge.db not found — run /arka index first")
|
|
97
|
+
from core.knowledge.vector_store import VectorStore
|
|
98
|
+
|
|
99
|
+
results = VectorStore(db_path).search(query, top_k=top_k)
|
|
100
|
+
return {"available": True, "results": results, "count": len(results)}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@mcp.tool()
|
|
104
|
+
def workflow_get() -> dict[str, Any]:
|
|
105
|
+
"""Current workflow state (phases, branch, violations) or none-active."""
|
|
106
|
+
if not _REPO:
|
|
107
|
+
return _unavailable("ArkaOS repo not resolvable")
|
|
108
|
+
from core.workflow.state import get_state
|
|
109
|
+
|
|
110
|
+
state = get_state()
|
|
111
|
+
if not state:
|
|
112
|
+
return {"available": True, "active": False}
|
|
113
|
+
return {"available": True, "active": True, "state": state}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@mcp.tool()
|
|
117
|
+
def workflow_update_phase(
|
|
118
|
+
phase: str, status: str, artifact: str = ""
|
|
119
|
+
) -> dict[str, Any]:
|
|
120
|
+
"""WRITE (gated): set a workflow phase status (pending/in_progress/
|
|
121
|
+
completed/skipped), optionally attaching an evidence artifact path."""
|
|
122
|
+
if not _writes_enabled():
|
|
123
|
+
return _writes_disabled_error()
|
|
124
|
+
if not _REPO:
|
|
125
|
+
return _unavailable("ArkaOS repo not resolvable")
|
|
126
|
+
from core.workflow.state import update_phase
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
state = update_phase(phase, status, artifact or None)
|
|
130
|
+
except ValueError as exc:
|
|
131
|
+
return _unavailable(str(exc))
|
|
132
|
+
return {"available": True, "state": state}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@mcp.tool()
|
|
136
|
+
def qg_pending(reviewer: str = "") -> dict[str, Any]:
|
|
137
|
+
"""Quality Gate submissions awaiting review (optionally per reviewer)."""
|
|
138
|
+
if not _REPO:
|
|
139
|
+
return _unavailable("ArkaOS repo not resolvable")
|
|
140
|
+
from core.governance.quality_api import list_pending
|
|
141
|
+
|
|
142
|
+
pending = list_pending(reviewer or None)
|
|
143
|
+
return {"available": True, "pending": pending, "count": len(pending)}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@mcp.tool()
|
|
147
|
+
def qg_status(submission_id: str) -> dict[str, Any]:
|
|
148
|
+
"""One Quality Gate submission by id, with its verdict history."""
|
|
149
|
+
if not _REPO:
|
|
150
|
+
return _unavailable("ArkaOS repo not resolvable")
|
|
151
|
+
from core.governance.quality_api import query
|
|
152
|
+
|
|
153
|
+
record = query(submission_id)
|
|
154
|
+
if record is None:
|
|
155
|
+
return _unavailable(f"no QG submission with id {submission_id!r}")
|
|
156
|
+
return {"available": True, "submission": record}
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@mcp.tool()
|
|
160
|
+
def qg_submit(
|
|
161
|
+
title: str, description: str, deliverable_type: str, submitter: str
|
|
162
|
+
) -> dict[str, Any]:
|
|
163
|
+
"""WRITE (gated): submit a deliverable to the Quality Gate queue."""
|
|
164
|
+
if not _writes_enabled():
|
|
165
|
+
return _writes_disabled_error()
|
|
166
|
+
if not _REPO:
|
|
167
|
+
return _unavailable("ArkaOS repo not resolvable")
|
|
168
|
+
from core.governance.quality_api import submit
|
|
169
|
+
|
|
170
|
+
record = submit(
|
|
171
|
+
title=title,
|
|
172
|
+
description=description,
|
|
173
|
+
deliverable_type=deliverable_type,
|
|
174
|
+
submitter=submitter,
|
|
175
|
+
)
|
|
176
|
+
return {"available": True, "submission": record}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# NOTE: qg_verdict is intentionally NOT a tool. A verdict emitted by the
|
|
180
|
+
# same model doing the work is not an independent gate — it is a
|
|
181
|
+
# rubber stamp. Verdicts stay with the CQO/human reviewer path.
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@mcp.tool()
|
|
185
|
+
def recipes_search(keywords: str = "", stack: str = "") -> dict[str, Any]:
|
|
186
|
+
"""QG-approved validated recipes matching keywords and/or stack.
|
|
187
|
+
|
|
188
|
+
Read-only by design — recipe capture stays operator-confirmed
|
|
189
|
+
(confidentiality contract of core/knowledge/recipes.py).
|
|
190
|
+
"""
|
|
191
|
+
if not _REPO:
|
|
192
|
+
return _unavailable("ArkaOS repo not resolvable")
|
|
193
|
+
from core.knowledge.recipes import list_recipes
|
|
194
|
+
|
|
195
|
+
wanted_kw = {w.strip().lower() for w in keywords.split(",") if w.strip()}
|
|
196
|
+
wanted_stack = {s.strip().lower() for s in stack.split(",") if s.strip()}
|
|
197
|
+
matches = []
|
|
198
|
+
for recipe in list_recipes():
|
|
199
|
+
recipe_kw = {k.lower() for k in recipe.feature_keywords}
|
|
200
|
+
recipe_stack = {s.lower() for s in recipe.stack}
|
|
201
|
+
if wanted_kw and not (wanted_kw & recipe_kw):
|
|
202
|
+
continue
|
|
203
|
+
if wanted_stack and not (wanted_stack & recipe_stack):
|
|
204
|
+
continue
|
|
205
|
+
matches.append({
|
|
206
|
+
"slug": recipe.slug, "name": recipe.name,
|
|
207
|
+
"problem": recipe.problem, "stack": recipe.stack,
|
|
208
|
+
"path": f"~/.arkaos/recipes/{recipe.slug}/",
|
|
209
|
+
})
|
|
210
|
+
return {"available": True, "recipes": matches, "count": len(matches)}
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@mcp.tool()
|
|
214
|
+
def recipe_get(slug: str) -> dict[str, Any]:
|
|
215
|
+
"""Full detail of one validated recipe (files, AC, apply notes)."""
|
|
216
|
+
if not _REPO:
|
|
217
|
+
return _unavailable("ArkaOS repo not resolvable")
|
|
218
|
+
from core.knowledge.recipes import load_recipe
|
|
219
|
+
|
|
220
|
+
recipe = load_recipe(slug)
|
|
221
|
+
if recipe is None:
|
|
222
|
+
return _unavailable(f"no recipe with slug {slug!r}")
|
|
223
|
+
return {"available": True, "recipe": recipe.model_dump()}
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@mcp.tool()
|
|
227
|
+
def session_memory_search(query: str, project: str) -> dict[str, Any]:
|
|
228
|
+
"""Cross-session turn memory for ONE project (scope is REQUIRED —
|
|
229
|
+
an unscoped search would leak one client's turns into another's
|
|
230
|
+
context; v2.18.0 confidentiality precedent). Labels are honest:
|
|
231
|
+
keyword-degraded results carry score=None."""
|
|
232
|
+
if not _REPO:
|
|
233
|
+
return _unavailable("ArkaOS repo not resolvable")
|
|
234
|
+
if not project.strip():
|
|
235
|
+
return _unavailable("project scope is required — never searches globally")
|
|
236
|
+
from core.memory.semantic_store import SessionMemoryStore, default_db_path
|
|
237
|
+
|
|
238
|
+
if not default_db_path().is_file():
|
|
239
|
+
return _unavailable("session-memory.db not found (no turns captured yet)")
|
|
240
|
+
from core.memory.semantic_store import neutralize_summary
|
|
241
|
+
|
|
242
|
+
hits = SessionMemoryStore().keyword_search(query, project.strip(), top_k=5)
|
|
243
|
+
# WHITELIST the model-visible surface to the canonical read-path's
|
|
244
|
+
# minimal shape (session_memory_layer.py L9.5) — keyword_search dumps
|
|
245
|
+
# the whole ~14-field record, and cwd/file_paths are attacker-stored
|
|
246
|
+
# free text NOT constrained by the project scope. Every string field
|
|
247
|
+
# is neutralized (OWASP LLM01): a stored [arka:design]/newline must
|
|
248
|
+
# never reach the model in a form able to forge a gate marker. ts is
|
|
249
|
+
# neutralized too (harmless on a valid ISO8601) so this surface's
|
|
250
|
+
# safety is LOCAL, not reliant on turn_capture's system-ts invariant.
|
|
251
|
+
results = [
|
|
252
|
+
{
|
|
253
|
+
"summary": neutralize_summary(hit.get("summary", "")),
|
|
254
|
+
"project_name": neutralize_summary(hit.get("project_name", "")),
|
|
255
|
+
"ts": neutralize_summary(hit.get("ts", "")),
|
|
256
|
+
"score": hit.get("score"),
|
|
257
|
+
"retrieval": hit.get("retrieval", ""),
|
|
258
|
+
}
|
|
259
|
+
for hit in hits
|
|
260
|
+
]
|
|
261
|
+
return {"available": True, "results": results, "count": len(results)}
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
@mcp.tool()
|
|
265
|
+
def telemetry_summary(period: str = "today") -> dict[str, Any]:
|
|
266
|
+
"""Enforcement + LLM-cost telemetry rollups (today/week/month/all)."""
|
|
267
|
+
if not _REPO:
|
|
268
|
+
return _unavailable("ArkaOS repo not resolvable")
|
|
269
|
+
out: dict[str, Any] = {"available": True}
|
|
270
|
+
try:
|
|
271
|
+
from core.governance.enforcement_telemetry import summarise as enf
|
|
272
|
+
out["enforcement"] = enf(period).__dict__
|
|
273
|
+
except Exception as exc:
|
|
274
|
+
out["enforcement"] = _unavailable(f"enforcement summary failed: {exc}")
|
|
275
|
+
try:
|
|
276
|
+
from core.runtime.llm_cost_telemetry import summarise as cost
|
|
277
|
+
out["llm_cost"] = cost(period).__dict__
|
|
278
|
+
except Exception as exc:
|
|
279
|
+
out["llm_cost"] = _unavailable(f"cost summary failed: {exc}")
|
|
280
|
+
return out
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
@mcp.tool()
|
|
284
|
+
def forge_plan() -> dict[str, Any]:
|
|
285
|
+
"""The active Forge plan (name, status, phases) or none-active."""
|
|
286
|
+
if not _REPO:
|
|
287
|
+
return _unavailable("ArkaOS repo not resolvable")
|
|
288
|
+
from core.forge.persistence import get_active_plan
|
|
289
|
+
|
|
290
|
+
plan = get_active_plan()
|
|
291
|
+
if plan is None:
|
|
292
|
+
return {"available": True, "active": False}
|
|
293
|
+
return {
|
|
294
|
+
"available": True, "active": True,
|
|
295
|
+
"name": getattr(plan, "name", ""),
|
|
296
|
+
"status": getattr(plan, "status", ""),
|
|
297
|
+
"phases": len(getattr(plan, "plan_phases", []) or []),
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
@mcp.tool()
|
|
302
|
+
def routing_scores() -> dict[str, Any]:
|
|
303
|
+
"""Per-department QG approval evidence (routing-scores.json, F1-B1) —
|
|
304
|
+
the citable counts behind [arka:redo-risk]."""
|
|
305
|
+
if not _REPO:
|
|
306
|
+
return _unavailable("ArkaOS repo not resolvable")
|
|
307
|
+
from core.governance.routing_feedback import load_scores
|
|
308
|
+
|
|
309
|
+
scores = load_scores()
|
|
310
|
+
if scores is None:
|
|
311
|
+
return _unavailable(
|
|
312
|
+
"routing-scores.json absent/invalid — run"
|
|
313
|
+
" core.governance.routing_feedback_cli rebuild"
|
|
314
|
+
)
|
|
315
|
+
return {"available": True, **scores.model_dump()}
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
if __name__ == "__main__":
|
|
319
|
+
mcp.run()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_meta": {
|
|
3
|
+
"name": "base",
|
|
4
|
+
"description": "Base MCP profile — included in ALL projects",
|
|
5
|
+
"extends": null
|
|
6
|
+
},
|
|
7
|
+
"mcps": [
|
|
8
|
+
"arka-prompts",
|
|
9
|
+
"arka-tools",
|
|
10
|
+
"obsidian",
|
|
11
|
+
"context7",
|
|
12
|
+
"playwright",
|
|
13
|
+
"sentry",
|
|
14
|
+
"gh-grep",
|
|
15
|
+
"clickup",
|
|
16
|
+
"firecrawl",
|
|
17
|
+
"supabase"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_meta": {
|
|
3
|
+
"name": "brand",
|
|
4
|
+
"description": "Brand department MCP profile — base + Canva for visual design",
|
|
5
|
+
"extends": "base"
|
|
6
|
+
},
|
|
7
|
+
"mcps": [
|
|
8
|
+
"arka-prompts",
|
|
9
|
+
"obsidian",
|
|
10
|
+
"context7",
|
|
11
|
+
"playwright",
|
|
12
|
+
"sentry",
|
|
13
|
+
"gh-grep",
|
|
14
|
+
"clickup",
|
|
15
|
+
"firecrawl",
|
|
16
|
+
"supabase",
|
|
17
|
+
"canva"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_meta": {
|
|
3
|
+
"name": "content",
|
|
4
|
+
"description": "Content department MCP profile — base + Higgsfield generation engine and Canva for the production pipeline",
|
|
5
|
+
"extends": "base"
|
|
6
|
+
},
|
|
7
|
+
"mcps": [
|
|
8
|
+
"arka-prompts",
|
|
9
|
+
"obsidian",
|
|
10
|
+
"context7",
|
|
11
|
+
"playwright",
|
|
12
|
+
"sentry",
|
|
13
|
+
"gh-grep",
|
|
14
|
+
"clickup",
|
|
15
|
+
"firecrawl",
|
|
16
|
+
"supabase",
|
|
17
|
+
"canva",
|
|
18
|
+
"higgsfield"
|
|
19
|
+
]
|
|
20
|
+
}
|