@caddis/cli 0.4.3 → 0.4.4

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.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "caddis",
3
- "version": "1.3.88",
3
+ "version": "1.3.90",
4
4
  "description": "caddis — agent-agnostic dev harness as an Antigravity (agy) plugin: TDD skills + commands, single-sourced with the Claude Code plugin."
5
5
  }
@@ -0,0 +1,268 @@
1
+ """caddis_glossary — propose the words a project INVENTED, so they can be defined once.
2
+
3
+ WHY. Every project coins vocabulary out of ordinary English, and those are the worst words in
4
+ the codebase: a reader who guesses from the plain word gets them wrong and never knows. caddis
5
+ itself coined `pool`, `relay`, `drift`, `parking-lot`, `live-fire` — and its own glossary went
6
+ five and a half months without mentioning any of them, because nothing measured the gap.
7
+
8
+ WHAT THIS DOES AND DOES NOT DO. It PROPOSES candidates by measurement. It never writes a
9
+ definition, because a definition is domain knowledge that only a person or a model reading the
10
+ code can supply. A file of correct words with empty meanings is worse than no file: it looks
11
+ authoritative and teaches nothing. `/caddis:glossary` is the half that writes the meanings.
12
+
13
+ python caddis_glossary.py # propose candidates, ranked
14
+ python caddis_glossary.py --check # exit 1 if a proposed term has no entry
15
+ python caddis_glossary.py --scaffold # create the file if it is missing
16
+
17
+ HOW A CANDIDATE IS CHOSEN. Frequency alone is useless — "the file" beats every real term. A
18
+ word is a candidate when it is COMMON IN THIS REPO but is not ordinary technical English. The
19
+ stop-list below is the load-bearing part, and it is deliberately generous: a false positive
20
+ costs a human three seconds to ignore, while a false negative leaves a coined word undefined
21
+ forever, which is the failure being fixed.
22
+
23
+ ACCURACY, MEASURED. On a normal application repo it is good: run against `docket` it proposed
24
+ `board`, `lane`, `card`, `gate` — that project's actual coined vocabulary, top of the list. On a
25
+ HARNESS repo full of documentation ABOUT agents it is noisier, because words like `protocol` and
26
+ `intent` are both ordinary English and genuinely overloaded here. It is a suggester, not an
27
+ oracle; a human or the model decides what earns an entry.
28
+
29
+ Stdlib only, cross-platform, and it never raises on a bad file — it is run by a command and by
30
+ a gate, and a traceback from either is worse than a missing suggestion.
31
+ """
32
+ from __future__ import annotations
33
+
34
+ import argparse
35
+ import collections
36
+ import os
37
+ import re
38
+ import sys
39
+
40
+ # ── what a project's own vocabulary lives in ────────────────────────────────────────────
41
+ DOC_GLOBS = (".md",)
42
+ SKIP_DIRS = {
43
+ ".git", "node_modules", ".venv", "venv", "dist", "build", "__pycache__",
44
+ ".mypy_cache", ".pytest_cache", "vendor", "site-packages", ".next", "target",
45
+ # Vendored and reference material is someone ELSE'S vocabulary. Without these, caddis's
46
+ # own run proposed `aws4` (33 files) and `mxgraph` (11) out of bundled AWS and draw-io
47
+ # reference docs — words no caddis reader will ever meet.
48
+ "references", "assets", "fixtures", ".archive", "archive", "examples", "sample",
49
+ "samples", "third_party", "vendored", "node_modules", "bundles", "runtime-resources",
50
+ }
51
+
52
+ # Ordinary technical English. A word here is never proposed, however often it appears.
53
+ # Longer than it looks necessary on purpose: the cost of a false positive is a human ignoring
54
+ # one line; the cost of a false negative is a coined word nobody ever defines.
55
+ COMMON = {
56
+ # english scaffolding
57
+ "the", "and", "for", "with", "that", "this", "from", "into", "than", "then", "when",
58
+ "where", "which", "what", "who", "why", "how", "not", "but", "all", "any", "each",
59
+ "one", "two", "only", "also", "more", "most", "less", "some", "such", "same", "other",
60
+ "its", "it's", "you", "your", "our", "their", "they", "them", "has", "have", "had",
61
+ "was", "were", "been", "being", "are", "can", "will", "would", "should", "must", "may",
62
+ "does", "did", "done", "doing", "use", "used", "using", "make", "made", "get", "got",
63
+ "see", "read", "write", "run", "runs", "running", "ran", "set", "put", "take", "give",
64
+ "new", "old", "first", "last", "next", "now", "here", "there", "before", "after",
65
+ "over", "under", "out", "off", "own", "per", "via", "yes", "no", "if", "in", "on", "of",
66
+ "to", "at", "by", "as", "is", "be", "or", "so", "up", "do", "an", "a",
67
+ # ordinary software vocabulary — predictable from the word itself
68
+ "file", "files", "path", "paths", "dir", "directory", "folder", "code", "line", "lines",
69
+ "test", "tests", "testing", "error", "errors", "bug", "fix", "fixed", "build", "built",
70
+ "commit", "commits", "branch", "merge", "push", "pull", "repo", "repository", "git",
71
+ "api", "cli", "url", "http", "https", "json", "yaml", "toml", "html", "css", "sql",
72
+ "user", "users", "name", "names", "value", "values", "key", "keys", "type", "types",
73
+ "list", "lists", "table", "tables", "row", "rows", "column", "field", "fields",
74
+ "function", "method", "class", "module", "package", "library", "script", "scripts",
75
+ "server", "client", "request", "response", "data", "input", "output", "config",
76
+ "version", "release", "install", "update", "delete", "create", "add", "remove",
77
+ "return", "returns", "call", "calls", "check", "checks", "log", "logs", "note", "notes",
78
+ "step", "steps", "case", "cases", "example", "default", "option", "options", "flag",
79
+ "flags", "command", "commands", "tool", "tools", "agent", "agents", "model", "models",
80
+ "session", "sessions", "context", "prompt", "prompts", "token", "tokens", "cache",
81
+ "state", "status", "result", "results", "change", "changes", "work", "works", "time",
82
+ "does", "doc", "docs", "document", "section", "page", "text", "content", "format",
83
+ "project", "projects", "team", "user", "system", "service", "process", "task", "tasks",
84
+ "issue", "issues", "problem", "solution", "reason", "cause", "point", "part", "way",
85
+ "thing", "things", "need", "needs", "want", "know", "like", "just", "even", "still",
86
+ "never", "always", "every", "both", "because", "while", "since", "about", "against",
87
+ # added after the first real run proposed all of these — ordinary in any codebase
88
+ "skill", "skills", "plan", "plans", "phase", "phases", "design", "designs", "review",
89
+ "reviews", "mode", "modes", "pattern", "patterns", "implementation", "reference",
90
+ "description", "descriptions", "workflow", "workflows", "feature", "features",
91
+ "component", "components", "interface", "structure", "approach", "detail", "details",
92
+ "requirement", "requirements", "specification", "architecture", "instructions",
93
+ "guidelines", "standard", "standards", "best", "practice", "practices", "overview",
94
+ "summary", "report", "reports", "action", "actions", "level", "levels", "order",
95
+ "number", "count", "size", "start", "stop", "begin", "end", "open", "close", "load",
96
+ "save", "store", "fetch", "send", "receive", "handle", "handles", "support", "supports",
97
+ "references", "security", "scope", "verification", "react", "python", "typescript",
98
+ }
99
+
100
+ _WORD = re.compile(r"[a-z][a-z0-9]*(?:-[a-z0-9]+)*", re.I)
101
+
102
+ # A term must appear at least this often, in at least this many files, to be proposed.
103
+ # One mention is a passing usage; a coined term earns its definition by recurring.
104
+ MIN_USES = 12
105
+ MIN_FILES = 3
106
+ MAX_SUGGESTIONS = 25
107
+ # A coined term is specific, so in a LARGE corpus it shows up in a minority of files. Measured:
108
+ # `description` appeared in 2112 of ~2500 caddis docs and outranked every real term.
109
+ #
110
+ # But this rule INVERTS on a small repo. Applied unconditionally it deleted docket's four best
111
+ # candidates — `board`, `lane`, `card`, `docket` — because in a 50-file repo the core vocabulary
112
+ # genuinely is in most files. So it only engages once there are enough files for "share" to
113
+ # mean anything.
114
+ MAX_FILE_SHARE = 0.25
115
+ SHARE_RULE_MIN_CORPUS = 200
116
+
117
+
118
+ def _doc_files(root: str) -> list[str]:
119
+ found = []
120
+ for dirpath, dirnames, filenames in os.walk(root):
121
+ dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS and not d.startswith(".venv")]
122
+ for n in filenames:
123
+ if n.endswith(DOC_GLOBS):
124
+ found.append(os.path.join(dirpath, n))
125
+ return found
126
+
127
+
128
+ def glossary_path(root: str) -> str:
129
+ """`.caddis/kb/GLOSSARY.md` — beside the KB, which is where reference docs live."""
130
+ return os.path.join(root, ".caddis", "kb", "GLOSSARY.md")
131
+
132
+
133
+ def defined_terms(path: str) -> set[str]:
134
+ """Canonical terms are the first cell of each markdown table row."""
135
+ if not os.path.isfile(path):
136
+ return set()
137
+ terms = set()
138
+ try:
139
+ with open(path, encoding="utf-8", errors="replace") as fh:
140
+ for line in fh:
141
+ if not line.startswith("|"):
142
+ continue
143
+ cells = [c.strip() for c in line.strip().strip("|").split("|")]
144
+ if len(cells) < 2 or not cells[0] or set(cells[0]) <= {"-", ":", " "}:
145
+ continue
146
+ head = cells[0].strip("`*").lower()
147
+ if head in {"term", "canonical term", "word"}:
148
+ continue
149
+ terms.add(head)
150
+ except OSError:
151
+ return set()
152
+ return terms
153
+
154
+
155
+ def propose(root: str) -> list[tuple[str, int, int]]:
156
+ """Return [(term, uses, files)] — words this repo says often that plain English does not."""
157
+ uses: collections.Counter = collections.Counter()
158
+ files: dict[str, set] = collections.defaultdict(set)
159
+ for f in _doc_files(root):
160
+ try:
161
+ text = open(f, encoding="utf-8", errors="replace").read().lower()
162
+ except OSError:
163
+ continue
164
+ # Strip fenced code and inline code: identifiers are not project vocabulary.
165
+ text = re.sub(r"```.*?```", " ", text, flags=re.S)
166
+ text = re.sub(r"`[^`]*`", " ", text)
167
+ for w in _WORD.findall(text):
168
+ if len(w) < 4 or w in COMMON:
169
+ continue
170
+ uses[w] += 1
171
+ files[w].add(f)
172
+ total = max(1, len(_doc_files(root)))
173
+ out = [
174
+ (w, n, len(files[w]))
175
+ for w, n in uses.items()
176
+ if n >= MIN_USES
177
+ and len(files[w]) >= MIN_FILES
178
+ and (total < SHARE_RULE_MIN_CORPUS or (len(files[w]) / total) <= MAX_FILE_SHARE)
179
+ ]
180
+ out.sort(key=lambda r: (-r[1], r[0]))
181
+ return out
182
+
183
+
184
+ TEMPLATE = """# Glossary
185
+
186
+ The words this project invented out of ordinary English, defined once.
187
+
188
+ **Admission test:** a term belongs here when its meaning in this project is *distinct enough
189
+ from its ordinary technical sense that a newcomer would misread it*. `commit` does not belong.
190
+ A word this team uses in its own way does.
191
+
192
+ Proposed by `caddis_glossary.py`, which measures which words this repo says often that plain
193
+ English does not. It cannot write the definitions — run `/caddis:glossary` and the model will
194
+ draft them from the actual code, or write them yourself.
195
+
196
+ | Term | Definition | DO NOT USE |
197
+ |---|---|---|
198
+ | _example_ | Replace this row. A definition should say what the word means HERE, and why the ordinary reading is wrong. | the synonyms that cause confusion |
199
+
200
+ ## Flagged ambiguities
201
+
202
+ Name the words that are still overloaded rather than pretending the vocabulary is clean. A
203
+ glossary that hides its own dirt starts lying. Delete this section only when there is genuinely
204
+ nothing to flag.
205
+ """
206
+
207
+
208
+ def main(argv: list[str] | None = None) -> int:
209
+ ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
210
+ ap.add_argument("--root", default=os.getcwd())
211
+ ap.add_argument("--check", action="store_true",
212
+ help="exit 1 if a proposed term has no entry")
213
+ ap.add_argument("--scaffold", action="store_true",
214
+ help="write the glossary template if it does not exist")
215
+ args = ap.parse_args(argv)
216
+
217
+ root = os.path.abspath(args.root)
218
+ path = glossary_path(root)
219
+
220
+ if args.scaffold:
221
+ if os.path.isfile(path):
222
+ print("[glossary] present — kept: %s" % os.path.relpath(path, root))
223
+ else:
224
+ os.makedirs(os.path.dirname(path), exist_ok=True)
225
+ with open(path, "w", encoding="utf-8", newline="\n") as fh:
226
+ fh.write(TEMPLATE)
227
+ print("[glossary] wrote %s" % os.path.relpath(path, root))
228
+ return 0
229
+
230
+ candidates = propose(root)
231
+ defined = defined_terms(path)
232
+ missing = [(w, n, f) for w, n, f in candidates if w not in defined]
233
+
234
+ if args.check:
235
+ # Advisory by design. A project may legitimately decide a frequent word needs no
236
+ # definition, and a gate that cannot be satisfied gets bypassed rather than obeyed.
237
+ if not os.path.isfile(path):
238
+ print("[glossary] no %s — run with --scaffold" % os.path.relpath(path, root))
239
+ return 0
240
+ if missing:
241
+ print("[glossary] %d frequent term(s) with no entry:" % len(missing))
242
+ for w, n, f in missing[:MAX_SUGGESTIONS]:
243
+ print(" %-24s %4d uses in %d files" % (w, n, f))
244
+ print(" Define them, or leave them — this is advisory. `/caddis:glossary` drafts them.")
245
+ return 1
246
+ print("[glossary] every frequent term is defined (%d entries)" % len(defined))
247
+ return 0
248
+
249
+ if not candidates:
250
+ print("[glossary] no candidates — too few docs, or nothing recurs enough.")
251
+ return 0
252
+ print("Candidate vocabulary for %s" % root)
253
+ print(" (>= %d uses across >= %d files, ordinary English excluded)\n" % (MIN_USES, MIN_FILES))
254
+ print(" %-26s %6s %6s %s" % ("TERM", "USES", "FILES", "DEFINED?"))
255
+ for w, n, f in candidates[:MAX_SUGGESTIONS]:
256
+ print(" %-26s %6d %6d %s" % (w, n, f, "yes" if w in defined else "-- no --"))
257
+ if missing:
258
+ print("\n %d undefined. Run `/caddis:glossary` to draft definitions from the code."
259
+ % len(missing))
260
+ return 0
261
+
262
+
263
+ if __name__ == "__main__":
264
+ try:
265
+ sys.exit(main())
266
+ except Exception as exc: # pragma: no cover — a suggester must never break a caller
267
+ print("[glossary] skipped: %s" % exc)
268
+ sys.exit(0)
@@ -902,6 +902,35 @@ memory.jsonl
902
902
  """
903
903
 
904
904
 
905
+ GLOSSARY_TEMPLATE = """\
906
+ # Glossary
907
+
908
+ The words this project invented out of ordinary English, defined once.
909
+
910
+ **Admission test:** a term belongs here when its meaning in this project is *distinct
911
+ enough from its ordinary technical sense that a newcomer would misread it*. `commit` does
912
+ not belong. A word this team uses in its own way does.
913
+
914
+ To find candidates, run the measurer. It ranks the words this repo says often that plain
915
+ English does not, and marks which already have an entry:
916
+
917
+ python <caddis>/scripts/caddis_glossary.py
918
+
919
+ It cannot write the definitions - only a person or the model reading the code can say what
920
+ a coined word MEANS here. Run `/caddis:glossary` to have them drafted, or write them.
921
+
922
+ | Term | Definition | DO NOT USE |
923
+ |---|---|---|
924
+ | _example_ | Replace this row. Say what the word means HERE, and why the ordinary reading is wrong. | the synonyms that cause confusion |
925
+
926
+ ## Flagged ambiguities
927
+
928
+ Name the words that are still overloaded rather than pretending the vocabulary is clean. A
929
+ glossary that hides its own dirt starts lying. Delete this section only when there is
930
+ genuinely nothing to flag.
931
+ """
932
+
933
+
905
934
  ARTIFACT_CONFIG_EXAMPLE = """\
