Skip to content

Commit 9ea3d96

Browse files
authored
Schema-consistency fixes from final whole-branch review (#31)
* docs: schema-consistency fixes from final whole-branch review * docs: align body-container prose with L1 origin
1 parent 3420933 commit 9ea3d96

9 files changed

Lines changed: 122 additions & 32 deletions

File tree

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ the dispatcher automatically (see [How the hook behaves](#how-the-hook-behaves)
2424

2525
The diagram and routing table below are copied verbatim from the dispatcher skill,
2626
[`using-cldk-devtools`](skills/using-cldk-devtools/SKILL.md) — the same ones an agent reads
27-
before acting on any codellm-devkit repo. Keep the two in sync; the consistency check in
28-
`tests/` diffs them.
27+
before acting on any codellm-devkit repo. Keep the two in sync;
28+
[`tests/consistency/check-readme-dispatcher-sync.sh`](tests/consistency/check-readme-dispatcher-sync.sh)
29+
diffs them.
2930

3031
```
3132
using-cldk-devtools (dispatcher)
@@ -158,6 +159,7 @@ tests/
158159
scenarios/ # per-skill prompts used to test routing and gates
159160
baselines/ # RED (no-skill) vs. GREEN (with-skill) evidence, ladder dry-runs
160161
hooks/ # hook behavior tests
162+
consistency/ # README/dispatcher-skill sync checks
161163
```
162164

163165
## How the hook behaves

skills/cldk-sdk-frontend/references/schema-contract.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ callers are unchanged.
2727
## The schema shape the models load
2828

2929
- **Root is an envelope, not `Application`.** `{ schema_version, language, max_level,
30-
k_limit?, application }`. `max_level` is authoritative — read it; never sniff for keys.
30+
k_limit?, analyzer{name,version}, application }`. `max_level` is authoritative — read it;
31+
never sniff for keys.
3132
- **`application`** = `{ id: can://<lang>/<app>, kind, symbol_table{path→module},
3233
call_graph[], param_in[], param_out[] }`. `symbol_table` and `call_graph` live
3334
**inside** `application`.
@@ -101,8 +102,12 @@ class Application(_NullSafeBase):
101102
id: str; kind: Literal["application"]="application"
102103
symbol_table: Dict[str, Module]; call_graph: List[Edge]=[]; param_in: List[Edge]=[]; param_out: List[Edge]=[]
103104

105+
class Analyzer(_NullSafeBase):
106+
name: str; version: str
107+
104108
class AnalysisPayload(_NullSafeBase): # the envelope / manifest — .application is the tree
105-
schema_version: str; language: str; max_level: int; k_limit: Optional[int]=None; application: Application
109+
schema_version: str; language: str; max_level: int; k_limit: Optional[int]=None
110+
analyzer: Optional[Analyzer]=None; application: Application
106111
```
107112

108113
Prefer a single `Node` with a **string** `kind` over per-language subclasses or a rigid

skills/codeanalyzer-backend/references/level-2-call-graph.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ source node's props.
6363
Run the analyzer at `-a 2` on the fixture and confirm all of:
6464

6565
- **No dangling endpoints** — every edge `src` and `dst` resolves to a real callable id in the symbol
66-
table (`for e in call_graph: assert e.src in all_sigs and e.dst in all_sigs`);
66+
table (`for e in call_graph: assert e.src in all_ids and e.dst in all_ids`);
6767
- every edge has a **non-empty `prov`** naming the resolver;
6868
- **`callee` is backfilled** (non-null id) on successfully resolved call sites, and still `null` on
6969
the honest-unresolved ones;

skills/codeanalyzer-backend/references/testing-and-validation.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ Run each level's gate on the fixture; do not build the next level until the curr
6666
non-eager run.
6767

6868
### L2 — call-graph gate (`-a 2`)
69-
1. Every edge endpoint matches a real signature — `for e in call_graph: assert e.src in all_sigs and
70-
e.dst in all_sigs` (no dangling).
69+
1. Every edge endpoint matches a real `can://` id — `for e in call_graph: assert e.src in all_ids and
70+
e.dst in all_ids` (no dangling).
7171
2. Every edge has a **non-empty `prov`** naming the resolver.
7272
3. **`callee` backfilled** (non-null id) on resolved sites; still `null` on honest-unresolved ones.
7373
4. A **named expected edge** present (exact `(src, dst)`), plus at least one **cross-package** edge.

skills/designing-cldk-changes/SKILL.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ shared contract or spans repos — is designed here, as a spec plus a GitHub epi
1313
## Entry Preconditions
1414

1515
You are here because the work is structural: a new language, schema v2
16-
evolution/migration, a new analysis level (L3/L4), a new SDK facade surface, or
16+
evolution/migration, a new analysis level (L2/L3/L4), a new SDK facade surface, or
1717
any cross-repo feature. If it arrived as a "small fix" that turned out to move
1818
the schema v2 output or the public SDK API, **maintaining-cldk**'s contract gate
1919
escalated it here — say so and continue; it is now a structural change, not a fix.
@@ -32,7 +32,7 @@ Then state the change-type → repos-affected mapping:
3232
| --- | --- | --- | --- |
3333
| New language | new `codeanalyzer-<lang>` | `python-sdk` (+ TS SDK) | docs |
3434
| Schema v2 evolution / migration | every affected `codeanalyzer-*` | every affected SDK | docs |
35-
| New analysis level (L3/L4) | that `codeanalyzer-<lang>` | SDK only if the surface changes | docs |
35+
| New analysis level (L2/L3/L4) | that `codeanalyzer-<lang>` | SDK only if the surface changes | docs |
3636
| New facade surface / SDK feature || `python-sdk` (+ TS SDK) | docs |
3737
| Docs-only structural change ||| docs |
3838

skills/designing-cldk-changes/references/canonical-schema.md

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,14 @@ application ← the root; carries an id
5858
module: { types{}, functions{} } ← L1 (per-file / compilation-unit container)
5959
type: { callables{}, fields{} } ← L1
6060
callable: { body{}, cfg[], cdg[], ddg[], summary[] }
61-
body: { <local-id>: node } ← L3+ (statements, then synthetic vertices)
61+
body: { <local-id>: node } ← L1: call nodes; completes at L3
6262
call_graph[], param_in[], param_out[] ← cross-function edges, at the application scope
6363
```
6464

6565
- **Above the callable**: name-keyed maps (`types`, `functions`, `callables`) — each node
6666
carries its full `id`.
67-
- **At the callable**: `body` is the container that grows at L3+. It is a map keyed by the
67+
- **At the callable**: `body` is the container that exists from L1 (holding the `call` nodes)
68+
and completes at L3. It is a map keyed by the
6869
node's **local id** (a source position `line:col`, or an `@tag` for synthetic vertices).
6970
- **Edges live at the lowest common ancestor of their endpoints**: intra-callable edges
7071
(`cfg`/`cdg`/`ddg`/`summary`) hang on the callable; cross-callable edges
@@ -77,17 +78,18 @@ application → file/module → type (class|struct|interface|enum|…) → calla
7778
→ statement (statement|call|return|branch|loop|…) → [expression, opt]
7879
```
7980

80-
plus the **synthetic** vertices introduced at L4: `entry`, `exit`, `formal_in`, `formal_out`,
81-
`actual_in`, `actual_out`. A node is therefore *either an AST region or a synthetic analysis
82-
vertex*; both fit the tree (synthetic vertices are children of the callable or of a call-site
83-
statement).
81+
plus the **synthetic** vertices: `entry`/`exit` introduced at L3, and `formal_in`/`formal_out`/
82+
`actual_in`/`actual_out` introduced at L4. A node is therefore *either an AST region or a
83+
synthetic analysis vertex*; both fit the tree (synthetic vertices are children of the callable or
84+
of a call-site statement).
8485

8586
## Identity
8687

8788
Two tiers, and the boundary is the **callable leaf line** — the same line where L1 stops.
8889

8990
- **Durable ids (≥ callable)** — files, modules, types, callables get stable
90-
`cldk://`-style URIs (grammar in `skills/cldk-sdk-frontend/references/schema-contract.md`) that
91+
`can://` URIs (an extension of the upstream `cldk://` RFC grammar; grammar in
92+
`skills/cldk-sdk-frontend/references/schema-contract.md`) that
9193
survive re-analysis and are what external tools (SCIP export, cross-repo joins) address. The grammar
9294
is a **containment path** with an application segment so multiple apps in one language don't
9395
collide:
@@ -214,6 +216,7 @@ Building **both** is a first-class deliverable for every analyzer, not an aftert
214216
```jsonc
215217
{
216218
"schema_version": "2.0.0", "language": "go", "max_level": 4, "k_limit": 3,
219+
"analyzer": { "name": "codeanalyzer-go", "version": "1.4.0" },
217220
"application": {
218221
"id": "can://go/myapp", "kind": "application",
219222
"symbol_table": {
@@ -228,7 +231,7 @@ Building **both** is a first-class deliverable for every analyzer, not an aftert
228231
"Hash(string)uint64": {
229232
"id": "can://go/myapp/src/util.go/Hasher/Hash(string)uint64", "kind": "method",
230233
"span": { "start":[14,1], "end":[22,1], "bytes":[42,180] },
231-
"body": { // L3+
234+
"body": { // L1: call nodes; completes at L3
232235
"@entry": { "kind":"entry" },
233236
"15:2": { "kind":"statement", "span":{ "start":[15,2],"end":[15,18],"bytes":[84,100] } },
234237
"16:2": { "kind":"call", "span":{...}, "callee":"can://go/myapp/src/fnv.go/New64()" },

skills/designing-cldk-changes/references/schema-design-loop.md

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,17 @@ For every divergence (step 1) and every new language concept (step 2), present i
5454
use `AskUserQuestion`. Don't batch a whole node into one vague question; ask per real decision,
5555
with a recommended default first. Use this shape:
5656

57-
> **`callable.decorators`/annotations.** In **Java** these are flat strings
58-
> (`annotations: List[str]` + `modifiers`). In **Python** they are structured `PyDecorator`
59-
> objects (name, qualified_name, positional/keyword args). TypeScript has decorators that carry
60-
> arguments (`@Component({selector: '...'})`).
61-
> *How do you want to model TS decorators?*
62-
> 1. **Structured `TSDecorator` (recommended)** — like Python; preserves args so entrypoint
63-
> finders can read `@Get('/path')` later. Costs a richer model.
64-
> 2. **Flat strings** — like Java; simplest, but throws away argument structure.
65-
> 3. **Structured + raw fallback** — structured fields plus the raw source string.
57+
> **Rust lifetime parameters.** Neither **Java** nor **Python** has a reference precedent —
58+
> lifetimes are a Rust-only construct (`fn longest<'a>(x: &'a str, y: &'a str) -> &'a str`). The
59+
> rubric (`references/canonical-schema.md` § Language expansion) offers three shapes for anything
60+
> with no precedent: a new typed field, folding it into an existing field, or an open-vocab `tags`
61+
> entry.
62+
> *How do you want to model lifetime parameters on `callable`?*
63+
> 1. **Typed field `lifetimes: LifetimeParam[]` (recommended)**`{ name, bounds[] }` per
64+
> callable; preserves borrow-checker-relevant structure for later dataflow work.
65+
> 2. **Fold into `parameters`** — attach a `lifetime` string to each `param` that borrows;
66+
> cheaper, but loses signature-level lifetime bounds (`'a: 'b`).
67+
> 3. **Open-vocab `tags`**`tags: ["lifetime:'a"]`; zero schema cost, but not machine-queryable.
6668
6769
Always include *why* each option exists and what it buys/costs, anchored in what the references
6870
did. When the language adds something with no reference precedent (TS generics, Go receiver types,

skills/maintaining-cldk/references/repo-map.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@
33
Where a fix lands, and what pins to what. Enumerated live via
44
`gh repo list codellm-devkit --limit 100 --json name,description` (27 returned,
55
25 after excluding the two org-meta repos `.github` / `.github-private`). Facts below
6-
are evidenced from that enumeration plus local checkouts under
7-
`/Users/rkrsn/workspace/codellm-devkit/`; anything not directly checked is
8-
marked **verify per repo**.
6+
are evidenced from that enumeration plus a local sibling checkout of the org's repos,
7+
if present; anything not directly checked is marked **verify per repo**.
98

109
## Tier 1 — SDKs (frontend)
1110

1211
| Repo | Owns | Evidence |
1312
| --- | --- | --- |
14-
| `python-sdk` | The `cldk` PyPI package — the public Python facade (`CLDK(language=...).analysis(...)`). Wires Java, Python, C, and TypeScript backends today (`cldk/analysis/{java,python,c,typescript}`); no Go/Rust wiring yet. | Checked out; `cldk/analysis/` listing. |
13+
| `python-sdk` | The `cldk` PyPI package — the public Python facade. Canonical surface today is the `CLDK.<lang>(project_path=..., backend=...)` factory, with the legacy `CLDK(language=...).analysis(...)` retained as a compat shim. Wires Java, Python, C, and TypeScript backends today (`cldk/analysis/{java,python,c,typescript}`); no Go/Rust wiring yet. | Checked out; `cldk/analysis/` listing. |
1514
| `typescript-sdk` | The `@codellm-devkit/cldk` npm package — the TS/JS facade. | No local checkout — **verify per repo**. |
1615

1716
## Tier 2 — Analysis backends (`codeanalyzer-<lang>`)
@@ -34,7 +33,7 @@ checks.
3433
| `codeanalyzer-java` | Java, via WALA + JavaParser. Has its own Neo4j `SchemaCatalog` (`SCHEMA_VERSION` constant, currently `1.1.0` in the local checkout) — **verify per repo** whether this has moved to schema v2 by the time you're reading this. | Checked out. |
3534
| `codeanalyzer-python` | Python, via Jedi + Tree-sitter (+ `codeanalyzer-codeql` for deeper dataflow). Local checkout's `codeanalyzer/core.py` gates its analysis cache on `schema_version == "2.0.0"` — already on schema v2. | Checked out. |
3635
| `codeanalyzer-typescript` | TypeScript/JavaScript, via ts-morph + Jelly. One repo, multiple in-flight branches: the `main`-tracking checkout sits on `feat/issue-2-program-graphs`; a second worktree of the **same repo** sits on `fix/issue-46-bolt-symbol-labels`, which is where the schema-v2 additive-CPG emitter + Neo4j v2 projection work is landing (`PROGRAM_GRAPHS_SCHEMA_VERSION` in `src/schema/graphs.ts`). Do not mistake the two worktrees for two repos. | Checked out (two worktrees, one `origin`). |
37-
| `codeanalyzer-go` | Go. **In development** per the org description; the local checkout of `main` is a near-empty stub (`LICENSE`, `README.md` only) — the working analyzer, if any, lives on a feature branch/PR, not `main`. This is also the scenario dry-run cwd: treat anything you find here as real repo state, not an answer key. | Checked out. |
36+
| `codeanalyzer-go` | Go. **In development** per the org description; the local checkout of `main` is a near-empty stub (`LICENSE`, `README.md` only) — the working analyzer, if any, lives on a feature branch/PR, not `main`. | Checked out. |
3837
| `codeanalyzer-rust` | Rust, via the Rust compiler's IR. **In development**; local checkout has no source tree yet (just `docs/`, `.devcontainer/`). | Checked out. |
3938
| `codeanalyzer-kotlin`, `codeanalyzer-swift`, `codeanalyzer-dotnet`, `codeanalyzer-abap` | Kotlin / Swift / .NET / ABAP backends. All **in development**; local checkouts are empty (git metadata only, no source). | Checked out. |
4039
| `codeanalyzer-clang` | C/C++ family. Has a `CLAUDE.md` and `docs/` but no visible source tree in the checkout — **verify per repo** for current depth. | Checked out. |
@@ -45,7 +44,7 @@ checks.
4544

4645
| Repo | Owns | Evidence |
4746
| --- | --- | --- |
48-
| `cldk-devtools` | This plugin — the skills that extend/maintain CLDK itself (this repo, worked in as a git worktree). Note: this repo was renamed from `cldk-forge` (`gh repo view codellm-devkit/cldk-forge` redirects to `cldk-devtools`) — a local checkout still pointing at the old `cldk-forge` remote is this same repo, pre-rename, not a distinct one. | Checked out (this worktree); `git remote`/`gh repo view` redirect confirmed. |
47+
| `cldk-devtools` | This plugin — the skills that extend/maintain CLDK itself (this repo). Note: this repo was renamed from `cldk-forge` (`gh repo view codellm-devkit/cldk-forge` redirects to `cldk-devtools`) — a local checkout still pointing at the old `cldk-forge` remote is this same repo, pre-rename, not a distinct one. | Checked out (`cldk-devtools`, this repo); `git remote`/`gh repo view` redirect confirmed. |
4948
| `cocoa` | COCOA (Code Context Agent) — the Python implementation + MCP toolbox server wrapping CLDK for coding agents. | Checked out. |
5049
| `cocoa-mcp` | A second COCOA/MCP repo; its `gh` description text says "Python Implementation" but `primaryLanguage` reports Java in the enumeration — **verify per repo** which is authoritative before treating this as pure-Python. | Checked out; description/language mismatch observed directly. |
5150
| `cocoa-ts` | The TypeScript implementation of COCOA + its MCP server. | No local checkout — **verify per repo**. |
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#!/usr/bin/env bash
2+
# Consistency check: README.md's "## The Ladder" diagram and "## Routing" table
3+
# must stay byte-identical to skills/using-cldk-devtools/SKILL.md's copies of the
4+
# same two blocks. Run from repo root: bash tests/consistency/check-readme-dispatcher-sync.sh
5+
set -u
6+
7+
root="$(cd "$(dirname "$0")/../.." && pwd)"
8+
readme="$root/README.md"
9+
skill="$root/skills/using-cldk-devtools/SKILL.md"
10+
fails=0
11+
12+
for f in "$readme" "$skill"; do
13+
if [ ! -f "$f" ]; then
14+
echo "FAIL missing file: $f"
15+
exit 1
16+
fi
17+
done
18+
19+
# extract_fenced_after_heading FILE HEADING
20+
# Prints the contents of the first fenced ``` code block that appears after an
21+
# exact-match heading line.
22+
extract_fenced_after_heading() {
23+
awk -v heading="$2" '
24+
$0 == heading { found=1; next }
25+
found && /^```/ { if (in_block) { exit } else { in_block=1; next } }
26+
found && in_block { print }
27+
' "$1"
28+
}
29+
30+
# extract_table_after_heading FILE HEADING
31+
# Prints the contiguous run of `|`-prefixed table rows that appears after an
32+
# exact-match heading line.
33+
extract_table_after_heading() {
34+
awk -v heading="$2" '
35+
$0 == heading { found=1; next }
36+
found && /^\|/ { print; seen=1; next }
37+
found && seen && $0 !~ /^\|/ { exit }
38+
' "$1"
39+
}
40+
41+
check_block() {
42+
name="$1"; a="$2"; b="$3"
43+
if diff -u "$a" "$b" >/tmp/check-readme-dispatcher-sync.diff.$$ 2>&1; then
44+
echo "OK $name"
45+
else
46+
echo "FAIL $name"
47+
cat /tmp/check-readme-dispatcher-sync.diff.$$
48+
fails=$((fails+1))
49+
fi
50+
rm -f /tmp/check-readme-dispatcher-sync.diff.$$
51+
}
52+
53+
tmp="$(mktemp -d)"
54+
trap 'rm -rf "$tmp"' EXIT
55+
56+
extract_fenced_after_heading "$readme" "## The Ladder" > "$tmp/readme_ladder.txt"
57+
extract_fenced_after_heading "$skill" "## The Ladder" > "$tmp/skill_ladder.txt"
58+
check_block "ladder diagram" "$tmp/readme_ladder.txt" "$tmp/skill_ladder.txt"
59+
60+
extract_table_after_heading "$readme" "## Routing" > "$tmp/readme_routing.txt"
61+
extract_table_after_heading "$skill" "## Routing" > "$tmp/skill_routing.txt"
62+
check_block "routing table" "$tmp/readme_routing.txt" "$tmp/skill_routing.txt"
63+
64+
if [ -s "$tmp/readme_ladder.txt" ]; then :; else
65+
echo "FAIL ladder diagram: extracted empty from README.md — extraction is broken"
66+
fails=$((fails+1))
67+
fi
68+
if [ -s "$tmp/readme_routing.txt" ]; then :; else
69+
echo "FAIL routing table: extracted empty from README.md — extraction is broken"
70+
fails=$((fails+1))
71+
fi
72+
73+
if [ "$fails" -eq 0 ]; then
74+
echo "All blocks in sync."
75+
exit 0
76+
else
77+
echo "$fails block(s) out of sync."
78+
exit 1
79+
fi

0 commit comments

Comments
 (0)