hexaswarm-core 0.1.1__py3-none-any.whl

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 (109) hide show
  1. hexa_swarm_core/__init__.py +7 -0
  2. hexa_swarm_core/_version.py +1 -0
  3. hexa_swarm_core/adapters/__init__.py +12 -0
  4. hexa_swarm_core/adapters/_node_base.py +66 -0
  5. hexa_swarm_core/adapters/base.py +70 -0
  6. hexa_swarm_core/adapters/generic_shell.py +47 -0
  7. hexa_swarm_core/adapters/go_chi.py +47 -0
  8. hexa_swarm_core/adapters/kotlin_ktor.py +55 -0
  9. hexa_swarm_core/adapters/node_express.py +38 -0
  10. hexa_swarm_core/adapters/node_nest.py +36 -0
  11. hexa_swarm_core/adapters/node_next.py +41 -0
  12. hexa_swarm_core/adapters/protocol.py +66 -0
  13. hexa_swarm_core/adapters/py_celery.py +68 -0
  14. hexa_swarm_core/adapters/py_click.py +75 -0
  15. hexa_swarm_core/adapters/py_django.py +101 -0
  16. hexa_swarm_core/adapters/py_fastapi.py +94 -0
  17. hexa_swarm_core/adapters/registry.py +58 -0
  18. hexa_swarm_core/adapters/rust_axum.py +46 -0
  19. hexa_swarm_core/adapters/swift_vapor.py +42 -0
  20. hexa_swarm_core/archetypes/__init__.py +15 -0
  21. hexa_swarm_core/archetypes/definitions.py +261 -0
  22. hexa_swarm_core/archetypes/protocol.py +54 -0
  23. hexa_swarm_core/archetypes/registry.py +122 -0
  24. hexa_swarm_core/assets/__init__.py +21 -0
  25. hexa_swarm_core/assets/tier_a/.ai-sync/ARCHITECTURE_DECISIONS/0001-adopted-hexa.md +22 -0
  26. hexa_swarm_core/assets/tier_a/.ai-sync/CODEOWNERS.md +29 -0
  27. hexa_swarm_core/assets/tier_a/.ai-sync/RULES.md +30 -0
  28. hexa_swarm_core/assets/tier_a/.ai-sync/SYSTEM_STATE.md +22 -0
  29. hexa_swarm_core/assets/tier_a/.ai-sync/audit/.gitkeep +0 -0
  30. hexa_swarm_core/assets/tier_a/.ai-sync/changelog/BE_CURRENT.md +10 -0
  31. hexa_swarm_core/assets/tier_a/.ai-sync/changelog/FE_CURRENT.md +10 -0
  32. hexa_swarm_core/assets/tier_a/.ai-sync/contracts/README.md +8 -0
  33. hexa_swarm_core/assets/tier_a/.ai-sync/events/.gitkeep +0 -0
  34. hexa_swarm_core/assets/tier_a/.ai-sync/locks/.gitkeep +0 -0
  35. hexa_swarm_core/assets/tier_a/.ai-sync/plans/.gitkeep +0 -0
  36. hexa_swarm_core/assets/tier_a/.claude/agents/alpha-data.md +31 -0
  37. hexa_swarm_core/assets/tier_a/.claude/agents/beta-core.md +34 -0
  38. hexa_swarm_core/assets/tier_a/.claude/agents/cursor-fe.md +36 -0
  39. hexa_swarm_core/assets/tier_a/.claude/agents/delta-redteam.md +45 -0
  40. hexa_swarm_core/assets/tier_a/.claude/agents/epsilon-edge.md +36 -0
  41. hexa_swarm_core/assets/tier_a/.claude/agents/explorer.md +40 -0
  42. hexa_swarm_core/assets/tier_a/.claude/agents/gamma-commerce.md +34 -0
  43. hexa_swarm_core/assets/tier_a/.claude/agents/planner.md +30 -0
  44. hexa_swarm_core/assets/tier_a/.claude/agents/reviewer.md +48 -0
  45. hexa_swarm_core/assets/tier_a/.claude/agents/writer.md +37 -0
  46. hexa_swarm_core/assets/tier_a/.claude/hooks/post_edit_ownership.py +113 -0
  47. hexa_swarm_core/assets/tier_a/.claude/hooks/post_session_archive.py +62 -0
  48. hexa_swarm_core/assets/tier_a/.claude/hooks/post_write_validate.py +100 -0
  49. hexa_swarm_core/assets/tier_a/.claude/hooks/pre_tool_use_guard.py +104 -0
  50. hexa_swarm_core/assets/tier_a/.claude/hooks/session_start_lock_cleanup.py +43 -0
  51. hexa_swarm_core/assets/tier_a/.claude/hooks/statusline.sh +32 -0
  52. hexa_swarm_core/assets/tier_a/.claude/mcp.json +21 -0
  53. hexa_swarm_core/assets/tier_a/.claude/settings.json +60 -0
  54. hexa_swarm_core/assets/tier_a/.claude/skills/auto-test/SKILL.md +38 -0
  55. hexa_swarm_core/assets/tier_a/.claude/skills/contract-sync/SKILL.md +41 -0
  56. hexa_swarm_core/assets/tier_a/.claude/skills/cost-tracker/SKILL.md +45 -0
  57. hexa_swarm_core/assets/tier_a/.claude/skills/cross-sync-alert/SKILL.md +32 -0
  58. hexa_swarm_core/assets/tier_a/.claude/skills/deploy-check/SKILL.md +33 -0
  59. hexa_swarm_core/assets/tier_a/.claude/skills/fix-issue/SKILL.md +48 -0
  60. hexa_swarm_core/assets/tier_a/.claude/skills/quality-gate/SKILL.md +49 -0
  61. hexa_swarm_core/assets/tier_a/.claude/skills/security-audit/SKILL.md +48 -0
  62. hexa_swarm_core/assets/tier_a/.claude/skills/ship/SKILL.md +33 -0
  63. hexa_swarm_core/assets/tier_a/.claude/skills/worktree-boot/SKILL.md +44 -0
  64. hexa_swarm_core/cli/__init__.py +0 -0
  65. hexa_swarm_core/cli/__main__.py +6 -0
  66. hexa_swarm_core/cli/adopt.py +125 -0
  67. hexa_swarm_core/cli/app.py +71 -0
  68. hexa_swarm_core/cli/archetype.py +31 -0
  69. hexa_swarm_core/cli/contract.py +57 -0
  70. hexa_swarm_core/cli/cost.py +53 -0
  71. hexa_swarm_core/cli/heartbeat.py +29 -0
  72. hexa_swarm_core/cli/killswitch.py +33 -0
  73. hexa_swarm_core/cli/lock.py +49 -0
  74. hexa_swarm_core/cli/quality_gate.py +128 -0
  75. hexa_swarm_core/cli/session.py +35 -0
  76. hexa_swarm_core/cli/worktree.py +70 -0
  77. hexa_swarm_core/exceptions.py +94 -0
  78. hexa_swarm_core/install.py +124 -0
  79. hexa_swarm_core/invariants.py +143 -0
  80. hexa_swarm_core/llm/__init__.py +7 -0
  81. hexa_swarm_core/llm/base.py +176 -0
  82. hexa_swarm_core/locks/__init__.py +17 -0
  83. hexa_swarm_core/locks/file_lock.py +156 -0
  84. hexa_swarm_core/logging/__init__.py +17 -0
  85. hexa_swarm_core/logging/config.py +103 -0
  86. hexa_swarm_core/logging/tracing.py +139 -0
  87. hexa_swarm_core/mcp/__init__.py +6 -0
  88. hexa_swarm_core/mcp/openapi_server.py +171 -0
  89. hexa_swarm_core/orchestrator/__init__.py +13 -0
  90. hexa_swarm_core/orchestrator/pipeline.py +138 -0
  91. hexa_swarm_core/orchestrator/stage.py +57 -0
  92. hexa_swarm_core/profile.py +94 -0
  93. hexa_swarm_core/providers/__init__.py +7 -0
  94. hexa_swarm_core/providers/base.py +47 -0
  95. hexa_swarm_core/safety/__init__.py +11 -0
  96. hexa_swarm_core/safety/ceiling.py +45 -0
  97. hexa_swarm_core/safety/killswitch.py +47 -0
  98. hexa_swarm_core/safety/prompt.py +107 -0
  99. hexa_swarm_core/session.py +55 -0
  100. hexa_swarm_core/swarm/__init__.py +26 -0
  101. hexa_swarm_core/swarm/contract_writer.py +126 -0
  102. hexa_swarm_core/swarm/heartbeat.py +77 -0
  103. hexa_swarm_core/swarm/worktree.py +143 -0
  104. hexa_swarm_core/telemetry/__init__.py +3 -0
  105. hexa_swarm_core/telemetry/cost.py +104 -0
  106. hexaswarm_core-0.1.1.dist-info/METADATA +64 -0
  107. hexaswarm_core-0.1.1.dist-info/RECORD +109 -0
  108. hexaswarm_core-0.1.1.dist-info/WHEEL +4 -0
  109. hexaswarm_core-0.1.1.dist-info/entry_points.txt +2 -0
