hexaswarm-core 0.1.1__tar.gz

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 (129) hide show
  1. hexaswarm_core-0.1.1/.gitignore +63 -0
  2. hexaswarm_core-0.1.1/PKG-INFO +64 -0
  3. hexaswarm_core-0.1.1/README.md +31 -0
  4. hexaswarm_core-0.1.1/hexa_swarm_core/__init__.py +7 -0
  5. hexaswarm_core-0.1.1/hexa_swarm_core/_version.py +1 -0
  6. hexaswarm_core-0.1.1/hexa_swarm_core/adapters/__init__.py +12 -0
  7. hexaswarm_core-0.1.1/hexa_swarm_core/adapters/_node_base.py +66 -0
  8. hexaswarm_core-0.1.1/hexa_swarm_core/adapters/base.py +70 -0
  9. hexaswarm_core-0.1.1/hexa_swarm_core/adapters/generic_shell.py +47 -0
  10. hexaswarm_core-0.1.1/hexa_swarm_core/adapters/go_chi.py +47 -0
  11. hexaswarm_core-0.1.1/hexa_swarm_core/adapters/kotlin_ktor.py +55 -0
  12. hexaswarm_core-0.1.1/hexa_swarm_core/adapters/node_express.py +38 -0
  13. hexaswarm_core-0.1.1/hexa_swarm_core/adapters/node_nest.py +36 -0
  14. hexaswarm_core-0.1.1/hexa_swarm_core/adapters/node_next.py +41 -0
  15. hexaswarm_core-0.1.1/hexa_swarm_core/adapters/protocol.py +66 -0
  16. hexaswarm_core-0.1.1/hexa_swarm_core/adapters/py_celery.py +68 -0
  17. hexaswarm_core-0.1.1/hexa_swarm_core/adapters/py_click.py +75 -0
  18. hexaswarm_core-0.1.1/hexa_swarm_core/adapters/py_django.py +101 -0
  19. hexaswarm_core-0.1.1/hexa_swarm_core/adapters/py_fastapi.py +94 -0
  20. hexaswarm_core-0.1.1/hexa_swarm_core/adapters/registry.py +58 -0
  21. hexaswarm_core-0.1.1/hexa_swarm_core/adapters/rust_axum.py +46 -0
  22. hexaswarm_core-0.1.1/hexa_swarm_core/adapters/swift_vapor.py +42 -0
  23. hexaswarm_core-0.1.1/hexa_swarm_core/archetypes/__init__.py +15 -0
  24. hexaswarm_core-0.1.1/hexa_swarm_core/archetypes/definitions.py +261 -0
  25. hexaswarm_core-0.1.1/hexa_swarm_core/archetypes/protocol.py +54 -0
  26. hexaswarm_core-0.1.1/hexa_swarm_core/archetypes/registry.py +122 -0
  27. hexaswarm_core-0.1.1/hexa_swarm_core/assets/__init__.py +21 -0
  28. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.ai-sync/ARCHITECTURE_DECISIONS/0001-adopted-hexa.md +22 -0
  29. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.ai-sync/CODEOWNERS.md +29 -0
  30. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.ai-sync/RULES.md +30 -0
  31. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.ai-sync/SYSTEM_STATE.md +22 -0
  32. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.ai-sync/audit/.gitkeep +0 -0
  33. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.ai-sync/changelog/BE_CURRENT.md +10 -0
  34. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.ai-sync/changelog/FE_CURRENT.md +10 -0
  35. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.ai-sync/contracts/README.md +8 -0
  36. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.ai-sync/events/.gitkeep +0 -0
  37. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.ai-sync/locks/.gitkeep +0 -0
  38. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.ai-sync/plans/.gitkeep +0 -0
  39. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/agents/alpha-data.md +31 -0
  40. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/agents/beta-core.md +34 -0
  41. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/agents/cursor-fe.md +36 -0
  42. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/agents/delta-redteam.md +45 -0
  43. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/agents/epsilon-edge.md +36 -0
  44. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/agents/explorer.md +40 -0
  45. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/agents/gamma-commerce.md +34 -0
  46. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/agents/planner.md +30 -0
  47. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/agents/reviewer.md +48 -0
  48. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/agents/writer.md +37 -0
  49. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/hooks/post_edit_ownership.py +113 -0
  50. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/hooks/post_session_archive.py +62 -0
  51. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/hooks/post_write_validate.py +100 -0
  52. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/hooks/pre_tool_use_guard.py +104 -0
  53. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/hooks/session_start_lock_cleanup.py +43 -0
  54. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/hooks/statusline.sh +32 -0
  55. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/mcp.json +21 -0
  56. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/settings.json +60 -0
  57. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/skills/auto-test/SKILL.md +38 -0
  58. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/skills/contract-sync/SKILL.md +41 -0
  59. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/skills/cost-tracker/SKILL.md +45 -0
  60. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/skills/cross-sync-alert/SKILL.md +32 -0
  61. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/skills/deploy-check/SKILL.md +33 -0
  62. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/skills/fix-issue/SKILL.md +48 -0
  63. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/skills/quality-gate/SKILL.md +49 -0
  64. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/skills/security-audit/SKILL.md +48 -0
  65. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/skills/ship/SKILL.md +33 -0
  66. hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/skills/worktree-boot/SKILL.md +44 -0
  67. hexaswarm_core-0.1.1/hexa_swarm_core/cli/__init__.py +0 -0
  68. hexaswarm_core-0.1.1/hexa_swarm_core/cli/__main__.py +6 -0
  69. hexaswarm_core-0.1.1/hexa_swarm_core/cli/adopt.py +125 -0
  70. hexaswarm_core-0.1.1/hexa_swarm_core/cli/app.py +71 -0
  71. hexaswarm_core-0.1.1/hexa_swarm_core/cli/archetype.py +31 -0
  72. hexaswarm_core-0.1.1/hexa_swarm_core/cli/contract.py +57 -0
  73. hexaswarm_core-0.1.1/hexa_swarm_core/cli/cost.py +53 -0
  74. hexaswarm_core-0.1.1/hexa_swarm_core/cli/heartbeat.py +29 -0
  75. hexaswarm_core-0.1.1/hexa_swarm_core/cli/killswitch.py +33 -0
  76. hexaswarm_core-0.1.1/hexa_swarm_core/cli/lock.py +49 -0
  77. hexaswarm_core-0.1.1/hexa_swarm_core/cli/quality_gate.py +128 -0
  78. hexaswarm_core-0.1.1/hexa_swarm_core/cli/session.py +35 -0
  79. hexaswarm_core-0.1.1/hexa_swarm_core/cli/worktree.py +70 -0
  80. hexaswarm_core-0.1.1/hexa_swarm_core/exceptions.py +94 -0
  81. hexaswarm_core-0.1.1/hexa_swarm_core/install.py +124 -0
  82. hexaswarm_core-0.1.1/hexa_swarm_core/invariants.py +143 -0
  83. hexaswarm_core-0.1.1/hexa_swarm_core/llm/__init__.py +7 -0
  84. hexaswarm_core-0.1.1/hexa_swarm_core/llm/base.py +176 -0
  85. hexaswarm_core-0.1.1/hexa_swarm_core/locks/__init__.py +17 -0
  86. hexaswarm_core-0.1.1/hexa_swarm_core/locks/file_lock.py +156 -0
  87. hexaswarm_core-0.1.1/hexa_swarm_core/logging/__init__.py +17 -0
  88. hexaswarm_core-0.1.1/hexa_swarm_core/logging/config.py +103 -0
  89. hexaswarm_core-0.1.1/hexa_swarm_core/logging/tracing.py +139 -0
  90. hexaswarm_core-0.1.1/hexa_swarm_core/mcp/__init__.py +6 -0
  91. hexaswarm_core-0.1.1/hexa_swarm_core/mcp/openapi_server.py +171 -0
  92. hexaswarm_core-0.1.1/hexa_swarm_core/orchestrator/__init__.py +13 -0
  93. hexaswarm_core-0.1.1/hexa_swarm_core/orchestrator/pipeline.py +138 -0
  94. hexaswarm_core-0.1.1/hexa_swarm_core/orchestrator/stage.py +57 -0
  95. hexaswarm_core-0.1.1/hexa_swarm_core/profile.py +94 -0
  96. hexaswarm_core-0.1.1/hexa_swarm_core/providers/__init__.py +7 -0
  97. hexaswarm_core-0.1.1/hexa_swarm_core/providers/base.py +47 -0
  98. hexaswarm_core-0.1.1/hexa_swarm_core/safety/__init__.py +11 -0
  99. hexaswarm_core-0.1.1/hexa_swarm_core/safety/ceiling.py +45 -0
  100. hexaswarm_core-0.1.1/hexa_swarm_core/safety/killswitch.py +47 -0
  101. hexaswarm_core-0.1.1/hexa_swarm_core/safety/prompt.py +107 -0
  102. hexaswarm_core-0.1.1/hexa_swarm_core/session.py +55 -0
  103. hexaswarm_core-0.1.1/hexa_swarm_core/swarm/__init__.py +26 -0
  104. hexaswarm_core-0.1.1/hexa_swarm_core/swarm/contract_writer.py +126 -0
  105. hexaswarm_core-0.1.1/hexa_swarm_core/swarm/heartbeat.py +77 -0
  106. hexaswarm_core-0.1.1/hexa_swarm_core/swarm/worktree.py +143 -0
  107. hexaswarm_core-0.1.1/hexa_swarm_core/telemetry/__init__.py +3 -0
  108. hexaswarm_core-0.1.1/hexa_swarm_core/telemetry/cost.py +104 -0
  109. hexaswarm_core-0.1.1/pyproject.toml +90 -0
  110. hexaswarm_core-0.1.1/tests/__init__.py +0 -0
  111. hexaswarm_core-0.1.1/tests/conftest.py +38 -0
  112. hexaswarm_core-0.1.1/tests/test_adapters.py +61 -0
  113. hexaswarm_core-0.1.1/tests/test_archetypes.py +108 -0
  114. hexaswarm_core-0.1.1/tests/test_cli.py +137 -0
  115. hexaswarm_core-0.1.1/tests/test_cost_and_safety.py +54 -0
  116. hexaswarm_core-0.1.1/tests/test_install.py +90 -0
  117. hexaswarm_core-0.1.1/tests/test_invariants.py +77 -0
  118. hexaswarm_core-0.1.1/tests/test_llm.py +107 -0
  119. hexaswarm_core-0.1.1/tests/test_locks.py +73 -0
  120. hexaswarm_core-0.1.1/tests/test_logging.py +26 -0
  121. hexaswarm_core-0.1.1/tests/test_mcp_openapi.py +97 -0
  122. hexaswarm_core-0.1.1/tests/test_new_adapters.py +130 -0
  123. hexaswarm_core-0.1.1/tests/test_orchestrator.py +130 -0
  124. hexaswarm_core-0.1.1/tests/test_profile_session.py +49 -0
  125. hexaswarm_core-0.1.1/tests/test_prompt_safety.py +67 -0
  126. hexaswarm_core-0.1.1/tests/test_swarm.py +169 -0
  127. hexaswarm_core-0.1.1/tests/test_tracing.py +108 -0
  128. hexaswarm_core-0.1.1/tests/test_w7.py +206 -0
  129. hexaswarm_core-0.1.1/tests/test_w8_skeletons.py +123 -0
