aepkit 0.6.0__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.
aepkit/__init__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.6.0"
aepkit/__main__.py ADDED
@@ -0,0 +1,7 @@
1
+ """Allow ``python -m aepkit``."""
2
+
3
+ import sys
4
+
5
+ from .cli import main
6
+
7
+ sys.exit(main())
aepkit/cli.py ADDED
@@ -0,0 +1,151 @@
1
+ """aepkit — mechanical support for the Auditor/Executor protocol.
2
+
3
+ See SKILL.md in the repo root for the protocol itself. This CLI operationalizes the
4
+ parts of it that are checklist work, not judgment: scaffolding the document set,
5
+ cross-checking it for drift, running a negative control end to end, and reporting
6
+ status.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ import sys
13
+
14
+ from . import __version__, install_skill, lint, negcontrol, scaffold, status
15
+
16
+
17
+ def build_parser() -> argparse.ArgumentParser:
18
+ parser = argparse.ArgumentParser(prog="aepkit", description=__doc__)
19
+ parser.add_argument("--version", action="version", version=f"aepkit {__version__}")
20
+ sub = parser.add_subparsers(dest="command", required=True)
21
+
22
+ p_init = sub.add_parser(
23
+ "init", help="scaffold the document set (three documents and an annexes/ directory)"
24
+ )
25
+ p_init.add_argument("target_dir", help="directory to scaffold (created if missing)")
26
+ p_init.add_argument(
27
+ "--name",
28
+ dest="project_name",
29
+ metavar="NAME",
30
+ default=None,
31
+ help="project name written into the documents (default: the directory name)",
32
+ )
33
+ p_init.add_argument("--force", action="store_true", help="overwrite existing documents")
34
+
35
+ p_lint = sub.add_parser("lint", help="cross-check the document set for drift")
36
+ p_lint.add_argument("target_dir", help="directory holding the document set")
37
+ p_lint.add_argument(
38
+ "--annex-threshold",
39
+ type=int,
40
+ metavar="N",
41
+ default=lint.DEFAULT_ANNEX_THRESHOLD,
42
+ help="flag more annexes than this, overall or per phase (default: %(default)s)",
43
+ )
44
+
45
+ p_neg = sub.add_parser("negcontrol", help="run a backup/break/test/restore/test cycle")
46
+ p_neg.add_argument(
47
+ "--file", dest="file_path", metavar="PATH", default=None, help="file to back up and restore"
48
+ )
49
+ p_neg.add_argument(
50
+ "--break-cmd",
51
+ dest="break_cmd",
52
+ metavar="CMD",
53
+ default=None,
54
+ help="shell command that removes the protection",
55
+ )
56
+ p_neg.add_argument(
57
+ "--test-cmd",
58
+ dest="test_cmd",
59
+ metavar="CMD",
60
+ required=True,
61
+ help="shell command that should fail without the protection",
62
+ )
63
+ p_neg.add_argument(
64
+ "--restore-cmd",
65
+ dest="restore_cmd",
66
+ metavar="CMD",
67
+ default=None,
68
+ help="shell command to restore; with --file, the backup still wins if the file differs",
69
+ )
70
+ p_neg.add_argument(
71
+ "--expect",
72
+ metavar="REGEX",
73
+ default=None,
74
+ help="the failing run's output must match this regular expression to count",
75
+ )
76
+ p_neg.add_argument(
77
+ "--timeout",
78
+ type=float,
79
+ metavar="SECONDS",
80
+ default=None,
81
+ help="seconds allowed per command (default: no limit)",
82
+ )
83
+
84
+ p_status = sub.add_parser(
85
+ "status", help="tally verdicts and reports in the compliance log; only APPROVED closes"
86
+ )
87
+ p_status.add_argument("target_dir", help="directory holding compliance-log.md")
88
+
89
+ p_install = sub.add_parser(
90
+ "install-skill", help="provision SKILL.md and references/ into an agent skills directory"
91
+ )
92
+ p_install.add_argument(
93
+ "target_dir",
94
+ nargs="?",
95
+ default=".",
96
+ help="target project directory (defaults to current directory)",
97
+ )
98
+ p_install.add_argument(
99
+ "--agent",
100
+ choices=["antigravity", "claude", "cursor", "auto"],
101
+ default="auto",
102
+ help="target agent framework (default: auto-detect)",
103
+ )
104
+ p_install.add_argument(
105
+ "--global",
106
+ dest="is_global",
107
+ action="store_true",
108
+ help="install to user-level global configuration directory",
109
+ )
110
+ p_install.add_argument(
111
+ "--dest",
112
+ dest="dest_path",
113
+ metavar="PATH",
114
+ default=None,
115
+ help="explicit destination (SKILL.md installs references/ beside it; any other name gets one bundled file)",
116
+ )
117
+ p_install.add_argument(
118
+ "--force", action="store_true", help="overwrite an existing installation"
119
+ )
120
+ return parser
121
+
122
+
123
+ def main(argv: list[str] | None = None) -> int:
124
+ args = build_parser().parse_args(argv)
125
+
126
+ if args.command == "init":
127
+ return scaffold.run(args.target_dir, args.project_name, args.force)
128
+ if args.command == "lint":
129
+ return lint.run(args.target_dir, args.annex_threshold)
130
+ if args.command == "negcontrol":
131
+ return negcontrol.run(
132
+ args.test_cmd,
133
+ break_cmd=args.break_cmd,
134
+ file_path=args.file_path,
135
+ restore_cmd=args.restore_cmd,
136
+ timeout=args.timeout,
137
+ expect=args.expect,
138
+ )
139
+ if args.command == "status":
140
+ return status.run(args.target_dir)
141
+ return install_skill.run(
142
+ target_dir=args.target_dir,
143
+ agent=args.agent,
144
+ is_global=args.is_global,
145
+ dest_path=args.dest_path,
146
+ force=args.force,
147
+ )
148
+
149
+
150
+ if __name__ == "__main__":
151
+ sys.exit(main())
@@ -0,0 +1,126 @@
1
+ """Provision the protocol skill (SKILL.md and references/) into an agent or workspace."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import shutil
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ REFERENCES_DIR = "references"
11
+
12
+
13
+ def _skill_source_dir() -> Path:
14
+ """Directory holding SKILL.md and references/: the packaged copy, else the repo root."""
15
+ packaged = Path(__file__).parent / "skill"
16
+ if (packaged / "SKILL.md").exists():
17
+ return packaged
18
+ repo_root = Path(__file__).resolve().parent.parent.parent
19
+ if (repo_root / "SKILL.md").exists():
20
+ return repo_root
21
+ raise FileNotFoundError("SKILL.md could not be found.")
22
+
23
+
24
+ def _reference_files(source: Path) -> list[Path]:
25
+ ref_dir = source / REFERENCES_DIR
26
+ return sorted(ref_dir.glob("*.md")) if ref_dir.is_dir() else []
27
+
28
+
29
+ def _bundle(source: Path) -> str:
30
+ """Single-file form for destinations that hold one file (Cursor rules, custom paths)."""
31
+ parts = [(source / "SKILL.md").read_text(encoding="utf-8").rstrip("\n")]
32
+ for ref in _reference_files(source):
33
+ body = ref.read_text(encoding="utf-8").rstrip("\n")
34
+ parts.append(f"<!-- {REFERENCES_DIR}/{ref.name} -->\n\n{body}")
35
+ return "\n\n---\n\n".join(parts) + "\n"
36
+
37
+
38
+ def _install(source: Path, dest: Path) -> None:
39
+ dest.parent.mkdir(parents=True, exist_ok=True)
40
+ if dest.name != "SKILL.md":
41
+ dest.write_text(_bundle(source), encoding="utf-8")
42
+ return
43
+ shutil.copyfile(source / "SKILL.md", dest)
44
+ refs = _reference_files(source)
45
+ if refs:
46
+ ref_dest = dest.parent / REFERENCES_DIR
47
+ ref_dest.mkdir(exist_ok=True)
48
+ for ref in refs:
49
+ shutil.copyfile(ref, ref_dest / ref.name)
50
+
51
+
52
+ def _detect_agent(target: Path) -> str:
53
+ # 1. Check workspace marker directories
54
+ if (target / ".agents").exists() or (target / ".gemini").exists():
55
+ return "antigravity"
56
+ if (target / ".claude").exists():
57
+ return "claude"
58
+ if (target / ".cursor").exists():
59
+ return "cursor"
60
+
61
+ # 2. Check runtime environment variables
62
+ if os.environ.get("ANTIGRAVITY_AGENT") or os.environ.get("GEMINI_CLI"):
63
+ return "antigravity"
64
+ if os.environ.get("CLAUDE_CODE") or os.environ.get("CLAUDE_PROJECT_DIR"):
65
+ return "claude"
66
+ if os.environ.get("CURSOR_PROJECT_DIR"):
67
+ return "cursor"
68
+
69
+ # 3. Default fallback
70
+ return "antigravity"
71
+
72
+
73
+ def resolve_dest_path(
74
+ target_dir: str = ".",
75
+ agent: str = "auto",
76
+ is_global: bool = False,
77
+ dest_path: str | None = None,
78
+ ) -> Path:
79
+ if dest_path:
80
+ # A directory (existing, or written with a trailing separator) receives SKILL.md.
81
+ if Path(dest_path).is_dir() or dest_path.endswith(("/", os.sep)):
82
+ return Path(dest_path) / "SKILL.md"
83
+ return Path(dest_path)
84
+
85
+ home = Path.home()
86
+ if is_global:
87
+ if agent == "claude":
88
+ return home / ".claude" / "skills" / "auditor-executor-protocol" / "SKILL.md"
89
+ if agent == "cursor":
90
+ return home / ".cursor" / "rules" / "auditor-executor-protocol.mdc"
91
+ return home / ".gemini" / "config" / "skills" / "auditor-executor-protocol" / "SKILL.md"
92
+
93
+ target = Path(target_dir).resolve()
94
+ chosen_agent = _detect_agent(target) if agent == "auto" else agent
95
+
96
+ if chosen_agent == "claude":
97
+ return target / ".claude" / "skills" / "auditor-executor-protocol" / "SKILL.md"
98
+ if chosen_agent == "cursor":
99
+ return target / ".cursor" / "rules" / "auditor-executor-protocol.mdc"
100
+ return target / ".agents" / "skills" / "auditor-executor-protocol" / "SKILL.md"
101
+
102
+
103
+ def run(
104
+ target_dir: str = ".",
105
+ agent: str = "auto",
106
+ is_global: bool = False,
107
+ dest_path: str | None = None,
108
+ force: bool = False,
109
+ ) -> int:
110
+ try:
111
+ source = _skill_source_dir()
112
+ except FileNotFoundError as e:
113
+ print(f"error: {e}", file=sys.stderr)
114
+ return 2
115
+
116
+ dest = resolve_dest_path(
117
+ target_dir=target_dir, agent=agent, is_global=is_global, dest_path=dest_path
118
+ )
119
+
120
+ if dest.exists() and not force:
121
+ print(f"skip: {dest} already exists (use --force to overwrite)")
122
+ return 0
123
+
124
+ _install(source, dest)
125
+ print(f"installed {dest}")
126
+ return 0