906
935
  # caddis per-repo configuration — a documented template.
907
936
  #
@@ -1198,6 +1227,20 @@ def scaffold_artifact_dir(target: Path, dry: bool) -> list[str]:
1198
1227
  em.parent.mkdir(parents=True, exist_ok=True)
1199
1228
  em.write_text(ENVIRONMENT_MAP_TEMPLATE, encoding="utf-8")
1200
1229
  notes.append(f"scaffold: wrote {label}/kb/environment-map.md")
1230
+ # A vocabulary stub, seeded but never auto-filled. `caddis_glossary.py` can measure
1231
+ # which words a repo coined, but only a person or the model reading the code can say
1232
+ # what they MEAN - and a file of correct words with empty meanings looks authoritative
1233
+ # while teaching nothing. The stub carries the admission test so whoever fills it in
1234
+ # knows what qualifies.
1235
+ gl = root / "kb" / "GLOSSARY.md"
1236
+ if gl.exists():
1237
+ notes.append(f"scaffold: {label}/kb/GLOSSARY.md present - kept")
1238
+ else:
1239
+ if not dry:
1240
+ gl.parent.mkdir(parents=True, exist_ok=True)
1241
+ gl.write_text(GLOSSARY_TEMPLATE, encoding="utf-8")
1242
+ notes.append(f"scaffold: wrote {label}/kb/GLOSSARY.md (run /caddis:glossary to fill it)")
1243
+
1201
1244
  gi = root / ".gitignore"
