arkaos 2.11.0 → 2.13.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/config/hooks/post-tool-use.sh +65 -2
- package/config/hooks/session-start.sh +14 -0
- package/config/hooks/user-prompt-submit.sh +27 -1
- package/core/workflow/__init__.py +14 -1
- package/core/workflow/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/state.cpython-313.pyc +0 -0
- package/core/workflow/state.py +128 -0
- package/core/workflow/state_reader.sh +68 -0
- package/installer/index.js +46 -0
- package/installer/update.js +38 -0
- package/package.json +1 -1
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.
|
|
1
|
+
2.13.0
|
|
@@ -170,6 +170,65 @@ fi
|
|
|
170
170
|
|
|
171
171
|
) 200>"$LOCK_FILE"
|
|
172
172
|
|
|
173
|
+
# ─── Workflow Violation Detection ────────────────────────────────────────
|
|
174
|
+
VIOLATION_MSG=""
|
|
175
|
+
STATE_READER=""
|
|
176
|
+
[ -f "$HOME/.arkaos/.repo-path" ] && STATE_READER="$(cat "$HOME/.arkaos/.repo-path")/core/workflow/state_reader.sh"
|
|
177
|
+
|
|
178
|
+
if [ -n "$STATE_READER" ] && [ -f "$STATE_READER" ] && bash "$STATE_READER" active 2>/dev/null; then
|
|
179
|
+
ARKAOS_PY=""
|
|
180
|
+
[ -f "$HOME/.arkaos/venv/bin/python3" ] && ARKAOS_PY="$HOME/.arkaos/venv/bin/python3"
|
|
181
|
+
[ -z "$ARKAOS_PY" ] && [ -f "$HOME/.arkaos/.venv/bin/python3" ] && ARKAOS_PY="$HOME/.arkaos/.venv/bin/python3"
|
|
182
|
+
[ -z "$ARKAOS_PY" ] && ARKAOS_PY=$(command -v python3 2>/dev/null)
|
|
183
|
+
ARKAOS_ROOT=$(cat "$HOME/.arkaos/.repo-path" 2>/dev/null)
|
|
184
|
+
|
|
185
|
+
# Rule 1: Branch isolation — commit on master while workflow active
|
|
186
|
+
if [ "$TOOL_NAME" = "Bash" ]; then
|
|
187
|
+
if echo "$TOOL_OUTPUT" | grep -qE '^\[(master|main)' 2>/dev/null; then
|
|
188
|
+
CMD_TEXT=$(echo "$input" | jq -r '.command // ""' 2>/dev/null)
|
|
189
|
+
if echo "$CMD_TEXT" | grep -qE 'git commit'; then
|
|
190
|
+
[ -n "$ARKAOS_PY" ] && [ -n "$ARKAOS_ROOT" ] && \
|
|
191
|
+
PYTHONPATH="$ARKAOS_ROOT" $ARKAOS_PY -c "
|
|
192
|
+
from core.workflow.state import add_violation
|
|
193
|
+
add_violation('branch-isolation', 'Commit on master/main while workflow active', 'Bash')
|
|
194
|
+
" 2>/dev/null
|
|
195
|
+
VIOLATION_MSG="VIOLATION [branch-isolation]: Commit on master while workflow active. Use a feature branch."
|
|
196
|
+
fi
|
|
197
|
+
fi
|
|
198
|
+
fi
|
|
199
|
+
|
|
200
|
+
# Rule 2: Spec-driven — code edited without completed spec
|
|
201
|
+
if [ "$TOOL_NAME" = "Write" ] || [ "$TOOL_NAME" = "Edit" ]; then
|
|
202
|
+
FILE_PATH=$(echo "$input" | jq -r '.file_path // ""' 2>/dev/null)
|
|
203
|
+
if echo "$FILE_PATH" | grep -qE '\.(py|js|ts|vue|php|jsx|tsx)$'; then
|
|
204
|
+
if ! bash "$STATE_READER" check spec 2>/dev/null; then
|
|
205
|
+
[ -n "$ARKAOS_PY" ] && [ -n "$ARKAOS_ROOT" ] && \
|
|
206
|
+
PYTHONPATH="$ARKAOS_ROOT" _V_TOOL="$TOOL_NAME" _V_FILE="$FILE_PATH" $ARKAOS_PY -c "
|
|
207
|
+
import os; from core.workflow.state import add_violation
|
|
208
|
+
add_violation('spec-driven', 'Code edited without completed spec', os.environ['_V_TOOL'], os.environ['_V_FILE'])
|
|
209
|
+
" 2>/dev/null
|
|
210
|
+
VIOLATION_MSG="VIOLATION [spec-driven]: Code edited without completed spec ($FILE_PATH). Complete the spec phase first."
|
|
211
|
+
fi
|
|
212
|
+
fi
|
|
213
|
+
fi
|
|
214
|
+
|
|
215
|
+
# Rule 3: Sequential — implementation before planning
|
|
216
|
+
if [ -z "$VIOLATION_MSG" ] && { [ "$TOOL_NAME" = "Write" ] || [ "$TOOL_NAME" = "Edit" ]; }; then
|
|
217
|
+
FILE_PATH=$(echo "$input" | jq -r '.file_path // ""' 2>/dev/null)
|
|
218
|
+
if echo "$FILE_PATH" | grep -qE '\.(py|js|ts|vue|php|jsx|tsx)$'; then
|
|
219
|
+
IMPL_STATUS=$(bash "$STATE_READER" phase implementation 2>/dev/null)
|
|
220
|
+
if [ "$IMPL_STATUS" = "pending" ]; then
|
|
221
|
+
[ -n "$ARKAOS_PY" ] && [ -n "$ARKAOS_ROOT" ] && \
|
|
222
|
+
PYTHONPATH="$ARKAOS_ROOT" _V_TOOL="$TOOL_NAME" _V_FILE="$FILE_PATH" $ARKAOS_PY -c "
|
|
223
|
+
import os; from core.workflow.state import add_violation
|
|
224
|
+
add_violation('sequential-validation', 'Code written before implementation phase started', os.environ['_V_TOOL'], os.environ['_V_FILE'])
|
|
225
|
+
" 2>/dev/null
|
|
226
|
+
VIOLATION_MSG="VIOLATION [sequential-validation]: Implementation started before planning completed ($FILE_PATH)."
|
|
227
|
+
fi
|
|
228
|
+
fi
|
|
229
|
+
fi
|
|
230
|
+
fi
|
|
231
|
+
|
|
173
232
|
# ─── Log Metrics ─────────────────────────────────────────────────────────
|
|
174
233
|
_DURATION_MS=$(_hook_ms)
|
|
175
234
|
METRICS_FILE="$HOME/.arkaos/hook-metrics.json"
|
|
@@ -184,5 +243,9 @@ mkdir -p "$HOME/.arkaos"
|
|
|
184
243
|
"$METRICS_FILE" > "$METRICS_FILE.tmp" 2>/dev/null && mv "$METRICS_FILE.tmp" "$METRICS_FILE"
|
|
185
244
|
) 200>"$METRICS_LOCK" 2>/dev/null
|
|
186
245
|
|
|
187
|
-
#
|
|
188
|
-
|
|
246
|
+
# Output violation as context if detected, otherwise empty
|
|
247
|
+
if [ -n "$VIOLATION_MSG" ]; then
|
|
248
|
+
echo "{\"additionalContext\": \"$VIOLATION_MSG\"}"
|
|
249
|
+
else
|
|
250
|
+
echo '{}'
|
|
251
|
+
fi
|
|
@@ -49,6 +49,20 @@ MSG+="║ ║\\n"
|
|
|
49
49
|
MSG+="╚══════════════════════════════════════════════╝\\n"
|
|
50
50
|
MSG+="\\n"
|
|
51
51
|
MSG+="${GREETING}, ${NAME} (${COMPANY})\\n"
|
|
52
|
+
# ─── Active Workflow ──────────────────────────────────────────────────
|
|
53
|
+
STATE_READER="$REPO/core/workflow/state_reader.sh"
|
|
54
|
+
if [ -n "$REPO" ] && [ -f "$STATE_READER" ] && bash "$STATE_READER" active 2>/dev/null; then
|
|
55
|
+
WF_SUMMARY=$(bash "$STATE_READER" summary 2>/dev/null)
|
|
56
|
+
WF_NAME=$(echo "$WF_SUMMARY" | cut -d'|' -f1)
|
|
57
|
+
WF_PHASE=$(echo "$WF_SUMMARY" | cut -d'|' -f2)
|
|
58
|
+
WF_PROGRESS=$(echo "$WF_SUMMARY" | cut -d'|' -f3)
|
|
59
|
+
WF_BRANCH=$(echo "$WF_SUMMARY" | cut -d'|' -f4)
|
|
60
|
+
WF_VIOLATIONS=$(echo "$WF_SUMMARY" | cut -d'|' -f5)
|
|
61
|
+
MSG+="\\nWorkflow: ${WF_NAME} (${WF_PROGRESS})"
|
|
62
|
+
[ -n "$WF_BRANCH" ] && MSG+=" branch:${WF_BRANCH}"
|
|
63
|
+
[ "$WF_VIOLATIONS" != "0" ] && MSG+=" VIOLATIONS:${WF_VIOLATIONS}"
|
|
64
|
+
MSG+="\\n"
|
|
65
|
+
fi
|
|
52
66
|
MSG+="ArkaOS v${VERSION} | 65 agents | 17 departments | 244+ skills"
|
|
53
67
|
MSG+="${DRIFT}"
|
|
54
68
|
|
|
@@ -99,6 +99,19 @@ if command -v python3 &>/dev/null && [ -f "$BRIDGE_SCRIPT" ]; then
|
|
|
99
99
|
if [ -n "$bridge_output" ]; then
|
|
100
100
|
python_result=$(echo "$bridge_output" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('context_string',''))" 2>/dev/null)
|
|
101
101
|
fi
|
|
102
|
+
|
|
103
|
+
# Append workflow state to synapse context
|
|
104
|
+
_WF_READER="$ARKAOS_ROOT/core/workflow/state_reader.sh"
|
|
105
|
+
if [ -f "$_WF_READER" ] && bash "$_WF_READER" active 2>/dev/null; then
|
|
106
|
+
_WF_SUM=$(bash "$_WF_READER" summary 2>/dev/null)
|
|
107
|
+
_WF_N=$(echo "$_WF_SUM" | cut -d'|' -f1)
|
|
108
|
+
_WF_P=$(echo "$_WF_SUM" | cut -d'|' -f2)
|
|
109
|
+
_WF_B=$(echo "$_WF_SUM" | cut -d'|' -f4)
|
|
110
|
+
_WF_V=$(echo "$_WF_SUM" | cut -d'|' -f5)
|
|
111
|
+
_WF_TAG="[workflow:${_WF_N}] [phase:${_WF_P}] [branch:${_WF_B}] [violations:${_WF_V}]"
|
|
112
|
+
[ "$_WF_V" != "0" ] && _WF_TAG="WARNING: ${_WF_V} workflow violation(s). $_WF_TAG"
|
|
113
|
+
python_result="${python_result} ${_WF_TAG}"
|
|
114
|
+
fi
|
|
102
115
|
fi
|
|
103
116
|
|
|
104
117
|
# ─── Fallback: Bash-only context (if Python unavailable) ────────────────
|
|
@@ -130,7 +143,20 @@ if [ -z "$python_result" ]; then
|
|
|
130
143
|
L7="[time:evening]"
|
|
131
144
|
fi
|
|
132
145
|
|
|
133
|
-
|
|
146
|
+
# L8: Workflow state
|
|
147
|
+
L8=""
|
|
148
|
+
_WF_READER="$ARKAOS_ROOT/core/workflow/state_reader.sh"
|
|
149
|
+
if [ -f "$_WF_READER" ] && bash "$_WF_READER" active 2>/dev/null; then
|
|
150
|
+
_WF_SUM=$(bash "$_WF_READER" summary 2>/dev/null)
|
|
151
|
+
_WF_N=$(echo "$_WF_SUM" | cut -d'|' -f1)
|
|
152
|
+
_WF_P=$(echo "$_WF_SUM" | cut -d'|' -f2)
|
|
153
|
+
_WF_B=$(echo "$_WF_SUM" | cut -d'|' -f4)
|
|
154
|
+
_WF_V=$(echo "$_WF_SUM" | cut -d'|' -f5)
|
|
155
|
+
L8="[workflow:${_WF_N}] [phase:${_WF_P}] [branch:${_WF_B}] [violations:${_WF_V}]"
|
|
156
|
+
[ "$_WF_V" != "0" ] && L8="WARNING: ${_WF_V} workflow violation(s). $L8"
|
|
157
|
+
fi
|
|
158
|
+
|
|
159
|
+
python_result="$L0 $L4 $L7 $L8"
|
|
134
160
|
fi
|
|
135
161
|
|
|
136
162
|
# ─── Output ──────────────────────────────────────────────────────────────
|
|
@@ -6,5 +6,18 @@ Declarative workflows with phases, conditions, gates, and parallelization.
|
|
|
6
6
|
from core.workflow.schema import Workflow, Phase, Gate, PhaseStatus
|
|
7
7
|
from core.workflow.engine import WorkflowEngine
|
|
8
8
|
from core.workflow.loader import load_workflow
|
|
9
|
+
from core.workflow.state import (
|
|
10
|
+
init_workflow as init_workflow_state,
|
|
11
|
+
get_state as get_workflow_state,
|
|
12
|
+
update_phase,
|
|
13
|
+
set_branch,
|
|
14
|
+
add_violation,
|
|
15
|
+
is_phase_completed,
|
|
16
|
+
clear_workflow,
|
|
17
|
+
)
|
|
9
18
|
|
|
10
|
-
__all__ = [
|
|
19
|
+
__all__ = [
|
|
20
|
+
"Workflow", "Phase", "Gate", "PhaseStatus", "WorkflowEngine", "load_workflow",
|
|
21
|
+
"init_workflow_state", "get_workflow_state", "update_phase", "set_branch",
|
|
22
|
+
"add_violation", "is_phase_completed", "clear_workflow",
|
|
23
|
+
]
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Workflow state tracker for ArkaOS governance enforcement.
|
|
2
|
+
|
|
3
|
+
Manages a JSON state file that records workflow phases, branch, and violations.
|
|
4
|
+
Read by hooks and skills to detect and surface governance violations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import uuid
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from tempfile import NamedTemporaryFile
|
|
13
|
+
|
|
14
|
+
_VALID_STATUSES = ("pending", "in_progress", "completed", "skipped")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _state_path() -> Path:
|
|
18
|
+
return Path.home() / ".arkaos" / "workflow-state.json"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _now_iso() -> str:
|
|
22
|
+
return datetime.now(timezone.utc).isoformat()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _read() -> dict | None:
|
|
26
|
+
path = _state_path()
|
|
27
|
+
if not path.exists():
|
|
28
|
+
return None
|
|
29
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _write(state: dict) -> dict:
|
|
33
|
+
"""Atomic write: write to temp file then rename."""
|
|
34
|
+
path = _state_path()
|
|
35
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
36
|
+
fd = NamedTemporaryFile(
|
|
37
|
+
mode="w", dir=str(path.parent), suffix=".tmp", delete=False, encoding="utf-8",
|
|
38
|
+
)
|
|
39
|
+
try:
|
|
40
|
+
json.dump(state, fd, indent=2)
|
|
41
|
+
fd.close()
|
|
42
|
+
os.replace(fd.name, str(path))
|
|
43
|
+
except BaseException:
|
|
44
|
+
fd.close()
|
|
45
|
+
os.unlink(fd.name)
|
|
46
|
+
raise
|
|
47
|
+
return state
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def init_workflow(workflow: str, project: str, phases: list[str]) -> dict:
|
|
51
|
+
"""Create a new workflow state file. Overwrites any existing state."""
|
|
52
|
+
state = {
|
|
53
|
+
"session_id": str(uuid.uuid4()),
|
|
54
|
+
"started_at": _now_iso(),
|
|
55
|
+
"workflow": workflow,
|
|
56
|
+
"project": project,
|
|
57
|
+
"branch": "",
|
|
58
|
+
"phases": {p: {"status": "pending"} for p in phases},
|
|
59
|
+
"violations": [],
|
|
60
|
+
}
|
|
61
|
+
return _write(state)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_state() -> dict | None:
|
|
65
|
+
"""Read current workflow state. Returns None if no active workflow."""
|
|
66
|
+
return _read()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def clear_workflow() -> None:
|
|
70
|
+
"""Remove the state file."""
|
|
71
|
+
path = _state_path()
|
|
72
|
+
if path.exists():
|
|
73
|
+
path.unlink()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _require_state() -> dict:
|
|
77
|
+
"""Read state or raise if no active workflow."""
|
|
78
|
+
state = _read()
|
|
79
|
+
if state is None:
|
|
80
|
+
raise RuntimeError("No active workflow")
|
|
81
|
+
return state
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def update_phase(phase: str, status: str, artifact: str | None = None) -> dict:
|
|
85
|
+
"""Update a phase status. Validates phase exists and status is valid."""
|
|
86
|
+
if status not in _VALID_STATUSES:
|
|
87
|
+
raise ValueError(f"Invalid status: {status}. Must be one of {_VALID_STATUSES}")
|
|
88
|
+
state = _require_state()
|
|
89
|
+
if phase not in state["phases"]:
|
|
90
|
+
raise ValueError(f"Unknown phase: {phase}. Available: {list(state['phases'])}")
|
|
91
|
+
state["phases"][phase]["status"] = status
|
|
92
|
+
if status in ("in_progress", "completed"):
|
|
93
|
+
state["phases"][phase]["at"] = _now_iso()
|
|
94
|
+
if artifact:
|
|
95
|
+
state["phases"][phase]["artifact"] = artifact
|
|
96
|
+
return _write(state)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def set_branch(branch: str) -> dict:
|
|
100
|
+
"""Record the git branch for the current workflow."""
|
|
101
|
+
state = _require_state()
|
|
102
|
+
state["branch"] = branch
|
|
103
|
+
return _write(state)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def add_violation(
|
|
107
|
+
rule: str, detail: str, tool: str | None = None, file: str | None = None,
|
|
108
|
+
) -> dict:
|
|
109
|
+
"""Append a violation to the violations list."""
|
|
110
|
+
state = _require_state()
|
|
111
|
+
violation: dict = {"rule": rule, "detail": detail, "at": _now_iso()}
|
|
112
|
+
if tool:
|
|
113
|
+
violation["tool"] = tool
|
|
114
|
+
if file:
|
|
115
|
+
violation["file"] = file
|
|
116
|
+
state["violations"].append(violation)
|
|
117
|
+
return _write(state)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def is_phase_completed(phase: str) -> bool:
|
|
121
|
+
"""Check if a specific phase is completed."""
|
|
122
|
+
state = _read()
|
|
123
|
+
if state is None:
|
|
124
|
+
return False
|
|
125
|
+
phase_data = state["phases"].get(phase)
|
|
126
|
+
if phase_data is None:
|
|
127
|
+
return False
|
|
128
|
+
return phase_data["status"] == "completed"
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ============================================================================
|
|
3
|
+
# ArkaOS — Workflow State Reader (for hooks)
|
|
4
|
+
# Reads ~/.arkaos/workflow-state.json and outputs requested fields.
|
|
5
|
+
# Dependencies: jq (required)
|
|
6
|
+
# ============================================================================
|
|
7
|
+
|
|
8
|
+
STATE_FILE="$HOME/.arkaos/workflow-state.json"
|
|
9
|
+
|
|
10
|
+
if [ ! -f "$STATE_FILE" ]; then
|
|
11
|
+
case "${1:-}" in
|
|
12
|
+
active) exit 1 ;;
|
|
13
|
+
summary) exit 1 ;;
|
|
14
|
+
violations) echo "0"; exit 0 ;;
|
|
15
|
+
phase) echo "none"; exit 0 ;;
|
|
16
|
+
check) exit 1 ;;
|
|
17
|
+
*) echo "No active workflow"; exit 1 ;;
|
|
18
|
+
esac
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
CMD="${1:-summary}"
|
|
22
|
+
ARG="${2:-}"
|
|
23
|
+
|
|
24
|
+
case "$CMD" in
|
|
25
|
+
active)
|
|
26
|
+
exit 0
|
|
27
|
+
;;
|
|
28
|
+
|
|
29
|
+
phase)
|
|
30
|
+
if [ -z "$ARG" ]; then
|
|
31
|
+
echo "Usage: state-reader.sh phase <name>" >&2
|
|
32
|
+
exit 1
|
|
33
|
+
fi
|
|
34
|
+
STATUS=$(jq -r ".phases.\"$ARG\".status // \"unknown\"" "$STATE_FILE" 2>/dev/null)
|
|
35
|
+
echo "$STATUS"
|
|
36
|
+
;;
|
|
37
|
+
|
|
38
|
+
check)
|
|
39
|
+
if [ -z "$ARG" ]; then
|
|
40
|
+
echo "Usage: state-reader.sh check <name>" >&2
|
|
41
|
+
exit 1
|
|
42
|
+
fi
|
|
43
|
+
STATUS=$(jq -r ".phases.\"$ARG\".status // \"pending\"" "$STATE_FILE" 2>/dev/null)
|
|
44
|
+
[ "$STATUS" = "completed" ] && exit 0 || exit 1
|
|
45
|
+
;;
|
|
46
|
+
|
|
47
|
+
violations)
|
|
48
|
+
COUNT=$(jq '.violations | length' "$STATE_FILE" 2>/dev/null || echo "0")
|
|
49
|
+
echo "$COUNT"
|
|
50
|
+
;;
|
|
51
|
+
|
|
52
|
+
summary)
|
|
53
|
+
WORKFLOW=$(jq -r '.workflow // ""' "$STATE_FILE" 2>/dev/null)
|
|
54
|
+
BRANCH=$(jq -r '.branch // ""' "$STATE_FILE" 2>/dev/null)
|
|
55
|
+
VIOLATIONS=$(jq '.violations | length' "$STATE_FILE" 2>/dev/null || echo "0")
|
|
56
|
+
TOTAL=$(jq '.phases | length' "$STATE_FILE" 2>/dev/null || echo "0")
|
|
57
|
+
COMPLETED=$(jq '[.phases[] | select(.status == "completed")] | length' "$STATE_FILE" 2>/dev/null || echo "0")
|
|
58
|
+
CURRENT=$(jq -r '[.phases | to_entries[] | select(.value.status == "in_progress")] | .[0].key // "none"' "$STATE_FILE" 2>/dev/null)
|
|
59
|
+
[ "$CURRENT" = "null" ] && CURRENT="none"
|
|
60
|
+
echo "${WORKFLOW}|${CURRENT}|${COMPLETED}/${TOTAL}|${BRANCH}|${VIOLATIONS}"
|
|
61
|
+
;;
|
|
62
|
+
|
|
63
|
+
*)
|
|
64
|
+
echo "Unknown command: $CMD" >&2
|
|
65
|
+
echo "Usage: state-reader.sh {active|phase|check|violations|summary} [arg]" >&2
|
|
66
|
+
exit 1
|
|
67
|
+
;;
|
|
68
|
+
esac
|
package/installer/index.js
CHANGED
|
@@ -587,6 +587,52 @@ function installSkill(config, installDir) {
|
|
|
587
587
|
if (agentDeployed > 0) {
|
|
588
588
|
ok(`${agentDeployed} agent personas installed (~/.claude/agents/arka-*.md)`);
|
|
589
589
|
}
|
|
590
|
+
|
|
591
|
+
// ── MCP infrastructure ──────────────────────────────────────────────
|
|
592
|
+
// Deploy mcps/ subdirectories (profiles, stacks, scripts) and registry
|
|
593
|
+
// to ~/.claude/skills/arka/mcps/, and the arka-prompts server files to
|
|
594
|
+
// ~/.claude/skills/arka/mcp-server/. Mirrors the mcps/ tree in the repo.
|
|
595
|
+
const mcpsSrc = join(ARKAOS_ROOT, "mcps");
|
|
596
|
+
if (existsSync(mcpsSrc)) {
|
|
597
|
+
const mcpsDest = join(skillDest, "mcps");
|
|
598
|
+
ensureDir(mcpsDest);
|
|
599
|
+
|
|
600
|
+
// Copy subdirectories: profiles, stacks, scripts
|
|
601
|
+
for (const subdir of ["profiles", "stacks", "scripts"]) {
|
|
602
|
+
const src = join(mcpsSrc, subdir);
|
|
603
|
+
if (!existsSync(src)) continue;
|
|
604
|
+
const dest = join(mcpsDest, subdir);
|
|
605
|
+
ensureDir(dest);
|
|
606
|
+
try {
|
|
607
|
+
cpSync(src, dest, { recursive: true });
|
|
608
|
+
} catch {}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// Copy registry.json to mcps root
|
|
612
|
+
const registrySrc = join(mcpsSrc, "registry.json");
|
|
613
|
+
if (existsSync(registrySrc)) {
|
|
614
|
+
copyFileSync(registrySrc, join(mcpsDest, "registry.json"));
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// Make apply-mcps.sh executable
|
|
618
|
+
const applyScript = join(mcpsDest, "scripts", "apply-mcps.sh");
|
|
619
|
+
if (existsSync(applyScript)) {
|
|
620
|
+
try { chmodSync(applyScript, 0o755); } catch {}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// Deploy arka-prompts server to mcp-server/
|
|
624
|
+
const mcpServerSrc = join(mcpsSrc, "arka-prompts");
|
|
625
|
+
const mcpServerDest = join(skillsBase, "arka", "mcp-server");
|
|
626
|
+
if (existsSync(mcpServerSrc)) {
|
|
627
|
+
ensureDir(mcpServerDest);
|
|
628
|
+
for (const f of ["server.py", "commands.py", "pyproject.toml"]) {
|
|
629
|
+
const src = join(mcpServerSrc, f);
|
|
630
|
+
if (existsSync(src)) copyFileSync(src, join(mcpServerDest, f));
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
ok("MCP infrastructure deployed (profiles, stacks, scripts, arka-prompts server)");
|
|
635
|
+
}
|
|
590
636
|
}
|
|
591
637
|
|
|
592
638
|
function deployCognitiveScheduler(installDir, arkaosRoot) {
|
package/installer/update.js
CHANGED
|
@@ -342,6 +342,44 @@ export async function update() {
|
|
|
342
342
|
console.log(` ✓ ${agentCount} agent personas updated`);
|
|
343
343
|
}
|
|
344
344
|
|
|
345
|
+
// MCP infrastructure: deploy mcps/ subdirectories, registry, and
|
|
346
|
+
// arka-prompts server — mirrors the same block in installSkill().
|
|
347
|
+
const mcpsSrc = join(ARKAOS_ROOT, "mcps");
|
|
348
|
+
if (existsSync(mcpsSrc)) {
|
|
349
|
+
const mcpsDest = join(skillDest, "mcps");
|
|
350
|
+
if (!existsSync(mcpsDest)) mkdirSync(mcpsDest, { recursive: true });
|
|
351
|
+
|
|
352
|
+
for (const subdir of ["profiles", "stacks", "scripts"]) {
|
|
353
|
+
const src = join(mcpsSrc, subdir);
|
|
354
|
+
if (!existsSync(src)) continue;
|
|
355
|
+
const dest = join(mcpsDest, subdir);
|
|
356
|
+
if (!existsSync(dest)) mkdirSync(dest, { recursive: true });
|
|
357
|
+
try { cpSync(src, dest, { recursive: true }); } catch {}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const registrySrc = join(mcpsSrc, "registry.json");
|
|
361
|
+
if (existsSync(registrySrc)) {
|
|
362
|
+
copyFileSync(registrySrc, join(mcpsDest, "registry.json"));
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const applyScript = join(mcpsDest, "scripts", "apply-mcps.sh");
|
|
366
|
+
if (existsSync(applyScript)) {
|
|
367
|
+
try { chmodSync(applyScript, 0o755); } catch {}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const mcpServerSrc = join(mcpsSrc, "arka-prompts");
|
|
371
|
+
const mcpServerDest = join(skillsBase, "arka", "mcp-server");
|
|
372
|
+
if (existsSync(mcpServerSrc)) {
|
|
373
|
+
if (!existsSync(mcpServerDest)) mkdirSync(mcpServerDest, { recursive: true });
|
|
374
|
+
for (const f of ["server.py", "commands.py", "pyproject.toml"]) {
|
|
375
|
+
const src = join(mcpServerSrc, f);
|
|
376
|
+
if (existsSync(src)) copyFileSync(src, join(mcpServerDest, f));
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
console.log(" ✓ MCP infrastructure updated (profiles, stacks, scripts, arka-prompts server)");
|
|
381
|
+
}
|
|
382
|
+
|
|
345
383
|
// ── 7. Update .repo-path + .arkaos-root ──
|
|
346
384
|
// Two references point at the source repo. Both MUST be updated on
|
|
347
385
|
// every update pass, otherwise running `npx arkaos update` from a
|