@@ -0,0 +1,63 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+ MANIFEST
23
+
24
+ # Virtual environments
25
+ .venv/
26
+ venv/
27
+ env/
28
+ ENV/
29
+
30
+ # Pytest / coverage
31
+ .pytest_cache/
32
+ .coverage
33
+ .coverage.*
34
+ htmlcov/
35
+ .cache/
36
+ .mypy_cache/
37
+ .ruff_cache/
38
+ .tox/
39
+ .nox/
40
+
41
+ # IDE
42
+ .vscode/
43
+ .idea/
44
+ *.swp
45
+ *.swo
46
+ *~
47
+ .DS_Store
48
+ Thumbs.db
49
+
50
+ # Secrets
51
+ .env
52
+ .env.*
53
+ !.env.example
54
+ .secrets/
55
+ *.key
56
+ *.pem
57
+
58
+ # Hexa runtime (never committed — these are generated per-clone)
59
+ .hexa/session.uuid
60
+ .ai-sync/locks/*.lock
61
+ .ai-sync/locks/*.lock.hb
62
+ .ai-sync/KILL
63
+ .ai-sync/logs/
@@ -0,0 +1,64 @@
1
+ Metadata-Version: 2.4
2
+ Name: hexaswarm-core
3
+ Version: 0.1.1
4
+ Summary: Universal primitives for Claude-native multi-agent software delivery — orchestrator, cost tracker, safety, stack adapters, and the `hexa` CLI.
5
+ Project-URL: Homepage, https://github.com/pna03100/hexa-swarm-template
6
+ Project-URL: Issues, https://github.com/pna03100/hexa-swarm-template/issues
7
+ Author-email: PNA Company <taemin@pnamarketing.co.kr>
8
+ License: MIT
9
+ Keywords: agentic,anthropic,claude,claude-code,enterprise-ai,hexa-swarm,mcp
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Requires-Python: >=3.11
20
+ Requires-Dist: pydantic<3,>=2.9
21
+ Requires-Dist: pyyaml<7,>=6.0
22
+ Requires-Dist: structlog<26,>=24.4
23
+ Requires-Dist: typer<0.20,>=0.12
24
+ Provides-Extra: dev
25
+ Requires-Dist: mypy<2,>=1.12; extra == 'dev'
26
+ Requires-Dist: pytest-cov<7,>=5; extra == 'dev'
27
+ Requires-Dist: pytest<9,>=8.3; extra == 'dev'
28
+ Requires-Dist: ruff<0.12,>=0.8; extra == 'dev'
29
+ Requires-Dist: types-pyyaml<7,>=6.0; extra == 'dev'
30
+ Provides-Extra: mcp
31
+ Requires-Dist: mcp<2,>=1.0; extra == 'mcp'
32
+ Description-Content-Type: text/markdown
33
+
34
+ # hexa-swarm-core
35
+
36
+ Universal primitives for Claude-native multi-agent software delivery.
37
+ Provides the `hexa` CLI plus a Python library used by `hexa-swarm-template` and any project adopted via `hexa adopt .`.
38
+
39
+ ## What lives here
40
+
41
+ - **`hexa` CLI** — `hexa adopt`, `hexa quality-gate`, `hexa lock`, `hexa session`, `hexa cost-track`
42
+ - **StackAdapter protocol** — pluggable language/framework support (py-fastapi, node-next, go-chi, …)
43
+ - **Orchestrator primitives** — atomic file locks, session UUID, heartbeat, contract writer queue
44
+ - **Telemetry** — JSONL cost tracker, structured logging, trace IDs
45
+ - **Safety** — KillSwitch, CostCeiling, CircuitBreaker, stochastic delay
46
+ - **Exceptions** — `SafetyViolationError(invariant=...)` hierarchy
47
+ - **Config** — Pydantic nested settings
48
+
49
+ ## Install
50
+
51
+ ```bash
52
+ pipx install hexa-swarm-core
53
+ hexa --help
54
+ ```
55
+
56
+ ## Adopt an existing repo (non-destructive)
57
+
58
+ ```bash
59
+ cd my-existing-project
60
+ hexa adopt . --dry-run # show what would change
61
+ hexa adopt . # install Tier A only
62
+ ```
63
+
64
+ See the parent repo's [README](../README.md) and [ARCHITECTURE.md](../ARCHITECTURE.md) for the full picture.
@@ -0,0 +1,31 @@
1
+ # hexa-swarm-core
2
+
3
+ Universal primitives for Claude-native multi-agent software delivery.
4
+ Provides the `hexa` CLI plus a Python library used by `hexa-swarm-template` and any project adopted via `hexa adopt .`.
5
+
6
+ ## What lives here
7
+
8
+ - **`hexa` CLI** — `hexa adopt`, `hexa quality-gate`, `hexa lock`, `hexa session`, `hexa cost-track`
9
+ - **StackAdapter protocol** — pluggable language/framework support (py-fastapi, node-next, go-chi, …)
10
+ - **Orchestrator primitives** — atomic file locks, session UUID, heartbeat, contract writer queue
11
+ - **Telemetry** — JSONL cost tracker, structured logging, trace IDs
12
+ - **Safety** — KillSwitch, CostCeiling, CircuitBreaker, stochastic delay
13
+ - **Exceptions** — `SafetyViolationError(invariant=...)` hierarchy
14
+ - **Config** — Pydantic nested settings
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ pipx install hexa-swarm-core
20
+ hexa --help
21
+ ```
22
+
23
+ ## Adopt an existing repo (non-destructive)
24
+
25
+ ```bash
26
+ cd my-existing-project
27
+ hexa adopt . --dry-run # show what would change
28
+ hexa adopt . # install Tier A only
29
+ ```
30
+
31
+ See the parent repo's [README](../README.md) and [ARCHITECTURE.md](../ARCHITECTURE.md) for the full picture.
@@ -0,0 +1,7 @@
1
+ """hexa_swarm_core — Universal primitives for Claude-native multi-agent delivery."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from hexa_swarm_core._version import __version__
6
+
7
+ __all__ = ["__version__"]
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,12 @@
1
+ from hexa_swarm_core.adapters.base import BaseAdapter
2
+ from hexa_swarm_core.adapters.protocol import InvariantEnforcer, StackAdapter
3
+ from hexa_swarm_core.adapters.registry import ADAPTERS, detect_adapters, get_adapter
4
+
5
+ __all__ = [
6
+ "ADAPTERS",
7
+ "BaseAdapter",
8
+ "InvariantEnforcer",
9
+ "StackAdapter",
10
+ "detect_adapters",
11
+ "get_adapter",
12
+ ]
@@ -0,0 +1,66 @@
1
+ """Shared plumbing for Node-family adapters (next, nest, express)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from hexa_swarm_core.adapters.base import BaseAdapter
8
+ from hexa_swarm_core.adapters.protocol import CommandSpec
9
+
10
+
11
+ class NodeAdapterBase(BaseAdapter):
12
+ """Base for Node/TS adapters - handles pnpm/yarn/npm dispatch uniformly."""
13
+
14
+ language = "typescript"
15
+
16
+ @staticmethod
17
+ def _pm(project_root: Path) -> str:
18
+ if (project_root / "pnpm-lock.yaml").exists():
19
+ return "pnpm"
20
+ if (project_root / "yarn.lock").exists():
21
+ return "yarn"
22
+ return "npm"
23
+
24
+ def _run(self, project_root: Path, script: str) -> list[CommandSpec]:
25
+ pm = self._pm(project_root)
26
+ if pm == "pnpm":
27
+ return [CommandSpec(["pnpm", "run", script], description=f"pnpm run {script}")]
28
+ if pm == "yarn":
29
+ return [CommandSpec(["yarn", script], description=f"yarn {script}")]
30
+ return [CommandSpec(["npm", "run", script, "--if-present"], description=f"npm run {script}")]
31
+
32
+ def lint_cmd(self, project_root: Path) -> list[CommandSpec]:
33
+ return self._run(project_root, "lint")
34
+
35
+ def test_cmd(self, project_root: Path) -> list[CommandSpec]:
36
+ return self._run(project_root, "test")
37
+
38
+ def build_cmd(self, project_root: Path) -> list[CommandSpec]:
39
+ return self._run(project_root, "build")
40
+
41
+ def format_check_cmd(self, project_root: Path) -> list[CommandSpec]:
42
+ pm = self._pm(project_root)
43
+ if pm == "pnpm":
44
+ return [CommandSpec(["pnpm", "exec", "prettier", "--check", "."], description="prettier --check")]
45
+ if pm == "yarn":
46
+ return [CommandSpec(["yarn", "prettier", "--check", "."], description="yarn prettier --check")]
47
+ return [CommandSpec(["npx", "--yes", "prettier", "--check", "."], description="npx prettier --check")]
48
+
49
+ def typecheck_cmd(self, project_root: Path) -> list[CommandSpec]:
50
+ pm = self._pm(project_root)
51
+ if pm == "pnpm":
52
+ return [CommandSpec(["pnpm", "exec", "tsc", "--noEmit"], description="tsc --noEmit")]
53
+ if pm == "yarn":
54
+ return [CommandSpec(["yarn", "tsc", "--noEmit"], description="yarn tsc --noEmit")]
55
+ return [CommandSpec(["npx", "--yes", "tsc", "--noEmit"], description="npx tsc --noEmit")]
56
+
57
+ def dep_audit_cmd(self, project_root: Path) -> list[CommandSpec]:
58
+ pm = self._pm(project_root)
59
+ if pm == "pnpm":
60
+ return [CommandSpec(["pnpm", "audit", "--audit-level=high"], description="pnpm audit")]
61
+ if pm == "yarn":
62
+ return [CommandSpec(["yarn", "npm", "audit", "--severity", "high"], description="yarn npm audit")]
63
+ return [CommandSpec(["npm", "audit", "--audit-level=high"], description="npm audit")]
64
+
65
+
66
+ __all__ = ["NodeAdapterBase"]
@@ -0,0 +1,70 @@
1
+ """BaseAdapter — a default skeleton most adapters can subclass."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from hexa_swarm_core.adapters.protocol import CommandSpec, InvariantEnforcer
8
+
9
+
10
+ class BaseAdapter:
11
+ """Common adapter plumbing.
12
+
13
+ Subclasses override attributes (name, language, detect_files, detect_imports,
14
+ priority) and `_*_cmd()` methods as needed. Default implementations return
15
+ empty lists — "no-op" is a valid adapter stance for a stage it doesn't own.
16
+ """
17
+
18
+ name: str = "base"
19
+ language: str = "unknown"
20
+ detect_files: list[str] = []
21
+ detect_imports: list[str] = []
22
+ priority: int = 0
23
+
24
+ # -- detection --------------------------------------------------------
25
+ def detect(self, project_root: Path) -> bool:
26
+ if not self.detect_files:
27
+ return False
28
+ matched: list[Path] = []
29
+ for glob in self.detect_files:
30
+ matched.extend(project_root.glob(glob))
31
+ if not matched:
32
+ return False
33
+ if not self.detect_imports:
34
+ return True
35
+ for path in matched:
36
+ try:
37
+ text = path.read_text(encoding="utf-8", errors="ignore")
38
+ except OSError:
39
+ continue
40
+ if any(imp in text for imp in self.detect_imports):
41
+ return True
42
+ return False
43
+
44
+ # -- commands (override in subclasses) --------------------------------
45
+ def lint_cmd(self, project_root: Path) -> list[CommandSpec]: # noqa: ARG002
46
+ return []
47
+
48
+ def format_check_cmd(self, project_root: Path) -> list[CommandSpec]: # noqa: ARG002
49
+ return []
50
+
51
+ def typecheck_cmd(self, project_root: Path) -> list[CommandSpec]: # noqa: ARG002
52
+ return []
53
+
54
+ def test_cmd(self, project_root: Path) -> list[CommandSpec]: # noqa: ARG002
55
+ return []
56
+
57
+ def build_cmd(self, project_root: Path) -> list[CommandSpec]: # noqa: ARG002
58
+ return []
59
+
60
+ def dep_audit_cmd(self, project_root: Path) -> list[CommandSpec]: # noqa: ARG002
61
+ return []
62
+
63
+ def contract_generator(self, project_root: Path) -> CommandSpec | None: # noqa: ARG002
64
+ return None
65
+
66
+ def invariant_enforcers(self, project_root: Path) -> list[InvariantEnforcer]: # noqa: ARG002
67
+ return []
68
+
69
+
70
+ __all__ = ["BaseAdapter"]
@@ -0,0 +1,47 @@
1
+ """generic-shell adapter — fallback for `hexa adopt` on unknown stacks.
2
+
3
+ Does not presume any linter/test runner. Reads `.hexa/profile.yaml` or a
4
+ `Makefile` for user-provided commands. Safe to use anywhere — just a thin
5
+ command registry that keeps the CLI interface uniform across stacks.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from pathlib import Path
11
+
12
+ from hexa_swarm_core.adapters.base import BaseAdapter
13
+ from hexa_swarm_core.adapters.protocol import CommandSpec
14
+
15
+
16
+ class GenericShellAdapter(BaseAdapter):
17
+ name = "generic-shell"
18
+ language = "shell"
19
+ detect_files = ["Makefile", "justfile", "Taskfile.yml"]
20
+ priority = 5 # very low — only matches when nothing else does
21
+
22
+ def detect(self, project_root: Path) -> bool:
23
+ # Always available as an explicit fallback, but `detect()` only returns
24
+ # True when a Make-like runner exists. Registry still allows forcing
25
+ # this adapter via `--stack generic-shell`.
26
+ return any((project_root / f).exists() for f in self.detect_files)
27
+
28
+ def _make_cmd(self, target: str, description: str) -> list[CommandSpec]:
29
+ return [CommandSpec(["make", target], description=description)]
30
+
31
+ def lint_cmd(self, project_root: Path) -> list[CommandSpec]:
32
+ return self._make_cmd("lint", "make lint") if (project_root / "Makefile").exists() else []
33
+
34
+ def format_check_cmd(self, project_root: Path) -> list[CommandSpec]:
35
+ return self._make_cmd("fmt", "make fmt") if (project_root / "Makefile").exists() else []
36
+
37
+ def typecheck_cmd(self, project_root: Path) -> list[CommandSpec]:
38
+ return self._make_cmd("typecheck", "make typecheck") if (project_root / "Makefile").exists() else []
39
+
40
+ def test_cmd(self, project_root: Path) -> list[CommandSpec]:
41
+ return self._make_cmd("test", "make test") if (project_root / "Makefile").exists() else []
42
+
43
+ def build_cmd(self, project_root: Path) -> list[CommandSpec]:
44
+ return self._make_cmd("build", "make build") if (project_root / "Makefile").exists() else []
45
+
46
+
47
+ __all__ = ["GenericShellAdapter"]
@@ -0,0 +1,47 @@
1
+ """go-chi StackAdapter - Go services using the chi router."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from pathlib import Path
7
+
8
+ from hexa_swarm_core.adapters.base import BaseAdapter
9
+ from hexa_swarm_core.adapters.protocol import CommandSpec, InvariantEnforcer
10
+
11
+
12
+ class GoChiAdapter(BaseAdapter):
13
+ name = "go-chi"
14
+ language = "go"
15
+ detect_files = ["go.mod"]
16
+ detect_imports = ["github.com/go-chi/chi"]
17
+ priority = 65
18
+
19
+ def lint_cmd(self, project_root: Path) -> list[CommandSpec]: # noqa: ARG002
20
+ return [CommandSpec(["golangci-lint", "run", "./..."], description="golangci-lint")]
21
+
22
+ def format_check_cmd(self, project_root: Path) -> list[CommandSpec]: # noqa: ARG002
23
+ return [CommandSpec(["gofmt", "-l", "."], description="gofmt -l (nonempty = drift)")]
24
+
25
+ def typecheck_cmd(self, project_root: Path) -> list[CommandSpec]: # noqa: ARG002
26
+ return [CommandSpec(["go", "build", "-o", os.devnull, "./..."], description="go build (typecheck)")]
27
+
28
+ def test_cmd(self, project_root: Path) -> list[CommandSpec]: # noqa: ARG002
29
+ return [CommandSpec(["go", "test", "-race", "-count=1", "./..."], description="go test -race")]
30
+
31
+ def build_cmd(self, project_root: Path) -> list[CommandSpec]: # noqa: ARG002
32
+ return [CommandSpec(["go", "build", "./..."], description="go build")]
33
+
34
+ def dep_audit_cmd(self, project_root: Path) -> list[CommandSpec]: # noqa: ARG002
35
+ return [CommandSpec(["govulncheck", "./..."], description="govulncheck")]
36
+
37
+ def invariant_enforcers(self, project_root: Path) -> list[InvariantEnforcer]: # noqa: ARG002
38
+ return [
39
+ InvariantEnforcer(
40
+ invariant="S5",
41
+ description="go vet must pass before merge",
42
+ cmd=["go", "vet", "./..."],
43
+ ),
44
+ ]
45
+
46
+
47
+ __all__ = ["GoChiAdapter"]
@@ -0,0 +1,55 @@
1
+ """kotlin-ktor StackAdapter - Kotlin services using Ktor."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from hexa_swarm_core.adapters.base import BaseAdapter
8
+ from hexa_swarm_core.adapters.protocol import CommandSpec, InvariantEnforcer
9
+
10
+
11
+ class KotlinKtorAdapter(BaseAdapter):
12
+ name = "kotlin-ktor"
13
+ language = "kotlin"
14
+ detect_files = [
15
+ "build.gradle.kts",
16
+ "build.gradle",
17
+ "settings.gradle.kts",
18
+ ]
19
+ detect_imports = ["io.ktor"]
20
+ priority = 65
21
+
22
+ @staticmethod
23
+ def _gradle(project_root: Path) -> str:
24
+ return "./gradlew" if (project_root / "gradlew").exists() else "gradle"
25
+
26
+ def lint_cmd(self, project_root: Path) -> list[CommandSpec]:
27
+ g = self._gradle(project_root)
28
+ return [CommandSpec([g, "ktlintCheck"], description="ktlint")]
29
+
30
+ def format_check_cmd(self, project_root: Path) -> list[CommandSpec]:
31
+ g = self._gradle(project_root)
32
+ return [CommandSpec([g, "detekt"], description="detekt")]
33
+
34
+ def typecheck_cmd(self, project_root: Path) -> list[CommandSpec]:
35
+ g = self._gradle(project_root)
36
+ return [CommandSpec([g, "compileKotlin", "--no-daemon"], description="kotlin compile")]
37
+
38
+ def test_cmd(self, project_root: Path) -> list[CommandSpec]:
39
+ g = self._gradle(project_root)
40
+ return [CommandSpec([g, "test", "--no-daemon"], description="gradle test")]
41
+
42
+ def build_cmd(self, project_root: Path) -> list[CommandSpec]:
43
+ g = self._gradle(project_root)
44
+ return [CommandSpec([g, "build", "-x", "test", "--no-daemon"], description="gradle build")]
45
+
46
+ def dep_audit_cmd(self, project_root: Path) -> list[CommandSpec]:
47
+ g = self._gradle(project_root)
48
+ # Requires the OWASP dependency-check plugin; a no-op when absent.
49
+ return [CommandSpec([g, "dependencyCheckAnalyze", "--no-daemon"], description="owasp dep-check")]
50
+
51
+ def invariant_enforcers(self, project_root: Path) -> list[InvariantEnforcer]: # noqa: ARG002
52
+ return []
53
+
54
+
55
+ __all__ = ["KotlinKtorAdapter"]
@@ -0,0 +1,38 @@
1
+ """node-express StackAdapter - Express / Fastify / Koa style Node services."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+
8
+ from hexa_swarm_core.adapters._node_base import NodeAdapterBase
9
+ from hexa_swarm_core.adapters.protocol import InvariantEnforcer
10
+
11
+
12
+ class NodeExpressAdapter(NodeAdapterBase):
13
+ name = "node-express"
14
+ detect_files = ["package.json"]
15
+ detect_imports = ["express", "fastify", "koa"]
16
+ priority = 58
17
+
18
+ def detect(self, project_root: Path) -> bool:
19
+ pkg = project_root / "package.json"
20
+ if not pkg.exists():
21
+ return False
22
+ try:
23
+ data = json.loads(pkg.read_text(encoding="utf-8"))
24
+ except (OSError, json.JSONDecodeError):
25
+ return False
26
+ deps = dict(data.get("dependencies") or {})
27
+ deps.update(data.get("devDependencies") or {})
28
+ # `next` and `@nestjs/*` are owned by dedicated adapters - opt out
29
+ # so polyglot repos don't get this adapter attached twice.
30
+ if "next" in deps or any(d.startswith("@nestjs/") for d in deps):
31
+ return False
32
+ return any(imp in deps for imp in self.detect_imports)
33
+
34
+ def invariant_enforcers(self, project_root: Path) -> list[InvariantEnforcer]: # noqa: ARG002
35
+ return []
36
+
37
+
38
+ __all__ = ["NodeExpressAdapter"]
@@ -0,0 +1,36 @@
1
+ """node-nest StackAdapter - NestJS backend services."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+
8
+ from hexa_swarm_core.adapters._node_base import NodeAdapterBase
9
+ from hexa_swarm_core.adapters.protocol import InvariantEnforcer
10
+
11
+
12
+ class NodeNestAdapter(NodeAdapterBase):
13
+ name = "node-nest"
14
+ detect_files = ["package.json", "nest-cli.json"]
15
+ detect_imports = ["@nestjs/core", "@nestjs/common"]
16
+ priority = 68
17
+
18
+ def detect(self, project_root: Path) -> bool:
19
+ if (project_root / "nest-cli.json").exists():
20
+ return True
21
+ pkg = project_root / "package.json"
22
+ if not pkg.exists():
23
+ return False
24
+ try:
25
+ data = json.loads(pkg.read_text(encoding="utf-8"))
26
+ except (OSError, json.JSONDecodeError):
27
+ return False
28
+ deps = dict(data.get("dependencies") or {})
29
+ deps.update(data.get("devDependencies") or {})
30
+ return any(d.startswith("@nestjs/") for d in deps)
31
+
32
+ def invariant_enforcers(self, project_root: Path) -> list[InvariantEnforcer]: # noqa: ARG002
33
+ return []
34
+
35
+
36
+ __all__ = ["NodeNestAdapter"]
@@ -0,0 +1,41 @@
1
+ """node-next StackAdapter - Next.js App Router projects."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+
8
+ from hexa_swarm_core.adapters._node_base import NodeAdapterBase
9
+ from hexa_swarm_core.adapters.protocol import InvariantEnforcer
10
+
11
+
12
+ class NodeNextAdapter(NodeAdapterBase):
13
+ name = "node-next"
14
+ detect_files = [
15
+ "package.json",
16
+ "next.config.ts",
17
+ "next.config.js",
18
+ "next.config.mjs",
19
+ ]
20
+ detect_imports = ["next"]
21
+ priority = 70
22
+
23
+ def detect(self, project_root: Path) -> bool:
24
+ if any((project_root / p).exists() for p in ("next.config.ts", "next.config.js", "next.config.mjs")):
25
+ return True
26
+ pkg = project_root / "package.json"
27
+ if not pkg.exists():
28
+ return False
29
+ try:
30
+ data = json.loads(pkg.read_text(encoding="utf-8"))
31
+ except (OSError, json.JSONDecodeError):
32
+ return False
33
+ deps = dict(data.get("dependencies") or {})
34
+ deps.update(data.get("devDependencies") or {})
35
+ return "next" in deps
36
+
37
+ def invariant_enforcers(self, project_root: Path) -> list[InvariantEnforcer]: # noqa: ARG002
38
+ return []
39
+
40
+
41
+ __all__ = ["NodeNextAdapter"]
@@ -0,0 +1,66 @@
1
+ """
2
+ StackAdapter Protocol — the contract every language/framework adapter fulfils.
3
+
4
+ A polymorphic `quality-gate` / `contract-sync` / `invariants-check` command works
5
+ identically across Python, Node, Go, Rust, etc. — because it dispatches to the
6
+ adapter that matches the current project.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass
12
+ from pathlib import Path
13
+ from typing import Protocol, runtime_checkable
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class CommandSpec:
18
+ """One shell invocation the adapter wants to run."""
19
+
20
+ cmd: list[str]
21
+ cwd: str | None = None # relative to project root; None = project root
22
+ env: dict[str, str] | None = None
23
+ description: str = ""
24
+
25
+
26
+ @dataclass(frozen=True)
27
+ class InvariantEnforcer:
28
+ """A check tied to a safety invariant (S1..S6) that an adapter wants to
29
+ enforce on this stack. Runs within the quality-gate or a dedicated
30
+ `invariants` job; failure is merge-blocking.
31
+ """
32
+
33
+ invariant: str # e.g. "S1", "S3"
34
+ description: str
35
+ cmd: list[str] # shell command; nonzero exit = violation
36
+
37
+
38
+ @runtime_checkable
39
+ class StackAdapter(Protocol):
40
+ """Every adapter implements this. See `BaseAdapter` for a default skeleton."""
41
+
42
+ name: str # e.g. "py-fastapi", "node-next"
43
+ language: str # "python", "typescript", "go", ...
44
+ detect_files: list[str] # file globs whose presence indicates this stack
45
+ detect_imports: list[str] # optional: substrings that must appear in a detect_file
46
+ priority: int # higher wins when multiple adapters match
47
+
48
+ def detect(self, project_root: Path) -> bool:
49
+ """Return True if this adapter applies to the given project root."""
50
+ ...
51
+
52
+ def lint_cmd(self, project_root: Path) -> list[CommandSpec]: ...
53
+ def format_check_cmd(self, project_root: Path) -> list[CommandSpec]: ...
54
+ def typecheck_cmd(self, project_root: Path) -> list[CommandSpec]: ...
55
+ def test_cmd(self, project_root: Path) -> list[CommandSpec]: ...
56
+ def build_cmd(self, project_root: Path) -> list[CommandSpec]: ...
57
+ def dep_audit_cmd(self, project_root: Path) -> list[CommandSpec]: ...
58
+
59
+ def contract_generator(self, project_root: Path) -> CommandSpec | None:
60
+ """Command that regenerates `.ai-sync/contracts/openapi.yaml` (or None)."""
61
+ ...
62
+
63
+ def invariant_enforcers(self, project_root: Path) -> list[InvariantEnforcer]: ...
64
+
65
+
66
+ __all__ = ["CommandSpec", "InvariantEnforcer", "StackAdapter"]