code-constraints 0.1.0__py3-none-any.whl

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.
Files changed (116) hide show
  1. code_constraints/__init__.py +1 -0
  2. code_constraints/cli/__init__.py +0 -0
  3. code_constraints/cli/__main__.py +1555 -0
  4. code_constraints/cli/_assets/agents/cdec-architect.md +468 -0
  5. code_constraints/cli/_assets/agents/oop-refactor-architect.md +317 -0
  6. code_constraints/cli/_assets/shims/csharp/CodeConstraintsRules.cs +94 -0
  7. code_constraints/cli/_assets/shims/julia/CdecRules.jl +129 -0
  8. code_constraints/cli/_assets/shims/lua/cdec_rules.lua +92 -0
  9. code_constraints/cli/_assets/shims/odin/cdec_rules.odin +67 -0
  10. code_constraints/cli/_assets/shims/python/cdec_rules.py +94 -0
  11. code_constraints/cli/_assets/skills/cdec-architecture-loop/SKILL.md +152 -0
  12. code_constraints/cli/depstamp.py +118 -0
  13. code_constraints/cli/detect.py +77 -0
  14. code_constraints/cli/interactive.py +304 -0
  15. code_constraints/cli/scaffold.py +602 -0
  16. code_constraints/cli/update.py +157 -0
  17. code_constraints/core/__init__.py +41 -0
  18. code_constraints/core/annotations.py +217 -0
  19. code_constraints/core/associations.py +134 -0
  20. code_constraints/core/diff.py +302 -0
  21. code_constraints/core/editor_io.py +280 -0
  22. code_constraints/core/graph_model.py +681 -0
  23. code_constraints/core/keys.py +105 -0
  24. code_constraints/core/model.py +294 -0
  25. code_constraints/core/model_io.py +65 -0
  26. code_constraints/core/receivers.py +34 -0
  27. code_constraints/core/rules.py +177 -0
  28. code_constraints/core/rulesdoc.py +208 -0
  29. code_constraints/core/tags.py +114 -0
  30. code_constraints/core/ts_fingerprint.py +88 -0
  31. code_constraints/core/xmi_reader.py +358 -0
  32. code_constraints/core/xmi_writer.py +373 -0
  33. code_constraints/csharp/__init__.py +3 -0
  34. code_constraints/csharp/activity.py +250 -0
  35. code_constraints/csharp/conformance.py +331 -0
  36. code_constraints/csharp/fingerprint.py +274 -0
  37. code_constraints/csharp/parser.py +436 -0
  38. code_constraints/csharp/rules_extract.py +78 -0
  39. code_constraints/csharp/sequence.py +295 -0
  40. code_constraints/enforce/__init__.py +15 -0
  41. code_constraints/enforce/engine.py +122 -0
  42. code_constraints/enforce/model.py +74 -0
  43. code_constraints/julia/__init__.py +5 -0
  44. code_constraints/julia/conformance.py +282 -0
  45. code_constraints/julia/fingerprint.py +226 -0
  46. code_constraints/julia/parser.py +523 -0
  47. code_constraints/julia/rules_extract.py +216 -0
  48. code_constraints/lint/__init__.py +10 -0
  49. code_constraints/lint/baseline.py +96 -0
  50. code_constraints/lint/config.py +239 -0
  51. code_constraints/lint/engine.py +179 -0
  52. code_constraints/lint/pipeline.py +108 -0
  53. code_constraints/lint/report.py +151 -0
  54. code_constraints/lint/rules/__init__.py +50 -0
  55. code_constraints/lint/rules/base.py +200 -0
  56. code_constraints/lint/rules/cyclic_package_dependencies.py +69 -0
  57. code_constraints/lint/rules/dangling_classes.py +98 -0
  58. code_constraints/lint/rules/forbidden_package_references.py +47 -0
  59. code_constraints/lint/rules/forbidden_references.py +48 -0
  60. code_constraints/lint/rules/frozen_members.py +67 -0
  61. code_constraints/lint/rules/frozen_rules.py +105 -0
  62. code_constraints/lint/rules/implementation_locks.py +156 -0
  63. code_constraints/lint/rules/layer_dependencies.py +92 -0
  64. code_constraints/lint/rules/max_class_fanout.py +41 -0
  65. code_constraints/lint/rules/no_new_classes.py +27 -0
  66. code_constraints/lint/rules/no_removed_classes.py +27 -0
  67. code_constraints/lint/rules/reference_architecture.py +111 -0
  68. code_constraints/lint/rules/subclass_naming.py +71 -0
  69. code_constraints/lint/rules/tag_conformance.py +76 -0
  70. code_constraints/lock/__init__.py +73 -0
  71. code_constraints/lock/engine.py +395 -0
  72. code_constraints/lock/model.py +235 -0
  73. code_constraints/lock/store.py +144 -0
  74. code_constraints/lua/__init__.py +5 -0
  75. code_constraints/lua/conformance.py +239 -0
  76. code_constraints/lua/fingerprint.py +252 -0
  77. code_constraints/lua/parser.py +500 -0
  78. code_constraints/lua/rules_extract.py +55 -0
  79. code_constraints/mcp/__init__.py +20 -0
  80. code_constraints/mcp/__main__.py +73 -0
  81. code_constraints/mcp/server.py +1203 -0
  82. code_constraints/odin/__init__.py +5 -0
  83. code_constraints/odin/conformance.py +244 -0
  84. code_constraints/odin/fingerprint.py +159 -0
  85. code_constraints/odin/parser.py +471 -0
  86. code_constraints/odin/rules_extract.py +38 -0
  87. code_constraints/python/__init__.py +3 -0
  88. code_constraints/python/activity.py +278 -0
  89. code_constraints/python/conformance.py +249 -0
  90. code_constraints/python/fingerprint.py +231 -0
  91. code_constraints/python/parser.py +330 -0
  92. code_constraints/python/rules_extract.py +83 -0
  93. code_constraints/python/sequence.py +257 -0
  94. code_constraints/reference/__init__.py +15 -0
  95. code_constraints/reference/compare.py +356 -0
  96. code_constraints/reference/report.py +38 -0
  97. code_constraints/svelte/__init__.py +3 -0
  98. code_constraints/svelte/parser.py +523 -0
  99. code_constraints/typescript/__init__.py +3 -0
  100. code_constraints/typescript/parser.py +590 -0
  101. code_constraints/waivers/__init__.py +89 -0
  102. code_constraints/waivers/collect.py +167 -0
  103. code_constraints/waivers/model.py +90 -0
  104. code_constraints/waivers/ops.py +150 -0
  105. code_constraints/waivers/review.py +156 -0
  106. code_constraints/waivers/store.py +300 -0
  107. code_constraints/web/__init__.py +0 -0
  108. code_constraints/web/_static/assets/index-3ivBsYY4.css +1 -0
  109. code_constraints/web/_static/assets/index-BTzTqGFp.js +9 -0
  110. code_constraints/web/_static/index.html +13 -0
  111. code_constraints/web/app.py +1076 -0
  112. code_constraints-0.1.0.dist-info/METADATA +663 -0
  113. code_constraints-0.1.0.dist-info/RECORD +116 -0
  114. code_constraints-0.1.0.dist-info/WHEEL +4 -0
  115. code_constraints-0.1.0.dist-info/entry_points.txt +3 -0
  116. code_constraints-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,663 @@
