@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.
Files changed (118) hide show
  1. package/.claude-plugin/plugin.json +10 -0
  2. package/.codex-plugin/plugin.json +13 -0
  3. package/.skills.json +19 -0
  4. package/AGENTS.md +1344 -0
  5. package/CLAUDE.md +178 -0
  6. package/GEMINI.md +102 -0
  7. package/LICENSE +21 -0
  8. package/README.md +350 -0
  9. package/SKILL.md +132 -0
  10. package/bin/agentic-hooks.sh +79 -0
  11. package/bin/cli.js +1060 -0
  12. package/core/__init__.py +52 -0
  13. package/core/ai_evaluator.py +117 -0
  14. package/core/autopilot_engine.py +368 -0
  15. package/core/clean_code_guard.py +188 -0
  16. package/core/engine_py/__init__.py +29 -0
  17. package/core/engine_py/agent_worker.py +136 -0
  18. package/core/engine_py/decider.py +150 -0
  19. package/core/engine_py/energy.py +45 -0
  20. package/core/engine_py/event_bus.py +63 -0
  21. package/core/engine_py/executor.py +186 -0
  22. package/core/engine_py/models.py +193 -0
  23. package/core/engine_py/queue.py +314 -0
  24. package/core/engine_py/runner.py +116 -0
  25. package/core/engine_py/system_workers.py +70 -0
  26. package/core/engine_py/toon_adapter.py +586 -0
  27. package/core/engine_py/verification_controller.py +208 -0
  28. package/core/engine_py/worker.py +167 -0
  29. package/core/engine_spec/event_schema.json +65 -0
  30. package/core/engine_spec/example_workflow.yaml +73 -0
  31. package/core/engine_spec/workflow_schema.json +127 -0
  32. package/core/hooks/__init__.py +29 -0
  33. package/core/hooks/adapters/__init__.py +25 -0
  34. package/core/hooks/adapters/claude_adapter.py +83 -0
  35. package/core/hooks/adapters/cli_agent_adapter.py +82 -0
  36. package/core/hooks/adapters/codex_adapter.py +78 -0
  37. package/core/hooks/adapters/cursor_adapter.py +73 -0
  38. package/core/hooks/adapters/gemini_adapter.py +93 -0
  39. package/core/hooks/adapters/homebrew_adapter.py +69 -0
  40. package/core/hooks/adapters/mcp_proxy.py +133 -0
  41. package/core/hooks/adapters/shell_adapter.py +65 -0
  42. package/core/hooks/dispatcher.py +118 -0
  43. package/core/hooks/policy_engine.py +375 -0
  44. package/core/hooks/session_end.py +141 -0
  45. package/core/hooks/types.py +147 -0
  46. package/core/integrations/__init__.py +28 -0
  47. package/core/integrations/installer.py +225 -0
  48. package/core/integrations/lifecycle_director.py +175 -0
  49. package/core/integrations/registry.py +105 -0
  50. package/core/multi_agent_system.py +164 -0
  51. package/core/skills_indexer.py +742 -0
  52. package/core/system/__init__.py +25 -0
  53. package/core/system/announcements.py +72 -0
  54. package/core/system/dependencies.py +69 -0
  55. package/core/system/doctor.py +171 -0
  56. package/core/system/health.py +144 -0
  57. package/core/system/installer.py +137 -0
  58. package/core/system/notifications.py +97 -0
  59. package/core/system/refresher.py +110 -0
  60. package/core/system/updater.py +167 -0
  61. package/core/system/version_tracker.py +65 -0
  62. package/docs/architecture_plan.md +7 -0
  63. package/docs/guides/failure-recovery.md +714 -0
  64. package/docs/implementation_summary.md +10 -0
  65. package/docs/protocols/autopilot-execution.md +148 -0
  66. package/docs/protocols/code-change-protocol.md +49 -0
  67. package/docs/protocols/context-preservation-detail.md +114 -0
  68. package/docs/protocols/quality-gates.md +110 -0
  69. package/docs/protocols/ulw-mode.md +60 -0
  70. package/docs/research_findings.md +10 -0
  71. package/docs/solutions/autonomous-autopilot-engine-architecture.md +38 -0
  72. package/install.sh +111 -0
  73. package/marketplace.json +37 -0
  74. package/package.json +81 -0
  75. package/skills/agentic-workflow/SKILL.md +132 -0
  76. package/skills/agentic-workflow/skill-spec.json +100 -0
  77. package/soul.md +445 -0
  78. package/src/engine_ts/decider.ts +186 -0
  79. package/src/engine_ts/event-bus.ts +57 -0
  80. package/src/engine_ts/executor.ts +262 -0
  81. package/src/engine_ts/index.ts +12 -0
  82. package/src/engine_ts/queue.ts +93 -0
  83. package/src/engine_ts/runner.ts +108 -0
  84. package/src/engine_ts/skills-indexer.ts +264 -0
  85. package/src/engine_ts/toon-adapter.ts +91 -0
  86. package/src/engine_ts/types.ts +134 -0
  87. package/src/engine_ts/verification-controller.ts +204 -0
  88. package/src/engine_ts/worker.ts +280 -0
  89. package/src/hooks/adapters/claude-adapter.ts +54 -0
  90. package/src/hooks/adapters/cli-agent-adapter.ts +46 -0
  91. package/src/hooks/adapters/codex-adapter.ts +69 -0
  92. package/src/hooks/adapters/cursor-adapter.ts +60 -0
  93. package/src/hooks/adapters/gemini-adapter.ts +71 -0
  94. package/src/hooks/adapters/homebrew-adapter.ts +36 -0
  95. package/src/hooks/adapters/mcp-proxy.ts +66 -0
  96. package/src/hooks/adapters/shell-adapter.ts +42 -0
  97. package/src/hooks/dispatcher.ts +113 -0
  98. package/src/hooks/index.ts +16 -0
  99. package/src/hooks/policy-engine.ts +376 -0
  100. package/src/hooks/session-end.ts +125 -0
  101. package/src/hooks/types.ts +61 -0
  102. package/src/index.d.ts +34 -0
  103. package/src/index.ts +23 -0
  104. package/src/integrations/index.ts +7 -0
  105. package/src/integrations/installer.ts +208 -0
  106. package/src/integrations/lifecycle-director.ts +139 -0
  107. package/src/integrations/registry.ts +82 -0
  108. package/src/system/announcements.ts +143 -0
  109. package/src/system/dependencies.ts +176 -0
  110. package/src/system/doctor.ts +374 -0
  111. package/src/system/health.ts +270 -0
  112. package/src/system/index.ts +14 -0
  113. package/src/system/installer.ts +262 -0
  114. package/src/system/notifications.ts +180 -0
  115. package/src/system/refresher.ts +207 -0
  116. package/src/system/types.ts +268 -0
  117. package/src/system/updater.ts +219 -0
  118. 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
+ }