@caddis/cli 0.4.53 → 0.4.54
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.
- package/bundles/antigravity-plugin/guard_agy.py +184 -0
- package/bundles/antigravity-plugin/plugin.json +1 -1
- package/bundles/antigravity-plugin/scripts/caddis_budget.py +283 -0
- package/bundles/antigravity-plugin/scripts/caddis_checkout_guard.py +5 -1
- package/bundles/antigravity-plugin/scripts/caddis_fleet.py +439 -0
- package/bundles/antigravity-plugin/scripts/caddis_gate.py +29 -0
- package/bundles/antigravity-plugin/scripts/caddis_lanes.py +127 -10
- package/bundles/antigravity-plugin/scripts/caddis_notify.py +5 -1
- package/bundles/antigravity-plugin/scripts/caddis_roadmap.py +56 -3
- package/bundles/antigravity-plugin/scripts/caddis_roadmap_page.py +676 -0
- package/bundles/antigravity-plugin/scripts/caddis_roadmap_run.py +39 -6
- package/bundles/antigravity-plugin/scripts/caddis_routing.py +4 -4
- package/bundles/antigravity-plugin/skills/_registry.md +2 -0
- package/bundles/antigravity-plugin/skills/catchup/SKILL.md +4 -0
- package/bundles/antigravity-plugin/skills/feature-plan/SKILL.md +6 -0
- package/bundles/antigravity-plugin/skills/fleet-close/SKILL.md +167 -0
- package/bundles/antigravity-plugin/skills/fleet-open/SKILL.md +110 -0
- package/bundles/antigravity-plugin/skills/gh-cli/SKILL.md +2 -1
- package/bundles/antigravity-plugin/skills/handoff/SKILL.md +101 -1
- package/bundles/antigravity-plugin/skills/migrate-dir/SKILL.md +3 -2
- package/bundles/antigravity-plugin/skills/pr-merge/SKILL.md +20 -6
- package/bundles/antigravity-plugin/skills/refactoring/SKILL.md +2 -1
- package/bundles/antigravity-plugin/skills/review-pr/SKILL.md +1 -1
- package/bundles/antigravity-plugin/skills/roadmap/SKILL.md +1 -0
- package/bundles/antigravity-plugin/skills/ship/SKILL.md +2 -2
- package/bundles/antigravity-plugin/skills/ship-merge/SKILL.md +9 -5
- package/bundles/antigravity-plugin/skills/ship-pr/SKILL.md +3 -2
- package/bundles/codex/.codex/skills/coding/refactoring/SKILL.md +2 -1
- package/bundles/codex/.codex/skills/devops/gh-cli/SKILL.md +2 -1
- package/bundles/codex/.codex/skills/workflow/roadmap/SKILL.md +1 -0
- package/bundles/manifest.json +2 -2
- package/package.json +1 -1
|
@@ -85,7 +85,11 @@ def push(message: str, *, run: Runner = subprocess.run, claude: Sequence[str] |
|
|
|
85
85
|
"""The fallback: a short `claude -p` call whose only task is the push notification. The prompt goes on stdin: on
|
|
86
86
|
Windows `claude` is a `.cmd` shim that cuts a multi-line argument at its first newline."""
|
|
87
87
|
claude = list(claude) if claude else [shutil.which("claude") or "claude"]
|
|
88
|
-
|
|
88
|
+
# An explicit, least mode: the only task is one PushNotification call. Since Claude Code 2.1.302 a headless run with
|
|
89
|
+
# no mode depends on the provider and the machine's settings; this one must not inherit bypassPermissions.
|
|
90
|
+
# `--allowedTools PushNotification`: in `default` mode a tool that asks would stall a print session with no human.
|
|
91
|
+
cmd = [*claude, "-p", "--permission-mode", "default", "--allowedTools", "PushNotification",
|
|
92
|
+
"--output-format", "json", "--max-budget-usd", PUSH_BUDGET_USD]
|
|
89
93
|
try:
|
|
90
94
|
proc = run(cmd, input=push_prompt(message), capture_output=True, text=True, encoding="utf-8",
|
|
91
95
|
errors="replace", timeout=300)
|
|
@@ -100,6 +100,7 @@ EXIT_CHECK_TIMEOUT = 300
|
|
|
100
100
|
RECHECK_TIMEOUT = 120
|
|
101
101
|
LOCK_TIMEOUT = 30
|
|
102
102
|
LOCK_MALFORMED_STALE_S = 60 # an empty or non-JSON lock file older than this is stale
|
|
103
|
+
_WINDOWS = os.name == "nt" # a create can fail with PermissionError while a deleted lock's delete is pending
|
|
103
104
|
OUTPUT_TAIL_BYTES = 64 * 1024
|
|
104
105
|
OUTPUT_FILE_CAP = 1024 * 1024 # a check's on-disk output never grows past this
|
|
105
106
|
GIT_NET_TIMEOUT = 120
|
|
@@ -1273,12 +1274,19 @@ class _Lock:
|
|
|
1273
1274
|
def __init__(self, path: Path, timeout: float | None = None):
|
|
1274
1275
|
self.path, self.fd = path, None
|
|
1275
1276
|
self.timeout = LOCK_TIMEOUT if timeout is None else timeout
|
|
1277
|
+
self.denied: PermissionError | None = None # the last Windows "busy" refusal, reported on timeout
|
|
1276
1278
|
|
|
1277
1279
|
def _try(self) -> bool:
|
|
1278
1280
|
try:
|
|
1279
1281
|
self.fd = os.open(str(self.path), os.O_CREAT | os.O_EXCL | os.O_WRONLY)
|
|
1280
1282
|
except FileExistsError:
|
|
1281
1283
|
return False
|
|
1284
|
+
except PermissionError as exc:
|
|
1285
|
+
# Windows refuses a create while the last holder's delete is still pending: busy, not an error.
|
|
1286
|
+
if not _WINDOWS:
|
|
1287
|
+
raise
|
|
1288
|
+
self.denied = exc
|
|
1289
|
+
return False
|
|
1282
1290
|
os.write(self.fd, json.dumps({"pid": os.getpid(), "host": socket.gethostname(),
|
|
1283
1291
|
"started_utc": _utc()}).encode())
|
|
1284
1292
|
return True
|
|
@@ -1337,7 +1345,8 @@ class _Lock:
|
|
|
1337
1345
|
if self._take_over():
|
|
1338
1346
|
print(f"took over a stale lock {self.path}", file=sys.stderr)
|
|
1339
1347
|
return self
|
|
1340
|
-
|
|
1348
|
+
why = f" (last refusal: {self.denied})" if self.denied else ""
|
|
1349
|
+
raise RoadmapError(f"{self.path} is held by another write after {self.timeout:.0f} s{why}",
|
|
1341
1350
|
caddis_exit.NOT_RUN)
|
|
1342
1351
|
time.sleep(0.2)
|
|
1343
1352
|
return self
|
|
@@ -1943,14 +1952,25 @@ def _repo_root(raw: str) -> Path:
|
|
|
1943
1952
|
|
|
1944
1953
|
def list_roadmaps(root: Path) -> list[str]:
|
|
1945
1954
|
"""Slugs of the roadmaps on origin's default branch: a folder under .caddis/roadmaps/ that holds roadmap.md."""
|
|
1955
|
+
return list_roadmaps_checked(root)[0]
|
|
1956
|
+
|
|
1957
|
+
|
|
1958
|
+
def list_roadmaps_checked(root: Path) -> tuple[list[str], set[str] | None]:
|
|
1959
|
+
"""(slugs, every folder name under .caddis/roadmaps/ on origin). The folder set is None when git could not list
|
|
1960
|
+
the tree: a caller that deletes by "not on origin" (the page prune) must then delete nothing."""
|
|
1946
1961
|
branch = fetch(root)
|
|
1947
1962
|
tree = _git(root, "ls-tree", "--name-only", f"origin/{branch}", ".caddis/roadmaps/", check=False)
|
|
1963
|
+
folders: set[str] | None = set() if tree.returncode == 0 else None
|
|
1948
1964
|
slugs: list[str] = []
|
|
1949
1965
|
for folder in tree.stdout.splitlines():
|
|
1950
1966
|
folder = folder.strip().rstrip("/")
|
|
1951
|
-
if
|
|
1967
|
+
if not folder:
|
|
1968
|
+
continue
|
|
1969
|
+
if folders is not None:
|
|
1970
|
+
folders.add(folder.rsplit("/", 1)[-1])
|
|
1971
|
+
if _git(root, "cat-file", "-e", f"origin/{branch}:{folder}/roadmap.md", check=False).returncode == 0:
|
|
1952
1972
|
slugs.append(folder.rsplit("/", 1)[-1])
|
|
1953
|
-
return sorted(slugs)
|
|
1973
|
+
return sorted(slugs), folders
|
|
1954
1974
|
|
|
1955
1975
|
|
|
1956
1976
|
def _build_parser() -> argparse.ArgumentParser:
|
|
@@ -1992,6 +2012,12 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
1992
2012
|
p = add("rollup", write=True)
|
|
1993
2013
|
p.add_argument("--id", required=True)
|
|
1994
2014
|
add("recheck", write=True)
|
|
2015
|
+
p = sub.add_parser("render")
|
|
2016
|
+
which = p.add_mutually_exclusive_group(required=True)
|
|
2017
|
+
which.add_argument("--roadmap")
|
|
2018
|
+
which.add_argument("--all", action="store_true", help="every roadmap on origin (used by hooks and commands)")
|
|
2019
|
+
p.add_argument("--root", default=".")
|
|
2020
|
+
p.add_argument("--out", default=None, help="the HTML file (default ~/.caddis/roadmap-pages/<repo>/<slug>.html)")
|
|
1995
2021
|
return parser
|
|
1996
2022
|
|
|
1997
2023
|
|
|
@@ -2036,6 +2062,33 @@ def _dispatch(args: argparse.Namespace) -> int:
|
|
|
2036
2062
|
return rollup(root, args.roadmap, args.id, args.dry_run)
|
|
2037
2063
|
if args.cmd == "recheck":
|
|
2038
2064
|
return recheck(root, args.roadmap, args.dry_run)
|
|
2065
|
+
if args.cmd == "render":
|
|
2066
|
+
import caddis_roadmap_page # the page is separate; only `render` needs it
|
|
2067
|
+
if args.all and args.out:
|
|
2068
|
+
raise RoadmapError("--out works with one --roadmap, not with --all", caddis_exit.ERROR)
|
|
2069
|
+
slugs, on_origin = list_roadmaps_checked(root) if args.all else ([args.roadmap], None)
|
|
2070
|
+
failed = written = pruned = 0
|
|
2071
|
+
for slug in slugs: # --all: one bad roadmap must not stop the others
|
|
2072
|
+
try:
|
|
2073
|
+
path = caddis_roadmap_page.write_page(root, slug, Path(args.out) if args.out else None,
|
|
2074
|
+
sys.modules[__name__])
|
|
2075
|
+
written += 1
|
|
2076
|
+
print(f"[ROADMAP] page written: {path.resolve()}")
|
|
2077
|
+
except Exception as exc: # --all: one roadmap must never stop the others
|
|
2078
|
+
if not args.all:
|
|
2079
|
+
raise
|
|
2080
|
+
failed += 1
|
|
2081
|
+
print(f"[ROADMAP] {slug}: page not written: {exc}", file=sys.stderr)
|
|
2082
|
+
try: # the hub is a convenience: nothing below ever fails a render or changes its exit code
|
|
2083
|
+
if args.all and on_origin is not None: # a roadmap removed from origin leaves the hub too; a listing
|
|
2084
|
+
# that failed removes nothing, and a folder still on origin keeps its page even if its render failed
|
|
2085
|
+
pruned = caddis_roadmap_page.prune_folder(
|
|
2086
|
+
caddis_roadmap_page.default_page_path(root, "_").parent, on_origin | set(slugs))
|
|
2087
|
+
if not args.out and (written or pruned):
|
|
2088
|
+
print(f"[ROADMAP] hub written: {caddis_roadmap_page.write_hub().resolve()}")
|
|
2089
|
+
except Exception as exc:
|
|
2090
|
+
print(f"[ROADMAP] hub not written: {exc}", file=sys.stderr)
|
|
2091
|
+
return caddis_exit.BLOCKED if failed else caddis_exit.CLEAN
|
|
2039
2092
|
return caddis_exit.ERROR
|
|
2040
2093
|
|
|
2041
2094
|
|