1
+ Metadata-Version: 2.5
2
+ Name: code-constraints
3
+ Version: 0.1.0
4
+ Summary: Enforce architectural and implementation constraints on a codebase: UML models, drift checks, conformance rules, and implementation locks.
5
+ Project-URL: Homepage, https://github.com/fleskovar/code-constraints
6
+ Project-URL: Repository, https://github.com/fleskovar/code-constraints
7
+ Project-URL: Issues, https://github.com/fleskovar/code-constraints/issues
8
+ Project-URL: Documentation, https://github.com/fleskovar/code-constraints/blob/main/docs/TUTORIAL.md
9
+ Author: Francisco Leskovar
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: architecture,ci,code-review,constraints,governance,lint,uml
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Topic :: Software Development :: Documentation
22
+ Classifier: Topic :: Software Development :: Quality Assurance
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.11
25
+ Requires-Dist: fastapi>=0.115
26
+ Requires-Dist: gitpython>=3.1
27
+ Requires-Dist: lxml>=5.0
28
+ Requires-Dist: python-multipart>=0.0.20
29
+ Requires-Dist: pyyaml>=6.0
30
+ Requires-Dist: questionary>=2.0
31
+ Requires-Dist: rich>=13.0
32
+ Requires-Dist: tree-sitter-c-sharp>=0.23
33
+ Requires-Dist: tree-sitter-julia>=0.23
34
+ Requires-Dist: tree-sitter-lua>=0.4
35
+ Requires-Dist: tree-sitter-odin>=1.2
36
+ Requires-Dist: tree-sitter-typescript>=0.23
37
+ Requires-Dist: tree-sitter>=0.23
38
+ Requires-Dist: typer>=0.12
39
+ Requires-Dist: uvicorn[standard]>=0.30
40
+ Provides-Extra: dev
41
+ Requires-Dist: build>=1.2; extra == 'dev'
42
+ Requires-Dist: httpx2>=0.1; extra == 'dev'
43
+ Requires-Dist: httpx>=0.27; extra == 'dev'
44
+ Requires-Dist: mypy>=1.10; extra == 'dev'
45
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
46
+ Requires-Dist: pytest>=8.0; extra == 'dev'
47
+ Requires-Dist: ruff>=0.6; extra == 'dev'
48
+ Provides-Extra: mcp
49
+ Requires-Dist: mcp>=2.0; extra == 'mcp'
50
+ Description-Content-Type: text/markdown
51
+
52
+ # code-constraints (`cdec`)
53
+
54
+ **Constrain what changes in a codebase.** `cdec` lets a team encode its architectural and
55
+ implementation decisions as machine-checkable constraints, then enforces them in CI and in
56
+ the day-to-day development loop — so junior developers, external contributors, and coding
57
+ agents work inside the boundaries the team agreed on instead of around them.
58
+
59
+ **One file, one command.** Everything a project commits lives in `.cdec/rules.yaml`, and
60
+ `cdec check` is the whole gate. Four kinds of rule run inside it, answering four different
61
+ questions:
62
+
63
+ | Rule kind | `type:` | Question it answers |
64
+ | --- | --- | --- |
65
+ | **Model rules** | `no-new-classes`, `forbidden-references`, `layer-dependencies`, … | Did the *architecture* change? (new/removed classes, forbidden dependencies, cycles, layer violations, weakened tags) |
66
+ | **Conformance** | `tag-conformance` | Does the *implementation* obey its tags? (`@no_instantiation`, `@factory`, `@immutable`, `@sealed`) |
67
+ | **Freeze** | `implementation-locks` | Did this body change **at all**? (AST-identity digests — reformatting and moving code never trip a lock; any semantic edit does) |
68
+ | **Reference gate** | `reference-architecture` | Has *anything* structural changed against the committed snapshot? |
69
+
70
+ Each one is opt-in, and they share a rule catalogue but no logic. `cdec check` exits
71
+ non-zero on violation, so it drops straight into a CI/CD pipeline or a pre-commit hook:
72
+
73
+ ```yaml
74
+ # .cdec/rules.yaml — the whole contract
75
+ language: python
76
+ source: src
77
+
78
+ rules:
79
+ - id: domain-is-a-leaf-package
80
+ type: forbidden-package-references
81
+ severity: error
82
+ from: ["myapp.domain.**"]
83
+ to: ["myapp.ui.**"]
84
+ message: |
85
+ Layering violation: '{source}' must not depend on '{target}'.
86
+ Move the reference to whichever package owns the workflow.
87
+
88
+ - id: tags-must-be-honoured
89
+ type: tag-conformance
90
+ severity: error
91
+
92
+ - id: frozen-implementations
93
+ type: implementation-locks
94
+ severity: error
95
+ ```
96
+
97
+ ```bash
98
+ cdec check
99
+ ```
100
+
101
+ Underneath the constraint layer sits a full **UML modelling pipeline** — it is how the tool
102
+ knows what your architecture *is*. `cdec` parses **Python, C#, Odin, Lua, Julia, TypeScript, and Svelte 5**
103
+ into a UML model, persists it as XMI 2.1 (or editor JSON), renders class / package /
104
+ activity / sequence diagrams, diffs any two revisions, and serves an interactive browser
105
+ canvas for reviewing and editing the target architecture before locking it in.
106
+
107
+ Every diagram — class, package, activity and sequence — is built on [SvelteFlow](https://svelteflow.dev/) (`@xyflow/svelte`) with [dagre](https://github.com/dagrejs/dagre) auto-layout. The server sends a JSON graph and the browser lays it out, so there is no external renderer to install.
108
+
109
+ ## Install (standalone)
110
+
111
+ The fastest way to get a working `cdec` command system-wide. These installers use your
112
+ existing `git` to clone the project, create and manage their own Python virtualenv, build
113
+ the web frontend, and put `cdec` on your PATH. **Re-running the installer updates** to the
114
+ latest `main` (re-syncing dependencies and rebuilding the frontend).
115
+
116
+ **Linux / macOS:**
117
+ ```bash
118
+ curl -fsSL https://raw.githubusercontent.com/fleskovar/code_constraints/main/install/install.sh | bash
119
+ ```
120
+
121
+ **Windows (PowerShell):**
122
+ ```powershell
123
+ irm https://raw.githubusercontent.com/fleskovar/code_constraints/main/install/install.ps1 | iex
124
+ ```
125
+
126
+ Requires `git`, Python 3.11+, and Node 20+. See [`install/`](install/) for configuration
127
+ (install location, repo URL, branch) and uninstall steps. For developing **on**
128
+ code-constraints itself, use the in-repo `scripts/bootstrap.{ps1,sh}` instead.
129
+
130
+ To **update** later, run `cdec update` (or just re-run the installer) — it pulls the latest
131
+ code, re-syncs dependencies, and rebuilds the web UI.
132
+
133
+ ## Quick start
134
+
135
+ From a clone, the `Makefile` is the shortest path — it creates the venv, installs
136
+ `code-constraints` in editable mode with dev extras, and builds the frontend:
137
+
138
+ ```bash
139
+ make setup # venv + editable install + frontend build
140
+ make test # full test suite
141
+ make demo # run `cdec check` against the bundled examples
142
+ make serve # http://127.0.0.1:8765
143
+ make help # every target
144
+ ```
145
+
146
+ Or do it by hand:
147
+
148
+ ```bash
149
+ pip install -e ".[dev]"
150
+
151
+ # (one-time) build the frontend so `cdec serve` has something to mount
152
+ cd frontend && npm install && npm run build && cd ..
153
+
154
+ # Parse a project to XMI
155
+ cdec parse path/to/code --lang python --out project.xmi
156
+
157
+ # Diff two git revisions
158
+ cdec diff main feature --lang python --out diff.xmi
159
+
160
+ # Launch the interactive web viewer at http://127.0.0.1:8765
161
+ cdec serve
162
+ ```
163
+
164
+ Everywhere a model file is read or written (`parse --out`, `render`, `diff*`, `reference *`,
165
+ `convert`) both **XMI 2.1** (`.xmi`) and **editor JSON** (`.json`) are accepted — the JSON
166
+ shape is the same document the web editor and the proposal endpoint use, and is far easier
167
+ for humans and AI agents to author than XMI.
168
+
169
+ ## Documentation
170
+
171
+ | Document | What it is |
172
+ | --- | --- |
173
+ | **[Tutorial](docs/TUTORIAL.md)** | **Start here.** A guided path from parsing your first codebase to a fully gated CI pipeline — every example runnable against the bundled demos. Covers every rule type, the design loop, CI recipes, and per-language specifics. |
174
+ | **[Rules & constraints catalogue](docs/RULES_CATALOGUE.md)** | Every constraint the tool can enforce, in one place — each `rules.yaml` rule type and each source tag with its options, a sample configuration, and a passing *and* failing example. The reference for developers and architects deciding what to encode. |
175
+ | [CLI reference](docs/CLI_REFERENCE.md) | Exhaustive reference: every command and option, the `.cdec/` config files, exit codes, and CI/CD recipes. |
176
+ | [MCP server](docs/MCP.md) | Run code-constraints as a stdio MCP server so a coding agent calls the engines as tools. Install, per-harness configuration, and the tool surface. |
177
+ | **[Per-language guides](docs/languages/README.md)** | One complete walk-through per language — how it maps onto the UML model, how its tags are spelled, what each engine can see, and its parser gotchas. [Python](docs/languages/PYTHON.md) · [C#](docs/languages/CSHARP.md) · [Odin](docs/languages/ODIN.md) · [Lua](docs/languages/LUA.md) · [Julia](docs/languages/JULIA.md) · [TypeScript & Svelte](docs/languages/TYPESCRIPT-SVELTE.md) |
178
+ | [Examples](examples/README.md) | The demo projects — Python, C#, C# MVC, Odin, Lua, Julia, TypeScript, Svelte. |
179
+ | [Installers](install/README.md) | Standalone install scripts, configuration, and uninstall steps. |
180
+
181
+ If you are new, read the tutorial's [Part 3](docs/TUTORIAL.md#part-3--your-first-guardrail)
182
+ first — it is the shortest path from "installed" to "CI rejects bad PRs". When you are
183
+ deciding *which* constraints to turn on, work from the
184
+ [rules & constraints catalogue](docs/RULES_CATALOGUE.md).
185
+
186
+ ## Propose → review → lock workflow
187
+
188
+ The fluent way to plan architectural changes with an agent (or by hand):
189
+
190
+ ```bash
191
+ # 1. Get an editable model of the current architecture
192
+ cdec parse src/ --lang python --out target.json # (or: cdec convert .cdec/reference.xmi target.json)
193
+
194
+ # 2. Edit target.json (you or your AI agent) to describe the target architecture
195
+
196
+ # 3. Push it to the viewer, diffed against the current code
197
+ cdec propose target.json --focus billing.Invoice,billing.PaymentGateway
198
+ # green = the code still needs to grow this, red = to be removed.
199
+ # Re-running `cdec propose` after more edits REFRESHES the open browser tab
200
+ # in place (no new tabs) — iterate: edit → propose → discuss → repeat.
201
+
202
+ # 4. Once agreed, lock it as the target architecture
203
+ cdec reference set target.json # writes .cdec/reference.xmi
204
+
205
+ # 5. Constrain development against it
206
+ cdec check # CI gate: exit 1 on structural deviation
207
+ cdec check # architectural drift rules vs the same reference
208
+ ```
209
+
210
+ `cdec propose` reuses an already-running `cdec serve` on the same port (pushing over HTTP;
211
+ any open viewer tab hot-swaps via polling) or starts one with the proposal pre-loaded.
212
+ `--against reference` diffs against the locked reference instead of the current source;
213
+ `--focus A,B` pre-filters the class canvas to the classes under discussion.
214
+
215
+ ## Use it from a coding agent (MCP)
216
+
217
+ The same workflow is available to any MCP-capable harness — Claude Code, Cursor, VS Code,
218
+ Windsurf, Zed — as a stdio server, so an agent runs the engines as tools and reads
219
+ structured JSON instead of parsing CLI output:
220
+
221
+ ```bash
222
+ pip install "code-constraints[mcp]"
223
+ ```
224
+
225
+ ```json
226
+ {
227
+ "mcpServers": {
228
+ "code-constraints": { "type": "stdio", "command": "cdec-mcp", "args": [] }
229
+ }
230
+ }
231
+ ```
232
+
233
+ Drop that in `.mcp.json` at your repo root (or the equivalent file for your harness — see
234
+ the [MCP guide](docs/MCP.md)). The agent gets `cdec_check` / `cdec_check` /
235
+ `cdec_check` for the gate, `cdec_issues` + `cdec_allow` for the review loop,
236
+ and `cdec_propose` + `cdec_reference_set` for the design loop. Issue keys are identical to
237
+ the ones the CLI prints, so the two interfaces are interchangeable mid-workflow.
238
+
239
+ ## Evolving the rules: the review loop
240
+
241
+ Constraints only stay switched on if there is a sane way to say "yes, this one is fine".
242
+ Every issue the engines report leads with a stable key, and accepting it is a recorded,
243
+ reviewable, revocable decision:
244
+
245
+ ```bash
246
+ cdec check --log-out check.log
247
+ # [no-new-classes] (error)
248
+ # - [V-DD3EA5B2] animals.Cat — animals/cat.py:1: New class 'animals.Cat' was added.
249
+
250
+ # …mark the lines you accept — add [ALLOW: agreed in ARCH-42] anywhere on them…
251
+ cdec exceptions patch --file check.log # → .cdec/rules.yaml, with the reason attached
252
+
253
+ # or, when you already know which one you mean:
254
+ cdec exceptions allow V-DD3EA5B2 --reason "agreed in ARCH-42"
255
+ ```
256
+
257
+ Keys are hashes of *what* an issue is — engine, rule, element, discriminator — never of
258
+ where it sits, so a waiver survives reformatting and moved code, and the same code always
259
+ produces the same key. That last property is what makes the loop scriptable: an agent can
260
+ run `cdec check --format json`, decide, and call `cdec exceptions allow <key>` with no prose
261
+ parsing in between.
262
+
263
+ See [Accepting known violations](docs/CLI_REFERENCE.md#24-accepting-known-violations-cdec-baseline)
264
+ for the full command set (`review`, `patch`, `allow`, `remove`, `list`, `prune`).
265
+
266
+ `cdec serve` mounts `frontend/dist/` at `/`. If you edit the frontend, rerun `npm run build` to refresh the bundle — otherwise the server keeps serving the stale build.
267
+
268
+ ## Supported languages
269
+
270
+ | Language | `--lang` | Class / package | Activity / sequence tags | Rule tags (badges, drift, enforce) |
271
+ | ----------- | ------------ | --------------- | ------------------------ | ---------------------------------- |
272
+ | Python | `python` | ✅ | ✅ | ✅ |
273
+ | C# | `csharp` | ✅ | ✅ | ✅ |
274
+ | TypeScript | `typescript` | ✅ | — | — |
275
+ | Svelte 5 | `svelte` | ✅ | — | — |
276
+
277
+ Activity/sequence comment tags and architectural rule tags are currently recognised in `.py` and `.cs` sources only. TypeScript and Svelte parse to class + package diagrams.
278
+
279
+ ## Interactive session
280
+
281
+ Run `cdec` with no arguments to enter the interactive session. It auto-detects the project language, walks you through first-time setup if needed, then offers a menu:
282
+
283
+ ```
284
+ 🌐 Launch the web app (interactive diagrams)
285
+ 🔍 Run architectural checks (cdec check + enforce)
286
+ 🧪 Run the project test suite
287
+ 📦 Generate CI/CD scripts (Windows + Linux)
288
+ 🔄 Update project assets (agents, shims)
289
+ ```
290
+
291
+ **First-time setup** scaffolds `.cdec/` (config, rule templates, reference XMI), copies the bundled Claude agents into `.claude/agents/`, and copies the language rule shims.
292
+
293
+ **Update project assets** re-installs the Claude agents and shims from the version of code-constraints currently installed. Run this after upgrading to pick up new agents or shim changes without re-initialising the whole project.
294
+
295
+ ## Bundled Claude agents & skill
296
+
297
+ code-constraints ships two Claude Code agents and one skill. Deploy them into your project's `.claude/` folder with **`cdec update-assets`** (which also installs the language rule shim), or via the prompts in the interactive `cdec` session — note that `cdec init` scaffolds `.cdec/` only:
298
+
299
+ | Asset | Trigger |
300
+ | ----- | ------- |
301
+ | `cdec-architect` (agent) | Design new features — translates specs into UML class diagrams, proposes design patterns, defines architectural constraints, and drives the propose → review → lock workflow with `.json`/`.xmi` model files. |
302
+ | `oop-refactor-architect` (agent) | Analyse an existing codebase's class structure and produce actionable refactoring proposals: simplification, decoupling, layer proposals, and code-constraints rule/constraint suggestions. |
303
+ | `cdec-architecture-loop` (skill) | Interactive architecture discussions — teaches any Claude session the propose → review → lock loop: author a JSON model, `cdec propose` it into the browser diffed against the code, iterate through chat while the open tab refreshes, then lock with `cdec reference set`. |
304
+
305
+ Once deployed, the agents are available in any Claude Code session on the project via the `/agents` command or by mentioning them by name; the skill activates automatically when an architecture discussion starts (or explicitly via `/cdec-architecture-loop`).
306
+
307
+ ## CLI reference
308
+
309
+ All commands are also runnable as `python -m code_constraints.cli <command>` if the `cdec` entry point isn't on your `$PATH`.
310
+
311
+ ### `cdec parse`
312
+ Parse a source tree and write a model file (`.xmi` for XMI 2.1, `.json` for editor JSON).
313
+ ```bash
314
+ cdec parse PATH --lang {python|csharp|odin|lua|julia|typescript|svelte} --out project.xmi # or .json
315
+ ```
316
+
317
+ ### `cdec convert`
318
+ Convert a model file between XMI 2.1 and editor JSON (either direction).
319
+ ```bash
320
+ cdec convert model.xmi model.json
321
+ cdec convert model.json model.xmi
322
+ ```
323
+
324
+ ### `cdec propose`
325
+ Push a proposed target architecture to the web viewer, diffed against a baseline
326
+ (default: the current source; `--against reference` for the locked reference;
327
+ `--against none` to render it standalone). Re-running refreshes any open viewer
328
+ tab in place. See the "Propose → review → lock" section above.
329
+ ```bash
330
+ cdec propose target.json [--source SRC] [--lang LANG] [--against {source|reference|none}]
331
+ [--focus Qname1,Qname2] [--port 8765] [--no-browser]
332
+ ```
333
+
334
+ ### `cdec reference`
335
+ Manage the locked target architecture (`.cdec/reference.xmi`).
336
+ ```bash
337
+ cdec reference set target.json # lock an authored model (.json or .xmi) as the reference
338
+ cdec check --automatic-exceptions reference [SOURCE] # re-snapshot the reference from the current code
339
+ cdec check [SOURCE] # CI gate: exit 1 if code deviates structurally
340
+ cdec reference show [SOURCE] # open the viewer on a code-vs-reference diff
341
+ ```
342
+
343
+ ### `cdec diff`
344
+ Diff two git revisions and emit an annotated XMI (added/removed/changed). Checks out both refs into a temp dir, never touching the working tree.
345
+ ```bash
346
+ cdec diff OLD_REF NEW_REF --lang LANG --out diff.xmi [--repo .] [--subpath SUBDIR]
347
+ ```
348
+
349
+ ### `cdec diff-vs-xmi`
350
+ Parse a source tree and diff it against a previously-saved reference XMI (the reference is the OLD side, source is NEW).
351
+ ```bash
352
+ cdec diff-vs-xmi reference.xmi SOURCE_PATH --lang LANG --out diff.xmi
353
+ ```
354
+
355
+ ### `cdec diff-xmi`
356
+ Diff two already-parsed XMI files (e.g. CI artefacts from two branches) without re-parsing source. Both must share the same `source_language`.
357
+ ```bash
358
+ cdec diff-xmi old.xmi new.xmi --out diff.xmi
359
+ ```
360
+
361
+ ### `cdec init`
362
+ Scaffold a `.cdec/` folder (config, rule templates, baseline, and a reference XMI snapshot) for `cdec check`.
363
+ ```bash
364
+ cdec init [--config .cdec] [--lang python] [--source .] [--force]
365
+ ```
366
+
367
+ ### `cdec update-assets`
368
+ Update the Claude agents and language shims to the version bundled with the currently-installed code-constraints. Run after upgrading to pick up new agents or shim changes in an existing project. Language is auto-detected from `.cdec/rules.yaml` when `--lang` is omitted.
369
+ ```bash
370
+ cdec update-assets [--project-root .] [--lang LANG] [--no-agents] [--no-shims]
371
+ ```
372
+
373
+ ### `cdec update`
374
+ Update the installation in place — equivalent to re-running the standalone installer. Pulls the latest code from GitHub, re-syncs Python dependencies (picking up requirement changes), and rebuilds the web frontend. The refreshed code takes effect on the next `cdec` invocation. Works on installs created by the standalone installer or a git clone.
375
+ ```bash
376
+ cdec update [--branch BRANCH] [--no-frontend]
377
+ ```
378
+
379
+ ### `cdec check` — the gate
380
+ Run every rule in `.cdec/rules.yaml`. Exits non-zero on any violation at or above
381
+ `--fail-on` severity (default `error`). This is the only enforcement command; the rule
382
+ types in that file decide what it actually does.
383
+ ```bash
384
+ cdec check [--config .cdec] [--source SRC] [--lang LANG]
385
+ [--reference model.xmi | --base-ref GIT_REF] [--repo .]
386
+ [--format {human|json}] [--json-out report.json] [--log-out check.log]
387
+ [--fail-on {error|warning|none}]
388
+ [--automatic-exceptions {rules|locks|reference|all}] # accept the current state
389
+ [--force] # with `locks`: accept a CHANGED body
390
+ [--bypass-locks --bypass-reason "..."] # report lock violations without failing
391
+ ```
392
+
393
+ `--automatic-exceptions` is how you accept the code as it stands rather than failing on it:
394
+
395
+ | Value | Effect |
396
+ | --- | --- |
397
+ | `rules` | Grandfather every current violation into `exceptions:` — the adoption move on an existing codebase. Only **new** violations fail afterwards. |
398
+ | `locks` | Record digests for newly `@locked` code. Safe for anyone: without `--force` it only *adds*. |
399
+ | `reference` | Re-snapshot `.cdec/reference.xmi` from the current source. |
400
+ | `all` | All three. |
401
+
402
+ ```bash
403
+ cdec locks [SRC] [--all] [--json] # read-only: what is lockable / locked, and its state
404
+ ```
405
+
406
+ ### `cdec exceptions` — accept known violations (the review loop)
407
+ Every issue the engines report carries a stable key (`V-` check, `F-` enforce, `L-` lock). Quote the key to accept an issue as known-and-allowed, with a reason, recorded in the `exceptions:` section of `.cdec/rules.yaml`. Keys are derived from *what* the issue is, not where — so a waiver survives reformatting and moved code.
408
+ ```bash
409
+ cdec exceptions review --out review.txt # one markable line per issue
410
+ # …add [ALLOW] (or [ALLOW: reason]) to the lines you accept…
411
+ cdec exceptions patch --file review.txt # apply exactly those; `--file -` reads stdin
412
+
413
+ cdec exceptions allow V-DD3EA5B2 --reason "agreed in ARCH-42" # or name keys directly
414
+ cdec exceptions remove V-DD3EA5B2 # withdraw: the issue blocks again
415
+ cdec exceptions list # what's accepted, and why
416
+ cdec exceptions prune # drop waivers whose issue is gone
417
+ ```
418
+ `cdec check --log-out check.log` output is directly patchable — the parser just needs a marker and a key on the same line. Lock violations (`L-`) are **not** waivable this way: a frozen implementation is re-baselined with `cdec check --automatic-exceptions locks --force`, which leaves its own reviewable diff.
419
+
420
+ ### `cdec serve`
421
+ Run the local web viewer (FastAPI + Svelte SPA). Default port is **8765**.
422
+ ```bash
423
+ cdec serve [--host 127.0.0.1] [--port 8765]
424
+ ```
425
+
426
+ ## Interactive viewer
427
+
428
+ After `cdec serve`, open http://127.0.0.1:8765 and register a project path. The class diagram view gives you:
429
+
430
+ - **Class list panel** with search + autocomplete; click a class to centre the canvas on it.
431
+ - **Related classes** sub-list showing inheritance and association neighbours of the selected class.
432
+ - **Visibility filtering** — per-class checkboxes plus Show all / Hide all / Isolate (with N-hop traversal).
433
+ - **Saved views** — Export the current visible set, drag positions, and selection to a `.cdecview.json` file; Import to restore them later.
434
+ - **Diff walkthrough** — when viewing a diff XMI, a Prev/Next change list appears that pans the camera to each affected class.
435
+ - **Rule badges** — tagged classes/operations carry colour-coded badges (hover for the rule + parameters); badge changes participate in the diff styling.
436
+
437
+ Package, activity and sequence diagrams all render on the same interactive canvas.
438
+
439
+ ### Editing diagrams in the browser
440
+
441
+ The **editor** (Home → editor, or "Edit this diagram" on any parsed view) supports:
442
+
443
+ - **Visual editing** — "+ Add class", drag between nodes to draw inheritance/associations,
444
+ hover a class for quick "+ attribute" / "+ method" buttons, double-click to edit in a
445
+ form, and press **Delete** to remove the selected class.
446
+ - **Code panel** — the "Code" toggle opens the model as editable JSON side-by-side with
447
+ the canvas; typing in either side updates the other (same JSON that `cdec convert` /
448
+ `cdec propose` consume).
449
+ - **Live baseline diff** — load a baseline (`Baseline…` file button, or "Compare vs
450
+ reference" when editing a parsed project) and the canvas shows added/removed/changed
451
+ styling *while you edit* — simultaneous edit + diff preview.
452
+ - **Open / save** both `.xmi` and `.json`; "Set as reference" locks the edited model as
453
+ the project's target architecture.
454
+
455
+ ## Architectural rule tags & the three enforcement engines
456
+
457
+ Tag classes and methods with design constraints using **Python decorators / C# attributes / Julia macros**, all shipped as no-op shims so tagged code still imports and compiles. A tag is only recognised when the shim is in scope (`from cdec_rules import …` / `using CodeConstraints.Rules;` / `using CdecRules`), so unrelated decorators never false-match.
458
+
459
+ **Lua and Odin have no construct to hang a no-op on** — Lua has no declaration modifiers, and the Odin compiler rejects unknown `@(...)` attributes — so both carry tags in a namespaced annotation comment placed where a decorator would go (`---@cdec sealed` / `//@cdec sealed`), with the `@cdec` prefix playing the gating role the import plays elsewhere.
460
+
461
+ | Tag | Python | C# | Julia | Lua / Odin | Meaning |
462
+ | --- | --- | --- | --- | --- | --- |
463
+ | no-instantiation | `@no_instantiation(allow=[...])` | `[NoInstantiation(Allow = ...)]` | `@no_instantiation allow=[...]` | `@cdec no_instantiation(allow = [...])` | the body may not construct objects (except `allow`-listed types) |
464
+ | factory | `@factory(creates=[...])` | `[Factory(Creates = ...)]` | `@factory creates=[...]` | `@cdec factory(creates = [...])` | the only place allowed to build the listed types |
465
+ | immutable | `@immutable` | `[Immutable]` | `@immutable` | `@cdec immutable` | fields may not be reassigned after construction |
466
+ | sealed | `@sealed` | `[Sealed]` | `@sealed` | `@cdec sealed` | the class may not be subclassed |
467
+ | layer | `@layer("name")` | `[Layer("name")]` | `@layer "name"` | `@cdec layer("name")` | assigns the class to an architectural layer |
468
+ | no-side-effects | `@no_side_effects` | `[NoSideEffects]` | `@no_side_effects` | `@cdec no_side_effects` | captured + visualised + drift-frozen (body analysis deferred) |
469
+ | locked | `@locked(reason="...")` | `[Locked(Reason = "...")]` | `@locked reason="..."` | `@cdec locked(reason = "...")` | the implementation is frozen — see [Implementation locks](#implementation-locks) |
470
+
471
+ Lua writes the comment as `---@cdec …` and Odin as `//@cdec …`. ⚠️ Julia macro arguments are **space-separated** — `@layer "orders"` is correct, `@layer("orders")` is a syntax error. Full detail per language: **[language guides](docs/languages/README.md)**.
472
+
473
+ The tags are captured on the model, round-trip through XMI, render as badges in the web class diagram, and participate in the diff. **Enforcement is split into three completely decoupled engines** sharing only the rule catalog:
474
+
475
+ - **Model rules (drift / architectural).** Model + baseline only, never read bodies. `frozen-rules` fails when a baseline tag is removed or weakened; `layer-dependencies` flags forbidden cross-layer references from `@layer` tags.
476
+ - **`tag-conformance` (implementation conformance).** Re-parses source ASTs and inspects method bodies. Detection is precise in C# and Odin (grammar node kinds), heuristic in Python (`allow` is the escape hatch), name-based in Julia, and idiom-based in Lua.
477
+ - **`implementation-locks` (implementation freeze).** Digests the normalised AST of a `@locked` element and fails if it changes at all.
478
+
479
+ The three are separate engines internally and share only the rule catalogue — the rule
480
+ types are thin adapters, so `cdec check` gives one report without coupling them.
481
+
482
+ ```bash
483
+ # One command runs every rule the demo configures. Its model rules pass — the
484
+ # architecture is intact — and its `tag-conformance` rule flags the one seeded
485
+ # body violation, so this exits 1 on purpose.
486
+ cdec check --config examples/python_demo/.cdec --source examples/python_demo
487
+ ```
488
+
489
+ See [`examples/`](examples/) for small bookstore codebases (Python, C#, Odin, Lua, Julia, TypeScript, Svelte); the Python, C#, Odin, Lua and Julia demos each carry a fully-worked tagged `billing` slice with one intentional violation.
490
+
491
+ ## Implementation locks
492
+
493
+ A locked class or function may not change **at all**. This is stronger than the architectural rules above: it is how you pin down test logic, or a sequence of steps that has been agreed and must not be quietly reordered, and force the rest of the codebase to adapt to it rather than the other way round.
494
+
495
+ The lock is over the **syntax tree**, not a range of lines. Adding code above a locked function, reformatting it, rewrapping an expression, or editing comments changes nothing. Renaming a local, swapping an operator, adding a statement, or reordering two steps fails the check.
496
+
497
+ **1. Declare the lock in code.**
498
+
499
+ ```python
500
+ from cdec_rules import locked
501
+
502
+ class Invoice:
503
+ @locked(reason="settlement order agreed with finance")
504
+ def settle(self, amount):
505
+ tax = amount * 0.2
506
+ return amount + tax
507
+ ```
508
+
509
+ ```csharp
510
+ using CodeConstraints.Rules;
511
+
512
+ [Locked(Reason = "settlement order agreed with finance")]
513
+ public decimal Settle(decimal amount) { ... }
514
+ ```
515
+
516
+ **2. Turn the rule on** in `.cdec/rules.yaml`:
517
+
518
+ ```yaml
519
+ - id: frozen-implementations
520
+ type: implementation-locks
521
+ severity: error
522
+ ```
523
+
524
+ **3. Baseline it.** `cdec check --automatic-exceptions locks` records the digest in the
525
+ `locks:` section of the same file — commit it.
526
+
527
+ **4. It is now enforced by `cdec check`**, so there is no new CI step:
528
+
529
+ ```
530
+ [frozen-implementations] (error)
531
+ NOT EXCEPTABLE: a frozen implementation changes only via
532
+ `cdec check --automatic-exceptions locks --force`.
533
+ - [L-3D246C33] Invoice.settle — billing.py:6: 'Invoice.settle' is a frozen
534
+ method and its implementation changed.
535
+ Lock reason: settlement order agreed with finance
536
+ Locked by: alice on 2026-08-02T10:15:00+00:00
537
+ Revert the change, or ask a lead to approve a re-baseline with
538
+ `cdec check --automatic-exceptions locks --force`.
539
+ ```
540
+
541
+ Five things fail the check, covering every way a freeze can be undone: the body **changed**, the element was **removed**, the `@locked` tag was deleted (**unlocked**), a tag was never baselined (**missing**), and the digest algorithm changed (**algo-mismatch**, reported separately so an upgrade never looks like tampering).
542
+
543
+ ### Locking without decorating
544
+
545
+ To freeze code you can't practically tag — a whole test package, say — give the rule qualified-name globs:
546
+
547
+ ```yaml
548
+ - id: frozen-implementations
549
+ type: implementation-locks
550
+ severity: error
551
+ include_docstrings: false # count docstrings / /// comments as implementation
552
+ targets:
553
+ - "tests.**" # every class and function under tests/ is frozen
554
+ ```
555
+
556
+ ### Keeping re-baselining a lead-only action
557
+
558
+ `cdec check --automatic-exceptions locks` is safe for anyone to run: it **adds** locks for newly tagged elements but will never overwrite the digest of an implementation that has drifted, and never drops an entry whose tag was deleted. Accepting a change requires `--force`:
559
+
560
+ ```bash
561
+ cdec check --automatic-exceptions locks --force
562
+ ```
563
+
564
+ That is the only path that rewrites the `locks:` section of `.cdec/rules.yaml`, so putting the file behind a CODEOWNERS entry makes approving a change to frozen code a reviewable, lead-gated event. `cdec exceptions allow` refuses an `L-` key for the same reason, and `--automatic-exceptions rules` will not grandfather one either.
565
+
566
+ To ship without re-baselining, bypass explicitly:
567
+
568
+ ```bash
569
+ cdec check --bypass-locks --bypass-reason "hotfix #42" # or CDEC_LOCK_BYPASS=1
570
+ ```
571
+
572
+ A bypassed run prints a banner, still collects every violation, and sets `summary.bypassed` in `--json-out` — reject that flag in CI to keep bypassing a deliberate, visible act.
573
+
574
+ ## Requirements
575
+
576
+ - Python 3.11+
577
+ - Node 20+ for building / iterating on the frontend.
578
+
579
+ There is no external binary to install. Every command — `parse`, `diff`, `check`, `serve` — runs on Python alone.
580
+
581
+ ## Frontend development
582
+
583
+ ```bash
584
+ cd frontend
585
+ npm install
586
+ npm run dev # Vite dev server with HMR, proxies /api to :8765
587
+ npm run build # production build into frontend/dist/
588
+ npm run check # svelte-check (TypeScript)
589
+ ```
590
+
591
+ Run `cdec serve` and `npm run dev` together when iterating; Vite proxies `/api/*` to the backend.
592
+
593
+ ## Embedded diagram tags
594
+
595
+ Authors can mark code regions for activity and sequence diagrams using XML-style comment tags (Python and C# only — Odin, Lua and Julia have parsers and tags but no activity/sequence support yet):
596
+
597
+ ```python
598
+ # <uml-activity name="checkout" granularity="control-flow">
599
+ def checkout(cart):
600
+ if cart.is_empty():
601
+ return
602
+ pay(cart)
603
+ # </uml-activity>
604
+ ```
605
+
606
+ ```csharp
607
+ // <uml-sequence name="login" root="HandleLogin">
608
+ public void HandleLogin(User u) {
609
+ _auth.Verify(u);
610
+ _session.Start(u);
611
+ }
612
+ // </uml-sequence>
613
+ ```
614
+
615
+ Supported tags: `<uml-class />`, `<uml-activity name="..." granularity="control-flow|statement|calls">`, `<uml-sequence name="..." root="...">`.
616
+
617
+ ## Testing
618
+
619
+ ```bash
620
+ make install-dev # or: pip install -e ".[dev]"
621
+ make test # or: pytest
622
+ pytest tests/test_python_parser.py # one file
623
+ make verify # ruff + mypy + pytest
624
+ make frontend-check # svelte-check (TypeScript)
625
+ ```
626
+
627
+ ## Building & installing
628
+
629
+ Every development and packaging flow has a `make` target (`make help` lists them all):
630
+
631
+ | Target | What it does |
632
+ | --- | --- |
633
+ | `make setup` | One-time dev environment: venv, editable install with dev extras, frontend deps + build. |
634
+ | `make install-dev` | Editable install with dev extras — the **development version**. Code edits take effect immediately. |
635
+ | `make install` | Non-editable **install from source** into the venv. |
636
+ | `make install-pipx` | Install the `cdec` CLI **globally** from this checkout via `pipx`. |
637
+ | `make dist` / `make package` | Build the frontend, then produce the wheel + sdist in `dist/`. |
638
+ | `make check-dist` | Build, then validate the artifacts with `twine`. |
639
+ | `make verify` | `ruff` + `mypy` + `pytest` — the gate to run before pushing. |
640
+ | `make clean` / `make distclean` | Remove build artifacts and caches / everything generated including `node_modules`. |
641
+
642
+ Override the interpreter or venv location on the command line:
643
+
644
+ ```bash
645
+ make setup BASE_PYTHON=python3.12 VENV=/tmp/cdec-venv
646
+ make serve PORT=9000
647
+ ```
648
+
649
+ Installing the built wheel elsewhere:
650
+
651
+ ```bash
652
+ make dist
653
+ pip install dist/code_constraints-0.1.0-py3-none-any.whl
654
+ cdec --help
655
+ ```
656
+
657
+ > **Note:** the wheel bundles the Python packages, the rule shims, and the Claude assets,
658
+ > but **not** `frontend/dist`. An installed wheel serves the API and a JSON placeholder at
659
+ > `/` rather than the SPA — use a clone (or the standalone installer above) if you want the
660
+ > web viewer.
661
+
662
+ Recipes run through `sh`. On Windows use Git Bash, or any shell that provides `sh` on
663
+ `PATH`, so the venv-layout detection works.