arkaos 2.22.1 → 2.28.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.
Files changed (67) hide show
  1. package/README.md +105 -9
  2. package/VERSION +1 -1
  3. package/arka/SKILL.md +1 -0
  4. package/arka/skills/bootstrap-agent/SKILL.md +76 -0
  5. package/arka/skills/comfyui/SKILL.md +2 -2
  6. package/arka/skills/comfyui/references/workflows.md +6 -6
  7. package/arka/skills/costs/SKILL.md +11 -0
  8. package/arka/skills/design-ops/SKILL.md +68 -0
  9. package/arka/skills/design-ops/scripts/extract-colors.py +86 -0
  10. package/arka/skills/design-ops/scripts/shadcn-tokens.py +59 -0
  11. package/arka/skills/design-ops/scripts/wcag-contrast.py +92 -0
  12. package/arka/skills/research/SKILL.md +117 -0
  13. package/config/cognition/prompts/dreaming.md +3 -3
  14. package/config/cognition/prompts/research.md +4 -4
  15. package/config/constitution.yaml +4 -0
  16. package/config/hooks/post-tool-use.sh +17 -0
  17. package/config/hooks/user-prompt-submit.sh +22 -1
  18. package/core/agents/__pycache__/loader.cpython-313.pyc +0 -0
  19. package/core/agents/__pycache__/schema.cpython-313.pyc +0 -0
  20. package/core/agents/loader.py +4 -1
  21. package/core/agents/schema.py +29 -0
  22. package/core/cognition/__pycache__/auto_documentor.cpython-313.pyc +0 -0
  23. package/core/cognition/__pycache__/retrieval.cpython-313.pyc +0 -0
  24. package/core/cognition/capture/__pycache__/collector.cpython-313.pyc +0 -0
  25. package/core/cognition/capture/collector.py +10 -3
  26. package/core/cognition/retrieval.py +383 -0
  27. package/core/cognition/scheduler/__pycache__/daemon.cpython-313.pyc +0 -0
  28. package/core/cognition/scheduler/daemon.py +5 -0
  29. package/core/jobs/__pycache__/auto_doc_worker.cpython-313.pyc +0 -0
  30. package/core/obsidian/__pycache__/writer.cpython-313.pyc +0 -0
  31. package/core/obsidian/writer.py +5 -4
  32. package/core/runtime/__pycache__/base.cpython-313.pyc +0 -0
  33. package/core/runtime/__pycache__/claude_code.cpython-313.pyc +0 -0
  34. package/core/runtime/__pycache__/codex_cli.cpython-313.pyc +0 -0
  35. package/core/runtime/__pycache__/gemini_cli.cpython-313.pyc +0 -0
  36. package/core/runtime/__pycache__/llm_provider.cpython-313.pyc +0 -0
  37. package/core/runtime/__pycache__/path_resolver.cpython-313.pyc +0 -0
  38. package/core/runtime/path_resolver.py +202 -0
  39. package/core/shared/__pycache__/__init__.cpython-313.pyc +0 -0
  40. package/core/shared/__pycache__/safe_session_id.cpython-313.pyc +0 -0
  41. package/core/specs/SPEC-installer-cross-os.md +227 -0
  42. package/core/specs/SPEC-paths-portability.md +209 -0
  43. package/core/synapse/__pycache__/kb_cache.cpython-313.pyc +0 -0
  44. package/core/synapse/__pycache__/layers.cpython-313.pyc +0 -0
  45. package/core/workflow/__pycache__/flow_enforcer.cpython-313.pyc +0 -0
  46. package/core/workflow/__pycache__/marker_cache.cpython-313.pyc +0 -0
  47. package/core/workflow/__pycache__/research_gate.cpython-313.pyc +0 -0
  48. package/departments/brand/agents/design-ops/design-ops-lead.yaml +78 -0
  49. package/departments/brand/agents/design-ops/extraction-script-writer.yaml +74 -0
  50. package/departments/brand/agents/design-ops/shadcn-padronizer.yaml +76 -0
  51. package/departments/brand/agents/design-ops/wcag-auditor.yaml +76 -0
  52. package/departments/dev/skills/mcp/SKILL.md +16 -0
  53. package/departments/dev/skills/scaffold/SKILL.md +4 -4
  54. package/departments/kb/skills/knowledge/SKILL.md +1 -1
  55. package/departments/ops/skills/operations/SKILL.md +1 -1
  56. package/installer/cli.js +2 -1
  57. package/installer/doctor.js +48 -0
  58. package/installer/index.js +26 -1
  59. package/installer/migrate-user-data.js +17 -1
  60. package/installer/migrations/v3_path_schema.js +109 -0
  61. package/installer/package-manager.js +191 -0
  62. package/installer/path-resolver.js +124 -0
  63. package/installer/system-tools.js +243 -0
  64. package/installer/update.js +24 -3
  65. package/knowledge/obsidian-config.json +1 -1
  66. package/package.json +1 -1
  67. package/pyproject.toml +1 -1
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Path template resolver for the ArkaOS installer (Node.js side).
3
+ *
4
+ * Mirrors core/runtime/path_resolver.py just enough to substitute
5
+ * ${VAULT_PATH}, ${ARKA_OS_REPOS}, ${PROJECT_ROOTS}, ${GIT_HOST}, ${HOME}
6
+ * inside SKILL.md, cognition prompts and other markdown that the installer
7
+ * copies to the user's ~/.claude/skills/arka/ tree or ~/.arkaos/.
8
+ *
9
+ * Source files contain templates so the arka-os repo stays portable across
10
+ * the 20K user base. The installer rewrites tokens at copy-time so each
11
+ * user's installation has concrete paths.
12
+ *
13
+ * The Python module is the single source of truth for the substitution
14
+ * contract. Keep this file in sync. See core/specs/SPEC-paths-portability.md.
15
+ */
16
+
17
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
18
+ import { homedir } from "node:os";
19
+ import { join } from "node:path";
20
+
21
+ const TOKEN_PATTERN = /\$\{([A-Z_]+)\}/g;
22
+ const DEFAULT_PROJECT_ROOTS = ["~/Herd", "~/Work", "~/AIProjects"];
23
+ const DEFAULT_REPOS_ROOT = "~/AIProjects";
24
+ const DEFAULT_GIT_HOST = "github.com";
25
+
26
+ let _profileCache = null;
27
+
28
+ function profilePath() {
29
+ return join(homedir(), ".arkaos", "profile.json");
30
+ }
31
+
32
+ export function loadProfile({ refresh = false } = {}) {
33
+ if (_profileCache && !refresh) return _profileCache;
34
+
35
+ const path = profilePath();
36
+ if (!existsSync(path)) {
37
+ // Installer can be invoked before profile.json exists (first install).
38
+ // Caller decides whether to treat that as fatal.
39
+ return null;
40
+ }
41
+ let raw;
42
+ try {
43
+ raw = JSON.parse(readFileSync(path, "utf8"));
44
+ } catch (err) {
45
+ throw new Error(
46
+ `~/.arkaos/profile.json could not be parsed (${err.message}). ` +
47
+ `Run /arka setup to repair, or restore from a .bak file.`
48
+ );
49
+ }
50
+
51
+ const vaultPath = raw.vaultPath || raw.vault_path || "";
52
+ const reposRoot = raw.reposRoot || raw.repos_root || DEFAULT_REPOS_ROOT;
53
+ const projectRoots = Array.isArray(raw.projectRoots) && raw.projectRoots.length
54
+ ? raw.projectRoots
55
+ : deriveProjectRoots(raw.projectsDir || "");
56
+
57
+ _profileCache = {
58
+ version: String(raw.version || "2"),
59
+ vaultPath,
60
+ reposRoot,
61
+ projectRoots,
62
+ raw,
63
+ };
64
+ return _profileCache;
65
+ }
66
+
67
+ export function resetCache() {
68
+ _profileCache = null;
69
+ }
70
+
71
+ export function resolveString(template, options = {}) {
72
+ if (typeof template !== "string" || !template.includes("${")) return template;
73
+ const profile = options.profile || loadProfile();
74
+ return template.replace(TOKEN_PATTERN, (match, name) =>
75
+ resolveToken(name, match, profile)
76
+ );
77
+ }
78
+
79
+ export function resolveFile(srcPath, dstPath, options = {}) {
80
+ const content = readFileSync(srcPath, "utf8");
81
+ const resolved = resolveString(content, options);
82
+ writeFileSync(dstPath, resolved, "utf8");
83
+ }
84
+
85
+ function resolveToken(name, original, profile) {
86
+ if (name === "HOME") return homedir();
87
+ if (name === "GIT_HOST") {
88
+ return envOr("ARKAOS_GIT_HOST", DEFAULT_GIT_HOST);
89
+ }
90
+ if (name === "VAULT_PATH") {
91
+ const envValue = envOr("ARKAOS_VAULT_PATH", envOr("ARKAOS_VAULT", ""));
92
+ if (envValue) return envValue;
93
+ return profile?.vaultPath || original;
94
+ }
95
+ if (name === "ARKA_OS_REPOS") {
96
+ const envValue = envOr("ARKAOS_REPOS_ROOT", "");
97
+ if (envValue) return envValue;
98
+ return profile?.reposRoot || original;
99
+ }
100
+ if (name === "PROJECT_ROOTS") {
101
+ const envValue = envOr("ARKAOS_PROJECT_ROOTS", "");
102
+ if (envValue) return envValue;
103
+ const roots = profile?.projectRoots || DEFAULT_PROJECT_ROOTS;
104
+ const sep = process.platform === "win32" ? ";" : ":";
105
+ return roots.join(sep);
106
+ }
107
+ return original;
108
+ }
109
+
110
+ function envOr(name, fallback) {
111
+ const value = process.env[name];
112
+ return value && value.length > 0 ? value : fallback;
113
+ }
114
+
115
+ function deriveProjectRoots(projectsDirText) {
116
+ if (!projectsDirText) return [...DEFAULT_PROJECT_ROOTS];
117
+ const posix = /(?:\/Users|\/home)\/\S+?\/(?:Herd|Work|AIProjects|code|repos)/g;
118
+ const windows = /[A-Z]:\\Users\\[^\s\\]+\\(?:Herd|Work|AIProjects|code|repos)/g;
119
+ const found = [
120
+ ...(projectsDirText.match(posix) || []),
121
+ ...(projectsDirText.match(windows) || []),
122
+ ];
123
+ return found.length > 0 ? found : [...DEFAULT_PROJECT_ROOTS];
124
+ }
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Cross-OS system-tool guarantor for the ArkaOS installer.
3
+ *
4
+ * Validates that the three external dependencies ArkaOS needs at runtime
5
+ * — Obsidian, Node.js ≥ 20, Python ≥ 3.12 — are present. Tools that can
6
+ * be installed without sudo (brew on macOS, winget on Windows) are
7
+ * installed automatically. Tools that need sudo (apt/snap on Linux) get
8
+ * a copy-paste command surfaced via `sudoCommands`.
9
+ *
10
+ * Idempotent: tools already at an acceptable version are no-ops.
11
+ *
12
+ * Spec: core/specs/SPEC-installer-cross-os.md (PR2 v2.24.0).
13
+ */
14
+
15
+ import { execSync } from "node:child_process";
16
+ import { existsSync } from "node:fs";
17
+ import { platform } from "node:os";
18
+ import { IS_WINDOWS, CMD_FINDER } from "./platform.js";
19
+ import {
20
+ buildInstallCommand,
21
+ detectAllPackageManagers,
22
+ detectPackageManager,
23
+ installViaPackageManager,
24
+ managerNeedsSudo,
25
+ } from "./package-manager.js";
26
+
27
+ const NODE_MIN_MAJOR = 20;
28
+ const PYTHON_MIN = { major: 3, minor: 12 };
29
+
30
+ const OBSIDIAN_PACKAGE = {
31
+ brew: "cask:obsidian",
32
+ snap: "obsidian:classic",
33
+ winget: "Obsidian.Obsidian",
34
+ choco: "obsidian",
35
+ };
36
+
37
+ const NODE_PACKAGE = {
38
+ brew: "node",
39
+ apt: "nodejs",
40
+ winget: "OpenJS.NodeJS",
41
+ choco: "nodejs",
42
+ };
43
+
44
+ const OBSIDIAN_FALLBACK_URL = "https://obsidian.md/download";
45
+ const NODE_FALLBACK_URL = "https://nodejs.org/en/download";
46
+ const PYTHON_FALLBACK_URL = "https://www.python.org/downloads/";
47
+
48
+ /**
49
+ * Validate every required tool, install what can be installed without
50
+ * sudo, and collect copy-paste commands for the ones that can't.
51
+ */
52
+ export function ensureSystemTools(options = {}) {
53
+ if (options.skipSystem) {
54
+ return {
55
+ skipped: true,
56
+ obsidian: null,
57
+ node: null,
58
+ python: null,
59
+ sudoCommands: [],
60
+ };
61
+ }
62
+
63
+ const obsidian = ensureTool("obsidian", checkObsidian, OBSIDIAN_PACKAGE, OBSIDIAN_FALLBACK_URL, options);
64
+ const node = ensureTool("node", checkNode, NODE_PACKAGE, NODE_FALLBACK_URL, options);
65
+ const python = checkPython(); // never auto-install Python — leave to OS
66
+
67
+ const sudoCommands = [obsidian, node]
68
+ .filter((t) => t?.needsSudo && t.suggestedCommand)
69
+ .map((t) => t.suggestedCommand);
70
+
71
+ if (python.needsAction !== "none" && python.suggestedCommand) {
72
+ sudoCommands.push(python.suggestedCommand);
73
+ }
74
+
75
+ return { skipped: false, obsidian, node, python, sudoCommands };
76
+ }
77
+
78
+ /**
79
+ * Cheap presence + version check for Obsidian. Looks for the binary
80
+ * via OS-native finder and falls back to known install paths.
81
+ */
82
+ export function checkObsidian() {
83
+ const location = findBinary("obsidian") || findObsidianAppPath();
84
+ if (location) {
85
+ return {
86
+ name: "obsidian",
87
+ installed: true,
88
+ location,
89
+ needsAction: "none",
90
+ };
91
+ }
92
+ const suggested = buildSuggestedInstall(OBSIDIAN_PACKAGE, OBSIDIAN_FALLBACK_URL);
93
+ return {
94
+ name: "obsidian",
95
+ installed: false,
96
+ needsAction: "install",
97
+ suggestedCommand: suggested.command,
98
+ needsSudo: suggested.needsSudo,
99
+ fallbackUrl: suggested.fallbackUrl,
100
+ };
101
+ }
102
+
103
+ export function checkNode() {
104
+ const location = findBinary("node");
105
+ if (!location) {
106
+ const suggested = buildSuggestedInstall(NODE_PACKAGE, NODE_FALLBACK_URL);
107
+ return {
108
+ name: "node",
109
+ installed: false,
110
+ needsAction: "install",
111
+ suggestedCommand: suggested.command,
112
+ needsSudo: suggested.needsSudo,
113
+ fallbackUrl: suggested.fallbackUrl,
114
+ };
115
+ }
116
+ const version = readNodeVersion();
117
+ const major = version ? Number(version.split(".")[0]) : 0;
118
+ if (major >= NODE_MIN_MAJOR) {
119
+ return { name: "node", installed: true, location, version, needsAction: "none" };
120
+ }
121
+ const suggested = buildSuggestedInstall(NODE_PACKAGE, NODE_FALLBACK_URL);
122
+ return {
123
+ name: "node",
124
+ installed: true,
125
+ location,
126
+ version,
127
+ needsAction: "upgrade",
128
+ suggestedCommand: suggested.command,
129
+ needsSudo: suggested.needsSudo,
130
+ fallbackUrl: suggested.fallbackUrl,
131
+ };
132
+ }
133
+
134
+ export function checkPython() {
135
+ const candidates = IS_WINDOWS ? ["python", "py"] : ["python3", "python3.12", "python3.13"];
136
+ for (const cmd of candidates) {
137
+ const location = findBinary(cmd);
138
+ if (!location) continue;
139
+ const version = readVersion(`${cmd} --version`);
140
+ const ok = versionAtLeast(version, PYTHON_MIN.major, PYTHON_MIN.minor);
141
+ if (ok) {
142
+ return { name: "python", installed: true, location, version, needsAction: "none" };
143
+ }
144
+ }
145
+ return {
146
+ name: "python",
147
+ installed: false,
148
+ needsAction: "install",
149
+ suggestedCommand: `Install Python ${PYTHON_MIN.major}.${PYTHON_MIN.minor}+ from ${PYTHON_FALLBACK_URL}`,
150
+ needsSudo: false,
151
+ fallbackUrl: PYTHON_FALLBACK_URL,
152
+ };
153
+ }
154
+
155
+ function ensureTool(name, checkFn, packageMap, fallbackUrl, options) {
156
+ const status = checkFn();
157
+ if (status.needsAction === "none") return status;
158
+
159
+ const manager = detectPackageManager();
160
+ if (!manager || !packageMap[manager]) {
161
+ return { ...status, fallbackUrl };
162
+ }
163
+
164
+ if (managerNeedsSudo(manager)) {
165
+ return {
166
+ ...status,
167
+ suggestedCommand: buildInstallCommand(manager, packageMap[manager]),
168
+ needsSudo: true,
169
+ };
170
+ }
171
+
172
+ if (options.dryRun) return status;
173
+
174
+ const result = installViaPackageManager(packageMap[manager], {
175
+ manager,
176
+ fallbackUrl,
177
+ });
178
+ if (result.installed) {
179
+ return { ...checkFn(), justInstalled: true };
180
+ }
181
+ return { ...status, error: result.error };
182
+ }
183
+
184
+ function buildSuggestedInstall(packageMap, fallbackUrl) {
185
+ const managers = detectAllPackageManagers();
186
+ for (const m of managers) {
187
+ if (packageMap[m]) {
188
+ return {
189
+ command: buildInstallCommand(m, packageMap[m]),
190
+ needsSudo: managerNeedsSudo(m),
191
+ fallbackUrl,
192
+ };
193
+ }
194
+ }
195
+ return { command: `Download from ${fallbackUrl}`, needsSudo: false, fallbackUrl };
196
+ }
197
+
198
+ function findBinary(name) {
199
+ try {
200
+ const out = execSync(`${CMD_FINDER} ${name}`, {
201
+ stdio: ["ignore", "pipe", "ignore"],
202
+ }).toString().trim().split(/\r?\n/)[0];
203
+ return out || null;
204
+ } catch {
205
+ return null;
206
+ }
207
+ }
208
+
209
+ function findObsidianAppPath() {
210
+ if (platform() === "darwin") {
211
+ const mac = "/Applications/Obsidian.app";
212
+ if (existsSync(mac)) return mac;
213
+ }
214
+ if (IS_WINDOWS) {
215
+ const localApp = process.env.LOCALAPPDATA;
216
+ if (localApp) {
217
+ const candidate = `${localApp}\\Obsidian\\Obsidian.exe`;
218
+ if (existsSync(candidate)) return candidate;
219
+ }
220
+ }
221
+ return null;
222
+ }
223
+
224
+ function readNodeVersion() {
225
+ return readVersion("node --version");
226
+ }
227
+
228
+ function readVersion(command) {
229
+ try {
230
+ const out = execSync(command, { stdio: ["ignore", "pipe", "ignore"] }).toString();
231
+ const match = out.match(/(\d+\.\d+(?:\.\d+)?)/);
232
+ return match ? match[1] : null;
233
+ } catch {
234
+ return null;
235
+ }
236
+ }
237
+
238
+ function versionAtLeast(version, major, minor) {
239
+ if (!version) return false;
240
+ const parts = version.split(".").map((n) => Number(n) || 0);
241
+ if (parts[0] !== major) return parts[0] > major;
242
+ return parts[1] >= minor;
243
+ }
@@ -6,6 +6,7 @@ import { ensureVenv, getArkaosPython, pipInstall } from "./python-resolver.js";
6
6
  import { getRuntimeConfig } from "./detect-runtime.js";