@@ -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"]
@@ -0,0 +1,68 @@
1
+ """py-celery StackAdapter - Celery workers / Airflow / staged Python pipelines."""
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 PyCeleryAdapter(BaseAdapter):
12
+ name = "py-celery"
13
+ language = "python"
14
+ detect_files = [
15
+ "pyproject.toml",
16
+ "requirements.txt",
17
+ "src/backend/pyproject.toml",
18
+ "src/backend/requirements.txt",
19
+ "backend/pyproject.toml",
20
+ "backend/requirements.txt",
21
+ ]
22
+ # airflow shares the "staged pipeline" archetype shape; also route to this.
23
+ detect_imports = ["celery", "apache-airflow", "prefect"]
24
+ # Below fastapi (70): a FastAPI app with celery workers should still be
25
+ # identified as py-fastapi primarily, with py-celery as an add-on.
26
+ priority = 60
27
+
28
+ def lint_cmd(self, project_root: Path) -> list[CommandSpec]: # noqa: ARG002
29
+ return [CommandSpec(["ruff", "check", "."], description="ruff check")]
30
+
31
+ def format_check_cmd(self, project_root: Path) -> list[CommandSpec]: # noqa: ARG002
32
+ return [CommandSpec(["ruff", "format", "--check", "."], description="ruff format")]
33
+
34
+ def typecheck_cmd(self, project_root: Path) -> list[CommandSpec]: # noqa: ARG002
35
+ return [CommandSpec(["mypy", "."], description="mypy")]
36
+
37
+ def test_cmd(self, project_root: Path) -> list[CommandSpec]: # noqa: ARG002
38
+ return [
39
+ CommandSpec(
40
+ ["pytest", "-m", "not slow and not e2e", "-q", "--timeout=60"],
41
+ description="pytest (pipeline tests)",
42
+ )
43
+ ]
44
+
45
+ def dep_audit_cmd(self, project_root: Path) -> list[CommandSpec]:
46
+ if (project_root / "requirements.txt").exists():
47
+ return [
48
+ CommandSpec(["python", "-m", "pip", "install", "--quiet", "pip-audit"], description="ensure pip-audit"),
49
+ CommandSpec(["pip-audit", "-r", "requirements.txt"], description="pip-audit"),
50
+ ]
51
+ return [
52
+ CommandSpec(["python", "-m", "pip", "install", "--quiet", "pip-audit"], description="ensure pip-audit"),
53
+ CommandSpec(["pip-audit"], description="pip-audit (env)"),
54
+ ]
55
+
56
+ def invariant_enforcers(self, project_root: Path) -> list[InvariantEnforcer]: # noqa: ARG002
57
+ # S3 (idempotency) is critical for pipeline stages - if a stage retries
58
+ # after a killswitch, it must not double-charge / double-write.
59
+ return [
60
+ InvariantEnforcer(
61
+ invariant="S3",
62
+ description="Pipeline stages marked @idempotent must pass resume-safety checks",
63
+ cmd=["pytest", "-m", "invariants and idempotent", "-q"],
64
+ ),
65
+ ]
66
+
67
+
68
+ __all__ = ["PyCeleryAdapter"]
@@ -0,0 +1,75 @@
1
+ """py-click StackAdapter — Python CLI tools built with Click or Typer."""
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 PyClickAdapter(BaseAdapter):
12
+ name = "py-click"
13
+ language = "python"
14
+ # CLI-only projects live at the repo root; monorepos may nest backends.
15
+ # Accept both layouts so a fullstack repo with click under src/backend
16
+ # still surfaces the adapter (below fastapi's priority, so it only fires
17
+ # as a fallback or for repos with no fastapi dep).
18
+ detect_files = [
19
+ "pyproject.toml",
20
+ "requirements.txt",
21
+ "setup.py",
22
+ "setup.cfg",
23
+ "src/backend/pyproject.toml",
24
+ "src/backend/requirements.txt",
25
+ "backend/pyproject.toml",
26
+ "backend/requirements.txt",
27
+ ]
28
+ detect_imports = ["click", "typer"] # typer is a click superset
29
+ # Lower than py-fastapi so fullstack projects that also import click
30
+ # (e.g. for a `manage.py`) don't steal the adapter slot.
31
+ priority = 55
32
+
33
+ def lint_cmd(self, project_root: Path) -> list[CommandSpec]: # noqa: ARG002
34
+ return [CommandSpec(["ruff", "check", "."], description="ruff check")]
35
+
36
+ def format_check_cmd(self, project_root: Path) -> list[CommandSpec]: # noqa: ARG002
37
+ return [CommandSpec(["ruff", "format", "--check", "."], description="ruff format")]
38
+
39
+ def typecheck_cmd(self, project_root: Path) -> list[CommandSpec]: # noqa: ARG002
40
+ return [CommandSpec(["mypy", "."], description="mypy")]
41
+
42
+ def test_cmd(self, project_root: Path) -> list[CommandSpec]: # noqa: ARG002
43
+ return [
44
+ CommandSpec(
45
+ ["pytest", "-m", "not slow and not e2e", "-q", "--timeout=30"],
46
+ description="pytest",
47
+ )
48
+ ]
49
+
50
+ def build_cmd(self, project_root: Path) -> list[CommandSpec]: # noqa: ARG002
51
+ # Optional: produce a wheel so distribution smoke is part of the gate.
52
+ return [CommandSpec(["python", "-m", "pip", "wheel", "--no-deps", "."], description="pip wheel")]
53
+
54
+ def dep_audit_cmd(self, project_root: Path) -> list[CommandSpec]:
55
+ if (project_root / "requirements.txt").exists():
56
+ return [
57
+ CommandSpec(["python", "-m", "pip", "install", "--quiet", "pip-audit"], description="ensure pip-audit"),
58
+ CommandSpec(["pip-audit", "-r", "requirements.txt"], description="pip-audit"),
59
+ ]
60
+ return [
61
+ CommandSpec(["python", "-m", "pip", "install", "--quiet", "pip-audit"], description="ensure pip-audit"),
62
+ CommandSpec(["pip-audit"], description="pip-audit (env)"),
63
+ ]
64
+
65
+ def invariant_enforcers(self, project_root: Path) -> list[InvariantEnforcer]: # noqa: ARG002
66
+ return [
67
+ InvariantEnforcer(
68
+ invariant="S2",
69
+ description="CLI functions marked @deterministic must remain deterministic",
70
+ cmd=["pytest", "-m", "invariants and deterministic", "-q"],
71
+ )
72
+ ]
73
+
74
+
75
+ __all__ = ["PyClickAdapter"]