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 CHANGED
@@ -1 +1 @@
1
- 2.11.0
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
- # Silent output no context injection needed from PostToolUse
188
- echo '{}'
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
- python_result="$L0 $L4 $L7"
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__ = ["Workflow", "Phase", "Gate", "PhaseStatus", "WorkflowEngine", "load_workflow"]
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
+ ]
@@ -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
@@ -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) {
@@ -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arkaos",
3
- "version": "2.11.0",
3
+ "version": "2.13.0",
4
4
  "description": "The Operating System for AI Agent Teams",
5
5
  "type": "module",
6
6
  "bin": {