@caddis/cli 0.4.29 → 0.4.32
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/caddis_statusline.py +0 -1
- package/bundles/antigravity-plugin/mcp_ping_server.py +1 -1
- package/bundles/antigravity-plugin/plugin.json +1 -1
- package/bundles/antigravity-plugin/scripts/caddis_adhoc.py +301 -66
- package/bundles/antigravity-plugin/scripts/caddis_exit.py +15 -0
- package/bundles/antigravity-plugin/scripts/caddis_frontmatter.py +145 -0
- package/bundles/antigravity-plugin/scripts/caddis_gate.py +33 -10
- package/bundles/antigravity-plugin/scripts/caddis_governor.py +5 -2
- package/bundles/antigravity-plugin/scripts/caddis_lanes.py +13 -1
- package/bundles/antigravity-plugin/scripts/caddis_migrate_dir.py +1 -1
- package/bundles/antigravity-plugin/scripts/caddis_spawn.py +2 -2
- package/bundles/antigravity-plugin/scripts/caddis_statusline.py +0 -1
- package/bundles/antigravity-plugin/scripts/caddis_tidy.py +9 -14
- package/bundles/antigravity-plugin/scripts/claudster_doctor.py +66 -0
- package/bundles/antigravity-plugin/scripts/convert_doc_to_confluence.py +1430 -0
- package/bundles/antigravity-plugin/scripts/new-machine.ps1 +1 -1
- package/bundles/antigravity-plugin/scripts/oss_ask.py +6 -5
- package/bundles/antigravity-plugin/scripts/oss_model.py +1 -1
- package/bundles/antigravity-plugin/scripts/oss_review.py +84 -15
- package/bundles/antigravity-plugin/scripts/secret_filter.py +316 -0
- package/bundles/antigravity-plugin/scripts/setup_project_ai.py +9 -4
- package/bundles/antigravity-plugin/skills/_registry.md +5 -3
- package/bundles/antigravity-plugin/skills/catchup/SKILL.md +37 -2
- package/bundles/antigravity-plugin/skills/cross-review/SKILL.md +1 -1
- package/bundles/antigravity-plugin/skills/data-lineage/SKILL.md +1 -1
- package/bundles/antigravity-plugin/skills/digress/SKILL.md +1 -1
- package/bundles/antigravity-plugin/skills/handoff/SKILL.md +8 -1
- package/bundles/antigravity-plugin/skills/hld/SKILL.md +1 -1
- package/bundles/antigravity-plugin/skills/implement/SKILL.md +3 -1
- package/bundles/antigravity-plugin/skills/install-statusline/SKILL.md +105 -0
- package/bundles/antigravity-plugin/skills/lld/SKILL.md +1 -1
- package/bundles/antigravity-plugin/skills/mmr/SKILL.md +23 -14
- package/bundles/antigravity-plugin/skills/resume/SKILL.md +2 -32
- package/bundles/antigravity-plugin/skills/review/SKILL.md +2 -96
- package/bundles/antigravity-plugin/skills/review-pr/SKILL.md +100 -0
- package/bundles/antigravity-plugin/skills/statusline/SKILL.md +2 -101
- package/bundles/antigravity-plugin-extras/plugin.json +1 -1
- package/bundles/antigravity-plugin-extras/skills/cross-review/SKILL.md +3 -3
- package/bundles/antigravity-plugin-extras/skills/golden-plan/SKILL.md +1 -1
- package/bundles/codex/.codex/config.toml.example +0 -16
- package/bundles/codex/AGENTS.md +2 -2
- package/bundles/manifest.json +3 -3
- package/dist/claude-deepseek.js +1 -1
- package/dist/claude-glm.js +1 -1
- package/dist/claude-oss.js +1 -1
- package/dist/cli.js +1 -1
- package/package.json +1 -1
|
@@ -37,6 +37,14 @@ import subprocess
|
|
|
37
37
|
import sys
|
|
38
38
|
from pathlib import Path
|
|
39
39
|
|
|
40
|
+
try:
|
|
41
|
+
import caddis_frontmatter
|
|
42
|
+
except ModuleNotFoundError as exc:
|
|
43
|
+
if exc.name != "caddis_frontmatter":
|
|
44
|
+
raise
|
|
45
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "claude-harness" / "scripts"))
|
|
46
|
+
import caddis_frontmatter
|
|
47
|
+
|
|
40
48
|
if hasattr(sys.stdout, "reconfigure"):
|
|
41
49
|
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
42
50
|
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
|
|
@@ -145,16 +153,7 @@ def gate_lane(plan: Path, phase: int) -> int:
|
|
|
145
153
|
# ── gate: verdict-gate ──────────────────────────────────────────────────────
|
|
146
154
|
|
|
147
155
|
def _frontmatter(p: Path) -> dict:
|
|
148
|
-
|
|
149
|
-
if not t.startswith("---"):
|
|
150
|
-
return {}
|
|
151
|
-
end = t.find("\n---", 3)
|
|
152
|
-
out = {}
|
|
153
|
-
for line in t[3:end if end > 0 else 400].splitlines():
|
|
154
|
-
if ":" in line:
|
|
155
|
-
k, _, v = line.partition(":")
|
|
156
|
-
out[k.strip()] = v.strip()
|
|
157
|
-
return out
|
|
156
|
+
return caddis_frontmatter.parse(p.read_text(encoding="utf-8", errors="ignore"))
|
|
158
157
|
|
|
159
158
|
|
|
160
159
|
def _covers(phase_field: str, n: int) -> bool:
|
|
@@ -602,6 +601,22 @@ def gate_review_trigger(repo_root: Path, rev_range: str | None = None) -> int:
|
|
|
602
601
|
return EXIT_NOTE
|
|
603
602
|
|
|
604
603
|
|
|
604
|
+
def gate_docs_check(root: Path) -> int:
|
|
605
|
+
"""Report every typed .caddis Markdown document with an invalid header."""
|
|
606
|
+
failures = 0
|
|
607
|
+
for path in sorted((root / ".caddis").rglob("*.md")):
|
|
608
|
+
relative = path.relative_to(root).as_posix()
|
|
609
|
+
if not caddis_frontmatter.doc_in_scope(relative, root):
|
|
610
|
+
continue
|
|
611
|
+
error = caddis_frontmatter.validate(
|
|
612
|
+
path.read_text(encoding="utf-8"), caddis_frontmatter.ALLOWED_TYPES
|
|
613
|
+
)
|
|
614
|
+
if error:
|
|
615
|
+
print(f"{relative}: {error}")
|
|
616
|
+
failures += 1
|
|
617
|
+
return EXIT_BLOCKED if failures else EXIT_OK
|
|
618
|
+
|
|
619
|
+
|
|
605
620
|
def main(argv: list[str] | None = None) -> int:
|
|
606
621
|
ap = argparse.ArgumentParser(description="caddis machine gates")
|
|
607
622
|
sub = ap.add_subparsers(dest="cmd", required=True)
|
|
@@ -625,9 +640,17 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
625
640
|
hc = sub.add_parser("handover-check")
|
|
626
641
|
hc.add_argument("--doc", required=True)
|
|
627
642
|
hc.add_argument("--repo-root", default=".")
|
|
643
|
+
dc = sub.add_parser("docs-check")
|
|
644
|
+
dc.add_argument("--root", default=".")
|
|
628
645
|
a = ap.parse_args(argv)
|
|
629
646
|
|
|
630
647
|
plan = Path(getattr(a, "plan", "") or ".")
|
|
648
|
+
if a.cmd == "docs-check":
|
|
649
|
+
try:
|
|
650
|
+
return gate_docs_check(Path(a.root).resolve())
|
|
651
|
+
except Exception as exc:
|
|
652
|
+
sys.stderr.write(f"[caddis-gate] docs-check cannot evaluate: {type(exc).__name__}: {exc}\n")
|
|
653
|
+
return EXIT_BLOCKED # CI must not pass without completing the scan.
|
|
631
654
|
try:
|
|
632
655
|
if a.cmd == "lane-check":
|
|
633
656
|
return gate_lane(plan, a.phase)
|
|
@@ -55,8 +55,11 @@ import sys
|
|
|
55
55
|
from dataclasses import dataclass
|
|
56
56
|
from pathlib import Path
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
59
|
+
import caddis_exit # noqa: E402
|
|
60
|
+
|
|
61
|
+
EXIT_USAGE = caddis_exit.ERROR
|
|
62
|
+
EXIT_CONFIG = caddis_exit.NOT_RUN
|
|
60
63
|
SCOPES = ("session", "lane")
|
|
61
64
|
MAX_CPU_PERCENT = 100
|
|
62
65
|
MAX_MEMORY_MB = 1_048_576
|
|
@@ -20,11 +20,23 @@ from dataclasses import dataclass
|
|
|
20
20
|
from datetime import datetime, timezone
|
|
21
21
|
from pathlib import Path
|
|
22
22
|
|
|
23
|
+
try:
|
|
24
|
+
import caddis_exit
|
|
25
|
+
except ModuleNotFoundError as exc:
|
|
26
|
+
if exc.name != "caddis_exit":
|
|
27
|
+
raise
|
|
28
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "claude-harness" / "scripts"))
|
|
29
|
+
import caddis_exit
|
|
30
|
+
|
|
23
31
|
if hasattr(sys.stdout, "reconfigure"):
|
|
24
32
|
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
25
33
|
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
|
|
26
34
|
|
|
27
|
-
EXIT_OK
|
|
35
|
+
EXIT_OK = caddis_exit.CLEAN
|
|
36
|
+
EXIT_FAIL = caddis_exit.BLOCKED
|
|
37
|
+
EXIT_USAGE = caddis_exit.ERROR
|
|
38
|
+
EXIT_CONFIG = caddis_exit.NOT_RUN
|
|
39
|
+
EXIT_ALL_OUT = caddis_exit.NOT_RUN
|
|
28
40
|
RESULT_OK = "OK"
|
|
29
41
|
RESULT_LANE_FAILED = "LANE-FAILED"
|
|
30
42
|
RESULT_OUT_OF_BUDGET = "OUT-OF-BUDGET"
|
|
@@ -45,7 +45,7 @@ NEW_DIR = ".caddis"
|
|
|
45
45
|
OLD_DIR = ".claudster"
|
|
46
46
|
|
|
47
47
|
#: Append-only JSONL logs: safe to concatenate when both dirs carry one (legacy lines first).
|
|
48
|
-
JSONL_MERGE_NAMES = frozenset({"usage-log.jsonl", "agent-log.jsonl"
|
|
48
|
+
JSONL_MERGE_NAMES = frozenset({"usage-log.jsonl", "agent-log.jsonl"})
|
|
49
49
|
|
|
50
50
|
#: Files whose CONTENT may name the old dir and is read live after the migration. Historical
|
|
51
51
|
#: artifacts (plans, handoffs, past reviews) are deliberately NOT rewritten — they are a record of
|
|
@@ -273,7 +273,7 @@ def read_context_pct(repo_root: Path) -> tuple[float | None, str]:
|
|
|
273
273
|
age = max(0, int(time.time()) - int(raw.get("at", 0)))
|
|
274
274
|
except Exception:
|
|
275
275
|
return None, ("no runtime context figure at "
|
|
276
|
-
+ f"{ART}/context-window.json — run `/caddis:statusline` to install the "
|
|
276
|
+
+ f"{ART}/context-window.json — run `/caddis:install-statusline` to install the "
|
|
277
277
|
"status line that writes it")
|
|
278
278
|
if age > CONTEXT_MAX_AGE_S:
|
|
279
279
|
return pct, f"runtime figure, STALE by {age}s (a lower bound — the real number is >= this)"
|
|
@@ -386,7 +386,7 @@ def _gate_context(p: Preflight, repo_root: Path, self_reported: float | None) ->
|
|
|
386
386
|
if pct is None:
|
|
387
387
|
p.refusals.append(
|
|
388
388
|
"no context figure, so the 95% gate cannot be applied — and a gate that defaults to "
|
|
389
|
-
"proceed is not a gate. Either run `/caddis:statusline` (its status line caches the "
|
|
389
|
+
"proceed is not a gate. Either run `/caddis:install-statusline` (its status line caches the "
|
|
390
390
|
"runtime figure for this repo), or pass `--context-pct <n>` with the number your "
|
|
391
391
|
"harness reports. A self-reported figure is accepted and recorded as such.")
|
|
392
392
|
return
|
|
@@ -615,7 +615,6 @@ def render_agy(data: dict, cfg: dict) -> str:
|
|
|
615
615
|
used = as_int(cw.get("total_input_tokens"))
|
|
616
616
|
max_tokens = as_int(cw.get("context_window_size")) or 1_048_576
|
|
617
617
|
pct = float(as_int(cw.get("used_percentage")))
|
|
618
|
-
record_context(cwd, pct)
|
|
619
618
|
parts.append(context_segment(c, cfg, used, max_tokens, pct))
|
|
620
619
|
loud = cached_segment(c, cfg, cached)
|
|
621
620
|
if loud:
|
|
@@ -38,6 +38,14 @@ import sys
|
|
|
38
38
|
from dataclasses import dataclass, field
|
|
39
39
|
from pathlib import Path
|
|
40
40
|
|
|
41
|
+
try:
|
|
42
|
+
import caddis_frontmatter
|
|
43
|
+
except ModuleNotFoundError as exc:
|
|
44
|
+
if exc.name != "caddis_frontmatter":
|
|
45
|
+
raise
|
|
46
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "claude-harness" / "scripts"))
|
|
47
|
+
import caddis_frontmatter
|
|
48
|
+
|
|
41
49
|
TERMINAL_STATUSES = {"done", "superseded", "shipped", "implemented"}
|
|
42
50
|
|
|
43
51
|
# Legacy prompts written before the frontmatter convention existed (2026-07-23). They were
|
|
@@ -95,20 +103,7 @@ PARKING_LOT_MAX_BYTES = 20_000
|
|
|
95
103
|
|
|
96
104
|
|
|
97
105
|
def _parse_frontmatter(text: str) -> dict:
|
|
98
|
-
|
|
99
|
-
if not text.startswith("---"):
|
|
100
|
-
return {}
|
|
101
|
-
lines = text.splitlines()
|
|
102
|
-
if lines[0].strip() != "---":
|
|
103
|
-
return {}
|
|
104
|
-
fm: dict[str, str] = {}
|
|
105
|
-
for line in lines[1:]:
|
|
106
|
-
if line.strip() == "---":
|
|
107
|
-
break
|
|
108
|
-
if ":" in line:
|
|
109
|
-
k, _, v = line.partition(":")
|
|
110
|
-
fm[k.strip()] = v.strip()
|
|
111
|
-
return fm
|
|
106
|
+
return caddis_frontmatter.parse(text)
|
|
112
107
|
|
|
113
108
|
|
|
114
109
|
def _phases_all_done(text: str) -> bool:
|
|
@@ -15,11 +15,14 @@ Usage:
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
17
|
import argparse
|
|
18
|
+
import importlib.util
|
|
18
19
|
import json
|
|
19
20
|
import os
|
|
21
|
+
import re
|
|
20
22
|
import shutil
|
|
21
23
|
import subprocess
|
|
22
24
|
import sys
|
|
25
|
+
import time
|
|
23
26
|
from datetime import datetime, timezone
|
|
24
27
|
from pathlib import Path
|
|
25
28
|
|
|
@@ -97,6 +100,28 @@ def rules_findings(dest: Path) -> list[str]:
|
|
|
97
100
|
return out
|
|
98
101
|
|
|
99
102
|
|
|
103
|
+
def has_document_frontmatter_rule(dest: Path) -> bool:
|
|
104
|
+
"""True when root AGENTS.md puts type: in its Document frontmatter section."""
|
|
105
|
+
path = Path(dest) / "AGENTS.md"
|
|
106
|
+
try:
|
|
107
|
+
lines = path.read_text(encoding="utf-8").splitlines()
|
|
108
|
+
except OSError:
|
|
109
|
+
return False
|
|
110
|
+
section = False
|
|
111
|
+
for line in lines:
|
|
112
|
+
if re.match(r"^\s*(?:#{1,6}\s+|[-*]\s+\*\*)Document frontmatter(?:\*\*)?\s*:?.*$", line):
|
|
113
|
+
section = True
|
|
114
|
+
if "type:" in line:
|
|
115
|
+
return True
|
|
116
|
+
continue
|
|
117
|
+
if section and ("type:" in line or re.match(r"^\s*[-*]\s+\*\*type\*\*\s*:", line)):
|
|
118
|
+
return True
|
|
119
|
+
if section and (re.match(r"^#{1,6}\s+", line) or
|
|
120
|
+
re.match(r"^\s*[-*]\s+\*\*[^*]+\*\*\s*:", line)):
|
|
121
|
+
section = False
|
|
122
|
+
return False
|
|
123
|
+
|
|
124
|
+
|
|
100
125
|
def oversize_rules_files(dest: Path, budget: int = AGENTS_MD_BUDGET) -> list[tuple[str, int]]:
|
|
101
126
|
"""Always-loaded rules files (AGENTS.md, or CLAUDE.md pre-migration) over the line budget."""
|
|
102
127
|
dest = Path(dest)
|
|
@@ -287,6 +312,36 @@ def nudge_line(dest: Path) -> str | None:
|
|
|
287
312
|
return f"[caddis] {head}{more}"
|
|
288
313
|
|
|
289
314
|
|
|
315
|
+
def hook_errors(dest: Path) -> str:
|
|
316
|
+
"""Recent hook failures, counted by hook. Empty when the ledger is clean."""
|
|
317
|
+
scripts = Path(__file__).resolve().parent
|
|
318
|
+
hook_log_path = scripts / "hook_log.py"
|
|
319
|
+
if not hook_log_path.is_file(): # source checkout; installed copies are siblings
|
|
320
|
+
hook_log_path = scripts.parent / "claude-harness" / "scripts" / "hook_log.py"
|
|
321
|
+
try:
|
|
322
|
+
spec = importlib.util.spec_from_file_location("caddis_hook_log", hook_log_path)
|
|
323
|
+
if spec is None or spec.loader is None:
|
|
324
|
+
return ""
|
|
325
|
+
hook_log = importlib.util.module_from_spec(spec)
|
|
326
|
+
spec.loader.exec_module(hook_log)
|
|
327
|
+
if not hook_log.summarise(str(dest), days=7):
|
|
328
|
+
return ""
|
|
329
|
+
cutoff = time.strftime("%Y-%m-%d", time.localtime(time.time() - 7 * 86400))
|
|
330
|
+
counts: dict[str, int] = {}
|
|
331
|
+
with (dest / ".caddis" / "hook-errors.jsonl").open(encoding="utf-8", errors="replace") as fh:
|
|
332
|
+
for line in fh:
|
|
333
|
+
try:
|
|
334
|
+
row = json.loads(line)
|
|
335
|
+
except (ValueError, TypeError):
|
|
336
|
+
continue
|
|
337
|
+
if isinstance(row, dict) and str(row.get("ts", ""))[:10] >= cutoff:
|
|
338
|
+
hook = str(row.get("hook", "?"))
|
|
339
|
+
counts[hook] = counts.get(hook, 0) + 1
|
|
340
|
+
return ", ".join(f"{hook} x{count}" for hook, count in sorted(counts.items()))
|
|
341
|
+
except Exception:
|
|
342
|
+
return "" # a damaged ledger must not break the doctor
|
|
343
|
+
|
|
344
|
+
|
|
290
345
|
# ── report ───────────────────────────────────────────────────────────────────
|
|
291
346
|
def run(dest: Path, quiet: bool) -> int:
|
|
292
347
|
if quiet: # SessionStart mode: only the nudge, never non-zero
|
|
@@ -316,6 +371,11 @@ def run(dest: Path, quiet: bool) -> int:
|
|
|
316
371
|
root = dest / "AGENTS.md"
|
|
317
372
|
print(f" OK root AGENTS.md {'present' if root.is_file() else 'ABSENT (not migrated yet?)'}; shims are shims")
|
|
318
373
|
|
|
374
|
+
if has_document_frontmatter_rule(dest):
|
|
375
|
+
print(" OK AGENTS.md carries the document-header rule")
|
|
376
|
+
else:
|
|
377
|
+
print(" WARN AGENTS.md carries the document-header rule: missing Document frontmatter section with type:")
|
|
378
|
+
|
|
319
379
|
print("-- maintenance signals (deterministic — read-only)")
|
|
320
380
|
sig = maintenance_signals(dest)
|
|
321
381
|
if sig:
|
|
@@ -324,6 +384,12 @@ def run(dest: Path, quiet: bool) -> int:
|
|
|
324
384
|
else:
|
|
325
385
|
print(" none")
|
|
326
386
|
|
|
387
|
+
errors = hook_errors(dest)
|
|
388
|
+
if errors:
|
|
389
|
+
print(f" WARN hook errors (7 days): {errors}")
|
|
390
|
+
else:
|
|
391
|
+
print(" OK hook errors (7 days): 0")
|
|
392
|
+
|
|
327
393
|
print("-- install registry")
|
|
328
394
|
reg = _user_scope_path("installs.json")
|
|
329
395
|
if reg.is_file():
|