arkaos 2.18.1 → 2.19.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/README.md +8 -8
- package/VERSION +1 -1
- package/config/cognition/prompts/dreaming.md +3 -3
- package/config/hooks/cwd-changed.ps1 +16 -8
- package/config/hooks/cwd-changed.sh +18 -9
- package/core/runtime/__pycache__/user_paths.cpython-313.pyc +0 -0
- package/core/runtime/user_paths.py +115 -0
- package/core/sync/__pycache__/engine.cpython-313.pyc +0 -0
- package/core/sync/engine.py +24 -4
- package/installer/adapters/claude-code.js +10 -9
- package/installer/cli.js +7 -0
- package/installer/index.js +42 -1
- package/installer/migrate-user-data.js +132 -0
- package/installer/update.js +10 -1
- package/knowledge/ecosystems.json +1 -1
- package/package.json +1 -1
- package/pyproject.toml +1 -1
package/README.md
CHANGED
|
@@ -161,7 +161,7 @@ ArkaOS doesn't just execute — it **learns, dreams, and researches**.
|
|
|
161
161
|
Every solution you implement is captured and indexed. When you need authentication in a new Laravel project, ArkaOS already knows how you did it in the last three projects — with the exact pattern, configuration, and lessons learned.
|
|
162
162
|
|
|
163
163
|
- **Dual-write**: Obsidian (human-readable) + Vector DB (semantic search)
|
|
164
|
-
- **Cross-project**: Knowledge from
|
|
164
|
+
- **Cross-project**: Knowledge from ClientRetail applies to ClientFashion
|
|
165
165
|
- **Confidence scoring**: Patterns validated 3+ times become "validated patterns"
|
|
166
166
|
|
|
167
167
|
### Dreaming (runs at 02:00)
|
|
@@ -185,7 +185,7 @@ Pending reflections from Dreaming:
|
|
|
185
185
|
|
|
186
186
|
2. [technical] Sync retry — improve
|
|
187
187
|
Fixed backoff can cause thundering herd. Use exponential
|
|
188
|
-
backoff with jitter (validated pattern from
|
|
188
|
+
backoff with jitter (validated pattern from ClientRetail).
|
|
189
189
|
|
|
190
190
|
Want me to elaborate?
|
|
191
191
|
```
|
|
@@ -204,11 +204,11 @@ Intelligence Briefing — 2026-04-10
|
|
|
204
204
|
|
|
205
205
|
ACTION REQUIRED:
|
|
206
206
|
- Laravel 12.1.3 security patch — SQL injection in whereHas.
|
|
207
|
-
Affects:
|
|
207
|
+
Affects: ClientFashion, ClientCommerce. Fix: composer update laravel/framework.
|
|
208
208
|
|
|
209
209
|
OPPORTUNITIES:
|
|
210
|
-
- Shopify Winter '26 bulk product API —
|
|
211
|
-
- Nuxt 4 RC2 migration guide published — start preparing
|
|
210
|
+
- Shopify Winter '26 bulk product API — ClientCommerce sync could be 10x faster.
|
|
211
|
+
- Nuxt 4 RC2 migration guide published — start preparing ClientVideo.
|
|
212
212
|
|
|
213
213
|
COMPETITOR WATCH:
|
|
214
214
|
- CrewAI v3 launched memory layer — similar to our Cognitive Layer
|
|
@@ -233,9 +233,9 @@ arkaos scheduler logs # View logs
|
|
|
233
233
|
ArkaOS manages client projects as **ecosystems** — groups of related projects with dedicated squads.
|
|
234
234
|
|
|
235
235
|
```
|
|
236
|
-
/
|
|
237
|
-
/
|
|
238
|
-
/
|
|
236
|
+
/client_retail → ClientRetail ecosystem (4 projects: API, frontend, admin, docs)
|
|
237
|
+
/client_commerce → ClientCommerce ecosystem (supplier sync + Shopify theme)
|
|
238
|
+
/client_fashion → ClientFashion (6 projects: CRM, store, API, migration...)
|
|
239
239
|
/edp → EDP (3 projects: portal, API, analytics)
|
|
240
240
|
```
|
|
241
241
|
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.
|
|
1
|
+
2.19.1
|
|
@@ -70,7 +70,7 @@ vector = VectorWriter(os.path.expanduser("~/.arkaos/knowledge.db"))
|
|
|
70
70
|
|
|
71
71
|
- Compare today's errors with past errors — if same error type appears > 2 times, create Anti-Pattern entry
|
|
72
72
|
- Compare today's solutions with past solutions — if same pattern appears > 2 times, promote to Validated Pattern
|
|
73
|
-
- Detect inconsistencies between projects ("In
|
|
73
|
+
- Detect inconsistencies between projects ("In ClientRetail used X, in ClientFashion used Y for same problem")
|
|
74
74
|
|
|
75
75
|
## Phase 4: Curation and Consolidation
|
|
76
76
|
|
|
@@ -123,7 +123,7 @@ quality_score: 75
|
|
|
123
123
|
entries_created: 4
|
|
124
124
|
entries_updated: 2
|
|
125
125
|
insights_generated: 3
|
|
126
|
-
projects_active: [
|
|
126
|
+
projects_active: [client_commerce, client_retail, client_fashion]
|
|
127
127
|
---
|
|
128
128
|
|
|
129
129
|
# Dreaming Report — YYYY-MM-DD
|
|
@@ -199,7 +199,7 @@ Write structured metrics to `~/.arkaos/logs/dreaming/YYYY-MM-DD.json`:
|
|
|
199
199
|
"entries_updated": 2,
|
|
200
200
|
"insights_generated": 3,
|
|
201
201
|
"captures_processed": 15,
|
|
202
|
-
"projects_reviewed": ["
|
|
202
|
+
"projects_reviewed": ["client_commerce", "client_retail"]
|
|
203
203
|
}
|
|
204
204
|
```
|
|
205
205
|
|
|
@@ -43,7 +43,13 @@ if ([string]::IsNullOrWhiteSpace($newCwd)) { exit 0 }
|
|
|
43
43
|
if (-not (Test-Path -LiteralPath $newCwd -PathType Container)) { exit 0 }
|
|
44
44
|
|
|
45
45
|
# ─── Detect ecosystem from ecosystems.json ─────────────────────────────
|
|
46
|
-
|
|
46
|
+
# Canonical path is %USERPROFILE%\.arkaos\ecosystems.json (ADR 2026-04-17).
|
|
47
|
+
# Falls back to the legacy skill-dir path until v2.21.0.
|
|
48
|
+
$ecosystemsFile = Join-Path $env:USERPROFILE '.arkaos\ecosystems.json'
|
|
49
|
+
if (-not (Test-Path -LiteralPath $ecosystemsFile)) {
|
|
50
|
+
$legacyEcosystems = Join-Path $env:USERPROFILE '.claude\skills\arka\knowledge\ecosystems.json'
|
|
51
|
+
if (Test-Path -LiteralPath $legacyEcosystems) { $ecosystemsFile = $legacyEcosystems }
|
|
52
|
+
}
|
|
47
53
|
$ecosystem = ''
|
|
48
54
|
$ecosystemName = ''
|
|
49
55
|
|
|
@@ -115,15 +121,17 @@ if (Test-Path -LiteralPath $composerJson) {
|
|
|
115
121
|
|
|
116
122
|
# ─── Check for project descriptor ─────────────────────────────────────
|
|
117
123
|
$dirName = Split-Path -Leaf $newCwd.TrimEnd('\','/')
|
|
118
|
-
$
|
|
119
|
-
$
|
|
120
|
-
$descriptorDir = Join-Path (Join-Path $projectsDir $dirName) 'PROJECT.md'
|
|
124
|
+
$newProjectsDir = Join-Path $env:USERPROFILE '.arkaos\projects'
|
|
125
|
+
$legacyProjectsDir = Join-Path $env:USERPROFILE '.claude\skills\arka\projects'
|
|
121
126
|
|
|
122
127
|
$descriptor = ''
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
$
|
|
128
|
+
foreach ($candidate in @(
|
|
129
|
+
(Join-Path $newProjectsDir "$dirName.md"),
|
|
130
|
+
(Join-Path (Join-Path $newProjectsDir $dirName) 'PROJECT.md'),
|
|
131
|
+
(Join-Path $legacyProjectsDir "$dirName.md"),
|
|
132
|
+
(Join-Path (Join-Path $legacyProjectsDir $dirName) 'PROJECT.md')
|
|
133
|
+
)) {
|
|
134
|
+
if (Test-Path -LiteralPath $candidate) { $descriptor = $candidate; break }
|
|
127
135
|
}
|
|
128
136
|
|
|
129
137
|
# ─── Build context output ─────────────────────────────────────────────
|
|
@@ -13,7 +13,13 @@ if [ -z "$NEW_CWD" ] || [ ! -d "$NEW_CWD" ]; then
|
|
|
13
13
|
fi
|
|
14
14
|
|
|
15
15
|
# ─── Detect ecosystem from ecosystems.json ─────────────────────────────
|
|
16
|
-
|
|
16
|
+
# Canonical path is ~/.arkaos/ecosystems.json (ADR 2026-04-17). During the
|
|
17
|
+
# deprecation window we fall back to the legacy skill-dir path. The legacy
|
|
18
|
+
# fallback is removed in v2.21.0.
|
|
19
|
+
ECOSYSTEMS_FILE="$HOME/.arkaos/ecosystems.json"
|
|
20
|
+
if [ ! -f "$ECOSYSTEMS_FILE" ] && [ -f "$HOME/.claude/skills/arka/knowledge/ecosystems.json" ]; then
|
|
21
|
+
ECOSYSTEMS_FILE="$HOME/.claude/skills/arka/knowledge/ecosystems.json"
|
|
22
|
+
fi
|
|
17
23
|
ECOSYSTEM=""
|
|
18
24
|
ECOSYSTEM_NAME=""
|
|
19
25
|
|
|
@@ -75,16 +81,19 @@ elif [ -f "$NEW_CWD/pyproject.toml" ]; then
|
|
|
75
81
|
fi
|
|
76
82
|
|
|
77
83
|
# ─── Check for project descriptor ─────────────────────────────────────
|
|
84
|
+
# New canonical path: ~/.arkaos/projects/. Legacy fallback remains until v2.21.0.
|
|
78
85
|
DIR_NAME=$(basename "$NEW_CWD")
|
|
79
86
|
DESCRIPTOR=""
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
for CANDIDATE in \
|
|
88
|
+
"$HOME/.arkaos/projects/${DIR_NAME}.md" \
|
|
89
|
+
"$HOME/.arkaos/projects/${DIR_NAME}/PROJECT.md" \
|
|
90
|
+
"$HOME/.claude/skills/arka/projects/${DIR_NAME}.md" \
|
|
91
|
+
"$HOME/.claude/skills/arka/projects/${DIR_NAME}/PROJECT.md"; do
|
|
92
|
+
if [ -f "$CANDIDATE" ]; then
|
|
93
|
+
DESCRIPTOR="$CANDIDATE"
|
|
94
|
+
break
|
|
95
|
+
fi
|
|
96
|
+
done
|
|
88
97
|
|
|
89
98
|
# ─── Build context output ─────────────────────────────────────────────
|
|
90
99
|
CONTEXT=""
|
|
Binary file
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Canonical resolver for ArkaOS user-local data paths.
|
|
2
|
+
|
|
3
|
+
User-mutable data lives under ~/.arkaos/. The installed skill bundle at
|
|
4
|
+
~/.claude/skills/arka/ is read-only and installer-managed. Two historical
|
|
5
|
+
paths still receive user writes in some installs:
|
|
6
|
+
|
|
7
|
+
~/.claude/skills/arka/projects/*.md
|
|
8
|
+
~/.claude/skills/arka/knowledge/ecosystems.json
|
|
9
|
+
|
|
10
|
+
This module returns the canonical new path when present, falls back to the
|
|
11
|
+
legacy path with a one-shot deprecation warning, and returns None when
|
|
12
|
+
neither exists. Callers treat None as empty. See ADR
|
|
13
|
+
docs/adr/2026-04-17-user-data-separation.md.
|
|
14
|
+
|
|
15
|
+
Legacy fallback sunsets in v2.21.0.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import logging
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
_LEGACY_SKILLS_ROOT = Path.home() / ".claude" / "skills" / "arka"
|
|
26
|
+
_USER_DATA_ROOT = Path.home() / ".arkaos"
|
|
27
|
+
|
|
28
|
+
_warned: set[str] = set()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def user_data_root() -> Path:
|
|
32
|
+
"""Root directory for ArkaOS user-local state."""
|
|
33
|
+
return _USER_DATA_ROOT
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def projects_dir() -> Path | None:
|
|
37
|
+
"""Resolve the user projects directory.
|
|
38
|
+
|
|
39
|
+
Returns the new path when it exists, else the legacy path with a
|
|
40
|
+
one-shot deprecation warning, else None.
|
|
41
|
+
"""
|
|
42
|
+
new = _USER_DATA_ROOT / "projects"
|
|
43
|
+
legacy = _LEGACY_SKILLS_ROOT / "projects"
|
|
44
|
+
return _resolve_with_fallback("projects_dir", new, legacy)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def ecosystems_file() -> Path | None:
|
|
48
|
+
"""Resolve the ecosystems registry file.
|
|
49
|
+
|
|
50
|
+
Returns the new path when it exists, else the legacy path with a
|
|
51
|
+
one-shot deprecation warning, else None.
|
|
52
|
+
"""
|
|
53
|
+
new = _USER_DATA_ROOT / "ecosystems.json"
|
|
54
|
+
legacy = _LEGACY_SKILLS_ROOT / "knowledge" / "ecosystems.json"
|
|
55
|
+
return _resolve_with_fallback("ecosystems_file", new, legacy)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def projects_dir_for_write() -> Path:
|
|
59
|
+
"""Return the write target for user project descriptors.
|
|
60
|
+
|
|
61
|
+
Writers always target the new path. The directory is created if absent.
|
|
62
|
+
"""
|
|
63
|
+
new = _USER_DATA_ROOT / "projects"
|
|
64
|
+
new.mkdir(parents=True, exist_ok=True)
|
|
65
|
+
return new
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def ecosystems_file_for_write() -> Path:
|
|
69
|
+
"""Return the write target for the ecosystems registry.
|
|
70
|
+
|
|
71
|
+
Writers always target the new path. Parent directory is created if absent.
|
|
72
|
+
"""
|
|
73
|
+
new = _USER_DATA_ROOT / "ecosystems.json"
|
|
74
|
+
new.parent.mkdir(parents=True, exist_ok=True)
|
|
75
|
+
return new
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def legacy_projects_dir() -> Path:
|
|
79
|
+
"""Legacy projects directory (for migration tooling only)."""
|
|
80
|
+
return _LEGACY_SKILLS_ROOT / "projects"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def legacy_ecosystems_file() -> Path:
|
|
84
|
+
"""Legacy ecosystems file (for migration tooling only)."""
|
|
85
|
+
return _LEGACY_SKILLS_ROOT / "knowledge" / "ecosystems.json"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def reset_warnings() -> None:
|
|
89
|
+
"""Clear the per-process deprecation warning cache.
|
|
90
|
+
|
|
91
|
+
Intended for tests. Real runtime should emit each warning exactly once.
|
|
92
|
+
"""
|
|
93
|
+
_warned.clear()
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _resolve_with_fallback(kind: str, new: Path, legacy: Path) -> Path | None:
|
|
97
|
+
if new.exists():
|
|
98
|
+
return new
|
|
99
|
+
if legacy.exists():
|
|
100
|
+
_warn_once(kind, legacy)
|
|
101
|
+
return legacy
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _warn_once(kind: str, legacy: Path) -> None:
|
|
106
|
+
if kind in _warned:
|
|
107
|
+
return
|
|
108
|
+
_warned.add(kind)
|
|
109
|
+
logger.warning(
|
|
110
|
+
"ArkaOS: reading %s from legacy location %s. "
|
|
111
|
+
"This path is deprecated and will be removed in v2.21.0. "
|
|
112
|
+
"Run `npx arkaos@latest migrate-user-data` or `/arka update` to move your data to ~/.arkaos/.",
|
|
113
|
+
kind,
|
|
114
|
+
legacy,
|
|
115
|
+
)
|
|
Binary file
|
package/core/sync/engine.py
CHANGED
|
@@ -11,6 +11,10 @@ import re
|
|
|
11
11
|
import sys
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
|
|
14
|
+
from core.runtime.user_paths import (
|
|
15
|
+
ecosystems_file as resolve_ecosystems_file,
|
|
16
|
+
projects_dir as resolve_projects_dir,
|
|
17
|
+
)
|
|
14
18
|
from core.sync.manifest import build_manifest
|
|
15
19
|
from core.sync.discovery import discover_all_projects
|
|
16
20
|
from core.sync.mcp_optimizer import optimize_all_mcps
|
|
@@ -169,13 +173,29 @@ def _parse_scan_dirs(projects_dir_str: str) -> list[Path]:
|
|
|
169
173
|
|
|
170
174
|
|
|
171
175
|
def _discover_projects(arkaos_home: Path, skills_dir: Path) -> list:
|
|
172
|
-
"""Combine profile.json dirs, descriptor dir, and ecosystems into projects.
|
|
173
|
-
|
|
174
|
-
|
|
176
|
+
"""Combine profile.json dirs, descriptor dir, and ecosystems into projects.
|
|
177
|
+
|
|
178
|
+
Project descriptors and the ecosystems registry are user-local data and
|
|
179
|
+
live under ~/.arkaos/ (see ADR 2026-04-17-user-data-separation). During
|
|
180
|
+
the deprecation window, reads fall back to the legacy paths under
|
|
181
|
+
skills_dir with a one-shot warning. `skills_dir` is kept in the
|
|
182
|
+
signature for backward compatibility and test ergonomics but is no
|
|
183
|
+
longer consulted for user data.
|
|
184
|
+
"""
|
|
185
|
+
del skills_dir # retained for signature stability; unused.
|
|
186
|
+
|
|
187
|
+
# resolve_*() returns None when neither the new nor legacy path exists.
|
|
188
|
+
# `discover_all_projects` requires concrete Path objects but calls
|
|
189
|
+
# `.exists()` internally, so substituting the (non-existent) canonical
|
|
190
|
+
# path keeps the contract: missing → returns an empty project list.
|
|
191
|
+
descriptor_dir = resolve_projects_dir() or (Path.home() / ".arkaos" / "projects")
|
|
192
|
+
ecosystems_path = resolve_ecosystems_file() or (
|
|
193
|
+
Path.home() / ".arkaos" / "ecosystems.json"
|
|
194
|
+
)
|
|
175
195
|
|
|
176
196
|
scan_dirs = _load_scan_dirs_from_profile(arkaos_home)
|
|
177
197
|
|
|
178
|
-
return discover_all_projects(descriptor_dir, scan_dirs,
|
|
198
|
+
return discover_all_projects(descriptor_dir, scan_dirs, ecosystems_path)
|
|
179
199
|
|
|
180
200
|
|
|
181
201
|
def _load_scan_dirs_from_profile(arkaos_home: Path) -> list[Path]:
|
|
@@ -105,19 +105,20 @@ export default {
|
|
|
105
105
|
];
|
|
106
106
|
|
|
107
107
|
// Statusline — ArkaOS branded status bar
|
|
108
|
+
// Claude Code reads the camelCase "statusLine" key; the lowercase
|
|
109
|
+
// "statusline" variant is silently ignored.
|
|
108
110
|
const configDir = join(installDir, "config");
|
|
109
111
|
const statuslineFile = IS_WINDOWS ? "statusline.ps1" : "statusline.sh";
|
|
110
112
|
const statuslinePath = join(configDir, statuslineFile);
|
|
111
113
|
if (existsSync(statuslinePath)) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
114
|
+
const command = IS_WINDOWS
|
|
115
|
+
? `powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "${statuslinePath}"`
|
|
116
|
+
: statuslinePath;
|
|
117
|
+
settings.statusLine = {
|
|
118
|
+
type: "command",
|
|
119
|
+
command,
|
|
120
|
+
padding: 2,
|
|
121
|
+
};
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
package/installer/cli.js
CHANGED
|
@@ -41,6 +41,7 @@ Usage:
|
|
|
41
41
|
npx arkaos init Initialize project config (.arkaos.json)
|
|
42
42
|
npx arkaos update Update to latest version
|
|
43
43
|
npx arkaos migrate Migrate from v1 to v2
|
|
44
|
+
npx arkaos migrate-user-data Move user data (~/.claude/skills/arka/ → ~/.arkaos/)
|
|
44
45
|
npx arkaos dashboard Start monitoring dashboard
|
|
45
46
|
npx arkaos keys Manage API keys (OpenAI, fal.ai, etc.)
|
|
46
47
|
npx arkaos doctor Run health checks
|
|
@@ -102,6 +103,12 @@ async function main() {
|
|
|
102
103
|
await migrate();
|
|
103
104
|
break;
|
|
104
105
|
|
|
106
|
+
case "migrate-user-data": {
|
|
107
|
+
const { migrateUserData, printMigrationReport } = await import("./migrate-user-data.js");
|
|
108
|
+
printMigrationReport(migrateUserData());
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
|
|
105
112
|
case "keys": {
|
|
106
113
|
const { keys: keysCmd } = await import("./keys.js");
|
|
107
114
|
await keysCmd(positionals.slice(1));
|
package/installer/index.js
CHANGED
|
@@ -34,8 +34,28 @@ export async function install({ runtime, path, force }) {
|
|
|
34
34
|
// ═══ Step 1: Create directories ═══
|
|
35
35
|
step(1, 14, "Creating directories...");
|
|
36
36
|
ensureDir(installDir);
|
|
37
|
-
const dirs = ["config", "config/hooks", "agents", "media", "session-digests", "vault"];
|
|
37
|
+
const dirs = ["config", "config/hooks", "agents", "media", "session-digests", "vault", "projects", "logs"];
|
|
38
38
|
for (const d of dirs) ensureDir(join(installDir, d));
|
|
39
|
+
|
|
40
|
+
// Seed an empty ecosystems.json the first time; never overwrite existing user data.
|
|
41
|
+
const ecosystemsPath = join(installDir, "ecosystems.json");
|
|
42
|
+
if (!existsSync(ecosystemsPath)) {
|
|
43
|
+
writeFileSync(
|
|
44
|
+
ecosystemsPath,
|
|
45
|
+
JSON.stringify(
|
|
46
|
+
{
|
|
47
|
+
_meta: {
|
|
48
|
+
description: "ArkaOS — user-local ecosystem registry",
|
|
49
|
+
note: "Populated by /arka onboard. Never committed to the public repo.",
|
|
50
|
+
updated: new Date().toISOString().slice(0, 10),
|
|
51
|
+
},
|
|
52
|
+
ecosystems: {},
|
|
53
|
+
},
|
|
54
|
+
null,
|
|
55
|
+
2,
|
|
56
|
+
) + "\n",
|
|
57
|
+
);
|
|
58
|
+
}
|
|
39
59
|
ok(`${dirs.length + 1} directories ready`);
|
|
40
60
|
|
|
41
61
|
// ═══ Step 2: Detect v1 installation ═══
|
|
@@ -239,6 +259,27 @@ export async function install({ runtime, path, force }) {
|
|
|
239
259
|
};
|
|
240
260
|
writeFileSync(join(installDir, "install-manifest.json"), JSON.stringify(manifest, null, 2));
|
|
241
261
|
|
|
262
|
+
// Seed sync-state.json so session-start.sh does not read a missing file as
|
|
263
|
+
// version drift and permanently show [arka:update-available] on a fresh
|
|
264
|
+
// install. Schema aligned with core/sync/reporter.py write_sync_state.
|
|
265
|
+
const syncStatePath = join(installDir, "sync-state.json");
|
|
266
|
+
if (!existsSync(syncStatePath)) {
|
|
267
|
+
writeFileSync(
|
|
268
|
+
syncStatePath,
|
|
269
|
+
JSON.stringify(
|
|
270
|
+
{
|
|
271
|
+
version: VERSION,
|
|
272
|
+
last_sync: new Date().toISOString(),
|
|
273
|
+
projects_synced: 0,
|
|
274
|
+
skills_synced: 0,
|
|
275
|
+
errors: [],
|
|
276
|
+
},
|
|
277
|
+
null,
|
|
278
|
+
2,
|
|
279
|
+
),
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
242
283
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
243
284
|
|
|
244
285
|
console.log(`
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
const LEGACY_SKILLS_ROOT = join(homedir(), ".claude", "skills", "arka");
|
|
6
|
+
const USER_DATA_ROOT = join(homedir(), ".arkaos");
|
|
7
|
+
|
|
8
|
+
const LEGACY_PROJECTS = join(LEGACY_SKILLS_ROOT, "projects");
|
|
9
|
+
const LEGACY_ECOSYSTEMS = join(LEGACY_SKILLS_ROOT, "knowledge", "ecosystems.json");
|
|
10
|
+
const NEW_PROJECTS = join(USER_DATA_ROOT, "projects");
|
|
11
|
+
const NEW_ECOSYSTEMS = join(USER_DATA_ROOT, "ecosystems.json");
|
|
12
|
+
const LOGS_DIR = join(USER_DATA_ROOT, "logs");
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Move user-local project descriptors and ecosystems registry from the
|
|
16
|
+
* legacy skill directory to ~/.arkaos/. Idempotent: items already present
|
|
17
|
+
* at the destination are left alone and logged as conflicts.
|
|
18
|
+
*
|
|
19
|
+
* @returns {{ moved: string[], skipped: string[], conflicts: string[], logPath: string|null }}
|
|
20
|
+
*/
|
|
21
|
+
export function migrateUserData({ dryRun = false } = {}) {
|
|
22
|
+
ensureDataRoot();
|
|
23
|
+
|
|
24
|
+
const projectsResult = migrateProjects({ dryRun });
|
|
25
|
+
const ecosystemsResult = migrateEcosystems({ dryRun });
|
|
26
|
+
|
|
27
|
+
const moved = [...projectsResult.moved, ...ecosystemsResult.moved];
|
|
28
|
+
const skipped = [...projectsResult.skipped, ...ecosystemsResult.skipped];
|
|
29
|
+
const conflicts = [...projectsResult.conflicts, ...ecosystemsResult.conflicts];
|
|
30
|
+
|
|
31
|
+
const logPath = writeReport({ moved, skipped, conflicts, dryRun });
|
|
32
|
+
return { moved, skipped, conflicts, logPath };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function printMigrationReport(result) {
|
|
36
|
+
const { moved, conflicts, logPath } = result;
|
|
37
|
+
if (moved.length === 0 && conflicts.length === 0) {
|
|
38
|
+
console.log(" ✓ User data already migrated, nothing to move.");
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
console.log(` ✓ Migration: ${moved.length} moved, ${conflicts.length} conflicts`);
|
|
42
|
+
for (const m of moved) console.log(` + ${m}`);
|
|
43
|
+
for (const c of conflicts) console.log(` ! ${c}`);
|
|
44
|
+
if (logPath) console.log(` Log: ${logPath}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function ensureDataRoot() {
|
|
48
|
+
if (!existsSync(USER_DATA_ROOT)) mkdirSync(USER_DATA_ROOT, { recursive: true });
|
|
49
|
+
if (!existsSync(NEW_PROJECTS)) mkdirSync(NEW_PROJECTS, { recursive: true });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function migrateProjects({ dryRun }) {
|
|
53
|
+
const moved = [];
|
|
54
|
+
const skipped = [];
|
|
55
|
+
const conflicts = [];
|
|
56
|
+
|
|
57
|
+
if (!existsSync(LEGACY_PROJECTS) || !statSync(LEGACY_PROJECTS).isDirectory()) {
|
|
58
|
+
skipped.push("projects/: legacy directory absent");
|
|
59
|
+
return { moved, skipped, conflicts };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
for (const entry of readdirSync(LEGACY_PROJECTS)) {
|
|
63
|
+
const src = join(LEGACY_PROJECTS, entry);
|
|
64
|
+
const dst = join(NEW_PROJECTS, entry);
|
|
65
|
+
if (existsSync(dst)) {
|
|
66
|
+
conflicts.push(`projects/${entry}: destination already present, left source untouched`);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (dryRun) {
|
|
70
|
+
moved.push(`projects/${entry} (dry-run)`);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
renameSync(src, dst);
|
|
75
|
+
moved.push(`projects/${entry}`);
|
|
76
|
+
} catch (err) {
|
|
77
|
+
conflicts.push(`projects/${entry}: ${err.message}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return { moved, skipped, conflicts };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function migrateEcosystems({ dryRun }) {
|
|
84
|
+
const moved = [];
|
|
85
|
+
const skipped = [];
|
|
86
|
+
const conflicts = [];
|
|
87
|
+
|
|
88
|
+
if (!existsSync(LEGACY_ECOSYSTEMS)) {
|
|
89
|
+
skipped.push("ecosystems.json: legacy file absent");
|
|
90
|
+
return { moved, skipped, conflicts };
|
|
91
|
+
}
|
|
92
|
+
if (existsSync(NEW_ECOSYSTEMS)) {
|
|
93
|
+
conflicts.push("ecosystems.json: destination already present, left source untouched");
|
|
94
|
+
return { moved, skipped, conflicts };
|
|
95
|
+
}
|
|
96
|
+
if (dryRun) {
|
|
97
|
+
moved.push("ecosystems.json (dry-run)");
|
|
98
|
+
return { moved, skipped, conflicts };
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
renameSync(LEGACY_ECOSYSTEMS, NEW_ECOSYSTEMS);
|
|
102
|
+
moved.push("ecosystems.json");
|
|
103
|
+
} catch (err) {
|
|
104
|
+
conflicts.push(`ecosystems.json: ${err.message}`);
|
|
105
|
+
}
|
|
106
|
+
return { moved, skipped, conflicts };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function writeReport({ moved, skipped, conflicts, dryRun }) {
|
|
110
|
+
if (moved.length === 0 && conflicts.length === 0) return null;
|
|
111
|
+
if (!existsSync(LOGS_DIR)) mkdirSync(LOGS_DIR, { recursive: true });
|
|
112
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
113
|
+
const logPath = join(LOGS_DIR, `migration-${stamp}.log`);
|
|
114
|
+
const body = [
|
|
115
|
+
`ArkaOS user-data migration — ${new Date().toISOString()}`,
|
|
116
|
+
`Source: ${LEGACY_SKILLS_ROOT}`,
|
|
117
|
+
`Destination: ${USER_DATA_ROOT}`,
|
|
118
|
+
`Dry-run: ${dryRun}`,
|
|
119
|
+
"",
|
|
120
|
+
`Moved (${moved.length}):`,
|
|
121
|
+
...moved.map(m => ` - ${m}`),
|
|
122
|
+
"",
|
|
123
|
+
`Conflicts (${conflicts.length}):`,
|
|
124
|
+
...conflicts.map(c => ` - ${c}`),
|
|
125
|
+
"",
|
|
126
|
+
`Skipped (${skipped.length}):`,
|
|
127
|
+
...skipped.map(s => ` - ${s}`),
|
|
128
|
+
"",
|
|
129
|
+
].join("\n");
|
|
130
|
+
if (!dryRun) writeFileSync(logPath, body);
|
|
131
|
+
return logPath;
|
|
132
|
+
}
|
package/installer/update.js
CHANGED
|
@@ -5,6 +5,7 @@ import { execSync } from "node:child_process";
|
|
|
5
5
|
import { ensureVenv, getArkaosPython, pipInstall } from "./python-resolver.js";
|
|
6
6
|
import { getRuntimeConfig } from "./detect-runtime.js";
|
|
7
7
|
import { loadAdapter } from "./index.js";
|
|
8
|
+
import { migrateUserData, printMigrationReport } from "./migrate-user-data.js";
|
|
8
9
|
import { IS_WINDOWS, HOOK_EXT } from "./platform.js";
|
|
9
10
|
import { fileURLToPath } from "node:url";
|
|
10
11
|
|
|
@@ -37,6 +38,14 @@ export async function update() {
|
|
|
37
38
|
console.log(` \u26a0 Legacy hook state migration skipped: ${err.message}`);
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
// User-data separation (ADR 2026-04-17): move descriptors and ecosystems
|
|
42
|
+
// from ~/.claude/skills/arka/ to ~/.arkaos/. Idempotent, non-destructive.
|
|
43
|
+
try {
|
|
44
|
+
printMigrationReport(migrateUserData());
|
|
45
|
+
} catch (err) {
|
|
46
|
+
console.log(` \u26a0 User-data migration skipped: ${err.message}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
40
49
|
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
41
50
|
const profile = existsSync(profilePath) ? JSON.parse(readFileSync(profilePath, "utf-8")) : {};
|
|
42
51
|
|
|
@@ -258,7 +267,7 @@ export async function update() {
|
|
|
258
267
|
// main `/arka` skill, so any department skill (arka-dev, arka-brand,
|
|
259
268
|
// etc.) or sub-skill (arka-code-review, arka-viral, etc.) or agent
|
|
260
269
|
// persona added after the original install was silently missing on
|
|
261
|
-
// upgrade. Discovered during
|
|
270
|
+
// upgrade. Discovered during ClientAdvisory's bake-in: 233 top-level arka-*
|
|
262
271
|
// skills on his WSL (deployed long ago by install.sh) vs 1 skill on
|
|
263
272
|
// his Windows install (only the main /arka). The Node installer
|
|
264
273
|
// never deployed anything else.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"_meta": {
|
|
3
3
|
"description": "ARKA OS — Project Ecosystem Registry (user-local, populated after install)",
|
|
4
4
|
"format": "Ecosystems group related projects that share squads, stacks, and conventions.",
|
|
5
|
-
"note": "This file ships empty. Users define their own ecosystems locally
|
|
5
|
+
"note": "This file ships empty. Users define their own ecosystems locally in ~/.arkaos/ecosystems.json (populated by /arka onboard). Client names and internal project data MUST NOT be committed to the public repository.",
|
|
6
6
|
"updated": "2026-04-17"
|
|
7
7
|
},
|
|
8
8
|
"ecosystems": {}
|
package/package.json
CHANGED