arkaos 2.77.0 → 2.78.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/core/runtime/__pycache__/codex_cli.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/llm_provider.cpython-313.pyc +0 -0
- package/core/sync/__pycache__/update_orchestrator.cpython-313.pyc +0 -0
- package/core/sync/update_orchestrator.py +244 -0
- package/departments/ops/skills/update/SKILL.md +20 -4
- package/package.json +1 -1
- package/pyproject.toml +1 -1
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.
|
|
1
|
+
2.78.0
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"""One-stop /arka update orchestrator (PR61 v2.78.0).
|
|
2
|
+
|
|
3
|
+
The published 2-step process (`npx arkaos@latest update` then
|
|
4
|
+
`/arka update`) is fragile in practice: operators run step 2 inside
|
|
5
|
+
Claude Code without remembering step 1, so the sync engine silently
|
|
6
|
+
runs from whichever npx cache `~/.arkaos/.repo-path` last pointed at.
|
|
7
|
+
When that cache is months old the sync becomes a no-op against
|
|
8
|
+
current versions.
|
|
9
|
+
|
|
10
|
+
This module makes `/arka update` self-sufficient:
|
|
11
|
+
|
|
12
|
+
1. Read the running ArkaOS version from `<repo>/VERSION`.
|
|
13
|
+
2. Probe the npm registry for the published latest (5s timeout,
|
|
14
|
+
1-hour cache on disk to keep repeat runs cheap).
|
|
15
|
+
3. If the running version is older than the latest, shell out to
|
|
16
|
+
`npx arkaos@latest update` and wait for it to finish before
|
|
17
|
+
touching the sync engine. The npx step rewrites
|
|
18
|
+
``~/.arkaos/.repo-path`` to the freshly-extracted package so the
|
|
19
|
+
sync engine below reads the right code.
|
|
20
|
+
4. Re-read VERSION (now updated) and dispatch to ``run_sync``.
|
|
21
|
+
|
|
22
|
+
The orchestrator NEVER raises on transient failures — npm offline,
|
|
23
|
+
slow registry, missing `npx` — it logs and falls through to the sync
|
|
24
|
+
engine using whatever code is currently installed. Worst case the
|
|
25
|
+
operator sees the same behaviour as before PR61.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import json
|
|
31
|
+
import os
|
|
32
|
+
import subprocess
|
|
33
|
+
import sys
|
|
34
|
+
import time
|
|
35
|
+
from pathlib import Path
|
|
36
|
+
from typing import Optional
|
|
37
|
+
|
|
38
|
+
from core.sync.engine import (
|
|
39
|
+
_read_current_version,
|
|
40
|
+
_read_repo_path,
|
|
41
|
+
run_sync,
|
|
42
|
+
)
|
|
43
|
+
from core.sync.schema import SyncReport
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Cache the npm-view result for an hour so repeated /arka update calls
|
|
47
|
+
# inside the same session don't re-hit the registry.
|
|
48
|
+
_NPM_CACHE_TTL_SECONDS = 3600
|
|
49
|
+
_NPM_TIMEOUT_SECONDS = 5
|
|
50
|
+
_NPM_PROBE_CMD = ("npm", "view", "arkaos", "version")
|
|
51
|
+
_NPX_UPDATE_CMD = ("npx", "-y", "arkaos@latest", "update")
|
|
52
|
+
_NPX_TIMEOUT_SECONDS = 600 # 10 minutes — large installs can be slow
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def orchestrate(
|
|
56
|
+
arkaos_home: Path,
|
|
57
|
+
skills_dir: Path,
|
|
58
|
+
home_path: str,
|
|
59
|
+
*,
|
|
60
|
+
npm_probe=None,
|
|
61
|
+
npx_run=None,
|
|
62
|
+
cache_path: Path | None = None,
|
|
63
|
+
) -> tuple[Optional[str], Optional[str], SyncReport]:
|
|
64
|
+
"""Run npm-side update when stale, then the sync engine.
|
|
65
|
+
|
|
66
|
+
Returns ``(installed_version_before, latest_version_seen, report)``.
|
|
67
|
+
The first two are None when probing failed; the third is always a
|
|
68
|
+
SyncReport (the engine itself never raises on individual project
|
|
69
|
+
failures).
|
|
70
|
+
"""
|
|
71
|
+
probe = npm_probe or _probe_npm_latest
|
|
72
|
+
runner = npx_run or _run_npx_update
|
|
73
|
+
cache = cache_path or (arkaos_home / "npm-latest.cache.json")
|
|
74
|
+
|
|
75
|
+
installed = _safe_read_version(arkaos_home)
|
|
76
|
+
latest = probe(cache)
|
|
77
|
+
|
|
78
|
+
if installed and latest and _is_older(installed, latest):
|
|
79
|
+
runner(arkaos_home)
|
|
80
|
+
|
|
81
|
+
report = run_sync(
|
|
82
|
+
arkaos_home=arkaos_home,
|
|
83
|
+
skills_dir=skills_dir,
|
|
84
|
+
home_path=home_path,
|
|
85
|
+
)
|
|
86
|
+
return installed, latest, report
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _safe_read_version(arkaos_home: Path) -> Optional[str]:
|
|
90
|
+
try:
|
|
91
|
+
v = _read_current_version(arkaos_home)
|
|
92
|
+
return v if v and v != "unknown" else None
|
|
93
|
+
except Exception: # noqa: BLE001 — never break the orchestrator
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _probe_npm_latest(cache_path: Path) -> Optional[str]:
|
|
98
|
+
"""Return the latest published arkaos version, or None on failure.
|
|
99
|
+
|
|
100
|
+
Reads from disk cache when fresh; otherwise shells out to
|
|
101
|
+
``npm view`` with a short timeout. Always swallows errors —
|
|
102
|
+
callers fall through to a no-op when probing fails.
|
|
103
|
+
"""
|
|
104
|
+
cached = _read_cache(cache_path)
|
|
105
|
+
if cached is not None:
|
|
106
|
+
return cached
|
|
107
|
+
try:
|
|
108
|
+
result = subprocess.run(
|
|
109
|
+
list(_NPM_PROBE_CMD),
|
|
110
|
+
capture_output=True,
|
|
111
|
+
text=True,
|
|
112
|
+
timeout=_NPM_TIMEOUT_SECONDS,
|
|
113
|
+
check=False,
|
|
114
|
+
)
|
|
115
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
116
|
+
return None
|
|
117
|
+
if result.returncode != 0:
|
|
118
|
+
return None
|
|
119
|
+
version = (result.stdout or "").strip()
|
|
120
|
+
if not _looks_like_semver(version):
|
|
121
|
+
return None
|
|
122
|
+
_write_cache(cache_path, version)
|
|
123
|
+
return version
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _read_cache(cache_path: Path) -> Optional[str]:
|
|
127
|
+
if not cache_path.exists():
|
|
128
|
+
return None
|
|
129
|
+
try:
|
|
130
|
+
data = json.loads(cache_path.read_text(encoding="utf-8"))
|
|
131
|
+
except (json.JSONDecodeError, OSError):
|
|
132
|
+
return None
|
|
133
|
+
if not isinstance(data, dict):
|
|
134
|
+
return None
|
|
135
|
+
ts = data.get("ts")
|
|
136
|
+
version = data.get("version")
|
|
137
|
+
if not isinstance(ts, (int, float)) or not isinstance(version, str):
|
|
138
|
+
return None
|
|
139
|
+
if time.time() - ts > _NPM_CACHE_TTL_SECONDS:
|
|
140
|
+
return None
|
|
141
|
+
return version if _looks_like_semver(version) else None
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _write_cache(cache_path: Path, version: str) -> None:
|
|
145
|
+
try:
|
|
146
|
+
cache_path.parent.mkdir(parents=True, exist_ok=True)
|
|
147
|
+
cache_path.write_text(
|
|
148
|
+
json.dumps({"version": version, "ts": time.time()}),
|
|
149
|
+
encoding="utf-8",
|
|
150
|
+
)
|
|
151
|
+
except OSError:
|
|
152
|
+
pass
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _looks_like_semver(value: str) -> bool:
|
|
156
|
+
if not value or len(value) > 32:
|
|
157
|
+
return False
|
|
158
|
+
# Strip any `-prerelease.suffix` so "2.77.0-beta.1" reduces to "2.77.0"
|
|
159
|
+
# before structural validation. npm canonical semver allows almost any
|
|
160
|
+
# ASCII after the dash; we don't try to validate that — we only need
|
|
161
|
+
# to confirm the leading major.minor.patch shape is intact.
|
|
162
|
+
base = value.split("-", 1)[0]
|
|
163
|
+
parts = base.split(".")
|
|
164
|
+
if len(parts) != 3:
|
|
165
|
+
return False
|
|
166
|
+
return all(part.isdigit() for part in parts)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _is_older(installed: str, latest: str) -> bool:
|
|
170
|
+
return _parse_semver(installed) < _parse_semver(latest)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _parse_semver(value: str) -> tuple[int, int, int]:
|
|
174
|
+
parts = value.split(".")
|
|
175
|
+
try:
|
|
176
|
+
major = int(parts[0])
|
|
177
|
+
minor = int(parts[1])
|
|
178
|
+
patch_str = parts[2].split("-", 1)[0]
|
|
179
|
+
patch = int(patch_str)
|
|
180
|
+
return major, minor, patch
|
|
181
|
+
except (ValueError, IndexError):
|
|
182
|
+
return (0, 0, 0)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _run_npx_update(arkaos_home: Path) -> None:
|
|
186
|
+
"""Best-effort shell-out to `npx arkaos@latest update`.
|
|
187
|
+
|
|
188
|
+
Inherits the parent's stdout/stderr so the operator sees the same
|
|
189
|
+
installer banner they would in a manual run. Swallows OSError /
|
|
190
|
+
TimeoutExpired so the surrounding sync still runs.
|
|
191
|
+
"""
|
|
192
|
+
del arkaos_home # passed for symmetry with _probe_npm_latest signature
|
|
193
|
+
try:
|
|
194
|
+
env = os.environ.copy()
|
|
195
|
+
# Some operators set CI=1 to suppress installer prompts; preserve it.
|
|
196
|
+
subprocess.run(
|
|
197
|
+
list(_NPX_UPDATE_CMD),
|
|
198
|
+
check=False,
|
|
199
|
+
timeout=_NPX_TIMEOUT_SECONDS,
|
|
200
|
+
env=env,
|
|
201
|
+
)
|
|
202
|
+
except (OSError, subprocess.TimeoutExpired) as exc:
|
|
203
|
+
sys.stderr.write(
|
|
204
|
+
f"[arkaos] npx arkaos@latest update failed: {exc}\n"
|
|
205
|
+
"[arkaos] continuing with the sync engine using the "
|
|
206
|
+
"currently-installed core.\n"
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def main(argv: list[str]) -> int:
|
|
211
|
+
"""CLI entry: python -m core.sync.update_orchestrator --home X --skills Y."""
|
|
212
|
+
import argparse
|
|
213
|
+
parser = argparse.ArgumentParser(description="ArkaOS one-stop /arka update")
|
|
214
|
+
parser.add_argument("--home", required=True)
|
|
215
|
+
parser.add_argument("--skills", required=True)
|
|
216
|
+
parser.add_argument("--output", choices=["text", "json"], default="text")
|
|
217
|
+
args = parser.parse_args(argv[1:])
|
|
218
|
+
|
|
219
|
+
installed, latest, report = orchestrate(
|
|
220
|
+
arkaos_home=Path(args.home),
|
|
221
|
+
skills_dir=Path(args.skills),
|
|
222
|
+
home_path=str(Path.home()),
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
if args.output == "json":
|
|
226
|
+
payload = {
|
|
227
|
+
"installed_version_before": installed,
|
|
228
|
+
"latest_version_seen": latest,
|
|
229
|
+
"report": report.model_dump(),
|
|
230
|
+
}
|
|
231
|
+
print(json.dumps(payload, indent=2))
|
|
232
|
+
else:
|
|
233
|
+
from core.sync.reporter import format_report
|
|
234
|
+
print(f"Installed: {installed or 'unknown'}")
|
|
235
|
+
print(f"Latest published: {latest or 'unknown'}")
|
|
236
|
+
if installed and latest and _is_older(installed, latest):
|
|
237
|
+
print(f"Updated from {installed} → {latest} via npx arkaos@latest")
|
|
238
|
+
print()
|
|
239
|
+
print(format_report(report))
|
|
240
|
+
return 0
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
if __name__ == "__main__":
|
|
244
|
+
sys.exit(main(sys.argv))
|
|
@@ -21,15 +21,31 @@ Hybrid sync engine: Python handles deterministic operations (MCPs, settings, des
|
|
|
21
21
|
|
|
22
22
|
## Orchestration (Summary)
|
|
23
23
|
|
|
24
|
-
1. **
|
|
24
|
+
1. **One-stop: npm refresh + engine (PR61 v2.78.0 orchestrator).**
|
|
25
25
|
```bash
|
|
26
|
-
cd $ARKAOS_ROOT && python -m core.sync.
|
|
26
|
+
cd $ARKAOS_ROOT && python -m core.sync.update_orchestrator --home ~/.arkaos --skills ~/.claude/skills --output json
|
|
27
27
|
```
|
|
28
|
-
|
|
28
|
+
The orchestrator detects whether the running ArkaOS is behind npm
|
|
29
|
+
latest. When stale, it shells out to `npx arkaos@latest update`
|
|
30
|
+
first so the sync engine below reads fresh code; when current, it
|
|
31
|
+
skips straight to the engine. Either way it runs the
|
|
32
|
+
deterministic engine (manifest, discovery, MCP sync, settings
|
|
33
|
+
sync, descriptors, content, agents) and writes
|
|
34
|
+
`~/.arkaos/sync-state.json`.
|
|
35
|
+
|
|
36
|
+
Probe is cached for 1 hour in `~/.arkaos/npm-latest.cache.json`
|
|
37
|
+
to keep repeat runs cheap. Offline / `npx` missing → orchestrator
|
|
38
|
+
silently skips the npm step and falls through to the engine.
|
|
39
|
+
|
|
40
|
+
Fallback (no orchestrator): the underlying engine still runs the
|
|
41
|
+
same way via `python -m core.sync.engine ...` for callers that
|
|
42
|
+
don't need the version-drift gate.
|
|
29
43
|
|
|
30
44
|
2. **Phase 4 (intelligent, AI subagent):** After the engine completes, dispatch ONE subagent with the engine's JSON report + the feature registry (`core/sync/features/*.yaml`). The subagent injects/removes feature sections in each `~/.claude/skills/arka-{ecosystem}/SKILL.md` while preserving all custom content.
|
|
31
45
|
|
|
32
|
-
3. **Report:** Display the formatted summary returned by the engine
|
|
46
|
+
3. **Report:** Display the formatted summary returned by the engine,
|
|
47
|
+
plus `installed_version_before` / `latest_version_seen` from the
|
|
48
|
+
orchestrator so the operator sees what got refreshed.
|
|
33
49
|
|
|
34
50
|
## Error Handling (Summary)
|
|
35
51
|
|
package/package.json
CHANGED