1202
1245
  if gi.exists():
1203
1246
  # APPEND what is missing rather than keeping the file frozen. The old behaviour was
@@ -76,6 +76,7 @@
76
76
  | Feature Plan | `feature-plan/` | Create a phased, TDD-structured implementation plan that acts as the durable spine for multi-session work |
77
77
  | Gate Preflight | `gate-preflight/` | Deterministic headless preflight — validates a plan against the actual codebase, self-contained (never depends on a skill activating) |
78
78
  | Gate Review | `gate-review/` | Deterministic headless code review — adversarial diff review, self-contained (never depends on a skill activating) |
79
+ | Glossary | `glossary/` | Define the words this project invented, in one place — .caddis/kb/GLOSSARY.md |
79
80
  | Handoff | `handoff/` | End-of-session handoff — capture exact state so the next session resumes with zero re-discovery |
80
81
  | Implement | `implement/` | Headless plan executor — implement an approved plan phase-by-phase on the current feature branch, TDD-first, committing per phase |
81
82
  | Kb | `kb/` | Rebuild the KB index (.caddis/kb/DOC-MAP.md) — create it if missing, index un-indexed notes, report dangling links |
@@ -0,0 +1,85 @@
1
+ ---
2
+ name: glossary
3
+ description: Define the words this project invented, in one place — .caddis/kb/GLOSSARY.md
4
+ ---
5
+
6
+ # /glossary — define what this project coined
7
+
8
+ Every project invents vocabulary out of ordinary English, and those are the most dangerous words
9
+ in the codebase: a reader who guesses from the plain word gets it wrong and never finds out.
10
+ caddis itself coined `pool`, `relay`, `drift`, `parking-lot` — and its own glossary went five and
11
+ a half months without mentioning any of them, because nothing measured the gap.
12
+
13
+ Your job is the half a script cannot do: **write what the words mean here.**
14
+
15
+ ## Step 1 — get the candidates
16
+
17
+ ```
18
+ python <caddis-plugin-root>/scripts/caddis_glossary.py
19
+ ```
20
+
21
+ It ranks the words this repo says often that plain English does not, and marks which already
22
+ have an entry. `--scaffold` creates `.caddis/kb/GLOSSARY.md` if it is missing.
23
+
24
+ It is a **suggester, not an oracle**. It is good on an application repo — run against a kanban
25
+ tool it proposed `board`, `lane`, `card` at the top — and noisier on a repo whose docs are ABOUT
26
+ software, where words like `protocol` are both ordinary and genuinely overloaded. Expect to
27
+ discard some suggestions. That is the intended cost: a false positive wastes three seconds, a
28
+ missing term stays undefined forever.
29
+
30
+ If the script is not available, read the repo yourself and look for the same thing.
31
+
32
+ ## Step 2 — apply the admission test to each candidate
33
+
34
+ A term belongs **only if its meaning here is distinct enough from its ordinary technical sense
35
+ that a newcomer would misread it.**
36
+
37
+ - `commit`, `branch`, `endpoint` — **no**. The ordinary meaning is correct.
38
+ - A word the team bent to its own purpose — **yes**. That is the whole point.
39
+
40
+ Discard the rest without comment. A padded glossary is skimmed and then ignored.
41
+
42
+ ## Step 3 — write each definition from the CODE, not from the word
43
+
44
+ Read where the term is actually used before defining it. A definition that restates the word
45
+ teaches nothing, and a wrong one is worse than a gap because it is believed.
46
+
47
+ Each row gets three things:
48
+
49
+ | Column | What goes in it |
50
+ |---|---|
51
+ | **Term** | The canonical spelling. Pick one and stop using the others. |
52
+ | **Definition** | What it means HERE, and — where it helps — why the ordinary reading misleads. Say the thing a newcomer would get wrong. |
53
+ | **DO NOT USE** | The synonyms actually causing confusion in this repo. Leave blank rather than inventing some. |
54
+
55
+ Good: *"pool — the single source of every shipped resource, in `.github/`. Everything installed is EXPORTED from it; nothing is authored in a bundle."*
56
+
57
+ Bad: *"pool — a pool of resources."*
58
+
59
+ ## Step 4 — flag what is still dirty
60
+
61
+ Fill the **Flagged ambiguities** section with the words that remain overloaded. This is the part
62
+ people skip and it is the most valuable part, because a glossary that hides its own dirt starts
63
+ lying.
64
+
65
+ Look for a term used in several incompatible senses. In caddis, `gate` is defined as one thing
66
+ and used as at least twelve — exit gate, quality gate, evidence gate, privacy gate. The honest
67
+ move is to name the overload and recommend the two-word form, not to pick a winner by fiat.
68
+
69
+ Also flag a term whose canonical spelling the code itself contradicts. Do not "fix" either side
70
+ when both are load-bearing; document the split and where each applies.
71
+
72
+ ## Step 5 — report
73
+
74
+ Say how many terms you added, how many candidates you rejected and roughly why, and what you
75
+ flagged as ambiguous. If you found nothing worth adding, say that — it is a real answer, and
76
+ better than padding the file to look productive.
77
+
78
+ ## Keeping it honest
79
+
80
+ Re-run `caddis_glossary.py --check` later. It exits 1 when a frequent term has no entry. It is
81
+ **advisory**: a project may reasonably decide a common word needs no definition, and a gate that
82
+ cannot be satisfied gets bypassed rather than obeyed.
83
+
84
+ Do not enforce the DO-NOT-USE column by grepping. Most banned synonyms are ordinary English —
85
+ "step", "block", "refuse" — and a check that cries wolf gets deleted, leaving no check at all.
@@ -1,7 +1,7 @@
1
1
  {
2
- "poolVersion": "1.3.88",
2
+ "poolVersion": "1.3.90",
3
3
  "bundles": {
4
- "antigravity-plugin": "1.3.88",
4
+ "antigravity-plugin": "1.3.90",
5
5
  "antigravity-plugin-extras": "1.3.28",
6
6
  "codex": "unknown"
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caddis/cli",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "Install, update and diagnose the caddis dev harness across every coding agent you use (Claude Code, agy/Antigravity, and more).",
5
5
  "keywords": [
6
6
  "caddis",