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.
- hexaswarm_core-0.1.1/.gitignore +63 -0
- hexaswarm_core-0.1.1/PKG-INFO +64 -0
- hexaswarm_core-0.1.1/README.md +31 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/__init__.py +7 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/_version.py +1 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/adapters/__init__.py +12 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/adapters/_node_base.py +66 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/adapters/base.py +70 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/adapters/generic_shell.py +47 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/adapters/go_chi.py +47 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/adapters/kotlin_ktor.py +55 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/adapters/node_express.py +38 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/adapters/node_nest.py +36 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/adapters/node_next.py +41 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/adapters/protocol.py +66 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/adapters/py_celery.py +68 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/adapters/py_click.py +75 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/adapters/py_django.py +101 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/adapters/py_fastapi.py +94 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/adapters/registry.py +58 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/adapters/rust_axum.py +46 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/adapters/swift_vapor.py +42 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/archetypes/__init__.py +15 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/archetypes/definitions.py +261 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/archetypes/protocol.py +54 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/archetypes/registry.py +122 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/__init__.py +21 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.ai-sync/ARCHITECTURE_DECISIONS/0001-adopted-hexa.md +22 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.ai-sync/CODEOWNERS.md +29 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.ai-sync/RULES.md +30 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.ai-sync/SYSTEM_STATE.md +22 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.ai-sync/audit/.gitkeep +0 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.ai-sync/changelog/BE_CURRENT.md +10 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.ai-sync/changelog/FE_CURRENT.md +10 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.ai-sync/contracts/README.md +8 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.ai-sync/events/.gitkeep +0 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.ai-sync/locks/.gitkeep +0 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.ai-sync/plans/.gitkeep +0 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/agents/alpha-data.md +31 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/agents/beta-core.md +34 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/agents/cursor-fe.md +36 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/agents/delta-redteam.md +45 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/agents/epsilon-edge.md +36 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/agents/explorer.md +40 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/agents/gamma-commerce.md +34 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/agents/planner.md +30 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/agents/reviewer.md +48 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/agents/writer.md +37 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/hooks/post_edit_ownership.py +113 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/hooks/post_session_archive.py +62 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/hooks/post_write_validate.py +100 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/hooks/pre_tool_use_guard.py +104 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/hooks/session_start_lock_cleanup.py +43 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/hooks/statusline.sh +32 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/mcp.json +21 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/settings.json +60 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/skills/auto-test/SKILL.md +38 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/skills/contract-sync/SKILL.md +41 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/skills/cost-tracker/SKILL.md +45 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/skills/cross-sync-alert/SKILL.md +32 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/skills/deploy-check/SKILL.md +33 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/skills/fix-issue/SKILL.md +48 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/skills/quality-gate/SKILL.md +49 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/skills/security-audit/SKILL.md +48 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/skills/ship/SKILL.md +33 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/assets/tier_a/.claude/skills/worktree-boot/SKILL.md +44 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/cli/__init__.py +0 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/cli/__main__.py +6 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/cli/adopt.py +125 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/cli/app.py +71 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/cli/archetype.py +31 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/cli/contract.py +57 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/cli/cost.py +53 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/cli/heartbeat.py +29 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/cli/killswitch.py +33 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/cli/lock.py +49 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/cli/quality_gate.py +128 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/cli/session.py +35 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/cli/worktree.py +70 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/exceptions.py +94 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/install.py +124 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/invariants.py +143 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/llm/__init__.py +7 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/llm/base.py +176 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/locks/__init__.py +17 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/locks/file_lock.py +156 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/logging/__init__.py +17 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/logging/config.py +103 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/logging/tracing.py +139 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/mcp/__init__.py +6 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/mcp/openapi_server.py +171 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/orchestrator/__init__.py +13 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/orchestrator/pipeline.py +138 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/orchestrator/stage.py +57 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/profile.py +94 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/providers/__init__.py +7 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/providers/base.py +47 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/safety/__init__.py +11 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/safety/ceiling.py +45 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/safety/killswitch.py +47 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/safety/prompt.py +107 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/session.py +55 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/swarm/__init__.py +26 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/swarm/contract_writer.py +126 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/swarm/heartbeat.py +77 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/swarm/worktree.py +143 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/telemetry/__init__.py +3 -0
- hexaswarm_core-0.1.1/hexa_swarm_core/telemetry/cost.py +104 -0
- hexaswarm_core-0.1.1/pyproject.toml +90 -0
- hexaswarm_core-0.1.1/tests/__init__.py +0 -0
- hexaswarm_core-0.1.1/tests/conftest.py +38 -0
- hexaswarm_core-0.1.1/tests/test_adapters.py +61 -0
- hexaswarm_core-0.1.1/tests/test_archetypes.py +108 -0
- hexaswarm_core-0.1.1/tests/test_cli.py +137 -0
- hexaswarm_core-0.1.1/tests/test_cost_and_safety.py +54 -0
- hexaswarm_core-0.1.1/tests/test_install.py +90 -0
- hexaswarm_core-0.1.1/tests/test_invariants.py +77 -0
- hexaswarm_core-0.1.1/tests/test_llm.py +107 -0
- hexaswarm_core-0.1.1/tests/test_locks.py +73 -0
- hexaswarm_core-0.1.1/tests/test_logging.py +26 -0
- hexaswarm_core-0.1.1/tests/test_mcp_openapi.py +97 -0
- hexaswarm_core-0.1.1/tests/test_new_adapters.py +130 -0
- hexaswarm_core-0.1.1/tests/test_orchestrator.py +130 -0
- hexaswarm_core-0.1.1/tests/test_profile_session.py +49 -0
- hexaswarm_core-0.1.1/tests/test_prompt_safety.py +67 -0
- hexaswarm_core-0.1.1/tests/test_swarm.py +169 -0
- hexaswarm_core-0.1.1/tests/test_tracing.py +108 -0
- hexaswarm_core-0.1.1/tests/test_w7.py +206 -0
- 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 @@
|
|
|
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"]
|