Skip to content

Commit 8d07198

Browse files
authored
Add finishing-cldk-work mode skill (#29)
* feat: add finishing-cldk-work mode skill * docs: correct Application import path in release gate command
1 parent 4d3db83 commit 8d07198

11 files changed

Lines changed: 578 additions & 341 deletions

File tree

skills/codeanalyzer-backend/references/packaging-and-release.md

Lines changed: 0 additions & 339 deletions
This file was deleted.
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
---
2+
name: finishing-cldk-work
3+
description: Use when implementation on a CLDK branch is complete and the work needs verification, merge, release, documentation updates, and issue closeout — before claiming any CLDK work is done.
4+
---
5+
6+
# Finishing CLDK work
7+
8+
The ladder's exit. Every other rung — `designing-cldk-changes`,
9+
`maintaining-cldk`, `codeanalyzer-backend`, `cldk-sdk-frontend` — terminates
10+
here. Nothing is "done" until it has passed through this gate: gates green,
11+
a real ship decision, release mechanics if warranted, and closeout.
12+
13+
## Entry Preconditions
14+
15+
Implementation is complete on a branch. Most arrivals come from
16+
`maintaining-cldk`, carrying a **propagation verdict** in hand (the
17+
required output of its propagation sweep — a list of follow-on repos, or
18+
`none` with its reasoning). Arrivals from `codeanalyzer-backend` or
19+
`cldk-sdk-frontend` carry no verdict of their own; treat their Terminal
20+
State handoff as "implementation complete, gates not yet re-run here."
21+
Either way, do not start from a bare "it's done" claim with no artifact
22+
behind it.
23+
24+
## Verification Gates
25+
26+
Which commands to run depend on the repo type — analyzer, SDK, or docs —
27+
see the full matrix in `references/release-gates.md`.
28+
29+
<HARD-GATE>
30+
No success claim, no tag, no release, no 'done' without running the gate
31+
commands NOW and reading their output. 'Passed earlier' is not evidence.
32+
</HARD-GATE>
33+
34+
A same-day CI run, a teammate's "LGTM," or your own memory of an earlier
35+
pass are all the same failure shape: assurance detached from a command you
36+
just watched execute. Re-run the gate on the exact commit you're about to
37+
act on.
38+
39+
## Ship Decision
40+
41+
Passing gates only tells you the branch is mergeable — it does not tell you
42+
whether to release. Decide explicitly:
43+
44+
- **Forces a release:** a behavior change reaches users (a bug fix, a new
45+
field, a new accessor), or a downstream pin needs to move (an SDK's
46+
`codeanalyzer-<lang>==X` needs the fix that just landed).
47+
- **Merge-only, no release:** docs-only changes, internal refactors with no
48+
observable behavior change, test-only changes. Merge and stop — cutting a
49+
release train for these is manufacturing release noise, not shipping
50+
value. The one exception: if docs are themselves published from a tagged
51+
release (a docs-site build keyed to a version), a "docs-only" change can
52+
still need one — check before assuming not.
53+
54+
## Release Mechanics
55+
56+
Once a release is warranted, mechanics differ by repo type — full detail in
57+
`references/packaging-and-release.md`:
58+
59+
- **Analyzer** (`codeanalyzer-<lang>`) — a tag-triggered pipeline: PyPI thin
60+
wheel, GitHub Release binaries, and a Homebrew formula push, all cut from
61+
one `vX.Y.Z` tag in lockstep.
62+
- **SDK** (`python-sdk`, `typescript-sdk`, …) — bump the analyzer version
63+
pin to the just-released version, then the SDK's own release.
64+
65+
Never tag off a stale local build; the tag is what the pipeline trusts.
66+
67+
## Closeout
68+
69+
Docs updates, issue/epic bookkeeping, and following the propagation verdict
70+
through — full etiquette in `references/docs-and-closeout.md`. In brief:
71+
update the surfaces that describe what changed, close the child issue(s)
72+
this work resolves, tick the epic's checklist if one exists, and for every
73+
repo the propagation verdict listed, file the follow-on issue before
74+
stopping — a verdict that lists a repo and gets no issue is a dropped
75+
thread, not a closed one.
76+
77+
## Terminal State
78+
79+
finishing-cldk-work is the ladder's exit. If the propagation verdict listed
80+
follow-on repos, each becomes a new maintaining-cldk entry (file the issues
81+
before stopping).
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Docs and closeout
2+
3+
The last mile: making the docs match what actually shipped, closing the paperwork, and honoring
4+
the propagation verdict `maintaining-cldk` handed off. Skipping this is how a merged, released,
5+
gate-green change still leaves the org in a stale or half-tracked state.
6+
7+
## Docs surfaces
8+
9+
Three surfaces, each with its own staleness question — check all three, not just the one that
10+
comes to mind first:
11+
12+
- **Repo READMEs** — the analyzer's or SDK's own `README.md`: supported languages/levels, install
13+
instructions (`pip install codeanalyzer-<lang>`, `brew install codeanalyzer-<lang>`), CLI flag
14+
docs, version badges. A new level, a new accessor, or a new install channel that isn't reflected
15+
here is invisible to anyone who doesn't read source.
16+
- **CLAUDE.md / AGENTS.md / GEMINI.md agent guides** — each repo's own agent-facing conventions
17+
(branch naming, test layout, commit style). These drift when a repo's structure changes (a new
18+
package, a renamed workflow) but the fix rarely thinks to touch them.
19+
- **The docs site** (`docs` repo, `codellm-devkit.info`) — two live fronts on the same repo: `main`
20+
(mkdocs) and the `astro` branch (Astro/Starlight redesign). A behavior-preserving bug fix rarely
21+
needs a docs change; a fix that changes an error message, a CLI flag's behavior, a documented
22+
limitation, or a version number usually does. Check whether the change reaches one front or both
23+
— don't assume parity between them.
24+
25+
If the release just cut changed a public surface (new field visible through an accessor, new CLI
26+
flag, new facade method), at least one of these three needs an update. "No behavior changed for
27+
users" is the only clean exemption, and it should already have been established in the Ship
28+
Decision step.
29+
30+
## Issue and epic closeout etiquette
31+
32+
- **Close the child issue(s)** this work resolves, with a comment naming the merge commit / release
33+
tag that resolved it — not a bare "done."
34+
- **Tick the epic's checklist**, if this work traces to one (`designing-cldk-changes`'s epic + child
35+
issue structure, `skills/designing-cldk-changes/references/epic-and-issue-templates.md`). A child
36+
issue closing is what advances the epic's `CHILDREN` checklist; go edit the epic, don't leave it
37+
for someone else to notice the child closed.
38+
- **Don't close the epic itself** until every child on its checklist is closed and its own
39+
Definition of Done (gates green across every affected repo, versions pinned in lockstep, docs
40+
updated) is genuinely satisfied — not just the child you personally worked.
41+
- For work with no epic (most `maintaining-cldk` entries — a bug fix or small feature stands alone),
42+
closing its single issue with the resolving commit/release reference is the whole of closeout on
43+
this axis.
44+
45+
## Propagation-verdict follow-through
46+
47+
`maintaining-cldk`'s propagation sweep hands off a **required** verdict in this exact shape:
48+
49+
> **Propagation verdict:** \<list of follow-on repos + why\> | none, because \<reason\>.
50+
51+
Reading that verdict here is not optional, and neither is acting on it:
52+
53+
- **If the verdict is `none`**, confirm its reasoning is still legible (it should already show its
54+
work — what was checked, why each came back negative) and stop; there is nothing further to file.
55+
- **If the verdict lists follow-on repos**, file a tracking issue for each one **before** declaring
56+
this work closed out. Each filed issue is the seed of a new `maintaining-cldk` entry — this is
57+
exactly what the Terminal State means by "each becomes a new maintaining-cldk entry." A verdict
58+
that names `codeanalyzer-java` and `python-sdk` but produces zero new issues is a verdict that was
59+
read and then ignored — the most common way this rung's work silently goes incomplete.
60+
- Link each follow-on issue back to the work that surfaced it, so the trail from "we found this
61+
while fixing X" to "here's the tracked fix for Y" survives past this conversation.
62+
63+
## Definition of done (closeout)
64+
65+
- [ ] Every gate in `references/release-gates.md` that applies to this repo ran on the commit being
66+
shipped, and its output was read.
67+
- [ ] The ship decision was made explicitly (release vs merge-only) and, if a release was cut, its
68+
mechanics followed `references/packaging-and-release.md` including version lockstep.
69+
- [ ] Docs surfaces checked against the actual change — README, agent guides, docs site — updated or
70+
explicitly ruled not-needed.
71+
- [ ] Child issue(s) closed with a reference to the resolving commit/tag; epic checklist ticked if
72+
one exists.
73+
- [ ] Propagation verdict re-read; every listed follow-on repo has a filed issue, or the verdict was
74+
`none` with visible reasoning.
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Packaging & release: tag-triggered automation
2+
3+
Release mechanics for both halves of the ladder that reach this rung: an **analyzer**
4+
(`codeanalyzer-<lang>`) cutting its own distribution, and an **SDK** (`python-sdk`,
5+
`typescript-sdk`, …) picking up a new analyzer release via a pin bump and cutting its own. Neither
6+
is a manual, one-off act — both are `git tag vX.Y.Z && git push origin vX.Y.Z` triggering a CI
7+
workflow that does the rest. Never publish by hand; if the tag-push doesn't produce the artifact,
8+
fix the workflow rather than uploading around it.
9+
10+
## Analyzer release: the thin-PyPI-binary model
11+
12+
The analyzer repo builds its self-contained binary for every supported platform, then a **tag-
13+
triggered** `.github/workflows/release.yml` publishes it three ways in one coordinated run. The
14+
reference implementation is `codeanalyzer-ts` (`packaging/python/` + `.github/workflows/release.yml`)
15+
— mirror it for any new analyzer.
16+
17+
1. **Thin PyPI package `codeanalyzer-<lang>`** — one platform-tagged wheel per OS/arch, each
18+
carrying the prebuilt binary under `_bin/` and exposing `bin_path()`. This is what the Python SDK
19+
`pip`-depends on.
20+
2. **Raw binaries as GitHub Release assets**, with a SHA256 checksum per artifact — for consumers
21+
that can't `pip install`, and what the TypeScript SDK downloads directly.
22+
3. **A Homebrew formula** (`Formula/codeanalyzer-<lang>.rb`) pushed to the shared tap
23+
`codellm-devkit/homebrew-tap`, so end users get `brew install codeanalyzer-<lang>`.
24+
25+
**Two wheel shapes.** Compiled-binary analyzers (TS, Go, Rust, C++, JVM) ship a platform-tagged
26+
wheel (`py3-none-<platform>`) carrying the binary, no usable sdist. A **Python** analyzer (e.g.
27+
`codeanalyzer-python`) is the exception: the wheel carries importable Python code, is platform-
28+
independent, and is imported in-process — no binary, no `bin_path()`.
29+
30+
**Build strategy** is a per-language decision, not a detail: single-host cross-compilation (Bun,
31+
Go, Rust — one CI job emits every platform binary) when the toolchain cross-compiles cleanly, or a
32+
native-runner build matrix (GraalVM `native-image`, clang with per-target sysroots — one CI leg per
33+
`(os, arch)` fanning in to one publish job) when it can't. State which one applies in the analyzer
34+
README's Architecture & Tooling section.
35+
36+
### The release workflow, in order
37+
38+
Trigger on `v*.*.*` tags (+ `workflow_dispatch`); `permissions: contents: write` (+ `id-token: write`
39+
for PyPI OIDC):
40+
41+
1. Verify the tag version matches the analyzer manifest; gate on the analyzer's own test suite
42+
**first** — on failure, delete the just-pushed tag (`git push --delete origin <tag>`) so a broken
43+
build leaves no half-released version behind.
44+
2. Build the platform wheels (single-host: one job runs the per-target loop; matrix: one leg per
45+
`(os, arch)`, then a fan-in job).
46+
3. `auditwheel repair` Linux wheels to a manylinux policy (cover musllinux or document it
47+
unsupported); emit a SHA256 checksum per artifact.
48+
4. **Smoke-test every wheel before publishing** — install on a clean runner, run
49+
`codeanalyzer-<lang> --help` or a tiny fixture analysis.
50+
5. Extract raw binaries into `release-bins/`.
51+
6. Publish the GitHub Release with the binaries + checksums as assets.
52+
7. Publish the wheels to PyPI as `codeanalyzer-<lang>` — Trusted Publishing (OIDC), no long-lived
53+
token; `skip-existing: true` so a re-run is idempotent.
54+
8. **Separate `homebrew` job** (`needs: release`, fed `release-bins/` via artifact — not a rebuild):
55+
generate the formula, push it to `codellm-devkit/homebrew-tap` with a `HOMEBREW_TAP_TOKEN` PAT.
56+
Isolating this job matters: it's the step most likely to fail (cross-repo PAT), and if it rode
57+
inside the publish job a token failure would mark an otherwise-successful PyPI+Release run red
58+
and unrecoverable-without-a-new-tag.
59+
60+
For a **Python analyzer**, drop steps 2–5 (compile/extract) and publish one pure wheel + sdist.
61+
Ship no sdist for compiled analyzers, or one whose build fails with a clear "no prebuilt binary for
62+
this platform" message — never let pip silently fall back to a from-source compile.
63+
64+
Tag the release `vX.Y.Z` with real notes: a hand-curated *Keep a Changelog* block (`### Added` /
65+
`### Changed` — mark **BREAKING** inline / `### Fixed`) plus an auto-generated "Detailed Changes"
66+
block. Don't ship a release with an empty body.
67+
68+
## SDK release: pin bump, then its own release
69+
70+
An SDK never re-publishes an analyzer's artifact — it depends on it. Once an analyzer release
71+
exists (PyPI + Release binaries, above), the SDK's release is a two-step act:
72+
73+
1. **Bump the pin** to the just-released version — the Python SDK's `pyproject.toml`
74+
`dependencies` (`codeanalyzer-<lang>==X.Y.Z`) and its `[tool.backend-versions]` table; the
75+
TypeScript SDK's equivalent pin to the same tag. This is its own PR, reviewed and merged before
76+
any tag.
77+
2. **Cut the SDK's own release** the same tag-triggered way: a `chore(release): X.Y.Z` PR bumps the
78+
SDK's own version + changelog (this alone ships nothing), then `git tag vX.Y.Z && git push
79+
origin vX.Y.Z` triggers the SDK's release workflow — its own test suite, its own PyPI/npm
80+
publish, and (if wired) a docs-repo dispatch to regenerate the API reference. A version bump PR
81+
with no tag pushed is a no-op release-wise; don't mistake merging it for shipping.
82+
83+
A pin bump with no analyzer release behind it, or an analyzer release with no consumer ever bumping
84+
the pin, both leave the fix unreachable by users — "the fix is released" and "the fix is
85+
consumable" are different claims; the pin is the gate between them.
86+
87+
## Version lockstep (the one rule that bites)
88+
89+
Keep the version identical across all of these for a given analyzer release, or pip resolves a
90+
mismatched binary:
91+
92+
- the analyzer manifest (`package.json` version, or the build version for Go/Rust/JVM);
93+
- `packaging/python/pyproject.toml`'s `project.version` **and** `__init__.py`'s `__version__`;
94+
- the Python SDK pin — both `dependencies` and `[tool.backend-versions]`;
95+
- the TypeScript SDK pin — the Release tag/binary filename it downloads;
96+
- the Homebrew formula's `version` and asset URLs/checksums — the release workflow regenerates this
97+
every tag; don't hand-edit it.
98+
99+
## How each SDK consumes the artifact
100+
101+
- **Python SDK** — resolves the binary in priority order: `analysis_backend_path`
102+
`$CODEANALYZER_<LANG>_BIN``codeanalyzer_<lang>.bin_path()` (the pip package) → an in-tree
103+
bundled fallback. (Python analyzer: `import codeanalyzer_<lang>`, run in-process.)
104+
- **TypeScript SDK** — cannot consume a PyPI wheel; resolves `analysisBackendPath`
105+
`$CODEANALYZER_<LANG>_BIN` → the GitHub Release asset binary.
106+
107+
This is the contract `cldk-sdk-frontend` wires against — publish accordingly, and don't change the
108+
resolution order without updating both sides.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Release gates — the verification matrix
2+
3+
What "run the gate commands NOW" (the SKILL.md HARD-GATE) means, concretely, per repo type. This
4+
file is a **summary matrix** for the ship decision — it names which gates exist and the commands
5+
that invoke them. It does not restate their pass/fail criteria or fixture design; those live in the
6+
two authoritative references and must stay in sync with them, not be re-derived here:
7+
8+
- **Analyzer repos** (`codeanalyzer-<lang>`) — full gate detail in
9+
`skills/codeanalyzer-backend/references/testing-and-validation.md`.
10+
- **SDK repos** (`python-sdk`, `typescript-sdk`, …) — full gate detail in
11+
`skills/cldk-sdk-frontend/references/sdk-testing.md`.
12+
13+
Re-run every gate in this matrix that applies to the repo you're finishing, on the exact commit
14+
you're about to tag or merge. A green run from an earlier commit, an earlier session, or someone
15+
else's machine does not satisfy the HARD-GATE.
16+
17+
## Analyzer repos (`codeanalyzer-<lang>`)
18+
19+
| Gate | Command | What it proves |
20+
| --- | --- | --- |
21+
| Fixture suite | `go test ./...` (or the ecosystem equivalent — `pytest`, `mvn test`, `cargo test`) | Per-level unit/fixture assertions pass, up to the analyzer's current `max_level`. |
22+
| Schema conformance | Run the CLI at the target level (`codeanalyzer-<lang> -i testdata/fixture -a <1\|2\|3\|4> -o out/`), then `python -c "import json; from cldk.models.cpg.models import Application; Application(**json.load(open('out/analysis.json')))"` | Output validates against the shared CPG models — not just "the binary ran." |
23+
| Monotonicity | Run `-a 1`, `-a 2`, `-a 3`, `-a 4` on the same fixture; diff the JSON | `json(-a 1) ⊆ json(-a 2) ⊆ json(-a 3) ⊆ json(-a 4)` — no level rewrites a lower level's facts. |
24+
| Determinism | `codeanalyzer-<lang> -i testdata/fixture -j 1 -o j1/` vs `... -j <N> -o jN/`; diff | `-j N` output is byte-identical to `-j 1`. |
25+
| Cross-projection | Compare `--emit neo4j` node/edge counts (at full depth) against the JSON at `max_level` | The two projections agree, modulo documented `HAS_*` containment edges. |
26+
27+
The full per-level assertions (named expected call-graph edges, exact PDG-slice sets, cache
28+
round-trip tests, the two-tier identity gate) are in `testing-and-validation.md` — this matrix only
29+
names the commands, not what "green" means field by field.
30+
31+
## SDK repos (`python-sdk`, `typescript-sdk`, …)
32+
33+
| Gate | Command | What it proves |
34+
| --- | --- | --- |
35+
| Mocked | `pytest tests/test_<lang>_analysis.py` (or `bun test` for the TS SDK) | Facade/view logic is correct given a fixture `analysis.json`; includes the public-API-stability assertion. |
36+
| E2E | `pytest tests/test_<lang>_e2e.py` | The real binary produces conformant output end to end; skips cleanly when the binary is absent, must pass when it's on `PATH`. |
37+
| Backend-contract | `pytest tests/test_<lang>_backend_contract.py` | The concrete backend implements every `<Lang>AnalysisBackend` ABC method (local/Neo4j parity). |
38+
| Client-analysis (if L3/L4 exposed) | the slice/taint gate tests named in `sdk-testing.md § Client-analysis gates` | Slice and taint queries over the analyzer's graph match hand-computed expected sets. |
39+
40+
All three core tiers must be green before a merge is even considered mergeable, independent of
41+
whether a release follows.
42+
43+
## Docs repos (`docs`)
44+
45+
| Gate | Command | What it proves |
46+
| --- | --- | --- |
47+
| Build | `mkdocs build --strict` (the `main` mkdocs front) and/or `npm run build` (the `astro` branch's Astro/Starlight front) | The site builds cleanly on the content you're about to publish — a broken build is not a docs-only no-op. |
48+
49+
Both fronts are the same repo (`skills/maintaining-cldk/references/repo-map.md`) — check whether the
50+
change reaches one or both before treating a single green build as sufficient.
51+
52+
## Reading the result
53+
54+
"Green" means every applicable row above ran on the current commit and its output was read, not
55+
assumed. A gate you didn't run is not a gate you passed.

skills/maintaining-cldk/references/propagation-checklist.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ pinned by a version-locked dependency (`python-sdk`'s
3131
reach a single SDK user until:
3232

3333
1. the analyzer cuts a release with the fix (per
34-
`skills/codeanalyzer-backend/references/packaging-and-release.md`), and
34+
`skills/finishing-cldk-work/references/packaging-and-release.md`), and
3535
2. the pinning SDK bumps its dependency to that version.
3636

3737
State explicitly whether a pin bump is needed, and in which SDK(s). "The fix

0 commit comments

Comments
 (0)