7
7
  import { loadAdapter } from "./index.js";
8
8
  import { migrateUserData, printMigrationReport } from "./migrate-user-data.js";
9
+ import { resolveFile } from "./path-resolver.js";
9
10
  import { IS_WINDOWS, HOOK_EXT } from "./platform.js";
10
11
  import { fileURLToPath } from "node:url";
11
12
 
@@ -277,7 +278,7 @@ export async function update() {
277
278
  const skillDest = join(skillsBase, "arka");
278
279
  mkdirSync(skillDest, { recursive: true });
279
280
  if (existsSync(skillSrc)) {
280
- copyFileSync(skillSrc, join(skillDest, "SKILL.md"));
281
+ safeResolveMarkdown(skillSrc, join(skillDest, "SKILL.md"));
281
282
  writeFileSync(join(skillDest, ".repo-path"), ARKAOS_ROOT);
282
283
  writeFileSync(join(skillDest, "VERSION"), VERSION);
283
284
  console.log(" ✓ /arka skill updated");
@@ -309,7 +310,7 @@ export async function update() {
309
310
  if (!existsSync(md)) return false;
310
311
  const dest = join(skillsBase, arkaName);
311
312
  mkdirSync(dest, { recursive: true });
312
- copyFileSync(md, join(dest, "SKILL.md"));
313
+ safeResolveMarkdown(md, join(dest, "SKILL.md"));
313
314
  copyResources(src, dest);
314
315
  return true;
315
316
  };
@@ -476,6 +477,20 @@ function ensureDir(dir) {
476
477
  }
477
478
  }
478
479
 
480
+ /**
481
+ * Copy a markdown file, substituting ${VAR} tokens from path-resolver when
482
+ * a profile.json is available. Falls back to plain copy on any error so a
483
+ * missing profile during first install never blocks deployment of the file
484
+ * itself; the agent / Python runtime will resolve later when profile lands.
485
+ */
486
+ function safeResolveMarkdown(src, dst) {
487
+ try {
488
+ resolveFile(src, dst);
489
+ } catch {
490
+ copyFileSync(src, dst);
491
+ }
492
+ }
493
+
479
494
  /**
480
495
  * Copy v2 hook state files from the legacy ~/.arka-os/ directory into
481
496
  * the canonical ~/.arkaos/ runtime directory. Safe to run every update:
@@ -563,7 +578,13 @@ function updateCognitiveScheduler(installDir, arkaosRoot) {
563
578
  const promptsSrc = join(arkaosRoot, "config", "cognition", "prompts");
564
579
  if (existsSync(promptsSrc)) {
565
580
  for (const f of readdirSync(promptsSrc)) {
566
- copyFileSync(join(promptsSrc, f), join(promptsDir, f));
581
+ const src = join(promptsSrc, f);
582
+ const dst = join(promptsDir, f);
583
+ if (f.endsWith(".md")) {
584
+ safeResolveMarkdown(src, dst);
585
+ } else {
586
+ copyFileSync(src, dst);
587
+ }
567
588
  }
568
589
  console.log(" \u2713 Cognitive prompts updated");
569
590
  }
@@ -3,7 +3,7 @@
3
3
  "description": "ARKA OS — Obsidian Vault Configuration",
4
4
  "notes": "ALL department output goes to this Obsidian vault. Conventions match existing vault format."
5
5
  },
6
- "vault_path": "/Users/andreagroferreira/Documents/Personal",
6
+ "vault_path": "${VAULT_PATH}",
7
7
  "paths": {
8
8
  "personas": "Personas",
9
9
  "sources_videos": "Sources/Videos",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arkaos",
3
- "version": "2.22.1",
3
+ "version": "2.28.0",
4
4
  "description": "The Operating System for AI Agent Teams",
5
5
  "type": "module",
6
6
  "bin": {
package/pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "arkaos-core"
3
- version = "2.22.1"
3
+ version = "2.28.0"
4
4
  description = "Core engine for ArkaOS — The Operating System for AI Agent Teams"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}