arkaos 4.3.6 → 4.5.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/bin/arka-py +22 -7
- package/config/hooks/_lib/arka_python.ps1 +31 -0
- package/config/hooks/_lib/arka_python.sh +38 -0
- package/config/hooks/post-tool-use.ps1 +8 -0
- package/config/hooks/post-tool-use.sh +11 -5
- package/config/hooks/pre-tool-use.ps1 +11 -6
- package/config/hooks/pre-tool-use.sh +11 -6
- package/config/hooks/session-start.ps1 +10 -0
- package/config/hooks/session-start.sh +5 -0
- package/config/hooks/stop.ps1 +9 -1
- package/config/hooks/stop.sh +11 -5
- package/config/hooks/user-prompt-submit.ps1 +18 -0
- package/config/hooks/user-prompt-submit.sh +11 -5
- package/config/mcp-policy.yaml +8 -8
- package/core/agents/__pycache__/__init__.cpython-312.pyc +0 -0
- package/core/agents/__pycache__/loader.cpython-312.pyc +0 -0
- package/core/agents/__pycache__/registry_gen.cpython-313.pyc +0 -0
- package/core/agents/__pycache__/schema.cpython-312.pyc +0 -0
- package/core/agents/registry_gen.py +3 -2
- package/core/cognition/scheduler/__pycache__/daemon.cpython-313.pyc +0 -0
- package/core/cognition/scheduler/daemon.py +7 -3
- package/core/forge/__pycache__/orchestrator.cpython-313.pyc +0 -0
- package/core/forge/orchestrator.py +2 -1
- package/core/hooks/__pycache__/_shared.cpython-313.pyc +0 -0
- package/core/hooks/__pycache__/_shared.cpython-314.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__/user_prompt_submit.cpython-313.pyc +0 -0
- package/core/hooks/_shared.py +29 -9
- package/core/hooks/stop.py +4 -3
- package/core/hooks/user_prompt_submit.py +5 -4
- package/core/knowledge/__pycache__/indexer.cpython-313.pyc +0 -0
- package/core/knowledge/__pycache__/vector_store.cpython-313.pyc +0 -0
- package/core/knowledge/indexer.py +1 -1
- package/core/knowledge/vector_store.py +10 -3
- package/core/runtime/__pycache__/model_routing_check.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/model_routing_context.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/native_usage.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/native_usage.cpython-314.pyc +0 -0
- package/core/runtime/gateway/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/runtime/gateway/__pycache__/__main__.cpython-313.pyc +0 -0
- package/core/runtime/gateway/__pycache__/litellm_config.cpython-313.pyc +0 -0
- package/core/runtime/native_usage.py +2 -1
- package/core/shared/__pycache__/temp_paths.cpython-313.pyc +0 -0
- package/core/shared/__pycache__/temp_paths.cpython-314.pyc +0 -0
- package/core/shared/temp_paths.py +40 -0
- package/core/synapse/__pycache__/kb_cache.cpython-313.pyc +0 -0
- package/core/synapse/__pycache__/kb_cache.cpython-314.pyc +0 -0
- package/core/synapse/kb_cache.py +3 -2
- package/core/sync/__pycache__/agent_provisioner.cpython-313.pyc +0 -0
- package/core/sync/__pycache__/ai_mcp_decider.cpython-313.pyc +0 -0
- package/core/sync/__pycache__/content_syncer.cpython-313.pyc +0 -0
- package/core/sync/__pycache__/descriptor_syncer.cpython-313.pyc +0 -0
- package/core/sync/__pycache__/discovery.cpython-313.pyc +0 -0
- package/core/sync/__pycache__/engine.cpython-313.pyc +0 -0
- package/core/sync/__pycache__/manifest.cpython-313.pyc +0 -0
- package/core/sync/__pycache__/mcp_optimizer.cpython-313.pyc +0 -0
- package/core/sync/__pycache__/mcp_syncer.cpython-313.pyc +0 -0
- package/core/sync/__pycache__/policy_loader.cpython-313.pyc +0 -0
- package/core/sync/__pycache__/reporter.cpython-313.pyc +0 -0
- package/core/sync/__pycache__/settings_syncer.cpython-313.pyc +0 -0
- package/core/sync/agent_provisioner.py +5 -5
- package/core/sync/ai_mcp_decider.py +2 -2
- package/core/sync/content_syncer.py +14 -14
- package/core/sync/descriptor_syncer.py +2 -2
- package/core/sync/discovery.py +4 -4
- package/core/sync/engine.py +4 -4
- package/core/sync/manifest.py +1 -1
- package/core/sync/mcp_optimizer.py +5 -5
- package/core/sync/mcp_syncer.py +7 -3
- package/core/sync/policy_loader.py +1 -1
- package/core/sync/reporter.py +1 -1
- package/core/sync/settings_syncer.py +2 -2
- package/core/terminal/__pycache__/session.cpython-313.pyc +0 -0
- package/core/terminal/__pycache__/session_windows.cpython-313.pyc +0 -0
- package/core/terminal/session.py +66 -11
- package/core/terminal/session_windows.py +211 -0
- package/core/workflow/__pycache__/flow_authorization.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/flow_authorization.cpython-314.pyc +0 -0
- package/core/workflow/__pycache__/flow_enforcer.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/flow_enforcer.cpython-314.pyc +0 -0
- package/core/workflow/__pycache__/marker_cache.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/marker_cache.cpython-314.pyc +0 -0
- package/core/workflow/__pycache__/research_gate.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/research_gate.cpython-314.pyc +0 -0
- package/core/workflow/flow_authorization.py +2 -1
- package/core/workflow/flow_enforcer.py +2 -1
- package/core/workflow/marker_cache.py +2 -1
- package/core/workflow/research_gate.py +2 -1
- package/dashboard/app/pages/index.vue +6 -1
- package/departments/dev/skills/mcp/SKILL.md +3 -1
- package/departments/ops/skills/update/references/sync-engine.md +2 -1
- package/departments/quality/agents/copy-director.yaml +2 -0
- package/departments/quality/agents/tech-director.yaml +2 -0
- package/installer/cli.js +5 -4
- package/installer/core-snapshot.js +53 -0
- package/installer/doctor.js +8 -3
- package/installer/index.js +29 -6
- package/installer/migrate.js +7 -4
- package/installer/update.js +13 -0
- package/knowledge/agents-registry-v2.json +487 -120
- package/package.json +1 -1
- package/pyproject.toml +2 -1
- package/scripts/__pycache__/dashboard-api.cpython-313.pyc +0 -0
- package/scripts/__pycache__/synapse-bridge.cpython-313.pyc +0 -0
- package/scripts/dashboard-api.py +69 -33
- package/scripts/knowledge-index.py +35 -2
- package/scripts/start-dashboard.ps1 +34 -14
- package/scripts/synapse-bridge.py +3 -1
|
@@ -36,6 +36,7 @@ import subprocess
|
|
|
36
36
|
import time
|
|
37
37
|
from pathlib import Path
|
|
38
38
|
|
|
39
|
+
from core.shared.temp_paths import arkaos_temp_dir
|
|
39
40
|
from core.hooks._shared import (
|
|
40
41
|
ensure_root_on_path,
|
|
41
42
|
get_str,
|
|
@@ -45,7 +46,7 @@ from core.hooks._shared import (
|
|
|
45
46
|
safe_session_id,
|
|
46
47
|
)
|
|
47
48
|
|
|
48
|
-
_CACHE_DIR =
|
|
49
|
+
_CACHE_DIR = arkaos_temp_dir("arkaos-context-cache")
|
|
49
50
|
_CACHE_TTL = 300 # Constitution cache: 5 minutes
|
|
50
51
|
|
|
51
52
|
_L0_FALLBACK = (
|
|
@@ -282,7 +283,7 @@ def _kb_auto_inject(root: str, user_input: str) -> str:
|
|
|
282
283
|
project_hash = hashlib.md5(
|
|
283
284
|
root.encode(), usedforsecurity=False
|
|
284
285
|
).hexdigest()[:12]
|
|
285
|
-
cache_dir =
|
|
286
|
+
cache_dir = arkaos_temp_dir(f"arkaos-kb-{project_hash}")
|
|
286
287
|
if not cache_dir.is_dir():
|
|
287
288
|
return ""
|
|
288
289
|
try:
|
|
@@ -439,7 +440,7 @@ def _wf_mark_required(session_id: str) -> None:
|
|
|
439
440
|
if safe_session_id(session_id) is None:
|
|
440
441
|
return
|
|
441
442
|
marker_dir = Path(
|
|
442
|
-
os.environ.get("ARKA_WF_REQUIRED_DIR", "
|
|
443
|
+
os.environ.get("ARKA_WF_REQUIRED_DIR", str(arkaos_temp_dir("arkaos-wf-required")))
|
|
443
444
|
)
|
|
444
445
|
try:
|
|
445
446
|
marker_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -469,7 +470,7 @@ def _cognitive_hits(session_id: str) -> str:
|
|
|
469
470
|
|
|
470
471
|
def _one_shot_nudge(subdir: str, session_id: str) -> str:
|
|
471
472
|
"""Read + delete a /tmp/<subdir>/<session>.json nudge state file."""
|
|
472
|
-
nudge_file =
|
|
473
|
+
nudge_file = arkaos_temp_dir(subdir) / f"{session_id}.json"
|
|
473
474
|
if not nudge_file.is_file():
|
|
474
475
|
return ""
|
|
475
476
|
nudge = ""
|
|
Binary file
|
|
Binary file
|
|
@@ -347,10 +347,17 @@ class VectorStore:
|
|
|
347
347
|
for r in rows
|
|
348
348
|
]
|
|
349
349
|
|
|
350
|
-
def is_file_indexed(self, file_hash: str) -> bool:
|
|
351
|
-
"""Check if a file
|
|
350
|
+
def is_file_indexed(self, source: str, file_hash: str) -> bool:
|
|
351
|
+
"""Check if a file (identified by source path + content hash) is indexed.
|
|
352
|
+
|
|
353
|
+
The source path is part of the key so identical content in two
|
|
354
|
+
different locations (e.g. two npx cache directories) each gets
|
|
355
|
+
indexed — a hash-only check would skip every subsequent cache
|
|
356
|
+
and leave the new location unsearchable.
|
|
357
|
+
"""
|
|
352
358
|
row = self._db.execute(
|
|
353
|
-
"SELECT COUNT(*) as cnt FROM chunks WHERE file_hash = ?",
|
|
359
|
+
"SELECT COUNT(*) as cnt FROM chunks WHERE source = ? AND file_hash = ?",
|
|
360
|
+
(source, file_hash),
|
|
354
361
|
).fetchone()
|
|
355
362
|
return row["cnt"] > 0
|
|
356
363
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -33,9 +33,10 @@ from typing import Any
|
|
|
33
33
|
from core.runtime.llm_cost_telemetry import record_cost
|
|
34
34
|
from core.runtime.pricing import estimate_cost_usd
|
|
35
35
|
from core.shared.safe_session_id import safe_session_id
|
|
36
|
+
from core.shared.temp_paths import arkaos_temp_dir
|
|
36
37
|
|
|
37
38
|
|
|
38
|
-
DEFAULT_CURSOR_DIR =
|
|
39
|
+
DEFAULT_CURSOR_DIR = arkaos_temp_dir("arkaos-native-cost")
|
|
39
40
|
|
|
40
41
|
_USAGE_KEYS = (
|
|
41
42
|
"input_tokens",
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Cross-platform base directory for ArkaOS inter-process coordination files.
|
|
2
|
+
|
|
3
|
+
Several core modules and the bash hooks coordinate through small marker
|
|
4
|
+
and cache files: workflow-required markers (``stop.sh`` ->
|
|
5
|
+
``flow_enforcer``), the turn-scoped KB query cache
|
|
6
|
+
(``user-prompt-submit.sh`` -> ``kb_cache``), hook metrics
|
|
7
|
+
(``user-prompt-submit.sh`` -> ``dashboard-api``). The hooks write them
|
|
8
|
+
under ``/tmp/arkaos-*``; the Python side MUST resolve to the *same*
|
|
9
|
+
directory or the coordination silently breaks.
|
|
10
|
+
|
|
11
|
+
Why this lives in ``core.shared``: every module that coordinates through
|
|
12
|
+
these files (flow_enforcer, marker_cache, research_gate, kb_cache,
|
|
13
|
+
forge.orchestrator, the consolidated hooks, native_usage, the synapse
|
|
14
|
+
bridge, the dashboard API, …) used to hardcode the literal string
|
|
15
|
+
``/tmp``. ``/tmp`` does not exist on Windows, so every one of those
|
|
16
|
+
features failed there. A single helper keeps the resolution in one place.
|
|
17
|
+
|
|
18
|
+
POSIX keeps the literal ``/tmp`` the bash hooks use (avoiding a
|
|
19
|
+
``$TMPDIR`` divergence on macOS where ``tempfile.gettempdir()`` returns
|
|
20
|
+
``/var/folders/...`` but the hooks still write ``/tmp``). On Windows
|
|
21
|
+
there is no ``/tmp``: Git-for-Windows maps a hook's ``/tmp`` to
|
|
22
|
+
``%TEMP%``, which is exactly what ``tempfile.gettempdir()`` returns, so
|
|
23
|
+
both sides still meet.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import os
|
|
29
|
+
import tempfile
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def arkaos_temp_dir(*parts: str) -> Path:
|
|
34
|
+
"""Return ``<coordination base>/<parts...>`` (the path is not created).
|
|
35
|
+
|
|
36
|
+
POSIX -> ``/tmp/<parts>`` (matches the bash hooks verbatim).
|
|
37
|
+
Windows -> ``<%TEMP%>/<parts>`` (matches Git-for-Windows ``/tmp``).
|
|
38
|
+
"""
|
|
39
|
+
base = Path(tempfile.gettempdir()) if os.name == "nt" else Path("/tmp")
|
|
40
|
+
return base.joinpath(*parts)
|
|
Binary file
|
|
Binary file
|
package/core/synapse/kb_cache.py
CHANGED
|
@@ -30,11 +30,12 @@ from pathlib import Path
|
|
|
30
30
|
from typing import Any, Optional
|
|
31
31
|
|
|
32
32
|
from core.shared import safe_session_id as _safe_session_id_module
|
|
33
|
+
from core.shared.temp_paths import arkaos_temp_dir
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
# Re-export for backward compatibility with any external importers.
|
|
36
37
|
SAFE_SESSION_ID_RE = _safe_session_id_module.SAFE_SESSION_ID_RE
|
|
37
|
-
KB_QUERY_MARKER_DIR =
|
|
38
|
+
KB_QUERY_MARKER_DIR = arkaos_temp_dir("arkaos-kb-query")
|
|
38
39
|
_MAX_QUERIES_PER_TURN = 32
|
|
39
40
|
_MAX_QUERY_LEN = 512
|
|
40
41
|
|
|
@@ -159,7 +160,7 @@ class KBSessionCache:
|
|
|
159
160
|
self._cache_dir = Path(cache_dir)
|
|
160
161
|
else:
|
|
161
162
|
project_hash = self._hash_project(project_path or "")
|
|
162
|
-
self._cache_dir =
|
|
163
|
+
self._cache_dir = arkaos_temp_dir(f"arkaos-kb-{project_hash}")
|
|
163
164
|
|
|
164
165
|
self._cache_file = self._cache_dir / f"{session_id}.json"
|
|
165
166
|
self._ensure_dir()
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -55,7 +55,7 @@ def _extend_from_file(path: Path, agents: set[str]) -> None:
|
|
|
55
55
|
if not path.exists():
|
|
56
56
|
return
|
|
57
57
|
try:
|
|
58
|
-
data = yaml.safe_load(path.read_text()) or {}
|
|
58
|
+
data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
|
59
59
|
except yaml.YAMLError:
|
|
60
60
|
return
|
|
61
61
|
for name in data.get("baseline", []) or []:
|
|
@@ -95,10 +95,10 @@ def _apply_allowlist(
|
|
|
95
95
|
continue
|
|
96
96
|
|
|
97
97
|
target = agents_dir / f"{name}.md"
|
|
98
|
-
if target.exists() and target.read_text() == rendered:
|
|
98
|
+
if target.exists() and target.read_text(encoding="utf-8") == rendered:
|
|
99
99
|
unchanged.append(name)
|
|
100
100
|
continue
|
|
101
|
-
target.write_text(rendered)
|
|
101
|
+
target.write_text(rendered, encoding="utf-8")
|
|
102
102
|
added.append(name)
|
|
103
103
|
|
|
104
104
|
return added, unchanged, errored
|
|
@@ -124,10 +124,10 @@ def _render_agent(core: Path, name: str) -> str | None:
|
|
|
124
124
|
parts: list[str] = []
|
|
125
125
|
if yaml_path is not None:
|
|
126
126
|
parts.append("---")
|
|
127
|
-
parts.append(yaml_path.read_text().strip())
|
|
127
|
+
parts.append(yaml_path.read_text(encoding="utf-8").strip())
|
|
128
128
|
parts.append("---")
|
|
129
129
|
if md_path is not None:
|
|
130
|
-
parts.append(md_path.read_text().rstrip())
|
|
130
|
+
parts.append(md_path.read_text(encoding="utf-8").rstrip())
|
|
131
131
|
|
|
132
132
|
return "\n".join(parts) + "\n"
|
|
133
133
|
|
|
@@ -76,11 +76,11 @@ def _load_cache(path: Path) -> dict[str, str]:
|
|
|
76
76
|
if not path.exists():
|
|
77
77
|
return {}
|
|
78
78
|
try:
|
|
79
|
-
return json.loads(path.read_text())
|
|
79
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
80
80
|
except (json.JSONDecodeError, OSError):
|
|
81
81
|
return {}
|
|
82
82
|
|
|
83
83
|
|
|
84
84
|
def _save_cache(path: Path, data: dict[str, str]) -> None:
|
|
85
85
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
86
|
-
path.write_text(json.dumps(data, indent=2, sort_keys=True))
|
|
86
|
+
path.write_text(json.dumps(data, indent=2, sort_keys=True), encoding="utf-8")
|
|
@@ -36,7 +36,7 @@ def sync_project_content(project: Project) -> ContentSyncResult:
|
|
|
36
36
|
|
|
37
37
|
def _do_sync(project: Project) -> ContentSyncResult:
|
|
38
38
|
core = _core_root()
|
|
39
|
-
version = (core / "VERSION").read_text().strip()
|
|
39
|
+
version = (core / "VERSION").read_text(encoding="utf-8").strip()
|
|
40
40
|
project_claude = Path(project.path) / ".claude"
|
|
41
41
|
project_claude.mkdir(parents=True, exist_ok=True)
|
|
42
42
|
|
|
@@ -73,28 +73,28 @@ def _sync_claude_md(
|
|
|
73
73
|
unchanged: list[str],
|
|
74
74
|
errored: list[str],
|
|
75
75
|
) -> None:
|
|
76
|
-
base = (core / "config" / "user-claude.md").read_text()
|
|
76
|
+
base = (core / "config" / "user-claude.md").read_text(encoding="utf-8")
|
|
77
77
|
overlays_dir = core / "config" / "standards" / "claude-md-overlays"
|
|
78
78
|
overlays: list[str] = []
|
|
79
79
|
for stack in project.stack:
|
|
80
80
|
overlay = overlays_dir / f"{stack}.md"
|
|
81
81
|
if overlay.exists():
|
|
82
|
-
overlays.append(overlay.read_text())
|
|
82
|
+
overlays.append(overlay.read_text(encoding="utf-8"))
|
|
83
83
|
|
|
84
84
|
managed_content = "\n\n".join([base, *overlays]).strip()
|
|
85
85
|
target_file = project_claude / "CLAUDE.md"
|
|
86
|
-
target_text = target_file.read_text() if target_file.exists() else ""
|
|
86
|
+
target_text = target_file.read_text(encoding="utf-8") if target_file.exists() else ""
|
|
87
87
|
|
|
88
88
|
result = merge_managed_content(target_text, managed_content, version)
|
|
89
89
|
if result.status == "error":
|
|
90
90
|
errored.append(f"CLAUDE.md: {result.error}")
|
|
91
91
|
sidecar = target_file.with_suffix(".md.arkaos-new")
|
|
92
|
-
sidecar.write_text(managed_content)
|
|
92
|
+
sidecar.write_text(managed_content, encoding="utf-8")
|
|
93
93
|
return
|
|
94
94
|
if result.status == "unchanged":
|
|
95
95
|
unchanged.append("CLAUDE.md")
|
|
96
96
|
return
|
|
97
|
-
target_file.write_text(result.new_text)
|
|
97
|
+
target_file.write_text(result.new_text, encoding="utf-8")
|
|
98
98
|
updated.append("CLAUDE.md")
|
|
99
99
|
|
|
100
100
|
|
|
@@ -111,11 +111,11 @@ def _sync_rules(
|
|
|
111
111
|
dst.mkdir(parents=True, exist_ok=True)
|
|
112
112
|
for rule in src.glob("*.md"):
|
|
113
113
|
target = dst / rule.name
|
|
114
|
-
src_text = rule.read_text()
|
|
115
|
-
if target.exists() and target.read_text() == src_text:
|
|
114
|
+
src_text = rule.read_text(encoding="utf-8")
|
|
115
|
+
if target.exists() and target.read_text(encoding="utf-8") == src_text:
|
|
116
116
|
unchanged.append(f"rules/{rule.name}")
|
|
117
117
|
continue
|
|
118
|
-
target.write_text(src_text)
|
|
118
|
+
target.write_text(src_text, encoding="utf-8")
|
|
119
119
|
updated.append(f"rules/{rule.name}")
|
|
120
120
|
|
|
121
121
|
|
|
@@ -131,8 +131,8 @@ def _sync_hooks(
|
|
|
131
131
|
dst.mkdir(parents=True, exist_ok=True)
|
|
132
132
|
for hook in src.glob("*.sh"):
|
|
133
133
|
target = dst / hook.name
|
|
134
|
-
src_text = hook.read_text()
|
|
135
|
-
if target.exists() and target.read_text() == src_text:
|
|
134
|
+
src_text = hook.read_text(encoding="utf-8")
|
|
135
|
+
if target.exists() and target.read_text(encoding="utf-8") == src_text:
|
|
136
136
|
unchanged.append(f"hooks/{hook.name}")
|
|
137
137
|
continue
|
|
138
138
|
shutil.copy2(hook, target)
|
|
@@ -149,16 +149,16 @@ def _sync_constitution(
|
|
|
149
149
|
) -> None:
|
|
150
150
|
src = core / "config" / "constitution.yaml"
|
|
151
151
|
target = project_claude / "constitution-applicable.md"
|
|
152
|
-
data = yaml.safe_load(src.read_text()) or {}
|
|
152
|
+
data = yaml.safe_load(src.read_text(encoding="utf-8")) or {}
|
|
153
153
|
rules = data.get("rules", [])
|
|
154
154
|
lines = ["# ArkaOS Constitution — Applicable Rules", ""]
|
|
155
155
|
for rule in rules:
|
|
156
156
|
lines.append(f"- **{rule.get('name', '?')}** — {rule.get('level', '?')}")
|
|
157
157
|
body = "\n".join(lines) + "\n"
|
|
158
|
-
if target.exists() and target.read_text() == body:
|
|
158
|
+
if target.exists() and target.read_text(encoding="utf-8") == body:
|
|
159
159
|
unchanged.append("constitution-applicable.md")
|
|
160
160
|
return
|
|
161
|
-
target.write_text(body)
|
|
161
|
+
target.write_text(body, encoding="utf-8")
|
|
162
162
|
updated.append("constitution-applicable.md")
|
|
163
163
|
|
|
164
164
|
|
|
@@ -47,7 +47,7 @@ def sync_descriptor(project: Project) -> DescriptorSyncResult:
|
|
|
47
47
|
def _do_sync(project: Project) -> DescriptorSyncResult:
|
|
48
48
|
"""Execute the descriptor sync logic for a single project."""
|
|
49
49
|
desc_path = Path(project.descriptor_path) # type: ignore[arg-type]
|
|
50
|
-
text = desc_path.read_text()
|
|
50
|
+
text = desc_path.read_text(encoding="utf-8")
|
|
51
51
|
frontmatter, body = _split_frontmatter(text)
|
|
52
52
|
changes: list[str] = []
|
|
53
53
|
|
|
@@ -193,4 +193,4 @@ def _get_last_commit_days(project_path: str) -> int | None:
|
|
|
193
193
|
def _write_descriptor(desc_path: Path, frontmatter: dict, body: str) -> None:
|
|
194
194
|
"""Write updated frontmatter and preserved body back to the descriptor file."""
|
|
195
195
|
fm_text = yaml.dump(frontmatter, default_flow_style=False, allow_unicode=True)
|
|
196
|
-
desc_path.write_text(f"---\n{fm_text}---{body}")
|
|
196
|
+
desc_path.write_text(f"---\n{fm_text}---{body}", encoding="utf-8")
|
package/core/sync/discovery.py
CHANGED
|
@@ -21,7 +21,7 @@ def _detect_from_composer(project_path: Path) -> list[str]:
|
|
|
21
21
|
if not composer.exists():
|
|
22
22
|
return []
|
|
23
23
|
try:
|
|
24
|
-
data = json.loads(composer.read_text())
|
|
24
|
+
data = json.loads(composer.read_text(encoding="utf-8"))
|
|
25
25
|
require = data.get("require", {})
|
|
26
26
|
if "laravel/framework" in require:
|
|
27
27
|
return ["php", "laravel"]
|
|
@@ -35,7 +35,7 @@ def _detect_from_package_json(project_path: Path) -> list[str]:
|
|
|
35
35
|
if not pkg.exists():
|
|
36
36
|
return []
|
|
37
37
|
try:
|
|
38
|
-
data = json.loads(pkg.read_text())
|
|
38
|
+
data = json.loads(pkg.read_text(encoding="utf-8"))
|
|
39
39
|
deps = {
|
|
40
40
|
**data.get("dependencies", {}),
|
|
41
41
|
**data.get("devDependencies", {}),
|
|
@@ -100,7 +100,7 @@ def _parse_descriptor_frontmatter(text: str) -> dict:
|
|
|
100
100
|
def _read_descriptor_item(item: Path) -> dict:
|
|
101
101
|
"""Read a descriptor file and return its frontmatter as a dict."""
|
|
102
102
|
try:
|
|
103
|
-
return _parse_descriptor_frontmatter(item.read_text())
|
|
103
|
+
return _parse_descriptor_frontmatter(item.read_text(encoding="utf-8"))
|
|
104
104
|
except OSError:
|
|
105
105
|
return {}
|
|
106
106
|
|
|
@@ -187,7 +187,7 @@ def discover_from_ecosystems(ecosystems_file: Path) -> list[Project]:
|
|
|
187
187
|
if not ecosystems_file.exists():
|
|
188
188
|
return []
|
|
189
189
|
try:
|
|
190
|
-
data = json.loads(ecosystems_file.read_text())
|
|
190
|
+
data = json.loads(ecosystems_file.read_text(encoding="utf-8"))
|
|
191
191
|
except (json.JSONDecodeError, OSError):
|
|
192
192
|
return []
|
|
193
193
|
|
package/core/sync/engine.py
CHANGED
|
@@ -117,7 +117,7 @@ def _read_previous_version(arkaos_home: Path) -> str:
|
|
|
117
117
|
if not state_file.exists():
|
|
118
118
|
return "pending-sync"
|
|
119
119
|
try:
|
|
120
|
-
data = json.loads(state_file.read_text())
|
|
120
|
+
data = json.loads(state_file.read_text(encoding="utf-8"))
|
|
121
121
|
return data.get("version", "pending-sync") or "pending-sync"
|
|
122
122
|
except (json.JSONDecodeError, OSError):
|
|
123
123
|
return "pending-sync"
|
|
@@ -132,7 +132,7 @@ def _read_current_version(arkaos_home: Path) -> str:
|
|
|
132
132
|
if not version_file.exists():
|
|
133
133
|
return "unknown"
|
|
134
134
|
try:
|
|
135
|
-
return version_file.read_text().strip()
|
|
135
|
+
return version_file.read_text(encoding="utf-8").strip()
|
|
136
136
|
except OSError:
|
|
137
137
|
return "unknown"
|
|
138
138
|
|
|
@@ -143,7 +143,7 @@ def _read_repo_path(arkaos_home: Path) -> Path | None:
|
|
|
143
143
|
if not repo_path_file.exists():
|
|
144
144
|
return None
|
|
145
145
|
try:
|
|
146
|
-
raw = repo_path_file.read_text().strip()
|
|
146
|
+
raw = repo_path_file.read_text(encoding="utf-8").strip()
|
|
147
147
|
return Path(raw) if raw else None
|
|
148
148
|
except OSError:
|
|
149
149
|
return None
|
|
@@ -204,7 +204,7 @@ def _load_scan_dirs_from_profile(arkaos_home: Path) -> list[Path]:
|
|
|
204
204
|
if not profile_file.exists():
|
|
205
205
|
return []
|
|
206
206
|
try:
|
|
207
|
-
data = json.loads(profile_file.read_text())
|
|
207
|
+
data = json.loads(profile_file.read_text(encoding="utf-8"))
|
|
208
208
|
projects_dir_str = data.get("projectsDir", "")
|
|
209
209
|
if not projects_dir_str:
|
|
210
210
|
return []
|
package/core/sync/manifest.py
CHANGED
|
@@ -18,7 +18,7 @@ def load_features(features_dir: Path) -> list[FeatureSpec]:
|
|
|
18
18
|
for path in sorted(features_dir.iterdir()):
|
|
19
19
|
if path.suffix != ".yaml":
|
|
20
20
|
continue
|
|
21
|
-
data = yaml.safe_load(path.read_text())
|
|
21
|
+
data = yaml.safe_load(path.read_text(encoding="utf-8"))
|
|
22
22
|
features.append(FeatureSpec(**data))
|
|
23
23
|
|
|
24
24
|
return features
|
|
@@ -92,7 +92,7 @@ def _load_override(project_path: Path) -> tuple[dict, list[str]]:
|
|
|
92
92
|
if not override.exists():
|
|
93
93
|
return {}, []
|
|
94
94
|
try:
|
|
95
|
-
return yaml.safe_load(override.read_text()) or {}, []
|
|
95
|
+
return yaml.safe_load(override.read_text(encoding="utf-8")) or {}, []
|
|
96
96
|
except (yaml.YAMLError, OSError) as exc:
|
|
97
97
|
return {}, [f"override YAML parse error: {exc}"]
|
|
98
98
|
|
|
@@ -123,7 +123,7 @@ def _inject_env_vars(project_path: Path, vault_path: Path | None, project_name:
|
|
|
123
123
|
return []
|
|
124
124
|
|
|
125
125
|
try:
|
|
126
|
-
data = json.loads(mcp_file.read_text())
|
|
126
|
+
data = json.loads(mcp_file.read_text(encoding="utf-8"))
|
|
127
127
|
except (json.JSONDecodeError, OSError):
|
|
128
128
|
return [".mcp.json malformed; skipped env injection"]
|
|
129
129
|
|
|
@@ -137,7 +137,7 @@ def _inject_env_vars(project_path: Path, vault_path: Path | None, project_name:
|
|
|
137
137
|
changed, missing = _merge_env(servers, merged_env)
|
|
138
138
|
|
|
139
139
|
if changed:
|
|
140
|
-
mcp_file.write_text(json.dumps(data, indent=2) + "\n")
|
|
140
|
+
mcp_file.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
|
|
141
141
|
|
|
142
142
|
if missing:
|
|
143
143
|
_write_env_example(project_path, missing)
|
|
@@ -157,7 +157,7 @@ def _load_vault(path: Path) -> tuple[dict, list[str]]:
|
|
|
157
157
|
if st.st_mode & (stat.S_IRWXG | stat.S_IRWXO):
|
|
158
158
|
return {}, ["vault permissions too permissive (group/world readable); secrets not injected"]
|
|
159
159
|
try:
|
|
160
|
-
return json.loads(path.read_text()), []
|
|
160
|
+
return json.loads(path.read_text(encoding="utf-8")), []
|
|
161
161
|
except (json.JSONDecodeError, OSError):
|
|
162
162
|
return {}, ["vault JSON parse error; secrets not injected"]
|
|
163
163
|
|
|
@@ -167,7 +167,7 @@ def _write_env_example(project_path: Path, missing: dict[str, str]) -> None:
|
|
|
167
167
|
for var, server in sorted(missing.items()):
|
|
168
168
|
lines.append(f"# required by {server}")
|
|
169
169
|
lines.append(f"{var}=")
|
|
170
|
-
(project_path / ".env.arkaos.example").write_text("\n".join(lines) + "\n")
|
|
170
|
+
(project_path / ".env.arkaos.example").write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
171
171
|
|
|
172
172
|
|
|
173
173
|
def optimize_all_mcps(
|
package/core/sync/mcp_syncer.py
CHANGED
|
@@ -39,7 +39,7 @@ def load_registry(registry_path: Path) -> dict:
|
|
|
39
39
|
if not registry_path.exists():
|
|
40
40
|
return {}
|
|
41
41
|
try:
|
|
42
|
-
data = json.loads(registry_path.read_text())
|
|
42
|
+
data = json.loads(registry_path.read_text(encoding="utf-8"))
|
|
43
43
|
return data.get("mcpServers", {})
|
|
44
44
|
except (json.JSONDecodeError, OSError):
|
|
45
45
|
return {}
|
|
@@ -60,6 +60,10 @@ def resolve_mcps_for_stack(
|
|
|
60
60
|
|
|
61
61
|
result: list[tuple[str, dict]] = []
|
|
62
62
|
for name, config in registry.items():
|
|
63
|
+
if "managed" in config:
|
|
64
|
+
# Runtime-managed servers (extension/plugin) are registered for
|
|
65
|
+
# governance and telemetry — they have no launchable command to write.
|
|
66
|
+
continue
|
|
63
67
|
if config.get("category") in allowed:
|
|
64
68
|
result.append((name, config))
|
|
65
69
|
return result
|
|
@@ -142,7 +146,7 @@ def _read_current_mcps(mcp_file: Path) -> dict:
|
|
|
142
146
|
if not mcp_file.exists():
|
|
143
147
|
return {}
|
|
144
148
|
try:
|
|
145
|
-
data = json.loads(mcp_file.read_text())
|
|
149
|
+
data = json.loads(mcp_file.read_text(encoding="utf-8"))
|
|
146
150
|
return data.get("mcpServers", {})
|
|
147
151
|
except (json.JSONDecodeError, OSError):
|
|
148
152
|
return {}
|
|
@@ -252,4 +256,4 @@ def _build_merged(
|
|
|
252
256
|
def _write_mcp_json(mcp_file: Path, servers: dict) -> None:
|
|
253
257
|
"""Write the mcpServers dict to .mcp.json with 2-space indentation."""
|
|
254
258
|
payload = {"mcpServers": servers}
|
|
255
|
-
mcp_file.write_text(json.dumps(payload, indent=2) + "\n")
|
|
259
|
+
mcp_file.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
|
|
@@ -30,7 +30,7 @@ class PolicyDecision:
|
|
|
30
30
|
|
|
31
31
|
def load_policy(path: Path) -> Policy:
|
|
32
32
|
"""Load and parse an mcp-policy.yaml file."""
|
|
33
|
-
data = yaml.safe_load(path.read_text()) or {}
|
|
33
|
+
data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
|
34
34
|
rules = [
|
|
35
35
|
PolicyRule(
|
|
36
36
|
match=r.get("match", {}),
|
package/core/sync/reporter.py
CHANGED
|
@@ -74,7 +74,7 @@ def write_sync_state(state_file: Path, report: SyncReport) -> None:
|
|
|
74
74
|
"skills_synced": len(report.skill_results),
|
|
75
75
|
"errors": report.errors,
|
|
76
76
|
}
|
|
77
|
-
state_file.write_text(json.dumps(state, indent=2))
|
|
77
|
+
state_file.write_text(json.dumps(state, indent=2), encoding="utf-8")
|
|
78
78
|
|
|
79
79
|
|
|
80
80
|
def format_report(report: SyncReport) -> str:
|
|
@@ -82,7 +82,7 @@ def _read_current_settings(settings_file: Path) -> dict:
|
|
|
82
82
|
if not settings_file.exists():
|
|
83
83
|
return {}
|
|
84
84
|
try:
|
|
85
|
-
return json.loads(settings_file.read_text())
|
|
85
|
+
return json.loads(settings_file.read_text(encoding="utf-8"))
|
|
86
86
|
except (json.JSONDecodeError, OSError):
|
|
87
87
|
return {}
|
|
88
88
|
|
|
@@ -118,4 +118,4 @@ def _build_merged_settings(current: dict, target_servers: list[str]) -> dict:
|
|
|
118
118
|
def _write_settings(settings_file: Path, data: dict) -> None:
|
|
119
119
|
"""Create parent dirs if needed and write settings as 2-space JSON."""
|
|
120
120
|
settings_file.parent.mkdir(parents=True, exist_ok=True)
|
|
121
|
-
settings_file.write_text(json.dumps(data, indent=2) + "\n")
|
|
121
|
+
settings_file.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
|
|
Binary file
|
|
Binary file
|