@mamdouh-aboammar/agentic-workflow 1.2.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/.claude-plugin/plugin.json +10 -0
- package/.codex-plugin/plugin.json +13 -0
- package/.skills.json +19 -0
- package/AGENTS.md +1344 -0
- package/CLAUDE.md +178 -0
- package/GEMINI.md +102 -0
- package/LICENSE +21 -0
- package/README.md +350 -0
- package/SKILL.md +132 -0
- package/bin/agentic-hooks.sh +79 -0
- package/bin/cli.js +1060 -0
- package/core/__init__.py +52 -0
- package/core/ai_evaluator.py +117 -0
- package/core/autopilot_engine.py +368 -0
- package/core/clean_code_guard.py +188 -0
- package/core/engine_py/__init__.py +29 -0
- package/core/engine_py/agent_worker.py +136 -0
- package/core/engine_py/decider.py +150 -0
- package/core/engine_py/energy.py +45 -0
- package/core/engine_py/event_bus.py +63 -0
- package/core/engine_py/executor.py +186 -0
- package/core/engine_py/models.py +193 -0
- package/core/engine_py/queue.py +314 -0
- package/core/engine_py/runner.py +116 -0
- package/core/engine_py/system_workers.py +70 -0
- package/core/engine_py/toon_adapter.py +586 -0
- package/core/engine_py/verification_controller.py +208 -0
- package/core/engine_py/worker.py +167 -0
- package/core/engine_spec/event_schema.json +65 -0
- package/core/engine_spec/example_workflow.yaml +73 -0
- package/core/engine_spec/workflow_schema.json +127 -0
- package/core/hooks/__init__.py +29 -0
- package/core/hooks/adapters/__init__.py +25 -0
- package/core/hooks/adapters/claude_adapter.py +83 -0
- package/core/hooks/adapters/cli_agent_adapter.py +82 -0
- package/core/hooks/adapters/codex_adapter.py +78 -0
- package/core/hooks/adapters/cursor_adapter.py +73 -0
- package/core/hooks/adapters/gemini_adapter.py +93 -0
- package/core/hooks/adapters/homebrew_adapter.py +69 -0
- package/core/hooks/adapters/mcp_proxy.py +133 -0
- package/core/hooks/adapters/shell_adapter.py +65 -0
- package/core/hooks/dispatcher.py +118 -0
- package/core/hooks/policy_engine.py +375 -0
- package/core/hooks/session_end.py +141 -0
- package/core/hooks/types.py +147 -0
- package/core/integrations/__init__.py +28 -0
- package/core/integrations/installer.py +225 -0
- package/core/integrations/lifecycle_director.py +175 -0
- package/core/integrations/registry.py +105 -0
- package/core/multi_agent_system.py +164 -0
- package/core/skills_indexer.py +742 -0
- package/core/system/__init__.py +25 -0
- package/core/system/announcements.py +72 -0
- package/core/system/dependencies.py +69 -0
- package/core/system/doctor.py +171 -0
- package/core/system/health.py +144 -0
- package/core/system/installer.py +137 -0
- package/core/system/notifications.py +97 -0
- package/core/system/refresher.py +110 -0
- package/core/system/updater.py +167 -0
- package/core/system/version_tracker.py +65 -0
- package/docs/architecture_plan.md +7 -0
- package/docs/guides/failure-recovery.md +714 -0
- package/docs/implementation_summary.md +10 -0
- package/docs/protocols/autopilot-execution.md +148 -0
- package/docs/protocols/code-change-protocol.md +49 -0
- package/docs/protocols/context-preservation-detail.md +114 -0
- package/docs/protocols/quality-gates.md +110 -0
- package/docs/protocols/ulw-mode.md +60 -0
- package/docs/research_findings.md +10 -0
- package/docs/solutions/autonomous-autopilot-engine-architecture.md +38 -0
- package/install.sh +111 -0
- package/marketplace.json +37 -0
- package/package.json +81 -0
- package/skills/agentic-workflow/SKILL.md +132 -0
- package/skills/agentic-workflow/skill-spec.json +100 -0
- package/soul.md +445 -0
- package/src/engine_ts/decider.ts +186 -0
- package/src/engine_ts/event-bus.ts +57 -0
- package/src/engine_ts/executor.ts +262 -0
- package/src/engine_ts/index.ts +12 -0
- package/src/engine_ts/queue.ts +93 -0
- package/src/engine_ts/runner.ts +108 -0
- package/src/engine_ts/skills-indexer.ts +264 -0
- package/src/engine_ts/toon-adapter.ts +91 -0
- package/src/engine_ts/types.ts +134 -0
- package/src/engine_ts/verification-controller.ts +204 -0
- package/src/engine_ts/worker.ts +280 -0
- package/src/hooks/adapters/claude-adapter.ts +54 -0
- package/src/hooks/adapters/cli-agent-adapter.ts +46 -0
- package/src/hooks/adapters/codex-adapter.ts +69 -0
- package/src/hooks/adapters/cursor-adapter.ts +60 -0
- package/src/hooks/adapters/gemini-adapter.ts +71 -0
- package/src/hooks/adapters/homebrew-adapter.ts +36 -0
- package/src/hooks/adapters/mcp-proxy.ts +66 -0
- package/src/hooks/adapters/shell-adapter.ts +42 -0
- package/src/hooks/dispatcher.ts +113 -0
- package/src/hooks/index.ts +16 -0
- package/src/hooks/policy-engine.ts +376 -0
- package/src/hooks/session-end.ts +125 -0
- package/src/hooks/types.ts +61 -0
- package/src/index.d.ts +34 -0
- package/src/index.ts +23 -0
- package/src/integrations/index.ts +7 -0
- package/src/integrations/installer.ts +208 -0
- package/src/integrations/lifecycle-director.ts +139 -0
- package/src/integrations/registry.ts +82 -0
- package/src/system/announcements.ts +143 -0
- package/src/system/dependencies.ts +176 -0
- package/src/system/doctor.ts +374 -0
- package/src/system/health.ts +270 -0
- package/src/system/index.ts +14 -0
- package/src/system/installer.ts +262 -0
- package/src/system/notifications.ts +180 -0
- package/src/system/refresher.ts +207 -0
- package/src/system/types.ts +268 -0
- package/src/system/updater.ts +219 -0
- package/src/system/version-tracker.ts +137 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
core/system/__init__.py — Python System Engines Package
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from core.system.updater import AutoUpdater
|
|
6
|
+
from core.system.installer import AutoInstaller
|
|
7
|
+
from core.system.refresher import Refresher
|
|
8
|
+
from core.system.doctor import DoctorEngine
|
|
9
|
+
from core.system.health import HealthEngine
|
|
10
|
+
from core.system.dependencies import DependenciesEngine
|
|
11
|
+
from core.system.notifications import NotificationEngine
|
|
12
|
+
from core.system.announcements import AnnouncementEngine
|
|
13
|
+
from core.system.version_tracker import VersionTracker
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"AutoUpdater",
|
|
17
|
+
"AutoInstaller",
|
|
18
|
+
"Refresher",
|
|
19
|
+
"DoctorEngine",
|
|
20
|
+
"HealthEngine",
|
|
21
|
+
"DependenciesEngine",
|
|
22
|
+
"NotificationEngine",
|
|
23
|
+
"AnnouncementEngine",
|
|
24
|
+
"VersionTracker",
|
|
25
|
+
]
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""
|
|
2
|
+
core/system/announcements.py — Python Announcement & Bulletin Engine
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Dict, Any, List
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AnnouncementEngine:
|
|
11
|
+
def __init__(self, project_dir: str = "."):
|
|
12
|
+
self.project_dir = Path(project_dir).resolve()
|
|
13
|
+
self.state_file = self.project_dir / ".announcements_seen.json"
|
|
14
|
+
|
|
15
|
+
def get_default_announcements(self) -> List[Dict[str, Any]]:
|
|
16
|
+
return [
|
|
17
|
+
{
|
|
18
|
+
"id": "ann_v110_engines",
|
|
19
|
+
"title": "Universal System Engines Suite Online",
|
|
20
|
+
"body": "AgenticWorkflow now features 9 core toolchain engines with dual-runtime parity.",
|
|
21
|
+
"category": "FEATURE",
|
|
22
|
+
"date": "2026-09-06",
|
|
23
|
+
"version": "1.1.0",
|
|
24
|
+
"priority": "high"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"id": "ann_toon_v41",
|
|
28
|
+
"title": "TOON Protocol v4.1 Released",
|
|
29
|
+
"body": "Token-Oriented Object Notation (v4.1) delivers 30-60% token compression.",
|
|
30
|
+
"category": "UPDATE",
|
|
31
|
+
"date": "2026-09-01",
|
|
32
|
+
"version": "1.0.5",
|
|
33
|
+
"priority": "normal"
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
def get_seen_ids(self) -> List[str]:
|
|
38
|
+
if self.state_file.exists():
|
|
39
|
+
try:
|
|
40
|
+
with open(self.state_file, "r", encoding="utf-8") as f:
|
|
41
|
+
return json.load(f).get("seen_ids", [])
|
|
42
|
+
except Exception:
|
|
43
|
+
pass
|
|
44
|
+
return []
|
|
45
|
+
|
|
46
|
+
def list_all(self) -> List[Dict[str, Any]]:
|
|
47
|
+
seen = set(self.get_seen_ids())
|
|
48
|
+
anns = self.get_default_announcements()
|
|
49
|
+
for a in anns:
|
|
50
|
+
a["seen"] = a["id"] in seen
|
|
51
|
+
return anns
|
|
52
|
+
|
|
53
|
+
def get_unread(self) -> List[Dict[str, Any]]:
|
|
54
|
+
return [a for a in self.list_all() if not a.get("seen")]
|
|
55
|
+
|
|
56
|
+
def mark_as_read(self, ann_id: str):
|
|
57
|
+
seen = self.get_seen_ids()
|
|
58
|
+
if ann_id not in seen:
|
|
59
|
+
seen.append(ann_id)
|
|
60
|
+
try:
|
|
61
|
+
with open(self.state_file, "w", encoding="utf-8") as f:
|
|
62
|
+
json.dump({"seen_ids": seen}, f, indent=2)
|
|
63
|
+
except Exception:
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
def mark_all_as_read(self):
|
|
67
|
+
all_ids = [a["id"] for a in self.list_all()]
|
|
68
|
+
try:
|
|
69
|
+
with open(self.state_file, "w", encoding="utf-8") as f:
|
|
70
|
+
json.dump({"seen_ids": all_ids}, f, indent=2)
|
|
71
|
+
except Exception:
|
|
72
|
+
pass
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
core/system/dependencies.py — Python Dependencies & Multi-Ecosystem Auditor Engine
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import json
|
|
7
|
+
import subprocess
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Dict, Any, List
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DependenciesEngine:
|
|
13
|
+
def __init__(self, project_dir: str = "."):
|
|
14
|
+
self.project_dir = Path(project_dir).resolve()
|
|
15
|
+
|
|
16
|
+
def audit(self) -> Dict[str, Any]:
|
|
17
|
+
deps = []
|
|
18
|
+
|
|
19
|
+
# 1. Package.json dependencies
|
|
20
|
+
pkg_file = self.project_dir / "package.json"
|
|
21
|
+
if pkg_file.exists():
|
|
22
|
+
try:
|
|
23
|
+
with open(pkg_file, "r", encoding="utf-8") as f:
|
|
24
|
+
pkg_data = json.load(f)
|
|
25
|
+
all_deps = {**pkg_data.get("dependencies", {}), **pkg_data.get("devDependencies", {})}
|
|
26
|
+
for name, ver in all_deps.items():
|
|
27
|
+
node_mod = self.project_dir / "node_modules" / name
|
|
28
|
+
deps.append({
|
|
29
|
+
"name": name,
|
|
30
|
+
"type": "bun-npm",
|
|
31
|
+
"status": "SATISFIED" if node_mod.exists() else "MISSING",
|
|
32
|
+
"version": ver
|
|
33
|
+
})
|
|
34
|
+
except Exception:
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
# 2. Python standard modules
|
|
38
|
+
for mod in ["json", "pathlib", "unittest", "dataclasses", "asyncio"]:
|
|
39
|
+
try:
|
|
40
|
+
__import__(mod)
|
|
41
|
+
deps.append({"name": f"python:{mod}", "type": "python", "status": "SATISFIED"})
|
|
42
|
+
except ImportError:
|
|
43
|
+
deps.append({"name": f"python:{mod}", "type": "python", "status": "MISSING"})
|
|
44
|
+
|
|
45
|
+
# 3. Binaries
|
|
46
|
+
for b in ["bun", "python3", "git"]:
|
|
47
|
+
try:
|
|
48
|
+
res = subprocess.run(f"{b} --version", shell=True, capture_output=True)
|
|
49
|
+
deps.append({"name": f"bin:{b}", "type": "system-binary", "status": "SATISFIED" if res.returncode == 0 else "MISSING"})
|
|
50
|
+
except Exception:
|
|
51
|
+
deps.append({"name": f"bin:{b}", "type": "system-binary", "status": "MISSING"})
|
|
52
|
+
|
|
53
|
+
satisfied = sum(1 for d in deps if d["status"] == "SATISFIED")
|
|
54
|
+
missing = sum(1 for d in deps if d["status"] == "MISSING")
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
"total": len(deps),
|
|
58
|
+
"satisfied": satisfied,
|
|
59
|
+
"missing": missing,
|
|
60
|
+
"dependencies": deps,
|
|
61
|
+
"all_satisfied": missing == 0
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
def install_missing(self) -> Dict[str, Any]:
|
|
65
|
+
try:
|
|
66
|
+
res = subprocess.run("bun install", shell=True, cwd=self.project_dir, capture_output=True, text=True)
|
|
67
|
+
return {"success": res.returncode == 0, "message": "bun install executed"}
|
|
68
|
+
except Exception as e:
|
|
69
|
+
return {"success": False, "message": str(e)}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""
|
|
2
|
+
core/system/doctor.py — Python Doctor Diagnostic & Auto-Fix Engine
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Dict, Any, List
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DoctorEngine:
|
|
12
|
+
def __init__(self, project_dir: str = "."):
|
|
13
|
+
self.project_dir = Path(project_dir).resolve()
|
|
14
|
+
self.home_dir = Path.home()
|
|
15
|
+
|
|
16
|
+
def _run_cmd(self, cmd: str) -> Dict[str, Any]:
|
|
17
|
+
try:
|
|
18
|
+
res = subprocess.run(cmd, shell=True, cwd=self.project_dir, capture_output=True, text=True)
|
|
19
|
+
return {"ok": res.returncode == 0, "stdout": res.stdout.strip()}
|
|
20
|
+
except Exception:
|
|
21
|
+
return {"ok": False, "stdout": ""}
|
|
22
|
+
|
|
23
|
+
def diagnose(self) -> Dict[str, Any]:
|
|
24
|
+
checks = []
|
|
25
|
+
|
|
26
|
+
# 1. Bun Runtime
|
|
27
|
+
bun = self._run_cmd("bun --version")
|
|
28
|
+
checks.append({
|
|
29
|
+
"id": "runtime-bun",
|
|
30
|
+
"title": "Bun Runtime",
|
|
31
|
+
"status": "PASS" if bun["ok"] else "FAIL",
|
|
32
|
+
"details": f"Bun v{bun['stdout']}" if bun["ok"] else "Bun not found.",
|
|
33
|
+
"fixable": False
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
# 2. Python 3 Runtime
|
|
37
|
+
py = self._run_cmd("python3 --version")
|
|
38
|
+
checks.append({
|
|
39
|
+
"id": "runtime-python",
|
|
40
|
+
"title": "Python 3 Runtime",
|
|
41
|
+
"status": "PASS" if py["ok"] else "FAIL",
|
|
42
|
+
"details": py["stdout"] if py["ok"] else "Python 3 not found.",
|
|
43
|
+
"fixable": False
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
# 3. CLI Permission
|
|
47
|
+
cli_js = self.project_dir / "bin" / "cli.js"
|
|
48
|
+
if cli_js.exists():
|
|
49
|
+
is_exec = os.access(cli_js, os.X_OK)
|
|
50
|
+
checks.append({
|
|
51
|
+
"id": "cli-permission",
|
|
52
|
+
"title": "CLI Permission (bin/cli.js)",
|
|
53
|
+
"status": "PASS" if is_exec else "WARN",
|
|
54
|
+
"details": "Executable" if is_exec else "Not executable",
|
|
55
|
+
"fixable": not is_exec
|
|
56
|
+
})
|
|
57
|
+
else:
|
|
58
|
+
checks.append({
|
|
59
|
+
"id": "cli-permission",
|
|
60
|
+
"title": "CLI Permission (bin/cli.js)",
|
|
61
|
+
"status": "FAIL",
|
|
62
|
+
"details": "bin/cli.js missing",
|
|
63
|
+
"fixable": False
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
# 4. CLI Symlink
|
|
67
|
+
local_bin = self.home_dir / ".local" / "bin" / "agentic-workflow"
|
|
68
|
+
usr_bin = Path("/usr/local/bin/agentic-workflow")
|
|
69
|
+
linked = local_bin.exists() or usr_bin.exists()
|
|
70
|
+
checks.append({
|
|
71
|
+
"id": "cli-symlink",
|
|
72
|
+
"title": "Global CLI Symlink",
|
|
73
|
+
"status": "PASS" if linked else "WARN",
|
|
74
|
+
"details": f"Linked at {local_bin if local_bin.exists() else usr_bin}" if linked else "Not linked in user PATH",
|
|
75
|
+
"fixable": not linked
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
# 5. Dependencies
|
|
79
|
+
node_modules = self.project_dir / "node_modules"
|
|
80
|
+
toon_pkg = node_modules / "@toon-format" / "toon"
|
|
81
|
+
has_deps = node_modules.exists() and toon_pkg.exists()
|
|
82
|
+
checks.append({
|
|
83
|
+
"id": "dependencies-npm",
|
|
84
|
+
"title": "Dependencies (@toon-format/toon)",
|
|
85
|
+
"status": "PASS" if has_deps else "WARN",
|
|
86
|
+
"details": "Installed" if has_deps else "node_modules missing or incomplete",
|
|
87
|
+
"fixable": not has_deps
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
# 6. SOT State
|
|
91
|
+
sot = self.project_dir / "state.yaml"
|
|
92
|
+
checks.append({
|
|
93
|
+
"id": "state-sot",
|
|
94
|
+
"title": "Single Source of Truth (state.yaml)",
|
|
95
|
+
"status": "PASS" if sot.exists() else "WARN",
|
|
96
|
+
"details": "state.yaml present" if sot.exists() else "Workflow state idle",
|
|
97
|
+
"fixable": False
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
# 7. UAHF Hooks
|
|
101
|
+
hooks = self.project_dir / ".claude" / "hooks" / "scripts" / "context_guard.py"
|
|
102
|
+
checks.append({
|
|
103
|
+
"id": "hooks-uahf",
|
|
104
|
+
"title": "UAHF Safety & Verification Scripts",
|
|
105
|
+
"status": "PASS" if hooks.exists() else "FAIL",
|
|
106
|
+
"details": "UAHF hooks online" if hooks.exists() else "Hooks missing",
|
|
107
|
+
"fixable": False
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
# 8. Skills Index
|
|
111
|
+
skills_toon = self.project_dir / "skills-index.toon"
|
|
112
|
+
checks.append({
|
|
113
|
+
"id": "skills-mesh",
|
|
114
|
+
"title": "Skills Mesh Index (skills-index.toon)",
|
|
115
|
+
"status": "PASS" if skills_toon.exists() else "WARN",
|
|
116
|
+
"details": "skills-index.toon present" if skills_toon.exists() else "Missing index",
|
|
117
|
+
"fixable": not skills_toon.exists()
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
passed = sum(1 for c in checks if c["status"] == "PASS")
|
|
121
|
+
warned = sum(1 for c in checks if c["status"] == "WARN")
|
|
122
|
+
failed = sum(1 for c in checks if c["status"] == "FAIL")
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
"passed": passed,
|
|
126
|
+
"warned": warned,
|
|
127
|
+
"failed": failed,
|
|
128
|
+
"total": len(checks),
|
|
129
|
+
"checks": checks,
|
|
130
|
+
"overall_healthy": failed == 0
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
def fix_all(self) -> List[Dict[str, Any]]:
|
|
134
|
+
diag = self.diagnose()
|
|
135
|
+
fixable = [c for c in diag["checks"] if c["fixable"] and c["status"] != "PASS"]
|
|
136
|
+
results = []
|
|
137
|
+
|
|
138
|
+
for c in fixable:
|
|
139
|
+
cid = c["id"]
|
|
140
|
+
if cid == "cli-permission":
|
|
141
|
+
cli_js = self.project_dir / "bin" / "cli.js"
|
|
142
|
+
try:
|
|
143
|
+
os.chmod(cli_js, 0o755)
|
|
144
|
+
results.append({"check_id": cid, "remediated": True, "message": "Chmod 0755 bin/cli.js"})
|
|
145
|
+
except Exception as e:
|
|
146
|
+
results.append({"check_id": cid, "remediated": False, "message": str(e)})
|
|
147
|
+
elif cid == "cli-symlink":
|
|
148
|
+
local_bin = self.home_dir / ".local" / "bin"
|
|
149
|
+
local_bin.mkdir(parents=True, exist_ok=True)
|
|
150
|
+
target = local_bin / "agentic-workflow"
|
|
151
|
+
src = self.project_dir / "bin" / "cli.js"
|
|
152
|
+
try:
|
|
153
|
+
if target.exists() or target.is_symlink():
|
|
154
|
+
target.unlink()
|
|
155
|
+
target.symlink_to(src)
|
|
156
|
+
results.append({"check_id": cid, "remediated": True, "message": f"Symlinked {target}"})
|
|
157
|
+
except Exception as e:
|
|
158
|
+
results.append({"check_id": cid, "remediated": False, "message": str(e)})
|
|
159
|
+
elif cid == "dependencies-npm":
|
|
160
|
+
try:
|
|
161
|
+
subprocess.run("bun install", shell=True, cwd=self.project_dir, capture_output=True)
|
|
162
|
+
results.append({"check_id": cid, "remediated": True, "message": "Ran bun install"})
|
|
163
|
+
except Exception as e:
|
|
164
|
+
results.append({"check_id": cid, "remediated": False, "message": str(e)})
|
|
165
|
+
elif cid == "skills-mesh":
|
|
166
|
+
try:
|
|
167
|
+
subprocess.run("python3 core/skills_indexer.py index", shell=True, cwd=self.project_dir, capture_output=True)
|
|
168
|
+
results.append({"check_id": cid, "remediated": True, "message": "Rebuilt skills index"})
|
|
169
|
+
except Exception as e:
|
|
170
|
+
results.append({"check_id": cid, "remediated": False, "message": str(e)})
|
|
171
|
+
return results
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""
|
|
2
|
+
core/system/health.py — Python Health Telemetry & Scoring Engine
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import platform
|
|
7
|
+
import subprocess
|
|
8
|
+
import time
|
|
9
|
+
import json
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Dict, Any, List
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class HealthEngine:
|
|
15
|
+
def __init__(self, project_dir: str = "."):
|
|
16
|
+
self.project_dir = Path(project_dir).resolve()
|
|
17
|
+
|
|
18
|
+
def collect_vitals(self) -> Dict[str, Any]:
|
|
19
|
+
bun_v = None
|
|
20
|
+
try:
|
|
21
|
+
res = subprocess.run("bun --version", shell=True, capture_output=True, text=True)
|
|
22
|
+
if res.returncode == 0:
|
|
23
|
+
bun_v = res.stdout.strip()
|
|
24
|
+
except Exception:
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
py_v = platform.python_version()
|
|
28
|
+
|
|
29
|
+
# Workflow vitals
|
|
30
|
+
sot = self.project_dir / "state.yaml"
|
|
31
|
+
has_wf = False
|
|
32
|
+
step = "idle"
|
|
33
|
+
if sot.exists():
|
|
34
|
+
try:
|
|
35
|
+
txt = sot.read_text(encoding="utf-8")
|
|
36
|
+
has_wf = "status: in_progress" in txt or "status: planning" in txt
|
|
37
|
+
for line in txt.splitlines():
|
|
38
|
+
if line.strip().startswith("current_step:"):
|
|
39
|
+
step = line.split(":", 1)[1].strip().strip('"\'')
|
|
40
|
+
except Exception:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
# Circuit breaker
|
|
44
|
+
cb = "CLOSED"
|
|
45
|
+
streak = 0
|
|
46
|
+
fable_state = self.project_dir / ".fable" / "state.json"
|
|
47
|
+
if fable_state.exists():
|
|
48
|
+
try:
|
|
49
|
+
with open(fable_state, "r", encoding="utf-8") as f:
|
|
50
|
+
data = json.load(f)
|
|
51
|
+
if data.get("circuit_breaker_tripped"):
|
|
52
|
+
cb = "OPEN"
|
|
53
|
+
streak = data.get("failure_streak", 0)
|
|
54
|
+
except Exception:
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
# Traces count
|
|
58
|
+
traces = 0
|
|
59
|
+
trace_dir = self.project_dir / ".traces"
|
|
60
|
+
if trace_dir.exists():
|
|
61
|
+
for f in trace_dir.glob("*.jsonl"):
|
|
62
|
+
try:
|
|
63
|
+
with open(f, "r", encoding="utf-8") as fp:
|
|
64
|
+
traces += sum(1 for _ in fp)
|
|
65
|
+
except Exception:
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
"platform": platform.system().lower(),
|
|
70
|
+
"arch": platform.machine(),
|
|
71
|
+
"bun_version": bun_v,
|
|
72
|
+
"python_version": py_v,
|
|
73
|
+
"has_workflow": has_wf,
|
|
74
|
+
"current_step": step,
|
|
75
|
+
"circuit_breaker": cb,
|
|
76
|
+
"failure_streak": streak,
|
|
77
|
+
"traces_count": traces
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
def evaluate_score(self, vitals: Dict[str, Any]) -> Dict[str, Any]:
|
|
81
|
+
score = 100
|
|
82
|
+
warnings = []
|
|
83
|
+
|
|
84
|
+
if not vitals.get("bun_version"):
|
|
85
|
+
score -= 10
|
|
86
|
+
warnings.append("Bun runtime missing")
|
|
87
|
+
if vitals.get("circuit_breaker") == "OPEN":
|
|
88
|
+
score -= 25
|
|
89
|
+
warnings.append("Circuit breaker OPEN")
|
|
90
|
+
elif vitals.get("failure_streak", 0) > 0:
|
|
91
|
+
score -= min(15, vitals["failure_streak"] * 5)
|
|
92
|
+
warnings.append(f"Active failure streak: {vitals['failure_streak']}")
|
|
93
|
+
|
|
94
|
+
if not (self.project_dir / "node_modules").exists():
|
|
95
|
+
score -= 10
|
|
96
|
+
warnings.append("node_modules missing")
|
|
97
|
+
|
|
98
|
+
if not (self.project_dir / "skills-index.toon").exists():
|
|
99
|
+
score -= 10
|
|
100
|
+
warnings.append("skills-index.toon missing")
|
|
101
|
+
|
|
102
|
+
score = max(0, min(100, score))
|
|
103
|
+
grade = "F"
|
|
104
|
+
if score >= 95:
|
|
105
|
+
grade = "A+"
|
|
106
|
+
elif score >= 85:
|
|
107
|
+
grade = "A"
|
|
108
|
+
elif score >= 70:
|
|
109
|
+
grade = "B"
|
|
110
|
+
elif score >= 50:
|
|
111
|
+
grade = "C"
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
"score": score,
|
|
115
|
+
"grade": grade,
|
|
116
|
+
"warnings": warnings
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
def get_report(self) -> Dict[str, Any]:
|
|
120
|
+
vitals = self.collect_vitals()
|
|
121
|
+
eval_res = self.evaluate_score(vitals)
|
|
122
|
+
return {
|
|
123
|
+
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
|
124
|
+
"score": eval_res["score"],
|
|
125
|
+
"grade": eval_res["grade"],
|
|
126
|
+
"warnings": eval_res["warnings"],
|
|
127
|
+
"vitals": vitals,
|
|
128
|
+
"services": {
|
|
129
|
+
"TS_ENGINE": "HEALTHY" if vitals["bun_version"] else "DEGRADED",
|
|
130
|
+
"PY_ENGINE": "HEALTHY",
|
|
131
|
+
"UAHF_HOOKS": "HEALTHY" if (self.project_dir / ".claude" / "hooks" / "scripts").exists() else "DOWN",
|
|
132
|
+
"SKILLS_MESH": "HEALTHY" if (self.project_dir / "skills-index.toon").exists() else "DEGRADED"
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
def format_toon(self, report: Dict[str, Any]) -> str:
|
|
137
|
+
v = report["vitals"]
|
|
138
|
+
return f"""health_telemetry{{timestamp:"{report['timestamp']}",score:{report['score']},grade:"{report['grade']}"}}:
|
|
139
|
+
runtime[1]{{os,arch,bun_v,py_v}}:
|
|
140
|
+
{v['platform']},{v['arch']},{v.get('bun_version') or 'none'},{v['python_version']}
|
|
141
|
+
workflow[1]{{active,step,circuit_breaker,streak,spans}}:
|
|
142
|
+
{v['has_workflow']},{v['current_step']},{v['circuit_breaker']},{v['failure_streak']},{v['traces_count']}
|
|
143
|
+
warnings[{len(report['warnings'])}]:
|
|
144
|
+
""" + "\n".join(f" - \"{w}\"" for w in report["warnings"])
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""
|
|
2
|
+
core/system/installer.py — Python Auto-Installer Engine for AgenticWorkflow
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Dict, Any, List
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AutoInstaller:
|
|
13
|
+
def __init__(self, project_dir: str = "."):
|
|
14
|
+
self.project_dir = Path(project_dir).resolve()
|
|
15
|
+
self.home_dir = Path.home()
|
|
16
|
+
|
|
17
|
+
def _check_binary(self, cmd: str) -> Dict[str, Any]:
|
|
18
|
+
try:
|
|
19
|
+
res = subprocess.run(f"{cmd} --version", shell=True, capture_output=True, text=True)
|
|
20
|
+
return {"available": res.returncode == 0, "version": res.stdout.strip().split("\n")[0]}
|
|
21
|
+
except Exception:
|
|
22
|
+
return {"available": False}
|
|
23
|
+
|
|
24
|
+
def get_host_targets(self) -> List[Dict[str, Any]]:
|
|
25
|
+
return [
|
|
26
|
+
{
|
|
27
|
+
"name": "Claude Code Skills",
|
|
28
|
+
"platform": "claude",
|
|
29
|
+
"path": str(self.home_dir / ".claude" / "skills" / "agentic-workflow"),
|
|
30
|
+
"installed": (self.home_dir / ".claude" / "skills" / "agentic-workflow").exists()
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "Gemini CLI / Antigravity Skills",
|
|
34
|
+
"platform": "gemini",
|
|
35
|
+
"path": str(self.home_dir / ".gemini" / "config" / "skills" / "agentic-workflow"),
|
|
36
|
+
"installed": (self.home_dir / ".gemini" / "config" / "skills" / "agentic-workflow").exists()
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"name": "Cursor IDE Skills",
|
|
40
|
+
"platform": "cursor",
|
|
41
|
+
"path": str(self.home_dir / ".cursor" / "skills" / "agentic-workflow"),
|
|
42
|
+
"installed": (self.home_dir / ".cursor" / "skills" / "agentic-workflow").exists()
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"name": "Codex / OpenCode Skills",
|
|
46
|
+
"platform": "codex",
|
|
47
|
+
"path": str(self.home_dir / ".codex" / "skills" / "agentic-workflow"),
|
|
48
|
+
"installed": (self.home_dir / ".codex" / "skills" / "agentic-workflow").exists()
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"name": "Universal Agent Kernel",
|
|
52
|
+
"platform": "agents",
|
|
53
|
+
"path": str(self.home_dir / ".agents" / "skills" / "agentic-workflow"),
|
|
54
|
+
"installed": (self.home_dir / ".agents" / "skills" / "agentic-workflow").exists()
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
def check_status(self) -> Dict[str, Any]:
|
|
59
|
+
targets = self.get_host_targets()
|
|
60
|
+
installed = [t for t in targets if t["installed"]]
|
|
61
|
+
skipped = [t for t in targets if not t["installed"]]
|
|
62
|
+
|
|
63
|
+
bin_paths = [
|
|
64
|
+
str(self.home_dir / ".local" / "bin" / "agentic-workflow"),
|
|
65
|
+
"/usr/local/bin/agentic-workflow"
|
|
66
|
+
]
|
|
67
|
+
linked = [p for p in bin_paths if os.path.exists(p)]
|
|
68
|
+
|
|
69
|
+
deps = [
|
|
70
|
+
{"name": "bun", **self._check_binary("bun")},
|
|
71
|
+
{"name": "node", **self._check_binary("node")},
|
|
72
|
+
{"name": "python3", **self._check_binary("python3")},
|
|
73
|
+
{"name": "git", **self._check_binary("git")}
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
"success": True,
|
|
78
|
+
"installed_targets": installed,
|
|
79
|
+
"skipped_targets": skipped,
|
|
80
|
+
"bin_linked": linked,
|
|
81
|
+
"system_deps": deps
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
def install(self, platforms: Optional[List[str]] = None, global_bin: bool = True) -> Dict[str, Any]:
|
|
85
|
+
requested = platforms or ["claude", "gemini", "cursor", "codex", "agents"]
|
|
86
|
+
messages = []
|
|
87
|
+
installed_targets = []
|
|
88
|
+
bin_linked = []
|
|
89
|
+
|
|
90
|
+
targets = self.get_host_targets()
|
|
91
|
+
for t in targets:
|
|
92
|
+
if t["platform"] not in requested:
|
|
93
|
+
continue
|
|
94
|
+
|
|
95
|
+
target_path = Path(t["path"])
|
|
96
|
+
target_path.parent.mkdir(parents=True, exist_ok=True)
|
|
97
|
+
if target_path.exists():
|
|
98
|
+
shutil.rmtree(target_path, ignore_errors=True)
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
shutil.copytree(
|
|
102
|
+
self.project_dir,
|
|
103
|
+
target_path,
|
|
104
|
+
ignore=shutil.ignore_patterns("node_modules", ".git", "__pycache__", ".pytest_cache")
|
|
105
|
+
)
|
|
106
|
+
t["installed"] = True
|
|
107
|
+
installed_targets.append(t)
|
|
108
|
+
messages.append(f"✓ Installed skill to {t['name']}")
|
|
109
|
+
except Exception as e:
|
|
110
|
+
messages.append(f"! Failed installing to {t['name']}: {e}")
|
|
111
|
+
|
|
112
|
+
# CLI symlink
|
|
113
|
+
cli_src = self.project_dir / "bin" / "cli.js"
|
|
114
|
+
if cli_src.exists():
|
|
115
|
+
try:
|
|
116
|
+
os.chmod(cli_src, 0o755)
|
|
117
|
+
except Exception:
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
local_bin = self.home_dir / ".local" / "bin"
|
|
121
|
+
local_bin.mkdir(parents=True, exist_ok=True)
|
|
122
|
+
link_target = local_bin / "agentic-workflow"
|
|
123
|
+
try:
|
|
124
|
+
if link_target.exists() or link_target.is_symlink():
|
|
125
|
+
link_target.unlink()
|
|
126
|
+
link_target.symlink_to(cli_src)
|
|
127
|
+
bin_linked.append(str(link_target))
|
|
128
|
+
messages.append(f"✓ Linked executable to {link_target}")
|
|
129
|
+
except Exception as e:
|
|
130
|
+
messages.append(f"! Failed linking CLI: {e}")
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
"success": len(installed_targets) > 0 or len(bin_linked) > 0,
|
|
134
|
+
"installed_targets": installed_targets,
|
|
135
|
+
"bin_linked": bin_linked,
|
|
136
|
+
"messages": messages
|
|
137
|
+
}
|