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
package/installer/index.js
CHANGED
|
@@ -6,7 +6,9 @@ import { fileURLToPath } from "node:url";
|
|
|
6
6
|
import { getRuntimeConfig } from "./detect-runtime.js";
|
|
7
7
|
import { findSystemPython, ensureVenv, getArkaosPython, getArkaosPip, pipInstall } from "./python-resolver.js";
|
|
8
8
|
import { IS_WINDOWS, HOOK_EXT } from "./platform.js";
|
|
9
|
-
import { copyHookLib } from "./hook-lib.js";
|
|
9
|
+
import { copyHookLib, copyHookAssets } from "./hook-lib.js";
|
|
10
|
+
import { deploySkills } from "./skill-deploy.js";
|
|
11
|
+
import { resolveSkillsMode } from "./skills-mode.js";
|
|
10
12
|
import { deployCoreSnapshot } from "./core-snapshot.js";
|
|
11
13
|
|
|
12
14
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -677,6 +679,8 @@ function installHooks(installDir) {
|
|
|
677
679
|
"cwd-changed",
|
|
678
680
|
"pre-tool-use",
|
|
679
681
|
"stop",
|
|
682
|
+
"subagent-stop",
|
|
683
|
+
"session-end",
|
|
680
684
|
];
|
|
681
685
|
const hookExt = HOOK_EXT;
|
|
682
686
|
|
|
@@ -712,171 +716,51 @@ function installHooks(installDir) {
|
|
|
712
716
|
if (copyHookLib(srcHooksDir, hooksDir)) {
|
|
713
717
|
ok("Hook lib: _lib/");
|
|
714
718
|
}
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
// Safe directory iteration. Returns an empty array instead of throwing
|
|
718
|
-
// when the target does not exist, which lets us skip missing sources
|
|
719
|
-
// (fresh repo checkouts may omit optional department resource dirs).
|
|
720
|
-
function listSubdirs(parent) {
|
|
721
|
-
if (!existsSync(parent)) return [];
|
|
722
|
-
try {
|
|
723
|
-
return readdirSync(parent, { withFileTypes: true })
|
|
724
|
-
.filter((e) => e.isDirectory())
|
|
725
|
-
.map((e) => e.name);
|
|
726
|
-
} catch {
|
|
727
|
-
return [];
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
719
|
|
|
731
|
-
//
|
|
732
|
-
//
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
function copySkillResources(skillSrcDir, skillDestDir) {
|
|
737
|
-
const resources = ["scripts", "references", "assets"];
|
|
738
|
-
for (const res of resources) {
|
|
739
|
-
const src = join(skillSrcDir, res);
|
|
740
|
-
if (!existsSync(src)) continue;
|
|
741
|
-
try {
|
|
742
|
-
cpSync(src, join(skillDestDir, res), { recursive: true });
|
|
743
|
-
} catch {
|
|
744
|
-
// Best-effort — a single missing resource dir shouldn't break install.
|
|
745
|
-
}
|
|
720
|
+
// F2-6 fast-path shims + gate manifest (shared deploy — the asset
|
|
721
|
+
// list lives in hook-lib.js so this path and update.js cannot drift).
|
|
722
|
+
const assetCount = copyHookAssets(srcHooksDir, hooksDir);
|
|
723
|
+
if (assetCount > 0) {
|
|
724
|
+
ok(`Hook fast-path assets: ${assetCount}`);
|
|
746
725
|
}
|
|
747
726
|
}
|
|
748
727
|
|
|
749
|
-
// Deploy one skill directory as a top-level `arka-<name>/` under the
|
|
750
|
-
// Claude Code skills base. Mirrors install.sh lines 396-405 and 414-425.
|
|
751
|
-
// Returns true if SKILL.md was deployed, false otherwise.
|
|
752
|
-
function deployTopLevelSkill(skillSrcDir, arkaName, skillsBase) {
|
|
753
|
-
const skillMd = join(skillSrcDir, "SKILL.md");
|
|
754
|
-
if (!existsSync(skillMd)) return false;
|
|
755
|
-
const dest = join(skillsBase, arkaName);
|
|
756
|
-
ensureDir(dest);
|
|
757
|
-
copyFileSync(skillMd, join(dest, "SKILL.md"));
|
|
758
|
-
copySkillResources(skillSrcDir, dest);
|
|
759
|
-
return true;
|
|
760
|
-
}
|
|
761
|
-
|
|
762
728
|
function installSkill(config, installDir) {
|
|
763
|
-
//
|
|
764
|
-
|
|
729
|
+
// Single shared deployment (installer/skill-deploy.js) — main /arka
|
|
730
|
+
// skill + nested reference bundle + department hubs + sub-skills +
|
|
731
|
+
// meta skills + agent personas. update.js §6 calls the SAME function,
|
|
732
|
+
// so install and update can no longer drift (F2-7c-pre: update-only
|
|
733
|
+
// machines were missing the 14 meta skills, including arka-flow).
|
|
765
734
|
const skillsBase = config.skillsDir || join(homedir(), ".claude", "skills");
|
|
766
735
|
const skillDest = join(skillsBase, "arka");
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
ensureDir(nestedSkillsOut);
|
|
781
|
-
try {
|
|
782
|
-
cpSync(nestedSkillsSrc, nestedSkillsOut, { recursive: true });
|
|
783
|
-
} catch {
|
|
784
|
-
// Silent — these are bundled references, not user-facing skills.
|
|
785
|
-
}
|
|
786
|
-
}
|
|
736
|
+
// Fresh installs default to the curated cut (F2-7c); a machine that
|
|
737
|
+
// already chose a mode keeps it (install --force over an existing
|
|
738
|
+
// setup must not flip the operator's choice).
|
|
739
|
+
const skillsMode = resolveSkillsMode({ fresh: true });
|
|
740
|
+
const skillCounts = deploySkills({
|
|
741
|
+
repoRoot: ARKAOS_ROOT,
|
|
742
|
+
skillsBase,
|
|
743
|
+
agentsBase: join(homedir(), ".claude", "agents"),
|
|
744
|
+
version: VERSION,
|
|
745
|
+
mode: skillsMode.mode,
|
|
746
|
+
});
|
|
747
|
+
ok(`skill set mode: ${skillsMode.mode}`);
|
|
748
|
+
if (skillCounts.main) {
|
|
787
749
|
ok("/arka skill installed");
|
|
788
750
|
} else {
|
|
789
751
|
warn("arka/SKILL.md not found in package");
|
|
790
752
|
}
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
// For each `departments/<dept>/SKILL.md`, deploy as top-level
|
|
794
|
-
// `~/.claude/skills/arka-<dept>/`. Mirrors install.sh:391-406, but
|
|
795
|
-
// iterates the source directory dynamically instead of hardcoding a
|
|
796
|
-
// department list (the bash list is stale — ships 8 of the 18 real
|
|
797
|
-
// departments on disk, so half are silently skipped on macOS/Linux
|
|
798
|
-
// too until a user edits install.sh).
|
|
799
|
-
const deptRoot = join(ARKAOS_ROOT, "departments");
|
|
800
|
-
let deptDeployed = 0;
|
|
801
|
-
for (const dept of listSubdirs(deptRoot)) {
|
|
802
|
-
if (deployTopLevelSkill(join(deptRoot, dept), `arka-${dept}`, skillsBase)) {
|
|
803
|
-
deptDeployed++;
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
if (deptDeployed > 0) {
|
|
807
|
-
ok(`${deptDeployed} department skills installed (/arka-<dept>)`);
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
// ── Sub-skills (departments/*/skills/*) ─────────────────────────────
|
|
811
|
-
// For each `departments/<dept>/skills/<skill>/SKILL.md`, deploy as
|
|
812
|
-
// top-level `~/.claude/skills/arka-<skill>/`. Mirrors install.sh:411-425.
|
|
813
|
-
// Collisions between departments (two depts defining the same
|
|
814
|
-
// sub-skill name) are resolved by "later wins", same semantics as
|
|
815
|
-
// the bash loop — in practice the repo has no collisions today.
|
|
816
|
-
let subSkillDeployed = 0;
|
|
817
|
-
for (const dept of listSubdirs(deptRoot)) {
|
|
818
|
-
const deptSkillsDir = join(deptRoot, dept, "skills");
|
|
819
|
-
for (const subSkill of listSubdirs(deptSkillsDir)) {
|
|
820
|
-
const subSkillSrc = join(deptSkillsDir, subSkill);
|
|
821
|
-
if (deployTopLevelSkill(subSkillSrc, `arka-${subSkill}`, skillsBase)) {
|
|
822
|
-
subSkillDeployed++;
|
|
823
|
-
}
|
|
824
|
-
}
|
|
753
|
+
if (skillCounts.depts > 0) {
|
|
754
|
+
ok(`${skillCounts.depts} department skills installed (/arka-<dept>)`);
|
|
825
755
|
}
|
|
826
|
-
if (
|
|
827
|
-
ok(`${
|
|
756
|
+
if (skillCounts.subs > 0) {
|
|
757
|
+
ok(`${skillCounts.subs} sub-skills installed (/arka-<skill>)`);
|
|
828
758
|
}
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
// For each `arka/skills/<skill>/SKILL.md`, deploy as top-level
|
|
832
|
-
// `~/.claude/skills/arka-<skill>/`. These are cross-cutting meta
|
|
833
|
-
// orchestrators (e.g. forge, platform-arka) that don't belong to a
|
|
834
|
-
// single department. They ALSO remain copied as bundled references
|
|
835
|
-
// under `~/.claude/skills/arka/skills/` by the nested block above —
|
|
836
|
-
// this additional deployment makes them invocable as `arka-<skill>`.
|
|
837
|
-
const arkaSkillsDir = join(ARKAOS_ROOT, "arka", "skills");
|
|
838
|
-
let metaSkillDeployed = 0;
|
|
839
|
-
for (const metaSkill of listSubdirs(arkaSkillsDir)) {
|
|
840
|
-
const metaSkillSrc = join(arkaSkillsDir, metaSkill);
|
|
841
|
-
if (deployTopLevelSkill(metaSkillSrc, `arka-${metaSkill}`, skillsBase)) {
|
|
842
|
-
metaSkillDeployed++;
|
|
843
|
-
}
|
|
759
|
+
if (skillCounts.meta > 0) {
|
|
760
|
+
ok(`${skillCounts.meta} meta skills installed (/arka-<skill>)`);
|
|
844
761
|
}
|
|
845
|
-
if (
|
|
846
|
-
ok(`${
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
// ── Agent personas ──────────────────────────────────────────────────
|
|
850
|
-
// For each `departments/<dept>/agents/<name>.md`, deploy to
|
|
851
|
-
// `~/.claude/agents/arka-<name>.md`. Mirrors install.sh:436-443.
|
|
852
|
-
// The agents dir parallels skillsBase — Claude Code reads it
|
|
853
|
-
// separately from the skills tree.
|
|
854
|
-
const agentsBase = join(homedir(), ".claude", "agents");
|
|
855
|
-
ensureDir(agentsBase);
|
|
856
|
-
let agentDeployed = 0;
|
|
857
|
-
for (const dept of listSubdirs(deptRoot)) {
|
|
858
|
-
const agentsSrc = join(deptRoot, dept, "agents");
|
|
859
|
-
if (!existsSync(agentsSrc)) continue;
|
|
860
|
-
try {
|
|
861
|
-
for (const agentFile of readdirSync(agentsSrc)) {
|
|
862
|
-
if (!agentFile.endsWith(".md")) continue;
|
|
863
|
-
const srcFile = join(agentsSrc, agentFile);
|
|
864
|
-
// Safety: don't deploy anything that's not a regular file.
|
|
865
|
-
try {
|
|
866
|
-
if (!statSync(srcFile).isFile()) continue;
|
|
867
|
-
} catch {
|
|
868
|
-
continue;
|
|
869
|
-
}
|
|
870
|
-
const baseName = agentFile.replace(/\.md$/, "");
|
|
871
|
-
copyFileSync(srcFile, join(agentsBase, `arka-${baseName}.md`));
|
|
872
|
-
agentDeployed++;
|
|
873
|
-
}
|
|
874
|
-
} catch {
|
|
875
|
-
// Silently skip departments with unreadable agents dirs.
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
if (agentDeployed > 0) {
|
|
879
|
-
ok(`${agentDeployed} agent personas installed (~/.claude/agents/arka-*.md)`);
|
|
762
|
+
if (skillCounts.agents > 0) {
|
|
763
|
+
ok(`${skillCounts.agents} agent personas installed (~/.claude/agents/arka-*.md)`);
|
|
880
764
|
}
|
|
881
765
|
|
|
882
766
|
// ── MCP infrastructure ──────────────────────────────────────────────
|
|
@@ -922,7 +806,18 @@ function installSkill(config, installDir) {
|
|
|
922
806
|
}
|
|
923
807
|
}
|
|
924
808
|
|
|
925
|
-
|
|
809
|
+
// Deploy arka-tools server to mcp-tools/ (F2-3: programmatic tools)
|
|
810
|
+
const mcpToolsSrc = join(mcpsSrc, "arka-tools");
|
|
811
|
+
const mcpToolsDest = join(skillsBase, "arka", "mcp-tools");
|
|
812
|
+
if (existsSync(mcpToolsSrc)) {
|
|
813
|
+
ensureDir(mcpToolsDest);
|
|
814
|
+
for (const f of ["server.py", "pyproject.toml"]) {
|
|
815
|
+
const src = join(mcpToolsSrc, f);
|
|
816
|
+
if (existsSync(src)) copyFileSync(src, join(mcpToolsDest, f));
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
ok("MCP infrastructure deployed (profiles, stacks, scripts, arka-prompts + arka-tools servers)");
|
|
926
821
|
|
|
927
822
|
// Higgsfield MCP is registered but requires an account + API key to connect.
|
|
928
823
|
// Non-blocking warning: the install succeeds even without a Higgsfield account.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
|
|
6
|
+
import { getArkaosPython, getRepoRoot } from "./python-resolver.js";
|
|
7
|
+
|
|
8
|
+
// F2-7a — `npx arkaos mcp start`: run the arka-tools MCP server (stdio)
|
|
9
|
+
// outside Claude Code (other runtimes, debugging, CI smoke tests).
|
|
10
|
+
//
|
|
11
|
+
// Resolution order mirrors production first: mcps/registry.json runs the
|
|
12
|
+
// server via `uv --directory <toolsDir> run server.py`, so when uv is on
|
|
13
|
+
// PATH we replicate that exact invocation. Fallback is the ArkaOS venv
|
|
14
|
+
// python — but NO installer pip path installs `mcp[cli]` (the pyproject
|
|
15
|
+
// [mcp] extra exists for tests only), so the fallback preflights
|
|
16
|
+
// `import mcp` and fails HONESTLY with both remediations instead of a
|
|
17
|
+
// stack trace at first tool call.
|
|
18
|
+
|
|
19
|
+
export function toolsDir(home = homedir()) {
|
|
20
|
+
return join(home, ".claude", "skills", "arka", "mcp-tools");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function uvAvailable(spawn = spawnSync) {
|
|
24
|
+
const probe = spawn("uv", ["--version"], { stdio: "ignore", timeout: 5000 });
|
|
25
|
+
return !probe.error && probe.status === 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function venvHasMcp(py, spawn = spawnSync) {
|
|
29
|
+
const probe = spawn(py, ["-c", "import mcp"], {
|
|
30
|
+
stdio: "ignore",
|
|
31
|
+
timeout: 15000,
|
|
32
|
+
});
|
|
33
|
+
return !probe.error && probe.status === 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Resolve how to launch the arka-tools server.
|
|
38
|
+
* Returns {cmd, args, kind: "uv"|"venv"} or {error: string}.
|
|
39
|
+
* Injectable deps keep this unit-testable without real binaries.
|
|
40
|
+
*/
|
|
41
|
+
export function resolveRunner({
|
|
42
|
+
home = homedir(),
|
|
43
|
+
spawn = spawnSync,
|
|
44
|
+
pythonResolver = getArkaosPython,
|
|
45
|
+
} = {}) {
|
|
46
|
+
const dir = toolsDir(home);
|
|
47
|
+
if (!existsSync(join(dir, "server.py"))) {
|
|
48
|
+
return {
|
|
49
|
+
error:
|
|
50
|
+
"arka-tools is not deployed (missing " + join(dir, "server.py") +
|
|
51
|
+
").\nRun: npx arkaos install",
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
if (uvAvailable(spawn)) {
|
|
55
|
+
// Exact replica of the mcps/registry.json invocation Claude Code uses.
|
|
56
|
+
return { kind: "uv", cmd: "uv", args: ["--directory", dir, "run", "server.py"] };
|
|
57
|
+
}
|
|
58
|
+
const py = pythonResolver();
|
|
59
|
+
if (!py) {
|
|
60
|
+
return { error: "No Python found. Run: npx arkaos install" };
|
|
61
|
+
}
|
|
62
|
+
if (!venvHasMcp(py, spawn)) {
|
|
63
|
+
return {
|
|
64
|
+
error:
|
|
65
|
+
"arka-tools needs the MCP SDK and neither runner is ready. Fix one of:\n" +
|
|
66
|
+
" 1. Install uv (recommended): https://docs.astral.sh/uv\n" +
|
|
67
|
+
" 2. ~/.arkaos/venv/bin/pip install 'mcp[cli]>=1.2.0'",
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return { kind: "venv", cmd: py, args: [join(dir, "server.py")] };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Start the server in the foreground (stdio transport — the MCP client
|
|
75
|
+
* owns the lifecycle; there is no daemon to manage). Returns the exit
|
|
76
|
+
* code to propagate.
|
|
77
|
+
*/
|
|
78
|
+
export function startServer({
|
|
79
|
+
write = false,
|
|
80
|
+
home = homedir(),
|
|
81
|
+
spawn = spawnSync,
|
|
82
|
+
pythonResolver = getArkaosPython,
|
|
83
|
+
repoRootResolver = getRepoRoot,
|
|
84
|
+
log = console.error,
|
|
85
|
+
} = {}) {
|
|
86
|
+
const runner = resolveRunner({ home, spawn, pythonResolver });
|
|
87
|
+
if (runner.error) {
|
|
88
|
+
log(runner.error);
|
|
89
|
+
return 1;
|
|
90
|
+
}
|
|
91
|
+
const env = { ...process.env };
|
|
92
|
+
// repoRootResolver walks .repo-path/install-manifest — NEVER anchor on
|
|
93
|
+
// the npx cache dir, which `npm cache clean` purges at any time.
|
|
94
|
+
// Injectable so tests assert the positive ARKA_OS path (QG 7a M1).
|
|
95
|
+
const repoRoot = repoRootResolver();
|
|
96
|
+
if (repoRoot) env.ARKA_OS = repoRoot;
|
|
97
|
+
if (write) {
|
|
98
|
+
env.ARKA_TOOLS_WRITE = "1"; // server.py requires the exact value "1"
|
|
99
|
+
}
|
|
100
|
+
log(`arka-tools MCP server starting (${runner.kind}, stdio)` +
|
|
101
|
+
(write ? " — WRITES ENABLED (ARKA_TOOLS_WRITE=1)" : " — read-only"));
|
|
102
|
+
const result = spawn(runner.cmd, runner.args, { stdio: "inherit", env });
|
|
103
|
+
if (result.error) {
|
|
104
|
+
log(`Failed to start: ${result.error.message}`);
|
|
105
|
+
return 1;
|
|
106
|
+
}
|
|
107
|
+
return result.status ?? 0;
|
|
108
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import {
|
|
2
|
+
copyFileSync, cpSync, existsSync, mkdirSync, readdirSync, readFileSync,
|
|
3
|
+
statSync, writeFileSync,
|
|
4
|
+
} from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
|
|
7
|
+
import { resolveFile } from "./path-resolver.js";
|
|
8
|
+
|
|
9
|
+
// Single skill-deployment implementation shared by the fresh-install
|
|
10
|
+
// path (index.js::installSkill) and the update path (update.js §6) —
|
|
11
|
+
// the same single-source pattern as hook-lib.js::copyHookLib, and for
|
|
12
|
+
// the same reason: the two loops HAD drifted. update.js §6 deployed
|
|
13
|
+
// hubs + sub-skills + agents but never the 14 meta skills
|
|
14
|
+
// (arka/skills/* — including arka-flow, the evidence-flow
|
|
15
|
+
// NON-NEGOTIABLE) nor the nested reference bundle, so any update-only
|
|
16
|
+
// machine was silently missing them (F2-7c-pre).
|
|
17
|
+
//
|
|
18
|
+
// Markdown semantics: safeResolveMarkdown everywhere — resolveFile
|
|
19
|
+
// substitutes ${VAR} path tokens and falls back to a raw copy when
|
|
20
|
+
// resolution fails. This is the update.js behavior; index.js used to
|
|
21
|
+
// raw-copy, so unifying on the resolving superset fixes install too.
|
|
22
|
+
|
|
23
|
+
function listSubdirs(parent) {
|
|
24
|
+
if (!existsSync(parent)) return [];
|
|
25
|
+
try {
|
|
26
|
+
return readdirSync(parent, { withFileTypes: true })
|
|
27
|
+
.filter((e) => e.isDirectory())
|
|
28
|
+
.map((e) => e.name);
|
|
29
|
+
} catch {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function safeResolveMarkdown(src, dst) {
|
|
35
|
+
try {
|
|
36
|
+
resolveFile(src, dst);
|
|
37
|
+
} catch {
|
|
38
|
+
copyFileSync(src, dst);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function copySkillResources(skillSrcDir, skillDestDir) {
|
|
43
|
+
for (const res of ["scripts", "references", "assets"]) {
|
|
44
|
+
const src = join(skillSrcDir, res);
|
|
45
|
+
if (!existsSync(src)) continue;
|
|
46
|
+
try {
|
|
47
|
+
cpSync(src, join(skillDestDir, res), { recursive: true });
|
|
48
|
+
} catch {
|
|
49
|
+
// Best-effort — a missing resource dir must not break deploy.
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function deployTopLevelSkill(skillSrcDir, arkaName, skillsBase) {
|
|
55
|
+
const skillMd = join(skillSrcDir, "SKILL.md");
|
|
56
|
+
if (!existsSync(skillMd)) return false;
|
|
57
|
+
const dest = join(skillsBase, arkaName);
|
|
58
|
+
mkdirSync(dest, { recursive: true });
|
|
59
|
+
safeResolveMarkdown(skillMd, join(dest, "SKILL.md"));
|
|
60
|
+
copySkillResources(skillSrcDir, dest);
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Deploy the full skill surface: main /arka skill (+ nested reference
|
|
66
|
+
* bundle), department hubs, sub-skills, meta skills and agent personas.
|
|
67
|
+
* Copy-only — never deletes anything already deployed.
|
|
68
|
+
*
|
|
69
|
+
* Returns per-category counts so both callers can keep their own log
|
|
70
|
+
* style. `version`/`repoRoot` stamp the main skill's VERSION and
|
|
71
|
+
* .repo-path exactly as both callers did before.
|
|
72
|
+
*/
|
|
73
|
+
export function deploySkills({
|
|
74
|
+
repoRoot,
|
|
75
|
+
skillsBase,
|
|
76
|
+
agentsBase = null,
|
|
77
|
+
version = "",
|
|
78
|
+
mode = "full",
|
|
79
|
+
log = () => {},
|
|
80
|
+
}) {
|
|
81
|
+
const counts = { main: 0, bundle: 0, depts: 0, subs: 0, meta: 0, agents: 0 };
|
|
82
|
+
// Curated mode (F2-7c): sub-skills outside the curated cut stay in
|
|
83
|
+
// the repo and ship via the marketplace plugins instead. The curated
|
|
84
|
+
// classification comes from the GENERATED knowledge/skills-manifest
|
|
85
|
+
// (drift-locked to config/skills-curated.yaml). Manifest unreadable
|
|
86
|
+
// -> deploy everything (fail-open: never silently shrink an install
|
|
87
|
+
// because a file was missing).
|
|
88
|
+
const curatedFilter = mode === "curated"
|
|
89
|
+
? loadCuratedFilter(repoRoot)
|
|
90
|
+
: null;
|
|
91
|
+
|
|
92
|
+
// ── Main /arka skill + nested reference bundle ──────────────────────
|
|
93
|
+
const skillSrc = join(repoRoot, "arka", "SKILL.md");
|
|
94
|
+
const skillDest = join(skillsBase, "arka");
|
|
95
|
+
mkdirSync(skillDest, { recursive: true });
|
|
96
|
+
if (existsSync(skillSrc)) {
|
|
97
|
+
safeResolveMarkdown(skillSrc, join(skillDest, "SKILL.md"));
|
|
98
|
+
writeFileSync(join(skillDest, ".repo-path"), repoRoot);
|
|
99
|
+
if (version) writeFileSync(join(skillDest, "VERSION"), version);
|
|
100
|
+
counts.main = 1;
|
|
101
|
+
const nestedSrc = join(repoRoot, "arka", "skills");
|
|
102
|
+
if (existsSync(nestedSrc)) {
|
|
103
|
+
try {
|
|
104
|
+
cpSync(nestedSrc, join(skillDest, "skills"), { recursive: true });
|
|
105
|
+
counts.bundle = 1;
|
|
106
|
+
} catch {
|
|
107
|
+
// Bundled references only — never fail the deploy over them.
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ── Department hubs + sub-skills ────────────────────────────────────
|
|
113
|
+
const deptRoot = join(repoRoot, "departments");
|
|
114
|
+
for (const dept of listSubdirs(deptRoot)) {
|
|
115
|
+
if (deployTopLevelSkill(join(deptRoot, dept), `arka-${dept}`, skillsBase)) {
|
|
116
|
+
counts.depts++;
|
|
117
|
+
}
|
|
118
|
+
const deptSkillsDir = join(deptRoot, dept, "skills");
|
|
119
|
+
for (const sub of listSubdirs(deptSkillsDir)) {
|
|
120
|
+
if (curatedFilter && !curatedFilter.has(sub)) continue;
|
|
121
|
+
if (deployTopLevelSkill(
|
|
122
|
+
join(deptSkillsDir, sub), `arka-${sub}`, skillsBase)) {
|
|
123
|
+
counts.subs++;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ── Meta skills (arka/skills/* as invocable top-level arka-<skill>) ─
|
|
129
|
+
const metaRoot = join(repoRoot, "arka", "skills");
|
|
130
|
+
for (const meta of listSubdirs(metaRoot)) {
|
|
131
|
+
if (deployTopLevelSkill(join(metaRoot, meta), `arka-${meta}`, skillsBase)) {
|
|
132
|
+
counts.meta++;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ── Agent personas ──────────────────────────────────────────────────
|
|
137
|
+
if (agentsBase) {
|
|
138
|
+
counts.agents = deployAgents(deptRoot, agentsBase);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
log(counts);
|
|
142
|
+
return counts;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function loadCuratedFilter(repoRoot) {
|
|
146
|
+
try {
|
|
147
|
+
const manifest = JSON.parse(readFileSync(
|
|
148
|
+
join(repoRoot, "knowledge", "skills-manifest.json"), "utf-8"));
|
|
149
|
+
const curated = new Set();
|
|
150
|
+
for (const [slug, entry] of Object.entries(manifest.skills || {})) {
|
|
151
|
+
if (entry.curated) curated.add(slug);
|
|
152
|
+
}
|
|
153
|
+
return curated.size > 0 ? curated : null;
|
|
154
|
+
} catch {
|
|
155
|
+
return null; // fail-open: full deploy beats a silently empty one
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function deployAgents(deptRoot, agentsBase) {
|
|
160
|
+
mkdirSync(agentsBase, { recursive: true });
|
|
161
|
+
let deployed = 0;
|
|
162
|
+
for (const dept of listSubdirs(deptRoot)) {
|
|
163
|
+
const agentsSrc = join(deptRoot, dept, "agents");
|
|
164
|
+
if (!existsSync(agentsSrc)) continue;
|
|
165
|
+
try {
|
|
166
|
+
deployed += deployDeptAgents(agentsSrc, agentsBase);
|
|
167
|
+
} catch {
|
|
168
|
+
// Unreadable agents dir — skip the department, not the deploy.
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return deployed;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function deployDeptAgents(agentsSrc, agentsBase) {
|
|
175
|
+
let deployed = 0;
|
|
176
|
+
for (const file of readdirSync(agentsSrc)) {
|
|
177
|
+
if (!file.endsWith(".md")) continue;
|
|
178
|
+
const srcFile = join(agentsSrc, file);
|
|
179
|
+
try {
|
|
180
|
+
if (!statSync(srcFile).isFile()) continue;
|
|
181
|
+
} catch {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
copyFileSync(
|
|
185
|
+
srcFile, join(agentsBase, `arka-${file.replace(/\.md$/, "")}.md`));
|
|
186
|
+
deployed++;
|
|
187
|
+
}
|
|
188
|
+
return deployed;
|
|
189
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
|
|
5
|
+
// Persisted skills-deploy mode (F2-7c deprecation window, user-data
|
|
6
|
+
// pattern of v2.19): fresh installs default to the curated cut; update
|
|
7
|
+
// preserves whatever the machine already chose; machines predating the
|
|
8
|
+
// mode file stay on "full" WITH a deprecation notice — an update must
|
|
9
|
+
// never silently shrink an installed surface. Phase 2 (curated default
|
|
10
|
+
// for everyone + whitelist-only sweep) ships separately, gated on
|
|
11
|
+
// doctor telemetry about how many installs remain "full".
|
|
12
|
+
|
|
13
|
+
const MODE_FILE = () => join(homedir(), ".arkaos", "skills-mode.json");
|
|
14
|
+
const VALID_MODES = new Set(["curated", "full"]);
|
|
15
|
+
|
|
16
|
+
export function readSkillsMode() {
|
|
17
|
+
try {
|
|
18
|
+
const data = JSON.parse(readFileSync(MODE_FILE(), "utf-8"));
|
|
19
|
+
return VALID_MODES.has(data.mode) ? data.mode : null;
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function writeSkillsMode(mode) {
|
|
26
|
+
if (!VALID_MODES.has(mode)) return false;
|
|
27
|
+
try {
|
|
28
|
+
mkdirSync(join(homedir(), ".arkaos"), { recursive: true });
|
|
29
|
+
writeFileSync(MODE_FILE(), JSON.stringify({ mode }) + "\n");
|
|
30
|
+
return true;
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Resolve the mode for THIS run. `flag` is the --skills CLI value
|
|
38
|
+
* (persisted when valid); `fresh` marks a fresh install (no mode file
|
|
39
|
+
* -> curated). Returns { mode, deprecated } where `deprecated` flags a
|
|
40
|
+
* legacy full-mode machine that should see the migration notice.
|
|
41
|
+
*/
|
|
42
|
+
export function resolveSkillsMode({ flag = "", fresh = false } = {}) {
|
|
43
|
+
if (VALID_MODES.has(flag)) {
|
|
44
|
+
writeSkillsMode(flag);
|
|
45
|
+
return { mode: flag, deprecated: false };
|
|
46
|
+
}
|
|
47
|
+
const persisted = readSkillsMode();
|
|
48
|
+
if (persisted) return { mode: persisted, deprecated: false };
|
|
49
|
+
if (fresh) {
|
|
50
|
+
writeSkillsMode("curated");
|
|
51
|
+
return { mode: "curated", deprecated: false };
|
|
52
|
+
}
|
|
53
|
+
return { mode: "full", deprecated: true };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function deprecationNotice() {
|
|
57
|
+
return (
|
|
58
|
+
"[arka:deprecated] Full skill set (274) is deprecated; the default "
|
|
59
|
+
+ "becomes the curated 69 in a future release. Switch now: "
|
|
60
|
+
+ "npx arkaos update --skills curated (a la carte packs: "
|
|
61
|
+
+ "/plugin install arkaos-<dept>@arkaos). Opt out permanently: "
|
|
62
|
+
+ "npx arkaos update --skills full"
|
|
63
|
+
);
|
|
64
|
+
}
|