-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathasdd_cli.py
More file actions
executable file
·126 lines (108 loc) · 6.17 KB
/
Copy pathasdd_cli.py
File metadata and controls
executable file
·126 lines (108 loc) · 6.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#!/usr/bin/env python3
"""asdd - one CLI for ASDD (v0.1).
Puts the operate-kit tools under a single command: the installer, the four
deterministic gates, the gates-as-an-MCP-server, and the validation runner.
Dispatches to the scripts that live alongside it, so run it from a checkout of
ASDD (or `pip install -e .`, which links to the checkout). Zero-dependency (stdlib).
asdd init --goose /path/to/repo
asdd merge-eligibility crypto/aes.py --protected '**/crypto/**' --posture earned-automerge
asdd mcp # the Goose stdio extension: cmd=asdd, args=[mcp]
asdd validate
"""
import os
import subprocess
import sys
__version__ = "0.1.0" # keep in sync with pyproject.toml
HERE = os.path.dirname(os.path.realpath(__file__)) # realpath: a symlink resolves to the checkout
def _kit_root():
"""Where the kit's scripts and templates actually live.
A checkout (or an editable install) has them right next to this file, and those stay the
single source of truth. An installed wheel has no checkout to point at, so setup.py stages
the same trees into the asdd_kit package at build time and we fall back to that. Checking
for cli/ rather than guessing means a checkout always wins, even if a stale wheel is also
importable.
"""
if os.path.isdir(os.path.join(HERE, "cli")):
return HERE
try:
import asdd_kit
staged = os.path.dirname(os.path.realpath(asdd_kit.__file__))
if os.path.isdir(os.path.join(staged, "cli")):
return staged
except Exception:
pass
return HERE # nothing found: the dispatch below fails loudly rather than silently
ROOT = _kit_root()
CLI = os.path.join(ROOT, "cli")
VAL = os.path.join(ROOT, "validation")
# subcommand -> the argv it runs (the tested scripts stay the single source of truth)
COMMANDS = {
"init": ["bash", os.path.join(CLI, "init.sh")],
"spec-check": ["python3", os.path.join(CLI, "spec-check.py")],
"openspec-gate": ["python3", os.path.join(CLI, "openspec-gate.py")],
"claim-check": ["python3", os.path.join(CLI, "claim-check.py")],
"merge-eligibility": ["python3", os.path.join(CLI, "merge-eligibility.py")],
"conventions-check": ["python3", os.path.join(CLI, "conventions-check.py")],
"check-models": ["bash", os.path.join(CLI, "check-models.sh")],
"resolve-model": ["bash", os.path.join(CLI, "resolve-model.sh")],
"setup": ["python3", os.path.join(CLI, "setup-goose.py")],
"setup-dashboard": ["python3", os.path.join(CLI, "setup-dashboard.py")],
"doctor": ["python3", os.path.join(CLI, "doctor.py")],
"connect-check": ["python3", os.path.join(CLI, "connect-check.py")],
"dashboard": ["python3", os.path.join(CLI, "dashboard.py")],
"recipe-lint": ["python3", os.path.join(CLI, "recipe-lint.py")],
"kit-check": ["python3", os.path.join(CLI, "kit-check.py")],
"audit": ["python3", os.path.join(CLI, "audit.py")],
"operate-run": ["python3", os.path.join(CLI, "operate-run.py")],
"dev-council": ["python3", os.path.join(CLI, "dev-council.py")],
"audit-ship": ["bash", os.path.join(ROOT, ".github", "asdd", "audit-export.sh")],
"audit-check": ["python3", os.path.join(VAL, "audit-check.py")],
"mcp": ["python3", os.path.join(CLI, "asdd-mcp.py")],
"validate": ["python3", os.path.join(VAL, "run-base.py")],
}
HELP = """asdd - the ASDD CLI
Usage: asdd <command> [args...]
Commands:
init [--goose] [DIR] scaffold ASDD into a repo (add --goose for the operate kit)
setup guided per-role model wiring for Goose (writes .asdd.yml)
setup-dashboard the same, as a local web page (non-engineer front door)
doctor [CONFIG] preflight the operate path (tools reachable, spec CLI, roster)
connect-check [CONFIG] is each agent's model connected, or dry-running? (pings per role)
dashboard --repo O/R the read-only governance + insights view (internal unless --public)
spec-check FILE definition-of-ready gate on a spec object (built-in spec tool)
openspec-gate CHANGE readiness gate for an OpenSpec project (delegates to `openspec validate`)
claim-check LEDGER ... claim-protocol decision (grant / refuse)
merge-eligibility PATHS the conforming-loader merge floor
conventions-check ... hold a change to the host project's declared conventions (brownfield)
check-models [FILE] enforce developer != tester
resolve-model ROLE the model a role actually runs on (roster, else ASDD_MODEL)
recipe-lint [DIR] structure gate for the Goose operate recipes
kit-check [CONFIG] keep asdd-kit.yml (the kit map) matching reality
audit SUB ... the agent audit ledger: append / verify / trail / corpus / knowledge
operate-run --role R ... run a Goose operate agent and record it deterministically
dev-council --change ID optional produce-loop developer: 2-5 models propose/critique/synthesise/verify
audit-ship LEDGER push a local ledger to the configured private sink (e.g. .asdd-work/audit.jsonl)
audit-check TRAIL ... audit-log properties P1-P9
mcp run the gates as an MCP server (Goose stdio extension)
validate run the runnable-today validation slice
Run `asdd <command> --help` for a command's own options; `asdd --version` prints the version.
The tools live alongside this script; run from a checkout of ASDD.
"""
def main():
if len(sys.argv) < 2 or sys.argv[1] in ("-h", "--help", "help"):
sys.stdout.write(HELP)
return 0
if sys.argv[1] in ("-V", "--version"):
sys.stdout.write(f"asdd {__version__}\n")
return 0
cmd = sys.argv[1]
target = COMMANDS.get(cmd)
if target is None:
sys.stderr.write(f"asdd: unknown command '{cmd}'\n\n{HELP}")
return 2
if not os.path.exists(target[-1]):
sys.stderr.write(f"asdd: '{target[-1]}' not found - run from a checkout of ASDD (or `pip install -e .`).\n")
return 1
return subprocess.run(target + sys.argv[2:]).returncode
if __name__ == "__main__":
sys.exit(main())