arcgentic 0.2.2a3__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.
- arcgentic-0.2.2a3/.gitignore +38 -0
- arcgentic-0.2.2a3/PKG-INFO +71 -0
- arcgentic-0.2.2a3/README.md +48 -0
- arcgentic-0.2.2a3/pyproject.toml +73 -0
- arcgentic-0.2.2a3/src/arcgentic/__init__.py +5 -0
- arcgentic-0.2.2a3/src/arcgentic/adapters/__init__.py +77 -0
- arcgentic-0.2.2a3/src/arcgentic/adapters/_local_env.py +125 -0
- arcgentic-0.2.2a3/src/arcgentic/adapters/base.py +108 -0
- arcgentic-0.2.2a3/src/arcgentic/adapters/claude_code.py +223 -0
- arcgentic-0.2.2a3/src/arcgentic/adapters/codex_cli.py +145 -0
- arcgentic-0.2.2a3/src/arcgentic/adapters/cursor.py +136 -0
- arcgentic-0.2.2a3/src/arcgentic/adapters/inline.py +101 -0
- arcgentic-0.2.2a3/src/arcgentic/adapters/vscode_codex.py +143 -0
- arcgentic-0.2.2a3/src/arcgentic/audit_check.py +422 -0
- arcgentic-0.2.2a3/src/arcgentic/cli.py +396 -0
- arcgentic-0.2.2a3/src/arcgentic/hooks/__init__.py +1 -0
- arcgentic-0.2.2a3/src/arcgentic/hooks/quality_gate_enforce.py +235 -0
- arcgentic-0.2.2a3/src/arcgentic/hooks/round_boundary_lesson_scan.py +79 -0
- arcgentic-0.2.2a3/src/arcgentic/skills_impl/__init__.py +1 -0
- arcgentic-0.2.2a3/src/arcgentic/skills_impl/codify_lesson.py +118 -0
- arcgentic-0.2.2a3/src/arcgentic/skills_impl/cross_session_handoff.py +157 -0
- arcgentic-0.2.2a3/src/arcgentic/skills_impl/execute_round.py +695 -0
- arcgentic-0.2.2a3/src/arcgentic/skills_impl/plan_round.py +338 -0
- arcgentic-0.2.2a3/src/arcgentic/skills_impl/track_refs.py +221 -0
- arcgentic-0.2.2a3/src/arcgentic/source_rules.py +134 -0
- arcgentic-0.2.2a3/src/arcgentic/utils/__init__.py +2 -0
- arcgentic-0.2.2a3/src/arcgentic/utils/pattern_detection.py +229 -0
- arcgentic-0.2.2a3/tests/__init__.py +0 -0
- arcgentic-0.2.2a3/tests/fixtures/sample_self_audit.md +21 -0
- arcgentic-0.2.2a3/tests/integration/__init__.py +0 -0
- arcgentic-0.2.2a3/tests/integration/test_end_to_end_round.py +332 -0
- arcgentic-0.2.2a3/tests/unit/__init__.py +0 -0
- arcgentic-0.2.2a3/tests/unit/adapters/__init__.py +0 -0
- arcgentic-0.2.2a3/tests/unit/adapters/test_base.py +163 -0
- arcgentic-0.2.2a3/tests/unit/adapters/test_claude_code.py +415 -0
- arcgentic-0.2.2a3/tests/unit/adapters/test_codex_cli.py +284 -0
- arcgentic-0.2.2a3/tests/unit/adapters/test_cursor.py +229 -0
- arcgentic-0.2.2a3/tests/unit/adapters/test_detect.py +278 -0
- arcgentic-0.2.2a3/tests/unit/adapters/test_inline.py +156 -0
- arcgentic-0.2.2a3/tests/unit/adapters/test_local_env.py +231 -0
- arcgentic-0.2.2a3/tests/unit/adapters/test_vscode_codex.py +275 -0
- arcgentic-0.2.2a3/tests/unit/hooks/__init__.py +0 -0
- arcgentic-0.2.2a3/tests/unit/hooks/test_quality_gate_enforce.py +433 -0
- arcgentic-0.2.2a3/tests/unit/hooks/test_round_boundary_lesson_scan.py +47 -0
- arcgentic-0.2.2a3/tests/unit/skills_impl/__init__.py +0 -0
- arcgentic-0.2.2a3/tests/unit/skills_impl/test_codify_lesson.py +43 -0
- arcgentic-0.2.2a3/tests/unit/skills_impl/test_cross_session_handoff.py +67 -0
- arcgentic-0.2.2a3/tests/unit/skills_impl/test_execute_round.py +944 -0
- arcgentic-0.2.2a3/tests/unit/skills_impl/test_plan_round.py +561 -0
- arcgentic-0.2.2a3/tests/unit/skills_impl/test_track_refs.py +72 -0
- arcgentic-0.2.2a3/tests/unit/test_audit_check.py +622 -0
- arcgentic-0.2.2a3/tests/unit/test_cli.py +397 -0
- arcgentic-0.2.2a3/tests/unit/test_pattern_detection.py +58 -0
- arcgentic-0.2.2a3/tests/unit/test_source_rules.py +107 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# macOS
|
|
2
|
+
.DS_Store
|
|
3
|
+
|
|
4
|
+
# Editor
|
|
5
|
+
.vscode/
|
|
6
|
+
.idea/
|
|
7
|
+
*.swp
|
|
8
|
+
*.swo
|
|
9
|
+
|
|
10
|
+
# Claude Code agent state (worktree metadata + per-session settings)
|
|
11
|
+
.claude/
|
|
12
|
+
|
|
13
|
+
# Logs
|
|
14
|
+
*.log
|
|
15
|
+
|
|
16
|
+
# Node (for tooling)
|
|
17
|
+
node_modules/
|
|
18
|
+
|
|
19
|
+
# Python (for tooling / tests)
|
|
20
|
+
__pycache__/
|
|
21
|
+
*.pyc
|
|
22
|
+
.pytest_cache/
|
|
23
|
+
.mypy_cache/
|
|
24
|
+
.ruff_cache/
|
|
25
|
+
|
|
26
|
+
# Build artifacts
|
|
27
|
+
dist/
|
|
28
|
+
build/
|
|
29
|
+
*.egg-info/
|
|
30
|
+
|
|
31
|
+
# Dogfood state (gitignore by default; projects opt-in to committing it)
|
|
32
|
+
.agentic-rounds/
|
|
33
|
+
|
|
34
|
+
# Local-only references (huge, gitignored per arcgentic ref-first discipline)
|
|
35
|
+
references/
|
|
36
|
+
# But skill reference docs inside the plugin itself ARE tracked
|
|
37
|
+
!skills/*/references/
|
|
38
|
+
!skills/*/references/**
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: arcgentic
|
|
3
|
+
Version: 0.2.2a3
|
|
4
|
+
Summary: Agentic harness for rigorous round-driven development — Python CLI for the arcgentic Claude Code plugin
|
|
5
|
+
Project-URL: Homepage, https://github.com/Arch1eSUN/Arcgentic
|
|
6
|
+
Project-URL: Repository, https://github.com/Arch1eSUN/Arcgentic
|
|
7
|
+
Author: Arc Studio
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: agentic-harness,claude-code,ide-adapter,round-driven-development
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
15
|
+
Requires-Python: >=3.13
|
|
16
|
+
Requires-Dist: jsonschema>=4.0
|
|
17
|
+
Requires-Dist: pyyaml>=6.0
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: mypy>=1.14; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
21
|
+
Requires-Dist: ruff>=0.15; extra == 'dev'
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# arcgentic toolkit
|
|
25
|
+
|
|
26
|
+
Python CLI + adapter layer for the `arcgentic` Claude Code plugin.
|
|
27
|
+
|
|
28
|
+
This is the **toolkit surface** of the arcgentic hybrid monorepo (see
|
|
29
|
+
[Spec Amendment 01](../docs/plans/2026-05-13-arcgentic-v0.2.0-spec-amendment-01-layout.md)
|
|
30
|
+
for why). The plugin surface (`skills/`, `agents/`, `hooks/`, `.githooks/` at repo root)
|
|
31
|
+
provides markdown contracts discoverable by Claude Code; this toolkit provides the
|
|
32
|
+
Python implementation that markdown skills shell out to.
|
|
33
|
+
|
|
34
|
+
## Install (dev)
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
cd toolkit
|
|
38
|
+
pip install -e ".[dev]"
|
|
39
|
+
arcgentic --help
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## CLI commands
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
arcgentic plan-round-impl --round R1.0 --type substrate-touching --anchor <sha40>
|
|
46
|
+
arcgentic execute-round-impl --round R1.0 --handoff docs/superpowers/plans/R1.0.md
|
|
47
|
+
arcgentic audit-check docs/audits/R1.0.md --strict-extended
|
|
48
|
+
arcgentic quality-gate-enforce --repo-root .
|
|
49
|
+
arcgentic validate-handoff docs/superpowers/plans/R1.0.md
|
|
50
|
+
arcgentic codify-lesson --audit-dir docs/audits
|
|
51
|
+
arcgentic track-refs add references/example --owner-repo owner/repo --round R1 --usage-evidence '{"pattern_only": true}'
|
|
52
|
+
arcgentic cross-session-handoff read
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Quality gates (run from `toolkit/`)
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
mypy --strict src/ tests/
|
|
59
|
+
pytest --tb=no
|
|
60
|
+
ruff check .
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Layout
|
|
64
|
+
|
|
65
|
+
- `src/arcgentic/adapters/` — IDE adapter Protocol + implementations
|
|
66
|
+
- `src/arcgentic/skills_impl/` — skill implementation backends
|
|
67
|
+
- `src/arcgentic/audit_check.py` — mechanical audit fact checker
|
|
68
|
+
- `src/arcgentic/source_rules.py` — Moirai-derived source-rule contract validators
|
|
69
|
+
- `src/arcgentic/utils/pattern_detection.py` — repeated audit-pattern clustering for lessons
|
|
70
|
+
- `src/arcgentic/cli.py` — command-line bridge for skills, gates, and validators
|
|
71
|
+
- `tests/unit/`, `tests/integration/` — pytest suites
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# arcgentic toolkit
|
|
2
|
+
|
|
3
|
+
Python CLI + adapter layer for the `arcgentic` Claude Code plugin.
|
|
4
|
+
|
|
5
|
+
This is the **toolkit surface** of the arcgentic hybrid monorepo (see
|
|
6
|
+
[Spec Amendment 01](../docs/plans/2026-05-13-arcgentic-v0.2.0-spec-amendment-01-layout.md)
|
|
7
|
+
for why). The plugin surface (`skills/`, `agents/`, `hooks/`, `.githooks/` at repo root)
|
|
8
|
+
provides markdown contracts discoverable by Claude Code; this toolkit provides the
|
|
9
|
+
Python implementation that markdown skills shell out to.
|
|
10
|
+
|
|
11
|
+
## Install (dev)
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
cd toolkit
|
|
15
|
+
pip install -e ".[dev]"
|
|
16
|
+
arcgentic --help
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## CLI commands
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
arcgentic plan-round-impl --round R1.0 --type substrate-touching --anchor <sha40>
|
|
23
|
+
arcgentic execute-round-impl --round R1.0 --handoff docs/superpowers/plans/R1.0.md
|
|
24
|
+
arcgentic audit-check docs/audits/R1.0.md --strict-extended
|
|
25
|
+
arcgentic quality-gate-enforce --repo-root .
|
|
26
|
+
arcgentic validate-handoff docs/superpowers/plans/R1.0.md
|
|
27
|
+
arcgentic codify-lesson --audit-dir docs/audits
|
|
28
|
+
arcgentic track-refs add references/example --owner-repo owner/repo --round R1 --usage-evidence '{"pattern_only": true}'
|
|
29
|
+
arcgentic cross-session-handoff read
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Quality gates (run from `toolkit/`)
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
mypy --strict src/ tests/
|
|
36
|
+
pytest --tb=no
|
|
37
|
+
ruff check .
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Layout
|
|
41
|
+
|
|
42
|
+
- `src/arcgentic/adapters/` — IDE adapter Protocol + implementations
|
|
43
|
+
- `src/arcgentic/skills_impl/` — skill implementation backends
|
|
44
|
+
- `src/arcgentic/audit_check.py` — mechanical audit fact checker
|
|
45
|
+
- `src/arcgentic/source_rules.py` — Moirai-derived source-rule contract validators
|
|
46
|
+
- `src/arcgentic/utils/pattern_detection.py` — repeated audit-pattern clustering for lessons
|
|
47
|
+
- `src/arcgentic/cli.py` — command-line bridge for skills, gates, and validators
|
|
48
|
+
- `tests/unit/`, `tests/integration/` — pytest suites
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "arcgentic"
|
|
7
|
+
version = "0.2.2-alpha.3"
|
|
8
|
+
description = "Agentic harness for rigorous round-driven development — Python CLI for the arcgentic Claude Code plugin"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.13"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Arc Studio" }]
|
|
13
|
+
keywords = ["agentic-harness", "round-driven-development", "claude-code", "ide-adapter"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3.13",
|
|
19
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"pyyaml>=6.0",
|
|
23
|
+
"jsonschema>=4.0",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.optional-dependencies]
|
|
27
|
+
dev = [
|
|
28
|
+
"mypy>=1.14",
|
|
29
|
+
"pytest>=8.0",
|
|
30
|
+
"ruff>=0.15",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.scripts]
|
|
34
|
+
arcgentic = "arcgentic.cli:main"
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://github.com/Arch1eSUN/Arcgentic"
|
|
38
|
+
Repository = "https://github.com/Arch1eSUN/Arcgentic"
|
|
39
|
+
|
|
40
|
+
[tool.hatch.build.targets.wheel]
|
|
41
|
+
packages = ["src/arcgentic"]
|
|
42
|
+
|
|
43
|
+
# === Quality gates per spec § 17 ===
|
|
44
|
+
|
|
45
|
+
[tool.mypy]
|
|
46
|
+
strict = true
|
|
47
|
+
python_version = "3.13"
|
|
48
|
+
files = ["src/", "tests/"]
|
|
49
|
+
exclude = ["build/", "dist/"]
|
|
50
|
+
|
|
51
|
+
[tool.pytest.ini_options]
|
|
52
|
+
testpaths = ["tests"]
|
|
53
|
+
python_files = ["test_*.py"]
|
|
54
|
+
addopts = "--strict-markers"
|
|
55
|
+
markers = [
|
|
56
|
+
"integration: slow integration tests (deselect with '-m \"not integration\"')",
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
[tool.ruff]
|
|
60
|
+
line-length = 100
|
|
61
|
+
target-version = "py313"
|
|
62
|
+
exclude = ["build/", "dist/"]
|
|
63
|
+
|
|
64
|
+
[tool.ruff.lint]
|
|
65
|
+
select = [
|
|
66
|
+
"E", "W", "F", # default
|
|
67
|
+
"I", # isort
|
|
68
|
+
"UP", # pyupgrade
|
|
69
|
+
"B", # bugbear
|
|
70
|
+
"A", # builtins shadowing
|
|
71
|
+
"N", # pep8-naming
|
|
72
|
+
]
|
|
73
|
+
ignore = []
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""IDE adapter auto-detection.
|
|
2
|
+
|
|
3
|
+
`detect_adapter()` inspects environment variables, filesystem markers, and
|
|
4
|
+
binary availability to select the appropriate adapter for the current host.
|
|
5
|
+
Falls back to InlineAdapter if no LLM host can be confidently identified.
|
|
6
|
+
|
|
7
|
+
Detection rules (each: env-var checks use Python truthiness — empty-string
|
|
8
|
+
env-var values fall through, which is the intended behavior since empty-string
|
|
9
|
+
markers are semantically equivalent to unset):
|
|
10
|
+
1. Claude Code — CLAUDE_CODE_SESSION (non-empty) OR ~/.claude/skills dir exists (home-relative)
|
|
11
|
+
2. Cursor — CURSOR_SESSION (non-empty) OR .cursor/rules dir exists (CWD-relative)
|
|
12
|
+
3. VSCode+Codex — VSCODE_PID (non-empty) AND `codex` binary on PATH
|
|
13
|
+
4. Codex CLI — CODEX_SESSION (non-empty) OR `codex` binary on PATH
|
|
14
|
+
5. Inline — fallback when no LLM host can be identified
|
|
15
|
+
|
|
16
|
+
Public API:
|
|
17
|
+
detect_adapter() -> IDEAdapter
|
|
18
|
+
IDEAdapter (re-export from .base for convenience)
|
|
19
|
+
AgentDispatchResult (re-export from .base)
|
|
20
|
+
|
|
21
|
+
Spec reference: docs/plans/2026-05-13-arcgentic-v0.2.0-spec.md § 3.6
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import os
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from shutil import which
|
|
29
|
+
|
|
30
|
+
from .base import AgentDispatchResult, IDEAdapter
|
|
31
|
+
|
|
32
|
+
__all__ = ["detect_adapter", "IDEAdapter", "AgentDispatchResult"]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def detect_adapter() -> IDEAdapter:
|
|
36
|
+
"""Return the IDEAdapter best matching the current runtime environment.
|
|
37
|
+
|
|
38
|
+
Falls back to InlineAdapter if no LLM host can be detected. Callers should
|
|
39
|
+
NOT assume the returned adapter can dispatch agents (InlineAdapter can't
|
|
40
|
+
actually dispatch — it's a degraded fallback for dry-run / headless use).
|
|
41
|
+
"""
|
|
42
|
+
# 1. Claude Code
|
|
43
|
+
if os.environ.get("CLAUDE_CODE_SESSION") or _has_dir("~/.claude/skills"):
|
|
44
|
+
from .claude_code import ClaudeCodeAdapter
|
|
45
|
+
return ClaudeCodeAdapter()
|
|
46
|
+
|
|
47
|
+
# rule 2 — Cursor (.cursor/rules is project-local, CWD-relative;
|
|
48
|
+
# detect_adapter() picks up the marker only when called from project root)
|
|
49
|
+
if os.environ.get("CURSOR_SESSION") or _has_dir(".cursor/rules"):
|
|
50
|
+
from .cursor import CursorAdapter
|
|
51
|
+
return CursorAdapter()
|
|
52
|
+
|
|
53
|
+
# 3. VSCode + Codex (must have BOTH a VSCode env marker AND codex binary)
|
|
54
|
+
if os.environ.get("VSCODE_PID") and which("codex"):
|
|
55
|
+
from .vscode_codex import VSCodeCodexAdapter
|
|
56
|
+
return VSCodeCodexAdapter()
|
|
57
|
+
|
|
58
|
+
# 4. Codex CLI standalone (CODEX_SESSION env, OR codex binary without VSCode)
|
|
59
|
+
if os.environ.get("CODEX_SESSION") or which("codex"):
|
|
60
|
+
from .codex_cli import CodexCLIAdapter
|
|
61
|
+
return CodexCLIAdapter()
|
|
62
|
+
|
|
63
|
+
# 5. Fallback
|
|
64
|
+
from .inline import InlineAdapter
|
|
65
|
+
return InlineAdapter()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _has_dir(path: str) -> bool:
|
|
69
|
+
"""Return True if `path` is a directory (after ~-expansion).
|
|
70
|
+
|
|
71
|
+
Paths starting with `~` are home-relative (expand to $HOME).
|
|
72
|
+
Other paths are interpreted relative to the current working directory.
|
|
73
|
+
Used by detect_adapter() for both home-scoped markers (e.g. ~/.claude/skills,
|
|
74
|
+
Claude Code installation) and project-scoped markers (e.g. .cursor/rules,
|
|
75
|
+
Cursor project rules).
|
|
76
|
+
"""
|
|
77
|
+
return Path(path).expanduser().is_dir()
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Shared local-environment helpers for IDEAdapter implementations.
|
|
2
|
+
|
|
3
|
+
These functions implement filesystem / shell / git operations that are identical
|
|
4
|
+
across all non-canonical IDE adapters (Cursor / VSCode-Codex / Codex CLI / Inline).
|
|
5
|
+
The canonical ClaudeCodeAdapter inlines these directly; a future cleanup task may
|
|
6
|
+
unify it with this module.
|
|
7
|
+
|
|
8
|
+
These are module-level functions (NOT a class) because they have no state — each
|
|
9
|
+
call is a fresh subprocess or filesystem op. Adapters call them as
|
|
10
|
+
`_local_env.read_file(path)` etc.
|
|
11
|
+
|
|
12
|
+
Spec reference: docs/plans/2026-05-13-arcgentic-v0.2.0-spec.md § 3.3–§ 3.5
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import subprocess
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def read_file(path: str) -> str:
|
|
22
|
+
"""Read a file and return its text content (utf-8)."""
|
|
23
|
+
return Path(path).read_text(encoding="utf-8")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def write_file(path: str, content: str) -> None:
|
|
27
|
+
"""Write `content` to `path` (utf-8); creates or overwrites."""
|
|
28
|
+
Path(path).write_text(content, encoding="utf-8")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def edit_file(path: str, old: str, new: str) -> None:
|
|
32
|
+
"""Replace exactly one occurrence of `old` with `new`.
|
|
33
|
+
|
|
34
|
+
Raises ValueError on zero or multi-match (identical contract to
|
|
35
|
+
ClaudeCodeAdapter.edit_file per spec § 3 IDEAdapter Protocol).
|
|
36
|
+
"""
|
|
37
|
+
p = Path(path)
|
|
38
|
+
text = p.read_text(encoding="utf-8")
|
|
39
|
+
count = text.count(old)
|
|
40
|
+
if count == 0:
|
|
41
|
+
raise ValueError(f"edit_file: `old` not found in {path}")
|
|
42
|
+
if count > 1:
|
|
43
|
+
raise ValueError(f"edit_file: `old` appears {count} times in {path} (ambiguous)")
|
|
44
|
+
p.write_text(text.replace(old, new, 1), encoding="utf-8")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def shell(command: str, timeout_seconds: int = 120) -> tuple[str, int]:
|
|
48
|
+
"""Run a shell command; return (stdout, exit_code).
|
|
49
|
+
|
|
50
|
+
On TimeoutExpired returns ('', 124) — same POSIX timeout convention as
|
|
51
|
+
ClaudeCodeAdapter.shell.
|
|
52
|
+
"""
|
|
53
|
+
try:
|
|
54
|
+
result = subprocess.run(
|
|
55
|
+
command,
|
|
56
|
+
shell=True,
|
|
57
|
+
capture_output=True,
|
|
58
|
+
text=True,
|
|
59
|
+
timeout=timeout_seconds,
|
|
60
|
+
check=False,
|
|
61
|
+
)
|
|
62
|
+
return result.stdout, result.returncode
|
|
63
|
+
except subprocess.TimeoutExpired:
|
|
64
|
+
return "", 124
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _run_git(args: list[str], timeout_seconds: int = 60) -> tuple[str, str, int]:
|
|
68
|
+
"""Run `git <args>` via list-form subprocess (no shell=True).
|
|
69
|
+
|
|
70
|
+
Returns (stdout, stderr, exit_code). Used by git_diff_staged / git_commit
|
|
71
|
+
which need stderr for error diagnostics — shell() can't return stderr
|
|
72
|
+
without breaking the IDEAdapter Protocol's tuple[str, int] signature.
|
|
73
|
+
|
|
74
|
+
Underscore-prefix signals this is an internal helper; callers should use
|
|
75
|
+
git_diff_staged() and git_commit() (the public surface).
|
|
76
|
+
"""
|
|
77
|
+
try:
|
|
78
|
+
result = subprocess.run(
|
|
79
|
+
["git", *args],
|
|
80
|
+
capture_output=True,
|
|
81
|
+
text=True,
|
|
82
|
+
timeout=timeout_seconds,
|
|
83
|
+
check=False,
|
|
84
|
+
)
|
|
85
|
+
return result.stdout, result.stderr, result.returncode
|
|
86
|
+
except subprocess.TimeoutExpired:
|
|
87
|
+
return "", f"git {args[0] if args else ''} timed out", 124
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def git_diff_staged() -> str:
|
|
91
|
+
"""Return the output of `git diff --staged`.
|
|
92
|
+
|
|
93
|
+
Raises RuntimeError if git exits non-zero (e.g., not in a git repo).
|
|
94
|
+
"""
|
|
95
|
+
stdout, stderr, code = _run_git(["diff", "--staged"])
|
|
96
|
+
if code != 0:
|
|
97
|
+
raise RuntimeError(f"git diff --staged failed (exit {code}): {stderr.strip()}")
|
|
98
|
+
return stdout
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def git_commit(message: str, files: list[str] | None = None) -> str:
|
|
102
|
+
"""Stage `files` (if provided) then commit; return the new SHA.
|
|
103
|
+
|
|
104
|
+
If `files` is None, commits whatever is already in the index.
|
|
105
|
+
Does NOT use --no-verify / --no-gpg-sign / --amend per Protocol contract.
|
|
106
|
+
"""
|
|
107
|
+
if files is not None:
|
|
108
|
+
for f in files:
|
|
109
|
+
_, stderr, code = _run_git(["add", "--", f])
|
|
110
|
+
if code != 0:
|
|
111
|
+
raise RuntimeError(f"git add {f} failed (exit {code}): {stderr.strip()}")
|
|
112
|
+
|
|
113
|
+
_, stderr, code = _run_git(["commit", "-m", message])
|
|
114
|
+
if code != 0:
|
|
115
|
+
raise RuntimeError(f"git commit failed (exit {code}): {stderr.strip()}")
|
|
116
|
+
|
|
117
|
+
stdout, stderr, code = _run_git(["rev-parse", "HEAD"])
|
|
118
|
+
if code != 0:
|
|
119
|
+
raise RuntimeError(f"git rev-parse HEAD failed (exit {code}): {stderr.strip()}")
|
|
120
|
+
return stdout.strip()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def shquote(s: str) -> str:
|
|
124
|
+
"""POSIX single-quote escape for safe shell=True interpolation."""
|
|
125
|
+
return "'" + s.replace("'", "'\\''") + "'"
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""IDE Adapter Protocol — the abstraction surface arcgentic skills/CLI use to talk
|
|
2
|
+
to whatever AI agent platform is hosting them (Claude Code / Cursor / VSCode-Codex /
|
|
3
|
+
Codex CLI / inline fallback).
|
|
4
|
+
|
|
5
|
+
Adding a new platform = implementing this Protocol; arcgentic skills/CLI then work
|
|
6
|
+
unchanged.
|
|
7
|
+
|
|
8
|
+
Anti-contamination invariant (spec § 1.5): adapter methods MUST NOT inject
|
|
9
|
+
`tools=` or `tool_choice=` at the agent level. Those belong one layer down
|
|
10
|
+
in the LLM-client layer.
|
|
11
|
+
|
|
12
|
+
Spec reference: docs/plans/2026-05-13-arcgentic-v0.2.0-spec.md § 3.1
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from typing import Literal, Protocol, runtime_checkable
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class AgentDispatchResult:
|
|
23
|
+
"""Result of dispatching a sub-agent through an IDE adapter.
|
|
24
|
+
|
|
25
|
+
`output` : the agent's stdout / response text
|
|
26
|
+
`exit_code` : 0 = success; non-zero = failure
|
|
27
|
+
`duration_ms` : wall-clock ms from dispatch to result
|
|
28
|
+
`agent_type` : the agent_name that was dispatched (echoed back for trace)
|
|
29
|
+
`error` : optional error message (None on success)
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
output: str
|
|
33
|
+
exit_code: int
|
|
34
|
+
duration_ms: int
|
|
35
|
+
agent_type: str
|
|
36
|
+
error: str | None = None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@runtime_checkable
|
|
40
|
+
class IDEAdapter(Protocol):
|
|
41
|
+
"""Adapter for an AI IDE/agent platform.
|
|
42
|
+
|
|
43
|
+
Each platform (claude-code / cursor / vscode-codex / codex-cli / inline)
|
|
44
|
+
implements this Protocol. arcgentic skills + CLI invoke platform-agnostic
|
|
45
|
+
methods; the adapter translates to platform-specific tool calls.
|
|
46
|
+
|
|
47
|
+
Anti-contamination invariant (spec § 1.5): adapter methods MUST NOT inject
|
|
48
|
+
`tools=` or `tool_choice=` at the agent level. Those belong one layer down
|
|
49
|
+
in the LLM-client layer.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
platform_name: str # "claude-code" / "cursor" / "vscode-codex" / "codex-cli" / "inline"
|
|
53
|
+
|
|
54
|
+
def dispatch_agent(
|
|
55
|
+
self,
|
|
56
|
+
agent_name: str,
|
|
57
|
+
prompt: str,
|
|
58
|
+
timeout_seconds: int = 600,
|
|
59
|
+
isolation: Literal["worktree"] | None = None,
|
|
60
|
+
) -> AgentDispatchResult:
|
|
61
|
+
"""Dispatch a sub-agent.
|
|
62
|
+
|
|
63
|
+
`agent_name` maps to a markdown file at agents/<name>.md.
|
|
64
|
+
`prompt` is the full self-contained brief; agent has zero session context.
|
|
65
|
+
Returns the agent's response wrapped in AgentDispatchResult.
|
|
66
|
+
"""
|
|
67
|
+
...
|
|
68
|
+
|
|
69
|
+
def invoke_skill(self, skill_name: str, args: str = "") -> str:
|
|
70
|
+
"""Invoke an arcgentic skill in-process.
|
|
71
|
+
|
|
72
|
+
`skill_name` maps to a markdown file at skills/<name>/SKILL.md.
|
|
73
|
+
`args` is the optional argument string for the skill.
|
|
74
|
+
Returns the skill's textual output.
|
|
75
|
+
"""
|
|
76
|
+
...
|
|
77
|
+
|
|
78
|
+
def read_file(self, path: str) -> str: ...
|
|
79
|
+
|
|
80
|
+
def write_file(self, path: str, content: str) -> None: ...
|
|
81
|
+
|
|
82
|
+
def edit_file(self, path: str, old: str, new: str) -> None:
|
|
83
|
+
"""Replace exactly one occurrence of `old` with `new` in file at `path`.
|
|
84
|
+
|
|
85
|
+
Match is exact-string (no regex). Implementations MUST raise an error if
|
|
86
|
+
`old` is not found, or if `old` appears more than once (ambiguous match).
|
|
87
|
+
For multi-occurrence replacement, callers should invoke `edit_file` multiple
|
|
88
|
+
times with disambiguating context, or use a higher-level batch API.
|
|
89
|
+
"""
|
|
90
|
+
...
|
|
91
|
+
|
|
92
|
+
def shell(self, command: str, timeout_seconds: int = 120) -> tuple[str, int]:
|
|
93
|
+
"""Run a shell command; return (output, exit_code)."""
|
|
94
|
+
...
|
|
95
|
+
|
|
96
|
+
def git_diff_staged(self) -> str: ...
|
|
97
|
+
|
|
98
|
+
def git_commit(self, message: str, files: list[str] | None = None) -> str:
|
|
99
|
+
"""Commit staged changes; return the commit SHA.
|
|
100
|
+
|
|
101
|
+
If `files` is provided (non-None), stage those files first via `git add <file>...`
|
|
102
|
+
then commit. If `files` is None, commit whatever is currently in the index without
|
|
103
|
+
staging anything (caller has already staged).
|
|
104
|
+
|
|
105
|
+
Implementations must NOT use `--no-verify`, `--no-gpg-sign`, or `--amend` unless
|
|
106
|
+
the adapter explicitly documents otherwise.
|
|
107
|
+
"""
|
|
108
|
+
...
|