arkaos 4.13.2 → 4.14.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/VERSION +1 -1
- package/arka/SKILL.md +16 -0
- package/bin/arka-doctor +20 -1
- package/config/cognition/schedules.yaml +16 -0
- package/config/hooks/_lib/fastpath/engine.cjs +337 -0
- package/config/hooks/gate-manifest.json +741 -0
- package/config/hooks/post-tool-use.cjs +113 -0
- package/config/hooks/pre-tool-use.cjs +109 -0
- package/config/hooks/session-end.ps1 +40 -0
- package/config/hooks/session-end.sh +55 -0
- package/config/hooks/session-start.sh +25 -189
- package/config/hooks/subagent-stop.ps1 +40 -0
- package/config/hooks/subagent-stop.sh +56 -0
- package/config/mcp-policy.yaml +8 -0
- package/config/skills-curated.yaml +39 -0
- package/config/statusline.sh +57 -1
- package/core/cognition/capture/__pycache__/store.cpython-313.pyc +0 -0
- package/core/cognition/capture/store.py +7 -2
- package/core/evals/__pycache__/__init__.cpython-314.pyc +0 -0
- package/core/evals/__pycache__/sanitizer.cpython-314.pyc +0 -0
- package/core/evals/__pycache__/schema.cpython-314.pyc +0 -0
- package/core/evals/__pycache__/verdict_labels.cpython-314.pyc +0 -0
- package/core/forge/__pycache__/complexity.cpython-314.pyc +0 -0
- package/core/governance/__pycache__/evidence_checks.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/judge.cpython-314.pyc +0 -0
- package/core/governance/__pycache__/leak_scanner.cpython-314.pyc +0 -0
- package/core/governance/__pycache__/qg_verdict.cpython-314.pyc +0 -0
- package/core/governance/__pycache__/routing_feedback.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/routing_feedback.cpython-314.pyc +0 -0
- package/core/governance/__pycache__/routing_feedback_cli.cpython-313.pyc +0 -0
- package/core/governance/evidence_checks.py +42 -7
- package/core/governance/routing_feedback.py +229 -0
- package/core/governance/routing_feedback_cli.py +49 -0
- package/core/hooks/__pycache__/_shared.cpython-312.pyc +0 -0
- package/core/hooks/__pycache__/gate_manifest.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__/session_end.cpython-313.pyc +0 -0
- package/core/hooks/__pycache__/session_start.cpython-312.pyc +0 -0
- package/core/hooks/__pycache__/session_start.cpython-313.pyc +0 -0
- package/core/hooks/__pycache__/stop.cpython-313.pyc +0 -0
- package/core/hooks/__pycache__/stop.cpython-314.pyc +0 -0
- package/core/hooks/__pycache__/subagent_stop.cpython-313.pyc +0 -0
- package/core/hooks/__pycache__/subagent_stop.cpython-314.pyc +0 -0
- package/core/hooks/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
- package/core/hooks/__pycache__/user_prompt_submit.cpython-314.pyc +0 -0
- package/core/hooks/gate_manifest.py +366 -0
- package/core/hooks/post_tool_use.py +5 -2
- package/core/hooks/session_end.py +127 -0
- package/core/hooks/session_start.py +322 -0
- package/core/hooks/stop.py +69 -0
- package/core/hooks/subagent_stop.py +186 -0
- package/core/hooks/user_prompt_submit.py +10 -6
- package/core/knowledge/__pycache__/embedding_backends.cpython-313.pyc +0 -0
- package/core/knowledge/__pycache__/embedding_backends.cpython-314.pyc +0 -0
- package/core/knowledge/__pycache__/pattern_cards.cpython-313.pyc +0 -0
- package/core/knowledge/__pycache__/pattern_cards.cpython-314.pyc +0 -0
- package/core/knowledge/__pycache__/recipes.cpython-314.pyc +0 -0
- package/core/knowledge/__pycache__/vector_store.cpython-313.pyc +0 -0
- package/core/knowledge/__pycache__/vector_store.cpython-314.pyc +0 -0
- package/core/knowledge/embedding_backends.py +278 -0
- package/core/knowledge/pattern_cards.py +49 -1
- package/core/knowledge/vector_store.py +14 -2
- package/core/memory/__pycache__/semantic_store.cpython-312.pyc +0 -0
- package/core/memory/__pycache__/semantic_store.cpython-313.pyc +0 -0
- package/core/memory/__pycache__/semantic_store.cpython-314.pyc +0 -0
- package/core/memory/__pycache__/turn_capture.cpython-313.pyc +0 -0
- package/core/memory/__pycache__/turn_capture.cpython-314.pyc +0 -0
- package/core/memory/semantic_store.py +301 -0
- package/core/memory/turn_capture.py +277 -0
- package/core/registry/__pycache__/generator.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/claude_code.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/codex_cli.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/cost_governor.cpython-314.pyc +0 -0
- package/core/runtime/__pycache__/gemini_cli.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/llm_cost_telemetry.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/mcp_telemetry.cpython-312.pyc +0 -0
- package/core/shared/__pycache__/decay.cpython-313.pyc +0 -0
- package/core/shared/__pycache__/decay.cpython-314.pyc +0 -0
- package/core/shared/__pycache__/sqlite_recovery.cpython-312.pyc +0 -0
- package/core/shared/__pycache__/sqlite_recovery.cpython-313.pyc +0 -0
- package/core/shared/__pycache__/sqlite_recovery.cpython-314.pyc +0 -0
- package/core/shared/decay.py +94 -0
- package/core/shared/sqlite_recovery.py +266 -0
- package/core/synapse/__pycache__/agent_experiences_layer.cpython-313.pyc +0 -0
- package/core/synapse/__pycache__/agent_experiences_layer.cpython-314.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__/engine.cpython-314.pyc +0 -0
- package/core/synapse/__pycache__/layers.cpython-312.pyc +0 -0
- package/core/synapse/__pycache__/pattern_library_layer.cpython-314.pyc +0 -0
- package/core/synapse/__pycache__/recipe_layer.cpython-313.pyc +0 -0
- package/core/synapse/__pycache__/recipe_layer.cpython-314.pyc +0 -0
- package/core/synapse/__pycache__/routing_feedback_layer.cpython-313.pyc +0 -0
- package/core/synapse/__pycache__/routing_feedback_layer.cpython-314.pyc +0 -0
- package/core/synapse/__pycache__/session_memory_layer.cpython-312.pyc +0 -0
- package/core/synapse/__pycache__/session_memory_layer.cpython-313.pyc +0 -0
- package/core/synapse/__pycache__/session_memory_layer.cpython-314.pyc +0 -0
- package/core/synapse/agent_experiences_layer.py +42 -3
- package/core/synapse/engine.py +13 -0
- package/core/synapse/recipe_layer.py +27 -4
- package/core/synapse/routing_feedback_layer.py +132 -0
- package/core/synapse/session_memory_layer.py +152 -0
- package/core/workflow/__pycache__/__init__.cpython-312.pyc +0 -0
- package/core/workflow/__pycache__/design_authorization.cpython-314.pyc +0 -0
- package/core/workflow/__pycache__/frontend_gate.cpython-314.pyc +0 -0
- package/core/workflow/__pycache__/specialist_enforcer.cpython-314.pyc +0 -0
- package/installer/adapters/claude-code.js +37 -0
- package/installer/claude-plugins.js +6 -0
- package/installer/cli.js +23 -2
- package/installer/config-seed.js +25 -16
- package/installer/doctor.js +116 -1
- package/installer/hook-lib.js +28 -0
- package/installer/index.js +48 -153
- package/installer/mcp-runner.js +108 -0
- package/installer/skill-deploy.js +189 -0
- package/installer/skills-mode.js +64 -0
- package/installer/update.js +55 -79
- package/knowledge/commands-registry.json.bak +171 -4
- package/knowledge/skills-manifest.json +2337 -0
- package/package.json +1 -1
- package/pyproject.toml +9 -1
- package/scripts/__pycache__/marketplace_gen.cpython-313.pyc +0 -0
- package/scripts/__pycache__/synapse-bridge.cpython-314.pyc +0 -0
- package/scripts/marketplace_gen.py +310 -0
- package/scripts/tools/__pycache__/docs_stats.cpython-313.pyc +0 -0
- package/scripts/tools/__pycache__/skill_budget.cpython-313.pyc +0 -0
- package/scripts/tools/docs_stats.py +6 -4
- package/scripts/tools/skill_budget.py +209 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""Synapse L9.5 — session semantic memory retrieval (F1-A3).
|
|
2
|
+
|
|
3
|
+
Closes the memory loop opened by F1-A2: reads the per-session cache the
|
|
4
|
+
detached turn-capture worker precomputed (semantic neighbours, labeled
|
|
5
|
+
``ranked@HH:MMZ`` with the cache's own computed_at — or ``pre-ranked``
|
|
6
|
+
when no timestamp exists; the label states WHEN the ranking happened,
|
|
7
|
+
never claims it was for this prompt) plus a live keyword LIKE over the
|
|
8
|
+
store for the current prompt.
|
|
9
|
+
|
|
10
|
+
Honesty contract (identical to L2.5 / vector_store.py): semantic hits
|
|
11
|
+
carry their cosine score; keyword hits carry ``score=None`` and are
|
|
12
|
+
labeled ``keyword — NOT semantic similarity``. The layer NEVER embeds —
|
|
13
|
+
zero embedding cost on the prompt hot path, ever.
|
|
14
|
+
|
|
15
|
+
Flag: ``synapse.l95SessionMemory`` (default ON) + env ``ARKA_BYPASS_L95``.
|
|
16
|
+
Inert (zero tokens) when the store is empty or the flag is off.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
import os
|
|
23
|
+
import time
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
from core.synapse.layers import Layer, LayerResult, PromptContext
|
|
27
|
+
|
|
28
|
+
_MAX_ITEMS = 5
|
|
29
|
+
_SUMMARY_CHARS = 160
|
|
30
|
+
_LIVE_KEYWORD_K = 2
|
|
31
|
+
_CACHE_SEMANTIC_K = 3
|
|
32
|
+
_CONFIG_PATH = Path.home() / ".arkaos" / "config.json"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _l95_feature_flag_on() -> bool:
|
|
36
|
+
if os.environ.get("ARKA_BYPASS_L95", "").strip() == "1":
|
|
37
|
+
return False
|
|
38
|
+
try:
|
|
39
|
+
data = json.loads(_CONFIG_PATH.read_text(encoding="utf-8"))
|
|
40
|
+
except (OSError, json.JSONDecodeError):
|
|
41
|
+
return True
|
|
42
|
+
synapse_cfg = data.get("synapse") or {}
|
|
43
|
+
return bool(synapse_cfg.get("l95SessionMemory", True))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _read_session_cache(session_id: str) -> tuple[list[dict], str]:
|
|
47
|
+
"""Precomputed neighbours + the timestamp they were ranked at."""
|
|
48
|
+
if not session_id:
|
|
49
|
+
return [], ""
|
|
50
|
+
path = Path.home() / ".arkaos" / "context-cache" / f"session-memory-{session_id}.json"
|
|
51
|
+
try:
|
|
52
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
53
|
+
except (OSError, json.JSONDecodeError):
|
|
54
|
+
return [], ""
|
|
55
|
+
if payload.get("version") != 1:
|
|
56
|
+
return [], ""
|
|
57
|
+
items = payload.get("items")
|
|
58
|
+
ranked_at = str(payload.get("computed_at") or "")
|
|
59
|
+
if not isinstance(items, list):
|
|
60
|
+
return [], ""
|
|
61
|
+
return items[:_CACHE_SEMANTIC_K], ranked_at
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _format_item(item: dict, ranked_at: str = "") -> str:
|
|
65
|
+
from core.memory.semantic_store import neutralize_summary
|
|
66
|
+
|
|
67
|
+
summary = neutralize_summary(item.get("summary") or "")[:_SUMMARY_CHARS]
|
|
68
|
+
if not summary:
|
|
69
|
+
return ""
|
|
70
|
+
score = item.get("score")
|
|
71
|
+
if item.get("retrieval") == "semantic" and isinstance(score, (int, float)):
|
|
72
|
+
# Provenance is the payload's own computed_at (UTC isoformat) —
|
|
73
|
+
# the detached worker may lag a turn, so the label states WHEN
|
|
74
|
+
# it ranked, never claims "last turn". Z marks UTC explicitly.
|
|
75
|
+
stamp = f" ranked@{ranked_at[11:16]}Z" if len(ranked_at) >= 16 else " pre-ranked"
|
|
76
|
+
label = f"semantic {score:.2f}{stamp}"
|
|
77
|
+
else:
|
|
78
|
+
label = "keyword — NOT semantic similarity"
|
|
79
|
+
project = neutralize_summary(item.get("project_name") or "")
|
|
80
|
+
suffix = f" ({project})" if project else ""
|
|
81
|
+
return f"- [{label}] {summary}{suffix}"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class SessionMemoryLayer(Layer):
|
|
85
|
+
"""L9.5: cross-session turn memory — cache read + live keyword only."""
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def id(self) -> str:
|
|
89
|
+
return "L9.5"
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def name(self) -> str:
|
|
93
|
+
return "SessionSemanticMemory"
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def cache_ttl(self) -> int:
|
|
97
|
+
# Best-effort freshness: the cache write is detached and may lag
|
|
98
|
+
# a turn — recomputing here is one file read + one indexed LIKE.
|
|
99
|
+
return 0
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def priority(self) -> int:
|
|
103
|
+
return 91 # right after L9 SessionContext (90)
|
|
104
|
+
|
|
105
|
+
def compute(self, ctx: PromptContext) -> LayerResult:
|
|
106
|
+
start = time.time()
|
|
107
|
+
content = ""
|
|
108
|
+
try:
|
|
109
|
+
if ctx.user_input and _l95_feature_flag_on():
|
|
110
|
+
content = self._retrieve(ctx)
|
|
111
|
+
except Exception: # noqa: BLE001 — layer must never break the prompt
|
|
112
|
+
content = ""
|
|
113
|
+
elapsed = int((time.time() - start) * 1000)
|
|
114
|
+
if not content:
|
|
115
|
+
return LayerResult(layer_id=self.id, tag="", content="",
|
|
116
|
+
tokens_est=0, compute_ms=elapsed, cached=False)
|
|
117
|
+
count = content.count("\n- ") + (1 if content.startswith("- ") else 0)
|
|
118
|
+
return LayerResult(
|
|
119
|
+
layer_id=self.id,
|
|
120
|
+
tag=f"[session-memory:{count}]",
|
|
121
|
+
content=content,
|
|
122
|
+
tokens_est=len(content.split()),
|
|
123
|
+
compute_ms=elapsed,
|
|
124
|
+
cached=False,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
def _retrieve(self, ctx: PromptContext) -> str:
|
|
128
|
+
session_id = str((ctx.extra or {}).get("session_id") or "")
|
|
129
|
+
lines: list[str] = []
|
|
130
|
+
seen: set[str] = set()
|
|
131
|
+
items, ranked_at = _read_session_cache(session_id)
|
|
132
|
+
for item in items:
|
|
133
|
+
line = _format_item(item, ranked_at)
|
|
134
|
+
if line and item.get("summary") not in seen:
|
|
135
|
+
seen.add(item.get("summary"))
|
|
136
|
+
lines.append(line)
|
|
137
|
+
# Scope-or-skip: an unscoped LIKE would surface one client's
|
|
138
|
+
# turns inside another client's prompt (QG blocker B2 —
|
|
139
|
+
# v2.18.0 confidentiality precedent). No resolvable project
|
|
140
|
+
# scope ⇒ NO live search, never a silently-global one.
|
|
141
|
+
project = ctx.project_name or (Path(ctx.cwd).name if ctx.cwd else "")
|
|
142
|
+
from core.memory.semantic_store import SessionMemoryStore, default_db_path
|
|
143
|
+
if project and default_db_path().is_file():
|
|
144
|
+
hits = SessionMemoryStore().keyword_search(
|
|
145
|
+
ctx.user_input, project, top_k=_LIVE_KEYWORD_K
|
|
146
|
+
)
|
|
147
|
+
for hit in hits:
|
|
148
|
+
line = _format_item(hit)
|
|
149
|
+
if line and hit.get("summary") not in seen:
|
|
150
|
+
seen.add(hit.get("summary"))
|
|
151
|
+
lines.append(line)
|
|
152
|
+
return "\n".join(lines[:_MAX_ITEMS])
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -79,6 +79,15 @@ export function deployProjectAgents(projectDir, sourceRoot = ARKAOS_ROOT) {
|
|
|
79
79
|
* `shell: "powershell"` field and letting Claude Code handle the
|
|
80
80
|
* PowerShell invocation itself.
|
|
81
81
|
*/
|
|
82
|
+
// F2-6: hooks with a Node fast-path shim. POSIX-only — Windows keeps
|
|
83
|
+
// the .ps1 -> Python chain untouched (the .cjs stdin contract is only
|
|
84
|
+
// validated against bash-spawned hooks). Registration is conditional on
|
|
85
|
+
// the deployed .cjs actually existing, so an older package or a partial
|
|
86
|
+
// deploy degrades to the .sh chain instead of a dead command. Install-
|
|
87
|
+
// time opt-out: ARKA_INSTALL_FASTPATH=0 registers the .sh as before
|
|
88
|
+
// (runtime kill-switch is ARKA_HOOK_FASTPATH=0, read by the shim).
|
|
89
|
+
const FASTPATH_HOOKS = new Set(["pre-tool-use", "post-tool-use"]);
|
|
90
|
+
|
|
82
91
|
function hookEntry(hooksDir, name, timeout) {
|
|
83
92
|
if (IS_WINDOWS) {
|
|
84
93
|
return {
|
|
@@ -88,6 +97,21 @@ function hookEntry(hooksDir, name, timeout) {
|
|
|
88
97
|
timeout,
|
|
89
98
|
};
|
|
90
99
|
}
|
|
100
|
+
if (
|
|
101
|
+
FASTPATH_HOOKS.has(name) &&
|
|
102
|
+
process.env.ARKA_INSTALL_FASTPATH !== "0" &&
|
|
103
|
+
existsSync(join(hooksDir, `${name}.cjs`)) &&
|
|
104
|
+
// QG B3: a split deploy (.cjs without _lib/fastpath) must never be
|
|
105
|
+
// registered — the shim would only ever delegate (it degrades
|
|
106
|
+
// safely, but registering the .sh directly is strictly better).
|
|
107
|
+
existsSync(join(hooksDir, "_lib", "fastpath", "engine.cjs"))
|
|
108
|
+
) {
|
|
109
|
+
return {
|
|
110
|
+
type: "command",
|
|
111
|
+
command: join(hooksDir, `${name}.cjs`),
|
|
112
|
+
timeout,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
91
115
|
return {
|
|
92
116
|
type: "command",
|
|
93
117
|
command: join(hooksDir, `${name}.sh`),
|
|
@@ -151,6 +175,19 @@ export default {
|
|
|
151
175
|
{ hooks: [hookEntry(hooksDir, "stop", 5)] },
|
|
152
176
|
];
|
|
153
177
|
|
|
178
|
+
// SubagentStop — persist each subagent's output + warn-only honesty
|
|
179
|
+
// QA (phantom-action, meta-tag), nudging deliverable output toward the
|
|
180
|
+
// Quality Gate. 10s: transcript read + sanitize + persist (F2-4).
|
|
181
|
+
settings.hooks.SubagentStop = [
|
|
182
|
+
{ hooks: [hookEntry(hooksDir, "subagent-stop", 10)] },
|
|
183
|
+
];
|
|
184
|
+
|
|
185
|
+
// SessionEnd — final session digest (PreCompact only fires on
|
|
186
|
+
// compaction) + mark session ended. 15s (F2-4).
|
|
187
|
+
settings.hooks.SessionEnd = [
|
|
188
|
+
{ hooks: [hookEntry(hooksDir, "session-end", 15)] },
|
|
189
|
+
];
|
|
190
|
+
|
|
154
191
|
// PreCompact — Session digest
|
|
155
192
|
settings.hooks.PreCompact = [
|
|
156
193
|
{ hooks: [hookEntry(hooksDir, "pre-compact", 30)] },
|
|
@@ -21,6 +21,12 @@ import { join } from "node:path";
|
|
|
21
21
|
// installed. Each entry is a GitHub `owner/repo` shorthand.
|
|
22
22
|
export const DEFAULT_CLAUDE_MARKETPLACES = [
|
|
23
23
|
"nextlevelbuilder/ui-ux-pro-max-skill",
|
|
24
|
+
// F2-7b: the ArkaOS marketplace itself — registered by GitHub
|
|
25
|
+
// owner/repo (NEVER a directory source anchored on the volatile npx
|
|
26
|
+
// cache). Registration costs zero context; the 16 per-department
|
|
27
|
+
// plugins stay opt-in pure (`/plugin install arkaos-<dept>@arkaos`),
|
|
28
|
+
// so DEFAULT_CLAUDE_PLUGINS deliberately does NOT change.
|
|
29
|
+
"andreagroferreira/arka-os",
|
|
24
30
|
];
|
|
25
31
|
|
|
26
32
|
// Each entry is "name@marketplace" matching the `claude plugin install`
|
package/installer/cli.js
CHANGED
|
@@ -31,6 +31,13 @@ const { values, positionals } = parseArgs({
|
|
|
31
31
|
// Fusion — `npx arkaos fusion [--save|--show] "question"`
|
|
32
32
|
save: { type: "boolean" },
|
|
33
33
|
show: { type: "boolean" },
|
|
34
|
+
// F2-7a — `npx arkaos mcp start [--write]`. Declared so it lands in
|
|
35
|
+
// `values.write` instead of a free positional under strict:false
|
|
36
|
+
// (the documented --fix lesson).
|
|
37
|
+
write: { type: "boolean" },
|
|
38
|
+
// F2-7c — `npx arkaos update --skills <curated|full>` (same
|
|
39
|
+
// strict:false declaration requirement).
|
|
40
|
+
skills: { type: "string" },
|
|
34
41
|
},
|
|
35
42
|
allowPositionals: true,
|
|
36
43
|
strict: false,
|
|
@@ -59,6 +66,8 @@ Usage:
|
|
|
59
66
|
npx arkaos keys Manage API keys (OpenAI, fal.ai, etc.)
|
|
60
67
|
npx arkaos models Model Fabric: which model runs each role
|
|
61
68
|
npx arkaos models set <role> <provider>/<model> Re-route a role
|
|
69
|
+
npx arkaos mcp start Start the arka-tools MCP server (stdio; --write enables writes)
|
|
70
|
+
npx arkaos update --skills <curated|full> Choose the deployed skill set (default: curated on fresh installs)
|
|
62
71
|
npx arkaos doctor Run health checks
|
|
63
72
|
npx arkaos uninstall Remove ArkaOS
|
|
64
73
|
|
|
@@ -110,10 +119,11 @@ async function main() {
|
|
|
110
119
|
break;
|
|
111
120
|
}
|
|
112
121
|
|
|
113
|
-
case "update":
|
|
122
|
+
case "update": {
|
|
114
123
|
const { update } = await import("./update.js");
|
|
115
|
-
await update();
|
|
124
|
+
await update({ skillsFlag: values.skills || "" });
|
|
116
125
|
break;
|
|
126
|
+
}
|
|
117
127
|
|
|
118
128
|
case "autostart": {
|
|
119
129
|
const { autostart } = await import("./autostart.js");
|
|
@@ -230,6 +240,17 @@ async function main() {
|
|
|
230
240
|
break;
|
|
231
241
|
}
|
|
232
242
|
|
|
243
|
+
case "mcp": {
|
|
244
|
+
const verb = positionals[1];
|
|
245
|
+
if (verb !== "start") {
|
|
246
|
+
console.error('Usage: npx arkaos mcp start [--write]');
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}
|
|
249
|
+
const { startServer } = await import("./mcp-runner.js");
|
|
250
|
+
process.exit(startServer({ write: Boolean(values.write) }));
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
|
|
233
254
|
default:
|
|
234
255
|
console.error(`Unknown command: ${command}`);
|
|
235
256
|
console.error('Run "npx arkaos help" for usage information.');
|
package/installer/config-seed.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// Seeded template keys (operator decisions):
|
|
8
8
|
// hooks.hardEnforcement = true (PR19 v2.41.0)
|
|
9
9
|
// hooks.kbFirst = true (PR-3 v4.1 — KB-first ON out of the box)
|
|
10
|
+
// memory.sessionMemory = true (F1-A2 — session semantic memory ON)
|
|
10
11
|
//
|
|
11
12
|
// Returns a status object:
|
|
12
13
|
// { action: "created" | "added-key" | "noop"
|
|
@@ -21,13 +22,19 @@ import {
|
|
|
21
22
|
import { homedir } from "node:os";
|
|
22
23
|
import { join, dirname } from "node:path";
|
|
23
24
|
|
|
24
|
-
// Template: every
|
|
25
|
-
const
|
|
25
|
+
// Template: every listed key seeded to `true` when unset, per section.
|
|
26
|
+
const SEEDED_SECTIONS = {
|
|
27
|
+
hooks: ["hardEnforcement", "kbFirst"],
|
|
28
|
+
memory: ["sessionMemory"],
|
|
29
|
+
};
|
|
26
30
|
|
|
27
31
|
function defaultConfig() {
|
|
28
|
-
const
|
|
29
|
-
for (const
|
|
30
|
-
|
|
32
|
+
const config = {};
|
|
33
|
+
for (const [section, keys] of Object.entries(SEEDED_SECTIONS)) {
|
|
34
|
+
config[section] = {};
|
|
35
|
+
for (const key of keys) config[section][key] = true;
|
|
36
|
+
}
|
|
37
|
+
return config;
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
export function seedArkaosConfig({ home = homedir() } = {}) {
|
|
@@ -52,20 +59,22 @@ export function seedArkaosConfig({ home = homedir() } = {}) {
|
|
|
52
59
|
return { action: "rewrote-corrupt", path: cfgPath, backup };
|
|
53
60
|
}
|
|
54
61
|
|
|
55
|
-
config.hooks = config.hooks && typeof config.hooks === "object" ? config.hooks : {};
|
|
56
|
-
|
|
57
62
|
let added = false;
|
|
58
63
|
let preservedFalse = false;
|
|
59
|
-
for (const
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
continue;
|
|
64
|
+
for (const [section, keys] of Object.entries(SEEDED_SECTIONS)) {
|
|
65
|
+
const existing = config[section];
|
|
66
|
+
config[section] = existing && typeof existing === "object" ? existing : {};
|
|
67
|
+
for (const key of keys) {
|
|
68
|
+
const current = config[section][key];
|
|
69
|
+
if (current === true) continue;
|
|
70
|
+
if (current === false) {
|
|
71
|
+
preservedFalse = true; // explicit user choice — never overwrite
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
// Key unset (undefined, null, or any non-boolean) — set to true.
|
|
75
|
+
config[section][key] = true;
|
|
76
|
+
added = true;
|
|
65
77
|
}
|
|
66
|
-
// Key unset (undefined, null, or any non-boolean) — set to true.
|
|
67
|
-
config.hooks[key] = true;
|
|
68
|
-
added = true;
|
|
69
78
|
}
|
|
70
79
|
|
|
71
80
|
if (added) {
|
package/installer/doctor.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { execSync } from "node:child_process";
|
|
@@ -34,6 +34,20 @@ export function hyperframesSkillsInstalled(
|
|
|
34
34
|
);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
// SQLite self-heal (F1-D1) leaves the corrupt original as
|
|
38
|
+
// <db>.corrupt-<ts>.bak next to the recovered file. Their presence means
|
|
39
|
+
// a store healed itself — surface it so the operator can inspect/delete.
|
|
40
|
+
// baseDir is injectable for tests.
|
|
41
|
+
export function corruptDbBackups(baseDir = INSTALL_DIR) {
|
|
42
|
+
try {
|
|
43
|
+
return readdirSync(baseDir, { recursive: true })
|
|
44
|
+
.map(String)
|
|
45
|
+
.filter((name) => /\.corrupt-\d+\.bak$/.test(name));
|
|
46
|
+
} catch {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
37
51
|
export const checks = [
|
|
38
52
|
{
|
|
39
53
|
name: "install-dir",
|
|
@@ -95,12 +109,106 @@ export const checks = [
|
|
|
95
109
|
"cwd-changed",
|
|
96
110
|
"pre-tool-use",
|
|
97
111
|
"stop",
|
|
112
|
+
"subagent-stop",
|
|
113
|
+
"session-end",
|
|
98
114
|
];
|
|
99
115
|
const hooksDir = join(INSTALL_DIR, "config", "hooks");
|
|
100
116
|
return required.every((h) => existsSync(join(hooksDir, `${h}${HOOK_EXT}`)));
|
|
101
117
|
},
|
|
102
118
|
fix: () => "Run: npx arkaos install --force",
|
|
103
119
|
},
|
|
120
|
+
{
|
|
121
|
+
name: "hook-fastpath",
|
|
122
|
+
description: "Hook fast-path shims consistent (F2-6)",
|
|
123
|
+
severity: "fail",
|
|
124
|
+
check: () => {
|
|
125
|
+
// Only meaningful when the .cjs shims are deployed (POSIX installs
|
|
126
|
+
// from v4.14+). If they are, the manifest + engine + a node binary
|
|
127
|
+
// must exist, or every PreToolUse/PostToolUse fires a dead command
|
|
128
|
+
// that silently fails open — governance off with a green doctor.
|
|
129
|
+
if (IS_WINDOWS) return true;
|
|
130
|
+
const hooksDir = join(INSTALL_DIR, "config", "hooks");
|
|
131
|
+
const shims = ["pre-tool-use.cjs", "post-tool-use.cjs"]
|
|
132
|
+
.map((f) => join(hooksDir, f));
|
|
133
|
+
if (!shims.some((p) => existsSync(p))) return true; // .sh-only install
|
|
134
|
+
if (!shims.every((p) => existsSync(p))) return false; // partial deploy
|
|
135
|
+
if (!existsSync(join(hooksDir, "gate-manifest.json"))) return false;
|
|
136
|
+
if (!existsSync(join(hooksDir, "_lib", "fastpath", "engine.cjs"))) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
return commandExists("node");
|
|
140
|
+
},
|
|
141
|
+
fix: () =>
|
|
142
|
+
"Run: npx arkaos@latest update (or export ARKA_HOOK_FASTPATH=0 to force the bash chain)",
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: "arka-tools-runner",
|
|
146
|
+
description: "arka-tools MCP server runnable (uv or venv with mcp SDK)",
|
|
147
|
+
severity: "warn",
|
|
148
|
+
check: () => {
|
|
149
|
+
// Deploy dir + at least one runner. WARN, never FAIL: uv OR a
|
|
150
|
+
// venv with the mcp extra is enough, and neither is mandatory
|
|
151
|
+
// for the core install (F2-7a, `npx arkaos mcp start`).
|
|
152
|
+
const toolsDir = join(
|
|
153
|
+
homedir(), ".claude", "skills", "arka", "mcp-tools");
|
|
154
|
+
if (!existsSync(join(toolsDir, "server.py"))) return false;
|
|
155
|
+
if (commandExists("uv")) return true;
|
|
156
|
+
const py = getArkaosPython();
|
|
157
|
+
if (!py) return false;
|
|
158
|
+
try {
|
|
159
|
+
// timeout parity with mcp-runner.js::venvHasMcp — a hung
|
|
160
|
+
// interpreter must not stall `npx arkaos doctor` (QG 7a M3).
|
|
161
|
+
execSync(`"${py}" -c "import mcp"`, { stdio: "ignore", timeout: 15000 });
|
|
162
|
+
return true;
|
|
163
|
+
} catch {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
fix: () =>
|
|
168
|
+
"Install uv (https://docs.astral.sh/uv) or: ~/.arkaos/venv/bin/pip install 'mcp[cli]>=1.2.0' — then: npx arkaos mcp start",
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
name: "skills-surface",
|
|
172
|
+
description: "Deployed skill set matches the chosen mode (curated/full)",
|
|
173
|
+
severity: "warn",
|
|
174
|
+
check: () => {
|
|
175
|
+
// WARN-only classifier — the doctor NEVER deletes skills. Repo
|
|
176
|
+
// sub-skills outside the curated cut are "plugin-eligible"
|
|
177
|
+
// leftovers on a curated-mode machine; anything not in the
|
|
178
|
+
// generated skills-manifest (ecosystem skills like user project
|
|
179
|
+
// packs) is unknown/user and untouchable by construction.
|
|
180
|
+
let mode = "full";
|
|
181
|
+
try {
|
|
182
|
+
mode = JSON.parse(readFileSync(
|
|
183
|
+
join(INSTALL_DIR, "skills-mode.json"), "utf-8")).mode || "full";
|
|
184
|
+
} catch {}
|
|
185
|
+
if (mode !== "curated") return true;
|
|
186
|
+
const repoRoot = getRepoRoot();
|
|
187
|
+
if (!repoRoot) return true;
|
|
188
|
+
let manifest;
|
|
189
|
+
try {
|
|
190
|
+
manifest = JSON.parse(readFileSync(
|
|
191
|
+
join(repoRoot, "knowledge", "skills-manifest.json"), "utf-8"));
|
|
192
|
+
} catch {
|
|
193
|
+
return true; // older core without the manifest — nothing to judge
|
|
194
|
+
}
|
|
195
|
+
const skillsBase = join(homedir(), ".claude", "skills");
|
|
196
|
+
let leftovers = 0;
|
|
197
|
+
try {
|
|
198
|
+
for (const dir of readdirSync(skillsBase)) {
|
|
199
|
+
if (!dir.startsWith("arka-")) continue;
|
|
200
|
+
const slug = dir.slice("arka-".length);
|
|
201
|
+
const entry = manifest.skills ? manifest.skills[slug] : null;
|
|
202
|
+
if (entry && !entry.curated) leftovers++;
|
|
203
|
+
}
|
|
204
|
+
} catch {
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
return leftovers === 0;
|
|
208
|
+
},
|
|
209
|
+
fix: () =>
|
|
210
|
+
"Curated mode with plugin-eligible leftovers deployed (harmless, uses context budget). Install packs a la carte (/plugin install arkaos-<dept>@arkaos) or keep everything: npx arkaos update --skills full",
|
|
211
|
+
},
|
|
104
212
|
{
|
|
105
213
|
name: "constitution",
|
|
106
214
|
description: "Constitution YAML present",
|
|
@@ -226,6 +334,13 @@ export const checks = [
|
|
|
226
334
|
check: () => commandExists("codebase-memory-mcp"),
|
|
227
335
|
fix: () => "Install codebase-memory-mcp (see mcps/registry.json entry for the one-liner), then /arka update to activate per project",
|
|
228
336
|
},
|
|
337
|
+
{
|
|
338
|
+
name: "sqlite-corrupt-backups",
|
|
339
|
+
description: "No self-healed SQLite stores awaiting inspection",
|
|
340
|
+
severity: "warn",
|
|
341
|
+
check: () => corruptDbBackups().length === 0,
|
|
342
|
+
fix: () => "Inspect then delete ~/.arkaos/**/*.corrupt-*.bak",
|
|
343
|
+
},
|
|
229
344
|
{
|
|
230
345
|
name: "magic-api-key",
|
|
231
346
|
description: "Magic API key configured (frontend UI/UX — Magic MCP)",
|
package/installer/hook-lib.js
CHANGED
|
@@ -23,3 +23,31 @@ export function copyHookLib(srcHooksDir, destHooksDir) {
|
|
|
23
23
|
} catch {}
|
|
24
24
|
return true;
|
|
25
25
|
}
|
|
26
|
+
|
|
27
|
+
// F2-6 fast-path assets: the Node shims + the generated gate manifest
|
|
28
|
+
// (engine.cjs travels inside _lib/ via copyHookLib). The list lives HERE,
|
|
29
|
+
// in the single shared deploy function, so index.js and update.js cannot
|
|
30
|
+
// drift the way the v4.3.2 hook-lib regression did. The .sh/.ps1 hooks
|
|
31
|
+
// keep deploying regardless — they are the delegation target and the
|
|
32
|
+
// ARKA_HOOK_FASTPATH=0 kill-switch path. POSIX-only feature: the assets
|
|
33
|
+
// still copy on Windows (harmless), but the adapter never registers them.
|
|
34
|
+
export const HOOK_ASSETS = [
|
|
35
|
+
"pre-tool-use.cjs",
|
|
36
|
+
"post-tool-use.cjs",
|
|
37
|
+
"gate-manifest.json",
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
export function copyHookAssets(srcHooksDir, destHooksDir) {
|
|
41
|
+
let copied = 0;
|
|
42
|
+
for (const name of HOOK_ASSETS) {
|
|
43
|
+
const srcPath = join(srcHooksDir, name);
|
|
44
|
+
if (!existsSync(srcPath)) continue;
|
|
45
|
+
mkdirSync(destHooksDir, { recursive: true });
|
|
46
|
+
cpSync(srcPath, join(destHooksDir, name));
|
|
47
|
+
if (name.endsWith(".cjs")) {
|
|
48
|
+
try { chmodSync(join(destHooksDir, name), 0o755); } catch {}
|
|
49
|
+
}
|
|
50
|
+
copied += 1;
|
|
51
|
+
}
|
|
52
|
+
return copied;
|
|
53
|
+
}
|