@archrad/deterministic 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/README.md +83 -48
- package/biome.json +32 -25
- package/dist/cli.js +50 -2
- package/dist/exportPipeline.d.ts +3 -2
- package/dist/exportPipeline.d.ts.map +1 -1
- package/dist/exportPipeline.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/ir-lint.d.ts +7 -2
- package/dist/ir-lint.d.ts.map +1 -1
- package/dist/ir-lint.js +5 -3
- package/dist/ir-normalize.d.ts +2 -0
- package/dist/ir-normalize.d.ts.map +1 -1
- package/dist/ir-normalize.js +2 -1
- package/dist/lint-graph.d.ts.map +1 -1
- package/dist/lint-graph.js +1 -0
- package/dist/mcp-server.d.ts +7 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +236 -0
- package/dist/nodeExpress.d.ts.map +1 -1
- package/dist/nodeExpress.js +5 -1
- package/dist/openapi-to-ir.d.ts.map +1 -1
- package/dist/openapi-to-ir.js +6 -4
- package/dist/policy-pack.d.ts +62 -0
- package/dist/policy-pack.d.ts.map +1 -0
- package/dist/policy-pack.js +220 -0
- package/dist/pythonFastAPI.d.ts.map +1 -1
- package/dist/pythonFastAPI.js +3 -1
- package/dist/static-rule-guidance.d.ts +19 -0
- package/dist/static-rule-guidance.d.ts.map +1 -0
- package/dist/static-rule-guidance.js +165 -0
- package/dist/stringEdgeStrip.d.ts +8 -0
- package/dist/stringEdgeStrip.d.ts.map +1 -0
- package/dist/stringEdgeStrip.js +25 -0
- package/dist/validate-drift.d.ts +3 -0
- package/dist/validate-drift.d.ts.map +1 -1
- package/dist/validate-drift.js +2 -0
- package/docs/DRIFT.md +52 -0
- package/docs/MCP.md +153 -0
- package/docs/RULE_CODES.md +208 -0
- package/fixtures/policies/ecommerce-demo.yaml +15 -0
- package/fixtures/policy-packs/duplicate-pack/first.yaml +10 -0
- package/fixtures/policy-packs/duplicate-pack/second.yaml +10 -0
- package/fixtures/policy-packs/sample-only/sample-node-tags.yaml +14 -0
- package/package.json +15 -9
- package/scripts/npm-postinstall.mjs +22 -0
- package/scripts/smoke-mcp.mjs +44 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.1.5] - 2026-04-07
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`archrad-mcp`** — [Model Context Protocol](https://modelcontextprotocol.io/) server over **stdio** (same deterministic engine as the CLI). Tools: **`archrad_validate_ir`**, **`archrad_lint_summary`**, **`archrad_validate_drift`**, **`archrad_policy_packs_load`**, **`archrad_suggest_fix`** (static remediation per built-in code — **not** generated patches), **`archrad_list_rule_codes`**. IR input: inline **`ir`** or **`irPath`** (JSON file; max **25 MiB**). **`archrad_suggest_fix`** **`docsUrl`** → GitHub **`docs/RULE_CODES.md`**. Scope: **`docs/MCP.md`** (OSS: IR + local paths; no tracking params in tool responses).
|
|
15
|
+
- **Docs:** **`docs/DRIFT.md`** (deterministic drift semantics), **`docs/RULE_CODES.md`** (built-in codes + anchors for MCP **`docsUrl`**), **`docs/MCP.md`** (local testing).
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- **CodeQL** `js/polynomial-redos` — linear-time hyphen/underscore edge stripping for **`safeId`** / OpenAPI-derived ids (replaces ambiguous alternation regexes).
|
|
20
|
+
|
|
21
|
+
## [0.1.4] - 2026-04-06
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- **`loadPolicyPacksFromFiles(sources)`** — compile PolicyPack YAML/JSON from in-memory **`{ name, content }[]`** (same semantics as **`loadPolicyPacksFromDirectory`**; no filesystem).
|
|
26
|
+
- **Declarative policy packs** — YAML/JSON documents (`apiVersion: archrad/v1`, `kind: PolicyPack`) with `rules` that match **nodes** or **edges** on the parsed lint graph. Loader: **`loadPolicyPacksFromDirectory(dir)`** (supports `*.yaml`, `*.yml`, `*.json`; duplicate `rule.id` across the directory is rejected).
|
|
27
|
+
- **CLI** — **`archrad validate`**, **`archrad export`**, and **`archrad validate-drift`** accept **`--policies <dir>`** to merge compiled rules after built-in **`IR-LINT-*`** (skipped when **`--skip-lint`** / **`--skip-ir-lint`** is set).
|
|
28
|
+
- **ArchRad Cloud (InkByte server)** — org **`settings.archPolicyPacks`** (array of **`{ name, content }`**) is merged into deterministic export when **`organizationId`** is on the export request; **`POST /api/drift-check`** accepts **`organizationId`**, **`policyPackFiles`**, and **`skipArchPolicyPacks`** (membership required when **`organizationId`** is set).
|
|
29
|
+
- **Library** — **`validateIrLint(ir, { policyRuleVisitors })`**; **`runDeterministicExport`** / drift helpers accept **`policyRuleVisitors`** the same way.
|
|
30
|
+
- **Normalization** — **`NormalizedNode`** now includes **`metadata`** (from each node’s `metadata` object) so **`buildParsedLintGraph`** / policy **`match.node.tags`** see **`metadata.tags`** on the graph.
|
|
31
|
+
- **`--policies`** applies to all three lint commands consistently —
|
|
32
|
+
`validate-drift` uses the same policy layer when building the
|
|
33
|
+
reference export, so org rules are enforced end-to-end across
|
|
34
|
+
validate → export → drift.
|
|
35
|
+
|
|
10
36
|
## [0.1.3] - 2026-04-04
|
|
11
37
|
|
|
12
38
|
### Fixed
|
package/README.md
CHANGED
|
@@ -2,28 +2,80 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
  
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Your architecture drifts before you write a single line of code. `archrad validate` catches it — deterministically, in CI, before the PR merges.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Define your system as a graph. ArchRAD compiles it, lints it against architecture rules, and tells you exactly what's wrong — with rule codes, not opinions.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Quick start (60 seconds)
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g @archrad/deterministic
|
|
17
|
+
archrad validate --ir fixtures/demo-direct-db-violation.json
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
You should see something like (exact wording may vary slightly by version):
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
⚠️ IR-LINT-DIRECT-DB-ACCESS-002: API node "orders-api" connects directly to datastore node "orders-db"
|
|
24
|
+
Fix: Introduce a service or domain layer between HTTP handlers and persistence.
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
For a smaller graph (single endpoint, no DB edge), try **`fixtures/minimal-graph.json`** — you will get different warnings (e.g. health/auth heuristics), not **`IR-LINT-DIRECT-DB-ACCESS-002`**.
|
|
28
|
+
|
|
29
|
+
No IR file yet? Cold-start from an existing OpenAPI spec:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
archrad ingest openapi --spec ./openapi.yaml --out ./graph.json
|
|
33
|
+
archrad validate --ir ./graph.json
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## What it does
|
|
39
|
+
|
|
40
|
+
ArchRAD is a blueprint compiler and governance layer. You define your architecture as an IR — nodes, edges, allowed connections — and ArchRAD validates it against a deterministic rule engine. The same IR, the same rules, the same inputs always produce the same findings.
|
|
12
41
|
|
|
13
|
-
|
|
42
|
+
| Command | What it checks | Codes |
|
|
43
|
+
|---------|---------------|-------|
|
|
44
|
+
| `archrad validate` | Graph structure + architecture lint | `IR-STRUCT-*` `IR-LINT-*` |
|
|
45
|
+
| `archrad validate-drift` | IR vs generated code on disk | `DRIFT-*` |
|
|
46
|
+
| `archrad ingest openapi` | Derive IR from existing OpenAPI spec | — |
|
|
47
|
+
| `archrad export` | Compile IR → FastAPI or Express + Docker | — |
|
|
48
|
+
|
|
49
|
+
## CI integration
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Fail on any structural error (default):
|
|
53
|
+
archrad validate --ir ./graph.json
|
|
54
|
+
|
|
55
|
+
# Also fail on lint warnings:
|
|
56
|
+
archrad validate --ir ./graph.json --fail-on-warning
|
|
14
57
|
|
|
15
|
-
|
|
58
|
+
# Machine-readable output for GitHub Actions:
|
|
59
|
+
archrad validate --ir ./graph.json --json
|
|
60
|
+
```
|
|
16
61
|
|
|
17
|
-
|
|
62
|
+
## MCP server (Cursor / Claude Desktop)
|
|
18
63
|
|
|
19
|
-
|
|
64
|
+
After install, `archrad-mcp` is on your PATH. Add it to your IDE:
|
|
20
65
|
|
|
21
|
-
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"mcpServers": {
|
|
69
|
+
"archrad": { "command": "archrad-mcp" }
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
22
73
|
|
|
23
|
-
|
|
74
|
+
Your agent can call the same engine as the CLI via **six** MCP tools (e.g. `archrad_validate_ir`, `archrad_lint_summary`, `archrad_validate_drift`, `archrad_policy_packs_load`, `archrad_list_rule_codes`, `archrad_suggest_fix`). See [docs/MCP.md](docs/MCP.md) for parameters and local testing.
|
|
24
75
|
|
|
25
76
|
---
|
|
26
77
|
|
|
78
|
+
|
|
27
79
|
## How it works (architecture)
|
|
28
80
|
|
|
29
81
|
```
|
|
@@ -67,7 +119,7 @@ IR (nodes/edges) → validateIrStructural (IR-STRUCT-*) → errors block exp
|
|
|
67
119
|
| **AI remediation** | Repair loops, suggested edits |
|
|
68
120
|
|
|
69
121
|
1. **IR structural validation:** duplicate/missing node ids, bad HTTP `config.url` / `config.method`, unknown edge endpoints, directed cycles.
|
|
70
|
-
2. **Architecture lint:** Implemented as a **registry of visitor functions** on a parsed graph (`buildParsedLintGraph` → **`LINT_RULE_REGISTRY`** in **`src/lint-rules.ts`**). If the IR cannot be parsed, **`buildParsedLintGraph`** returns **`{ findings }`** (IR-STRUCT-*) instead of **`null`**; use **`isParsedLintGraph()`** or call **`validateIrLint`**, which forwards those findings. Each rule returns **`IrStructuralFinding[]`**; **`runArchitectureLinting`** / **`validateIrLint`** flatten them. **Custom org rules:** compose **`runArchitectureLinting`** with your own **`(g) => findings`** in CI (worked example: **`docs/CUSTOM_RULES.md`**), or **fork** and append to **`LINT_RULE_REGISTRY`** if the stock **`archrad validate`** CLI must emit your codes. CLI **`archrad validate`** / **`archrad export`** print lint under **Architecture lint (IR-LINT-*)** (grouped separately from structural). Codes include **IR-LINT-DIRECT-DB-ACCESS-002**, **IR-LINT-SYNC-CHAIN-001**, **IR-LINT-NO-HEALTHCHECK-003**, **IR-LINT-HIGH-FANOUT-004**, **IR-LINT-ISOLATED-NODE-005**, **IR-LINT-DUPLICATE-EDGE-006**, **IR-LINT-HTTP-MISSING-NAME-007**, **IR-LINT-DATASTORE-NO-INCOMING-008**, **IR-LINT-MULTIPLE-HTTP-ENTRIES-009**. **Sync-chain** depth counts **synchronous** edges only; mark message/queue/async hops via **`edge.metadata.protocol`** / **`config.async`** (see **`edgeRepresentsAsyncBoundary`** in **`lint-graph.ts`** and **`docs/ENGINEERING_NOTES.md`**).
|
|
122
|
+
2. **Architecture lint:** Implemented as a **registry of visitor functions** on a parsed graph (`buildParsedLintGraph` → **`LINT_RULE_REGISTRY`** in **`src/lint-rules.ts`**). If the IR cannot be parsed, **`buildParsedLintGraph`** returns **`{ findings }`** (IR-STRUCT-*) instead of **`null`**; use **`isParsedLintGraph()`** or call **`validateIrLint`**, which forwards those findings. Each rule returns **`IrStructuralFinding[]`**; **`runArchitectureLinting`** / **`validateIrLint`** flatten them. **Custom org rules:** compose **`runArchitectureLinting`** with your own **`(g) => findings`** in CI (worked example: **`docs/CUSTOM_RULES.md`**), or **fork** and append to **`LINT_RULE_REGISTRY`** if the stock **`archrad validate`** CLI must emit your codes. CLI **`archrad validate`** / **`archrad export`** print lint under **Architecture lint (IR-LINT-*)** (grouped separately from structural). Codes include **IR-LINT-DIRECT-DB-ACCESS-002**, **IR-LINT-SYNC-CHAIN-001**, **IR-LINT-NO-HEALTHCHECK-003**, **IR-LINT-HIGH-FANOUT-004**, **IR-LINT-ISOLATED-NODE-005**, **IR-LINT-DUPLICATE-EDGE-006**, **IR-LINT-HTTP-MISSING-NAME-007**, **IR-LINT-DATASTORE-NO-INCOMING-008**, **IR-LINT-MULTIPLE-HTTP-ENTRIES-009**, **IR-LINT-MISSING-AUTH-010**, **IR-LINT-DEAD-NODE-011**. **Sync-chain** depth counts **synchronous** edges only; mark message/queue/async hops via **`edge.metadata.protocol`** / **`config.async`** (see **`edgeRepresentsAsyncBoundary`** in **`lint-graph.ts`** and **`docs/ENGINEERING_NOTES.md`**).
|
|
71
123
|
3. **Generators** → `openapi.yaml`, handlers, deps.
|
|
72
124
|
4. **Golden path** → `make run` / `docker compose up --build`.
|
|
73
125
|
5. **OpenAPI document shape** on the bundle — **not** [Spectral](https://github.com/stoplightio/spectral)-level lint. Issues → **`openApiStructuralWarnings`**.
|
|
@@ -76,6 +128,8 @@ IR (nodes/edges) → validateIrStructural (IR-STRUCT-*) → errors block exp
|
|
|
76
128
|
|
|
77
129
|
**Trust builder:** **IR-STRUCT-*** errors block export; **IR-LINT-*** warnings are visible and can **gate CI** via **`--fail-on-warning`** / **`--max-warnings`**; OpenAPI shape issues surface as export warnings.
|
|
78
130
|
|
|
131
|
+
**Reference (OSS):** **[`docs/DRIFT.md`](docs/DRIFT.md)** (deterministic **`validate-drift`**), **[`docs/RULE_CODES.md`](docs/RULE_CODES.md)** (finding codes; MCP **`docsUrl`** targets GitHub anchors), **[`docs/MCP.md`](docs/MCP.md)** (MCP tools + local testing).
|
|
132
|
+
|
|
79
133
|
### Codegen vs validation (retry, timeouts, policy)
|
|
80
134
|
|
|
81
135
|
Generators **may emit** retry/timeout/circuit-breaker **code** when the IR carries matching edge or node config (e.g. `retryPolicy`). That is **code generation**, not a guarantee. OSS does **not** currently **require** or **lint** “every external call must have timeout/retry” — that class of rule is **semantic / policy** and fits **ArchRad Cloud** or custom linters on top of the IR.
|
|
@@ -92,10 +146,17 @@ Generators **may emit** retry/timeout/circuit-breaker **code** when the IR carri
|
|
|
92
146
|
| **CLI validate** | CI / pre-commit: IR structural + architecture lint, no codegen | `archrad validate --ir graph.json` |
|
|
93
147
|
| **CLI validate-drift** | After export or merges: on-disk tree vs fresh deterministic export from same IR | `archrad validate-drift -i graph.json -t python -o ./out` |
|
|
94
148
|
| **Library** (`@archrad/deterministic`) | IDPs / pipelines | `runDeterministicExport` → files + findings; **`runValidateDrift`** / **`runDriftCheckAgainstFiles`** for drift |
|
|
149
|
+
| **MCP** (`archrad-mcp`) | Cursor / Claude Desktop / other MCP hosts | stdio server: validate IR, lint summary, drift, policy packs, static **`archrad_suggest_fix`** — see **`docs/MCP.md`** |
|
|
150
|
+
|
|
151
|
+
**MCP (Cursor example):** after `npm i -g @archrad/deterministic` (or `npx`), add a server with command **`archrad-mcp`** and no args (stdio). Pass **`ir`** inline or **`irPath`** to a JSON file for large graphs. **`archrad_suggest_fix`** returns curated text for a finding code (e.g. `IR-LINT-MISSING-AUTH-010`) — not machine-generated IR patches. **Step-by-step testing** (smoke script, MCP Inspector, Cursor chat prompts): **`docs/MCP.md`**, section **Local testing**.
|
|
95
152
|
|
|
96
153
|
### CLI
|
|
97
154
|
|
|
98
|
-
**Input is structured IR (JSON), not natural language.** There is no `archrad export --prompt "..."`.
|
|
155
|
+
**Input is structured IR (JSON), not natural language.** There is no `archrad export --prompt "..."`. Pass a **graph file** (nodes/edges).
|
|
156
|
+
|
|
157
|
+
**Fixtures** (in this repo): **`fixtures/minimal-graph.json`** (small); **`fixtures/demo-direct-db-violation.json`** / **`fixtures/demo-direct-db-layered.json`** (before/after **`IR-LINT-DIRECT-DB-ACCESS-002`**); **`fixtures/ecommerce-with-warnings.json`** (many lint rules); **`fixtures/payment-retry-demo.json`** (retry-related codegen in export). **`--target python`** is the FastAPI bundle; there is no separate `fastapi` target. To go from **plain English → IR**, use **ArchRad Cloud** or your own LLM step; this package only does **IR → files**.
|
|
158
|
+
|
|
159
|
+
**Recording demos and GIFs** (VHS, storyboards, drift replay): **`scripts/README_DEMO_RECORDING.md`** only — not required to use the CLI.
|
|
99
160
|
|
|
100
161
|
**OpenAPI → JSON (spec as source of truth):** each operation under `paths` becomes an `http` node (`config.url` + `config.method`). Then validate and export like any other IR:
|
|
101
162
|
|
|
@@ -117,24 +178,25 @@ archrad validate --ir ./graph.json
|
|
|
117
178
|
|
|
118
179
|
YAML must have either top-level **`graph:`** (object) or top-level **`nodes:`** (array); bare graphs are wrapped as `{ "graph": { ... } }` automatically.
|
|
119
180
|
|
|
120
|
-
After `npm
|
|
181
|
+
**After `npm install -g` or `npx`** (typical):
|
|
121
182
|
|
|
122
183
|
```bash
|
|
123
|
-
node dist/cli.js export --ir fixtures/minimal-graph.json --target python --out ./my-api
|
|
124
|
-
node dist/cli.js yaml-to-ir --yaml fixtures/minimal-graph.yaml --out /tmp/ir.json
|
|
125
|
-
# After global install / npx:
|
|
126
184
|
archrad export --ir ./graph.json --target node --out ./my-express-api
|
|
127
185
|
|
|
128
186
|
# Validate IR (structural + architecture lint). Pretty output; exit 1 on structural errors by default:
|
|
129
|
-
|
|
187
|
+
archrad validate --ir ./graph.json
|
|
130
188
|
# Machine-readable + CI gates:
|
|
131
189
|
archrad validate --ir ./graph.json --json
|
|
132
190
|
archrad validate --ir ./graph.json --fail-on-warning
|
|
133
191
|
archrad validate --ir ./graph.json --max-warnings 0
|
|
134
192
|
# Structural only (skip IR-LINT-*):
|
|
135
193
|
archrad validate --ir ./graph.json --skip-lint
|
|
194
|
+
# Declarative PolicyPack YAML/JSON in a directory (after IR-LINT-*; skipped with --skip-lint):
|
|
195
|
+
archrad validate --ir ./graph.json --policies ./policy-packs
|
|
136
196
|
```
|
|
137
197
|
|
|
198
|
+
**From a git clone** (contributors): run **`npm ci && npm run build`** in the package root (there is no `prepare` hook — see **`docs/ENGINEERING_NOTES.md`**), then use **`node dist/cli.js`** the same way you would use **`archrad`** (e.g. **`node dist/cli.js validate --ir fixtures/minimal-graph.json`**).
|
|
199
|
+
|
|
138
200
|
**Deterministic drift (thin, OSS):** compare an existing export tree on disk to a **fresh** export from the same IR. Detects **missing** / **changed** generated files (line endings normalized). Optional **`--strict-extra`** flags files present on disk but not in the reference export. Not semantic “does code match intent” — **ArchRad Cloud** adds builder/UI drift checks and broader governance.
|
|
139
201
|
|
|
140
202
|
```bash
|
|
@@ -147,8 +209,6 @@ archrad validate-drift -i ./graph.json -t python -o ./out --skip-host-port-check
|
|
|
147
209
|
archrad validate-drift -i ./graph.json -t python -o ./out --strict-extra
|
|
148
210
|
```
|
|
149
211
|
|
|
150
|
-
Regenerate the matching clip: **`npm run record:demo:drift`** (VHS) → **`demo-drift.gif`**, or **`scripts/run-demo-drift-sequence.ps1`** / **`.sh`** + ShareX/OBS if VHS is unavailable (see **`scripts/DEMO_GIF_STORYBOARD.md`**).
|
|
151
|
-
|
|
152
212
|
#### Example: validate architecture
|
|
153
213
|
|
|
154
214
|
```bash
|
|
@@ -181,13 +241,6 @@ By default, if **8080** (or your `--host-port`) looks **busy** on localhost, the
|
|
|
181
241
|
|
|
182
242
|
**Export** runs **IR structural validation**, then **architecture lint**, then codegen. **Structural errors** abort with **no files written**. **`irLintFindings`** contains only **`IR-LINT-*`**; **`IR-STRUCT-*`** from a failed parse always appear under **`irStructuralFindings`** (including when structural validation was skipped). **Lint warnings** print by default; use **`--fail-on-warning`** / **`--max-warnings`** to block writes for CI.
|
|
183
243
|
|
|
184
|
-
### Validate the package as a developer
|
|
185
|
-
|
|
186
|
-
1. `cd packages/deterministic && npm ci && npm run build && npm test`
|
|
187
|
-
2. `node dist/cli.js export --ir fixtures/minimal-graph.json --target python --out ./tmp-out`
|
|
188
|
-
3. `cd tmp-out && make run` then `curl` the URL shown in the generated **README** (port matches `--host-port` if you set it).
|
|
189
|
-
4. Optional: `node dist/cli.js export ... --host-port 18080` if **8080** is already taken.
|
|
190
|
-
|
|
191
244
|
### Library
|
|
192
245
|
|
|
193
246
|
```typescript
|
|
@@ -222,9 +275,9 @@ Optional: `isLocalHostPortFree` / `normalizeGoldenHostPort` from the same packag
|
|
|
222
275
|
|
|
223
276
|
---
|
|
224
277
|
|
|
225
|
-
## Golden path (
|
|
278
|
+
## Golden path (contributors — local clone)
|
|
226
279
|
|
|
227
|
-
|
|
280
|
+
This path assumes you **cloned the repo** and ran **`npm ci && npm run build`** in the package root. If you only installed with **`npm install -g @archrad/deterministic`**, use **`archrad`** instead of **`node dist/cli.js`** (same flags).
|
|
228
281
|
|
|
229
282
|
```bash
|
|
230
283
|
node dist/cli.js export --ir fixtures/minimal-graph.json --target python --out ./out
|
|
@@ -236,13 +289,9 @@ curl -sS -X POST http://localhost:8080/signup -H "Content-Type: application/json
|
|
|
236
289
|
|
|
237
290
|
You should see **422 Unprocessable Entity** (FastAPI/Pydantic) or **400** with a clear body — proof the stack is live and validation matches the spec, not a silent 500.
|
|
238
291
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
```bash
|
|
242
|
-
bash scripts/golden-path-demo.sh
|
|
243
|
-
```
|
|
292
|
+
Quick check from a clone: **`cd packages/deterministic && npm ci && npm run build && npm test`**, then export to **`./tmp-out`**, **`cd tmp-out && make run`**, **`curl`** as above. Use **`--host-port 18080`** (or **`node dist/cli.js export ... --host-port 18080`**) if **8080** is busy.
|
|
244
293
|
|
|
245
|
-
|
|
294
|
+
Optional: **`bash scripts/golden-path-demo.sh`** runs the same flow. **Demo recording** (GIFs, tapes, drift replays): **`scripts/README_DEMO_RECORDING.md`**.
|
|
246
295
|
|
|
247
296
|
---
|
|
248
297
|
|
|
@@ -259,20 +308,6 @@ See **`scripts/README_DEMO_RECORDING.md`** for **VHS / asciinema / ttyrec** tips
|
|
|
259
308
|
|
|
260
309
|
You can depend on this CLI and library **without** ArchRad Cloud. The cloud product stacks collaboration and AI on top of the same deterministic contract.
|
|
261
310
|
|
|
262
|
-
**InkByte vs this package:** Deeper workflow analysis, enterprise validation routes, and LLM-assisted flows may exist in the **private InkByte monorepo** (`server/`, etc.); they are **not** part of the **`@archrad/deterministic`** npm surface unless shipped here. This README describes **only** what the OSS package proves.
|
|
263
|
-
|
|
264
|
-
---
|
|
265
|
-
|
|
266
|
-
## Monorepo vs public OSS repo
|
|
267
|
-
|
|
268
|
-
The **canonical source** for this engine may live in a **private monorepo** next to the full product; `server` can depend on `file:../packages/deterministic`. The **public** GitHub repo should contain **only** this package — canonical clone: **`https://github.com/archradhq/arch-deterministic`**. Subtree publish: **`docs/OSS_VS_PRODUCT_REPOS.md`** and **`docs/PUBLISH_DETERMINISTIC_OSS.md`** (in the product monorepo).
|
|
269
|
-
|
|
270
|
-
---
|
|
271
|
-
|
|
272
|
-
## Publishing the public OSS repo
|
|
273
|
-
|
|
274
|
-
From the private monorepo root: **`docs/PUBLISH_DETERMINISTIC_OSS.md`**. This tree includes **`.github/workflows/ci.yml`** and **Dependabot**; they run when this folder is the **git root** of the public repo.
|
|
275
|
-
|
|
276
311
|
---
|
|
277
312
|
|
|
278
313
|
## Contributing
|
package/biome.json
CHANGED
|
@@ -1,25 +1,32 @@
|
|
|
1
|
-
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/2.4.10/schema.json",
|
|
3
|
+
"vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true },
|
|
4
|
+
"files": {
|
|
5
|
+
"includes": [
|
|
6
|
+
"**",
|
|
7
|
+
"!**/dist",
|
|
8
|
+
"!**/node_modules",
|
|
9
|
+
"!**/*.json",
|
|
10
|
+
"!**/fixtures",
|
|
11
|
+
"!**/schemas"
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
"formatter": {
|
|
15
|
+
"enabled": false
|
|
16
|
+
},
|
|
17
|
+
"assist": { "actions": { "source": { "organizeImports": "off" } } },
|
|
18
|
+
"linter": {
|
|
19
|
+
"enabled": true,
|
|
20
|
+
"rules": {
|
|
21
|
+
"recommended": false,
|
|
22
|
+
"suspicious": {
|
|
23
|
+
"noDebugger": "error"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"javascript": {
|
|
28
|
+
"formatter": {
|
|
29
|
+
"quoteStyle": "single"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -10,6 +10,7 @@ import { runDeterministicExport } from './exportPipeline.js';
|
|
|
10
10
|
import { isLocalHostPortFree, normalizeGoldenHostPort } from './hostPort.js';
|
|
11
11
|
import { validateIrStructural, hasIrStructuralErrors } from './ir-structural.js';
|
|
12
12
|
import { validateIrLint } from './ir-lint.js';
|
|
13
|
+
import { loadPolicyPacksFromDirectory } from './policy-pack.js';
|
|
13
14
|
import { printFindingsPretty, shouldFailFromFindings, sortFindings, } from './cli-findings.js';
|
|
14
15
|
import { parseYamlToCanonicalIr, canonicalIrToJsonString, YamlGraphParseError, } from './yamlToIr.js';
|
|
15
16
|
import { openApiStringToCanonicalIr, OpenApiIngestError } from './openapi-to-ir.js';
|
|
@@ -51,6 +52,20 @@ function parseMaxWarnings(v) {
|
|
|
51
52
|
const n = parseInt(v, 10);
|
|
52
53
|
return Number.isFinite(n) ? n : undefined;
|
|
53
54
|
}
|
|
55
|
+
/** Load `--policies` directory; on failure prints to stderr and returns null (caller should exit 1). */
|
|
56
|
+
async function loadPoliciesOption(policiesDir) {
|
|
57
|
+
if (policiesDir == null || policiesDir === '')
|
|
58
|
+
return {};
|
|
59
|
+
const dir = resolve(policiesDir);
|
|
60
|
+
const loaded = await loadPolicyPacksFromDirectory(dir);
|
|
61
|
+
if (!loaded.ok) {
|
|
62
|
+
for (const e of loaded.errors) {
|
|
63
|
+
console.error(`archrad: ${e}`);
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
return { policyRuleVisitors: loaded.visitors };
|
|
68
|
+
}
|
|
54
69
|
function exitPolicyFromOpts(opts) {
|
|
55
70
|
return {
|
|
56
71
|
failOnWarning: Boolean(opts.failOnWarning),
|
|
@@ -61,13 +76,14 @@ const program = new Command();
|
|
|
61
76
|
program
|
|
62
77
|
.name('archrad')
|
|
63
78
|
.description('Validate your architecture before you write code. Deterministic compiler + linter — FastAPI / Express (no LLM, no server).')
|
|
64
|
-
.version('0.1.
|
|
79
|
+
.version('0.1.5');
|
|
65
80
|
program
|
|
66
81
|
.command('validate')
|
|
67
82
|
.description('Validate your architecture before you write code — IR structural (IR-STRUCT-*) + architecture lint (IR-LINT-*)')
|
|
68
83
|
.requiredOption('-i, --ir <path>', 'Path to IR JSON (graph with nodes/edges or full wrapper)')
|
|
69
84
|
.option('--json', 'Print findings as JSON array to stdout')
|
|
70
85
|
.option('--skip-lint', 'Skip architecture lint (IR-LINT-*); structural only')
|
|
86
|
+
.option('--policies <dir>', 'Directory of PolicyPack YAML/JSON (*.yaml, *.yml, *.json); merged after IR-LINT-*')
|
|
71
87
|
.option('--fail-on-warning', 'Exit with error if any warning (CI gate)')
|
|
72
88
|
.option('--max-warnings <n>', 'Exit with error if warning count is greater than n (e.g. 0 allows no warnings)')
|
|
73
89
|
.action(async (cmdOpts) => {
|
|
@@ -78,8 +94,19 @@ program
|
|
|
78
94
|
return;
|
|
79
95
|
}
|
|
80
96
|
const noLint = Boolean(cmdOpts.skipLint);
|
|
97
|
+
let lintOpts = {};
|
|
98
|
+
if (!noLint && cmdOpts.policies) {
|
|
99
|
+
const loaded = await loadPoliciesOption(cmdOpts.policies);
|
|
100
|
+
if (loaded == null) {
|
|
101
|
+
process.exitCode = 1;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
lintOpts = loaded;
|
|
105
|
+
}
|
|
81
106
|
const structural = validateIrStructural(ir);
|
|
82
|
-
const lint = noLint || hasIrStructuralErrors(structural)
|
|
107
|
+
const lint = noLint || hasIrStructuralErrors(structural)
|
|
108
|
+
? []
|
|
109
|
+
: validateIrLint(ir, lintOpts);
|
|
83
110
|
const combined = sortFindings([...structural, ...lint]);
|
|
84
111
|
if (cmdOpts.json) {
|
|
85
112
|
const forJson = combined.map((f) => ({
|
|
@@ -224,11 +251,21 @@ program
|
|
|
224
251
|
}
|
|
225
252
|
const exportOpts = cmdOpts;
|
|
226
253
|
const skipStruct = Boolean(exportOpts.dangerSkipIrStructuralValidation || exportOpts.skipIrStructuralValidation);
|
|
254
|
+
let exportLintOpts = {};
|
|
255
|
+
if (!cmdOpts.skipIrLint && cmdOpts.policies) {
|
|
256
|
+
const loaded = await loadPoliciesOption(cmdOpts.policies);
|
|
257
|
+
if (loaded == null) {
|
|
258
|
+
process.exitCode = 1;
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
exportLintOpts = loaded;
|
|
262
|
+
}
|
|
227
263
|
try {
|
|
228
264
|
const { files, openApiStructuralWarnings, irStructuralFindings, irLintFindings } = await runDeterministicExport(actualIR, cmdOpts.target, {
|
|
229
265
|
hostPort,
|
|
230
266
|
skipIrStructuralValidation: skipStruct,
|
|
231
267
|
skipIrLint: cmdOpts.skipIrLint,
|
|
268
|
+
...exportLintOpts,
|
|
232
269
|
});
|
|
233
270
|
const combined = sortFindings([...irStructuralFindings, ...irLintFindings]);
|
|
234
271
|
if (combined.length) {
|
|
@@ -277,6 +314,7 @@ program
|
|
|
277
314
|
.addOption(new Option('--danger-skip-ir-structural-validation', 'UNSAFE: skip validateIrStructural during reference export'))
|
|
278
315
|
.addOption(new Option('--skip-ir-structural-validation', 'Deprecated alias').hideHelp())
|
|
279
316
|
.option('--skip-ir-lint', 'Skip architecture lint when building reference export')
|
|
317
|
+
.option('--policies <dir>', 'Directory of PolicyPack YAML/JSON; merged after IR-LINT-* for the reference export')
|
|
280
318
|
.option('--strict-extra', 'Fail if output directory contains files not in the reference export')
|
|
281
319
|
.option('--json', 'Print drift findings and export metadata as JSON')
|
|
282
320
|
.action(async (cmdOpts) => {
|
|
@@ -297,12 +335,22 @@ program
|
|
|
297
335
|
}
|
|
298
336
|
}
|
|
299
337
|
const skipStruct = Boolean(cmdOpts.dangerSkipIrStructuralValidation || cmdOpts.skipIrStructuralValidation);
|
|
338
|
+
let driftLintOpts = {};
|
|
339
|
+
if (!cmdOpts.skipIrLint && cmdOpts.policies) {
|
|
340
|
+
const loaded = await loadPoliciesOption(cmdOpts.policies);
|
|
341
|
+
if (loaded == null) {
|
|
342
|
+
process.exitCode = 1;
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
driftLintOpts = loaded;
|
|
346
|
+
}
|
|
300
347
|
try {
|
|
301
348
|
const result = await runValidateDrift(actualIR, cmdOpts.target, outDir, {
|
|
302
349
|
hostPort,
|
|
303
350
|
skipIrStructuralValidation: skipStruct,
|
|
304
351
|
skipIrLint: cmdOpts.skipIrLint,
|
|
305
352
|
strictExtra: cmdOpts.strictExtra,
|
|
353
|
+
...driftLintOpts,
|
|
306
354
|
});
|
|
307
355
|
const combined = sortFindings([
|
|
308
356
|
...result.exportResult.irStructuralFindings,
|
package/dist/exportPipeline.d.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Used by the ArchRad server and the `archrad` CLI.
|
|
4
4
|
*/
|
|
5
5
|
import { type IrStructuralFinding } from './ir-structural.js';
|
|
6
|
+
import { type ValidateIrLintOptions } from './ir-lint.js';
|
|
6
7
|
export type DeterministicExportResult = {
|
|
7
8
|
files: Record<string, string>;
|
|
8
9
|
/** Human-readable lines when generated OpenAPI fails **document-shape** checks (not full spec lint) */
|
|
@@ -13,11 +14,11 @@ export type DeterministicExportResult = {
|
|
|
13
14
|
* Errors block codegen; this field stays the single source for “graph does not compile.”
|
|
14
15
|
*/
|
|
15
16
|
irStructuralFindings: IrStructuralFinding[];
|
|
16
|
-
/** IR-LINT-*
|
|
17
|
+
/** IR-LINT-* plus optional declarative policy-pack findings; does not include IR-STRUCT-* (those live in `irStructuralFindings`). */
|
|
17
18
|
irLintFindings: IrStructuralFinding[];
|
|
18
19
|
};
|
|
19
20
|
/**
|
|
20
21
|
* Generate FastAPI or Express project files + golden Docker/Makefile + OpenAPI **document-shape** check.
|
|
21
22
|
*/
|
|
22
|
-
export declare function runDeterministicExport(actualIR: any, target: string, opts?: Record<string, any>): Promise<DeterministicExportResult>;
|
|
23
|
+
export declare function runDeterministicExport(actualIR: any, target: string, opts?: Record<string, any> & ValidateIrLintOptions): Promise<DeterministicExportResult>;
|
|
23
24
|
//# sourceMappingURL=exportPipeline.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exportPipeline.d.ts","sourceRoot":"","sources":["../src/exportPipeline.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"exportPipeline.d.ts","sourceRoot":"","sources":["../src/exportPipeline.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAkB,KAAK,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAE1E,MAAM,MAAM,yBAAyB,GAAG;IACtC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,uGAAuG;IACvG,yBAAyB,EAAE,MAAM,EAAE,CAAC;IACpC;;;;OAIG;IACH,oBAAoB,EAAE,mBAAmB,EAAE,CAAC;IAC5C,qIAAqI;IACrI,cAAc,EAAE,mBAAmB,EAAE,CAAC;CACvC,CAAC;AAEF;;GAEG;AACH,wBAAsB,sBAAsB,CAC1C,QAAQ,EAAE,GAAG,EACb,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,qBAA0B,GACrD,OAAO,CAAC,yBAAyB,CAAC,CAwDpC"}
|
package/dist/exportPipeline.js
CHANGED
|
@@ -21,7 +21,7 @@ export async function runDeterministicExport(actualIR, target, opts = {}) {
|
|
|
21
21
|
}
|
|
22
22
|
let irLintFindings = [];
|
|
23
23
|
if (!skipLint) {
|
|
24
|
-
const lintPass = validateIrLint(actualIR);
|
|
24
|
+
const lintPass = validateIrLint(actualIR, { policyRuleVisitors: opts.policyRuleVisitors });
|
|
25
25
|
if (skipIr) {
|
|
26
26
|
// Dangerous mode: full structural pass is off, but parse/normalize failures still return IR-STRUCT-* from
|
|
27
27
|
// validateIrLint — fold those into irStructuralFindings so InkByte / CLI consumers block and log like normal.
|
package/dist/index.d.ts
CHANGED
|
@@ -12,7 +12,8 @@ export { runDeterministicExport, type DeterministicExportResult } from './export
|
|
|
12
12
|
export { diffExpectedExportAgainstFiles, diffExpectedExportAgainstDirectory, readDirectoryAsExportMap, runValidateDrift, runDriftCheckAgainstFiles, normalizeExportFileContent, type DriftFinding, type DriftCode, type ValidateDriftResult, type DriftCheckFilesResult, } from './validate-drift.js';
|
|
13
13
|
export { normalizeIrGraph, validateIrStructural, hasIrStructuralErrors, detectCycles, type IrStructuralFinding, type IrStructuralSeverity, type IrFindingLayer, } from './ir-structural.js';
|
|
14
14
|
export { materializeNormalizedGraph, normalizeNodeSlot, normalizeEdgeSlot, type NormalizedGraph, type NormalizedNode, type NormalizedEdge, type MaterializeResult, } from './ir-normalize.js';
|
|
15
|
-
export { validateIrLint } from './ir-lint.js';
|
|
15
|
+
export { validateIrLint, type ValidateIrLintOptions } from './ir-lint.js';
|
|
16
|
+
export { loadPolicyPacksFromDirectory, loadPolicyPacksFromFiles, type LoadPolicyPacksResult, type PolicyPackFileSource, type PolicyPackDocumentV1, type PolicyRuleV1, type PolicyPackMetadataV1, type PolicyNodeSelectorV1, type PolicyEdgeMatchV1, type PolicySeverity, } from './policy-pack.js';
|
|
16
17
|
export { runArchitectureLinting, LINT_RULE_REGISTRY } from './lint-rules.js';
|
|
17
18
|
export { buildParsedLintGraph, isParsedLintGraph, type ParsedLintGraph, type BuildParsedLintGraphResult, } from './lint-graph.js';
|
|
18
19
|
export { isHttpLikeType, isHttpEndpointType, isDbLikeType, isQueueLikeNodeType, isAuthLikeNodeType } from './graphPredicates.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,yBAAyB,EACzB,mBAAmB,EACnB,iCAAiC,GAClC,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,uBAAuB,EACvB,2BAA2B,EAC3B,mBAAmB,EACnB,yBAAyB,EACzB,uBAAuB,EACvB,KAAK,WAAW,EAChB,KAAK,kBAAkB,GACxB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,wBAAwB,EACxB,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,eAAe,CAAC;AAEvB,cAAc,8BAA8B,CAAC;AAE7C,OAAO,EAAE,OAAO,IAAI,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAEvE,OAAO,EAAE,sBAAsB,EAAE,KAAK,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAE7F,OAAO,EACL,8BAA8B,EAC9B,kCAAkC,EAClC,wBAAwB,EACxB,gBAAgB,EAChB,yBAAyB,EACzB,0BAA0B,EAC1B,KAAK,YAAY,EACjB,KAAK,SAAS,EACd,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,qBAAqB,EACrB,YAAY,EACZ,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,cAAc,GACpB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,0BAA0B,EAC1B,iBAAiB,EACjB,iBAAiB,EACjB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,iBAAiB,GACvB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,yBAAyB,EACzB,mBAAmB,EACnB,iCAAiC,GAClC,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,uBAAuB,EACvB,2BAA2B,EAC3B,mBAAmB,EACnB,yBAAyB,EACzB,uBAAuB,EACvB,KAAK,WAAW,EAChB,KAAK,kBAAkB,GACxB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,wBAAwB,EACxB,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,eAAe,CAAC;AAEvB,cAAc,8BAA8B,CAAC;AAE7C,OAAO,EAAE,OAAO,IAAI,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAEvE,OAAO,EAAE,sBAAsB,EAAE,KAAK,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAE7F,OAAO,EACL,8BAA8B,EAC9B,kCAAkC,EAClC,wBAAwB,EACxB,gBAAgB,EAChB,yBAAyB,EACzB,0BAA0B,EAC1B,KAAK,YAAY,EACjB,KAAK,SAAS,EACd,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,qBAAqB,EACrB,YAAY,EACZ,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,cAAc,GACpB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,0BAA0B,EAC1B,iBAAiB,EACjB,iBAAiB,EACjB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,iBAAiB,GACvB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAE1E,OAAO,EACL,4BAA4B,EAC5B,wBAAwB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,YAAY,EACjB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,KAAK,cAAc,GACpB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC7E,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,KAAK,eAAe,EACpB,KAAK,0BAA0B,GAChC,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,YAAY,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAEjI,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAAE,KAAK,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAEpG,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,0BAA0B,EAC1B,4BAA4B,EAC5B,0BAA0B,EAC1B,2BAA2B,EAC3B,kBAAkB,EAClB,KAAK,eAAe,GACrB,MAAM,oBAAoB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -13,6 +13,7 @@ export { diffExpectedExportAgainstFiles, diffExpectedExportAgainstDirectory, rea
|
|
|
13
13
|
export { normalizeIrGraph, validateIrStructural, hasIrStructuralErrors, detectCycles, } from './ir-structural.js';
|
|
14
14
|
export { materializeNormalizedGraph, normalizeNodeSlot, normalizeEdgeSlot, } from './ir-normalize.js';
|
|
15
15
|
export { validateIrLint } from './ir-lint.js';
|
|
16
|
+
export { loadPolicyPacksFromDirectory, loadPolicyPacksFromFiles, } from './policy-pack.js';
|
|
16
17
|
export { runArchitectureLinting, LINT_RULE_REGISTRY } from './lint-rules.js';
|
|
17
18
|
export { buildParsedLintGraph, isParsedLintGraph, } from './lint-graph.js';
|
|
18
19
|
export { isHttpLikeType, isHttpEndpointType, isDbLikeType, isQueueLikeNodeType, isAuthLikeNodeType } from './graphPredicates.js';
|
package/dist/ir-lint.d.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Architecture lint (IR-LINT-*): thin entry — parses graph then runs visitor registry in `lint-rules.ts`.
|
|
3
3
|
*/
|
|
4
|
+
import type { ParsedLintGraph } from './lint-graph.js';
|
|
4
5
|
import type { IrStructuralFinding } from './ir-structural.js';
|
|
6
|
+
export type ValidateIrLintOptions = {
|
|
7
|
+
/** Extra visitors after built-in IR-LINT-* (declarative policy packs, org rules). */
|
|
8
|
+
policyRuleVisitors?: ReadonlyArray<(g: ParsedLintGraph) => IrStructuralFinding[]>;
|
|
9
|
+
};
|
|
5
10
|
/**
|
|
6
|
-
* Run architecture lint (IR-LINT-*). If the IR cannot be parsed (invalid root, empty graph, etc.),
|
|
11
|
+
* Run architecture lint (IR-LINT-*) plus optional policy visitors. If the IR cannot be parsed (invalid root, empty graph, etc.),
|
|
7
12
|
* returns the same **structural** findings as `normalizeIrGraph` / `validateIrStructural` would surface
|
|
8
13
|
* for that shape — callers that only invoke `validateIrLint` still see blockers instead of a silent `[]`.
|
|
9
14
|
*/
|
|
10
|
-
export declare function validateIrLint(ir: unknown): IrStructuralFinding[];
|
|
15
|
+
export declare function validateIrLint(ir: unknown, options?: ValidateIrLintOptions): IrStructuralFinding[];
|
|
11
16
|
//# sourceMappingURL=ir-lint.d.ts.map
|
package/dist/ir-lint.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ir-lint.d.ts","sourceRoot":"","sources":["../src/ir-lint.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"ir-lint.d.ts","sourceRoot":"","sources":["../src/ir-lint.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGvD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D,MAAM,MAAM,qBAAqB,GAAG;IAClC,qFAAqF;IACrF,kBAAkB,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,eAAe,KAAK,mBAAmB,EAAE,CAAC,CAAC;CACnF,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,mBAAmB,EAAE,CAMlG"}
|
package/dist/ir-lint.js
CHANGED
|
@@ -4,13 +4,15 @@
|
|
|
4
4
|
import { buildParsedLintGraph, isParsedLintGraph } from './lint-graph.js';
|
|
5
5
|
import { runArchitectureLinting } from './lint-rules.js';
|
|
6
6
|
/**
|
|
7
|
-
* Run architecture lint (IR-LINT-*). If the IR cannot be parsed (invalid root, empty graph, etc.),
|
|
7
|
+
* Run architecture lint (IR-LINT-*) plus optional policy visitors. If the IR cannot be parsed (invalid root, empty graph, etc.),
|
|
8
8
|
* returns the same **structural** findings as `normalizeIrGraph` / `validateIrStructural` would surface
|
|
9
9
|
* for that shape — callers that only invoke `validateIrLint` still see blockers instead of a silent `[]`.
|
|
10
10
|
*/
|
|
11
|
-
export function validateIrLint(ir) {
|
|
11
|
+
export function validateIrLint(ir, options) {
|
|
12
12
|
const built = buildParsedLintGraph(ir);
|
|
13
13
|
if (!isParsedLintGraph(built))
|
|
14
14
|
return built.findings;
|
|
15
|
-
|
|
15
|
+
const base = runArchitectureLinting(built);
|
|
16
|
+
const extra = options?.policyRuleVisitors?.flatMap((v) => v(built)) ?? [];
|
|
17
|
+
return [...base, ...extra];
|
|
16
18
|
}
|
package/dist/ir-normalize.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export type NormalizedNode = {
|
|
|
9
9
|
name: string;
|
|
10
10
|
config: Record<string, unknown>;
|
|
11
11
|
schema: Record<string, unknown>;
|
|
12
|
+
/** Node-level metadata (e.g. `tags` for policy packs / lint). */
|
|
13
|
+
metadata: Record<string, unknown>;
|
|
12
14
|
};
|
|
13
15
|
export type NormalizedEdge = {
|
|
14
16
|
id: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ir-normalize.d.ts","sourceRoot":"","sources":["../src/ir-normalize.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"ir-normalize.d.ts","sourceRoot":"","sources":["../src/ir-normalize.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,iEAAiE;IACjE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,+EAA+E;IAC/E,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,KAAK,EAAE,cAAc,EAAE,CAAC;CACzB,CAAC;AAOF;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,cAAc,CAkB9D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,cAAc,CAuB9D;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,UAAU,EAAE,eAAe,CAAC;IAC5B,sEAAsE;IACtE,sBAAsB,EAAE,OAAO,CAAC;CACjC,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,iBAAiB,CAkB5F"}
|
package/dist/ir-normalize.js
CHANGED
|
@@ -12,7 +12,7 @@ function emptyRecord(obj) {
|
|
|
12
12
|
*/
|
|
13
13
|
export function normalizeNodeSlot(raw) {
|
|
14
14
|
if (raw == null || typeof raw !== 'object') {
|
|
15
|
-
return { id: '', type: '', name: '', config: {}, schema: {} };
|
|
15
|
+
return { id: '', type: '', name: '', config: {}, schema: {}, metadata: {} };
|
|
16
16
|
}
|
|
17
17
|
const r = raw;
|
|
18
18
|
const id = String(r.id ?? '').trim();
|
|
@@ -26,6 +26,7 @@ export function normalizeNodeSlot(raw) {
|
|
|
26
26
|
name,
|
|
27
27
|
config: emptyRecord(r.config),
|
|
28
28
|
schema: emptyRecord(r.schema),
|
|
29
|
+
metadata: emptyRecord(r.metadata),
|
|
29
30
|
};
|
|
30
31
|
}
|
|
31
32
|
/**
|
package/dist/lint-graph.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lint-graph.d.ts","sourceRoot":"","sources":["../src/lint-graph.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAK9D,6FAA6F;AAC7F,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEzF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC/C,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC3B,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,+EAA+E;IAC/E,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG,eAAe,GAAG;IAAE,QAAQ,EAAE,mBAAmB,EAAE,CAAA;CAAE,CAAC;AAE/F,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAItF;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAE3D;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAWvD;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,EAAE,eAAe,GAAG,OAAO,CAqBxG;AAED,2EAA2E;AAC3E,wBAAgB,yBAAyB,CAAC,CAAC,EAAE,eAAe,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAYnF;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,OAAO,GAAG,0BAA0B,
|
|
1
|
+
{"version":3,"file":"lint-graph.d.ts","sourceRoot":"","sources":["../src/lint-graph.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAK9D,6FAA6F;AAC7F,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEzF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC/C,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC3B,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,+EAA+E;IAC/E,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG,eAAe,GAAG;IAAE,QAAQ,EAAE,mBAAmB,EAAE,CAAA;CAAE,CAAC;AAE/F,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAItF;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAE3D;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAWvD;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,EAAE,eAAe,GAAG,OAAO,CAqBxG;AAED,2EAA2E;AAC3E,wBAAgB,yBAAyB,CAAC,CAAC,EAAE,eAAe,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAYnF;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,OAAO,GAAG,0BAA0B,CAsD5E;AAED,2DAA2D;AAC3D,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,0BAA0B,GAAG,CAAC,IAAI,eAAe,CAErF"}
|