@archrad/deterministic 0.1.0
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 +66 -0
- package/CONTRIBUTING.md +15 -0
- package/LICENSE +17 -0
- package/README.md +284 -0
- package/SECURITY.md +26 -0
- package/biome.json +25 -0
- package/demo-validate.gif +0 -0
- package/dist/cli-findings.d.ts +23 -0
- package/dist/cli-findings.d.ts.map +1 -0
- package/dist/cli-findings.js +88 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +341 -0
- package/dist/edgeConfigCodeGenerator.d.ts +55 -0
- package/dist/edgeConfigCodeGenerator.d.ts.map +1 -0
- package/dist/edgeConfigCodeGenerator.js +249 -0
- package/dist/exportPipeline.d.ts +23 -0
- package/dist/exportPipeline.d.ts.map +1 -0
- package/dist/exportPipeline.js +65 -0
- package/dist/golden-bundle.d.ts +21 -0
- package/dist/golden-bundle.d.ts.map +1 -0
- package/dist/golden-bundle.js +166 -0
- package/dist/graphPredicates.d.ts +10 -0
- package/dist/graphPredicates.d.ts.map +1 -0
- package/dist/graphPredicates.js +33 -0
- package/dist/hostPort.d.ts +12 -0
- package/dist/hostPort.d.ts.map +1 -0
- package/dist/hostPort.js +39 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/ir-lint.d.ts +11 -0
- package/dist/ir-lint.d.ts.map +1 -0
- package/dist/ir-lint.js +16 -0
- package/dist/ir-normalize.d.ts +48 -0
- package/dist/ir-normalize.d.ts.map +1 -0
- package/dist/ir-normalize.js +81 -0
- package/dist/ir-structural.d.ts +40 -0
- package/dist/ir-structural.d.ts.map +1 -0
- package/dist/ir-structural.js +267 -0
- package/dist/lint-graph.d.ts +40 -0
- package/dist/lint-graph.d.ts.map +1 -0
- package/dist/lint-graph.js +133 -0
- package/dist/lint-rules.d.ts +40 -0
- package/dist/lint-rules.d.ts.map +1 -0
- package/dist/lint-rules.js +290 -0
- package/dist/nodeExpress.d.ts +2 -0
- package/dist/nodeExpress.d.ts.map +1 -0
- package/dist/nodeExpress.js +528 -0
- package/dist/openapi-structural.d.ts +26 -0
- package/dist/openapi-structural.d.ts.map +1 -0
- package/dist/openapi-structural.js +82 -0
- package/dist/openapi-to-ir.d.ts +26 -0
- package/dist/openapi-to-ir.d.ts.map +1 -0
- package/dist/openapi-to-ir.js +131 -0
- package/dist/pythonFastAPI.d.ts +2 -0
- package/dist/pythonFastAPI.d.ts.map +1 -0
- package/dist/pythonFastAPI.js +664 -0
- package/dist/validate-drift.d.ts +54 -0
- package/dist/validate-drift.d.ts.map +1 -0
- package/dist/validate-drift.js +184 -0
- package/dist/yamlToIr.d.ts +14 -0
- package/dist/yamlToIr.d.ts.map +1 -0
- package/dist/yamlToIr.js +39 -0
- package/docs/CONCEPT_ADOPTION_AND_LIMITS.md +47 -0
- package/docs/CUSTOM_RULES.md +87 -0
- package/docs/ENGINEERING_NOTES.md +42 -0
- package/docs/IR_CONTRACT.md +54 -0
- package/docs/STRUCTURAL_VS_SEMANTIC_VALIDATION.md +86 -0
- package/fixtures/demo-direct-db-layered.json +37 -0
- package/fixtures/demo-direct-db-violation.json +22 -0
- package/fixtures/ecommerce-with-warnings.json +89 -0
- package/fixtures/invalid-cycle.json +15 -0
- package/fixtures/invalid-edge-unknown-node.json +14 -0
- package/fixtures/minimal-graph.json +14 -0
- package/fixtures/minimal-graph.yaml +13 -0
- package/fixtures/payment-retry-demo.json +43 -0
- package/llms.txt +99 -0
- package/package.json +84 -0
- package/schemas/archrad-ir-graph-v1.schema.json +67 -0
- package/scripts/DEMO_GIF_STORYBOARD.md +100 -0
- package/scripts/GIF_RECORDING_STEP_BY_STEP.md +125 -0
- package/scripts/README_DEMO_RECORDING.md +314 -0
- package/scripts/SOCIAL_POST_DRIFT_AND_INGESTION.md +17 -0
- package/scripts/golden-path-demo.ps1 +25 -0
- package/scripts/golden-path-demo.sh +23 -0
- package/scripts/invoke-drift-check.ps1 +16 -0
- package/scripts/record-demo-drift.tape +50 -0
- package/scripts/record-demo-payment-retry.tape +36 -0
- package/scripts/record-demo-validate.tape +34 -0
- package/scripts/record-demo.tape +33 -0
- package/scripts/run-demo-drift-sequence.ps1 +45 -0
- package/scripts/run-demo-drift-sequence.sh +41 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to **`@archrad/deterministic`** are documented here.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **[docs/CUSTOM_RULES.md](docs/CUSTOM_RULES.md)** — custom **`IR-LINT`-style** visitors (`ParsedLintGraph` → **`IrStructuralFinding[]`**): worked **service / `config.timeout`** example, **compose** (`runArchitectureLinting` + org rules) vs **fork** (`LINT_RULE_REGISTRY`); no runtime registry mutation.
|
|
12
|
+
- **`archrad validate-drift`** — compare an on-disk export directory to a **fresh** deterministic export from the same IR (`DRIFT-MISSING` / `DRIFT-MODIFIED` / optional `DRIFT-EXTRA` with **`--strict-extra`**); **`--json`** for CI. Library: **`runValidateDrift`**, **`diffExpectedExportAgainstFiles`**, **`runDriftCheckAgainstFiles`**, etc. (`src/validate-drift.ts`).
|
|
13
|
+
- **VHS tape** **`scripts/record-demo-drift.tape`** → **`demo-drift.gif`**; **`npm run record:demo:drift`**. Storyboard and recording docs updated (**`scripts/DEMO_GIF_STORYBOARD.md`**). **Replay without VHS:** **`scripts/run-demo-drift-sequence.sh`** / **`.ps1`** for ShareX/OBS/asciinema capture; **`README_DEMO_RECORDING.md`** (**When VHS fails**).
|
|
14
|
+
- **`graphPredicates.ts`**: shared **`isHttpLikeType`** / **`isDbLikeType`** / **`isQueueLikeNodeType`** (exported from the package root) so structural HTTP checks and IR-LINT stay aligned.
|
|
15
|
+
- **`IR-STRUCT-EDGE_AMBIGUOUS_FROM` / `IR-STRUCT-EDGE_AMBIGUOUS_TO`** when an edge references a **duplicate** node id.
|
|
16
|
+
- **`ParsedLintGraph.inDegree`**, **`BuildParsedLintGraphResult`**, **`isParsedLintGraph()`** — `buildParsedLintGraph` returns **`{ findings }`** on parse failure instead of **`null`**.
|
|
17
|
+
- Richer **`IR-STRUCT-CYCLE`** message (example node on the cycle) and **`nodeId`** when detectable.
|
|
18
|
+
- **`archrad yaml-to-ir`** — YAML blueprint → canonical `{ "graph": … }` JSON (`-y/--yaml`, `-o/--out` or stdout). Library: **`parseYamlToCanonicalIr`**, **`canonicalIrToJsonString`**, **`YamlGraphParseError`**. Fixture **`fixtures/minimal-graph.yaml`**.
|
|
19
|
+
- **Five architecture lint rules** (`IR-LINT-ISOLATED-NODE-005` … **009**): isolated nodes when the graph has edges elsewhere, duplicate edges, HTTP missing `name`, datastore with no incoming edges, multiple HTTP entry nodes. CLI prints **Architecture lint (IR-LINT-*)** in a separate block from structural findings.
|
|
20
|
+
- Launch line: **Validate your architecture before you write code.** (README, `llms.txt`, npm `description`, `archrad --help` / `validate` description, clean `archrad validate` stdout).
|
|
21
|
+
- **`ir-normalize`** (`materializeNormalizedGraph`, `NormalizedGraph` / node / edge types) — parser boundary docs in **`docs/IR_CONTRACT.md`**; README **Validation levels** (JSON Schema → IR structural → export-time OpenAPI structural).
|
|
22
|
+
- **`llms.txt`** at package root — markdown summary for LLM/agent discovery (included in npm tarball).
|
|
23
|
+
- **IR structural validation** (`validateIrStructural`, `normalizeIrGraph`, `hasIrStructuralErrors`): node ids, HTTP path/method, edge endpoints, directed **cycles**; codes like `IR-STRUCT-*`.
|
|
24
|
+
- **Architecture lint** (`validateIrLint`, **`IR-LINT-*`**): deterministic rules only — direct **HTTP→DB** edge, **sync chain** depth from HTTP entry, **missing health** path, **high fan-out** (≥5 outbound edges). No AI, no org policy.
|
|
25
|
+
- **`archrad validate --ir <path>`** — structural + lint; **`--json`**; pretty output; **`--skip-lint`**, **`--fail-on-warning`**, **`--max-warnings <n>`** for CI.
|
|
26
|
+
- **`schemas/archrad-ir-graph-v1.schema.json`** — documented JSON shape for the graph IR (companion to code rules).
|
|
27
|
+
- **`runDeterministicExport`** returns **`irStructuralFindings`** and **`irLintFindings`**; **structural errors** block codegen unless **`skipIrStructuralValidation`**.
|
|
28
|
+
- CLI **`export`**: **`--skip-ir-structural-validation`**, **`--skip-ir-lint`**, **`--fail-on-warning`**, **`--max-warnings <n>`** (blocks writes when IR policy fails).
|
|
29
|
+
- Library: **`validateIrLint`**, **`sortFindings`**, **`shouldFailFromFindings`**, **`IrFindingLayer`**.
|
|
30
|
+
- Fixtures **`invalid-edge-unknown-node.json`**, **`invalid-cycle.json`** for negative tests; **`ecommerce-with-warnings.json`** triggers all four **`IR-LINT-*`** rules for demos and tests.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
- **`validateIrLint`** returns **structural findings** when the IR cannot be built (same codes as **`normalizeIrGraph`** / empty graph), instead of **`[]`**.
|
|
34
|
+
- **`runDeterministicExport`**: when **`skipIrStructuralValidation`** is set, **`IR-STRUCT-*`** from **`validateIrLint`** are merged into **`irStructuralFindings`** (and block codegen on errors); **`irLintFindings`** stays **`IR-LINT-*`** only — aligns server logging and product “fail closed” on invalid/empty IR.
|
|
35
|
+
- **`normalizeEdgeSlot`**: top-level **`edge.kind`** is copied into **`metadata.kind`** when metadata does not already set **`kind`** (preserves async lint signal).
|
|
36
|
+
- **`edgeRepresentsAsyncBoundary`**: considers top-level **`kind`**, optional **`ParsedLintGraph`** (queue-like **target** nodes), and normalized metadata.
|
|
37
|
+
- **`IR-LINT-DIRECT-DB-ACCESS-002`**: one finding per **`(from,to)`** pair (deduped parallel edges).
|
|
38
|
+
- **`looksLikeHealthUrl`**: **`/healthcheck`**, **`/ping`**, **`/status`**, **`/alive`** (and **`ruleNoHealthcheck`** message notes gateway/BFF heuristic).
|
|
39
|
+
- **`isHttpLikeType`**: **`gateway`**, **`bff`**, **`graphql`**, **`grpc`**, and word-boundary matches for common API surface types.
|
|
40
|
+
|
|
41
|
+
### Changed (engineering / safety)
|
|
42
|
+
- **TypeScript `strict: true`** + **`noUnusedLocals` / `noUnusedParameters`**; **`npm test`** runs **`tsc --noEmit`** before Vitest.
|
|
43
|
+
- **`prepare`** removed; **`prepublishOnly`** runs **`npm run build`** for npm publishes. Monorepo consumers must build this package explicitly (unchanged for InkByte CI).
|
|
44
|
+
- **Biome** added with a **minimal** lint ruleset (`npm run lint`); expand rules incrementally — see **`docs/ENGINEERING_NOTES.md`**.
|
|
45
|
+
- **IR-LINT-SYNC-CHAIN-001** uses **sync-only** adjacency; edges marked async (`metadata.protocol`, `config.async`, etc.) are excluded — see **`edgeRepresentsAsyncBoundary`** in `lint-graph.ts`.
|
|
46
|
+
- **CLI:** **`--danger-skip-ir-structural-validation`** documented; **`--skip-ir-structural-validation`** hidden (deprecated alias).
|
|
47
|
+
|
|
48
|
+
### Changed (documentation / messaging)
|
|
49
|
+
- **`docs/CONCEPT_ADOPTION_AND_LIMITS.md`** — honest framing: strengths (IR as SoT, compiler model, tiered validation), adoption friction (IR authoring, one-way export), OSS-as-trust vs platform adoption; README + `llms.txt` summaries and links.
|
|
50
|
+
- Canonical **OSS positioning** line in README, `llms.txt`, monorepo/OSS docs: *Includes structural validation + basic architecture linting (rule-based, deterministic).*
|
|
51
|
+
- Clarified OpenAPI pass as **document shape** (parse + required top-level fields), explicitly **not** Spectral-style lint; README + `docs/STRUCTURAL_VS_SEMANTIC_VALIDATION.md` + code comments.
|
|
52
|
+
- Documented **codegen vs validation** for retry/timeout IR fields and **InkByte vs OSS** scope in README and structural/semantic doc.
|
|
53
|
+
- README positioning: **deterministic compiler and linter for system architecture**; validation layers table (OSS vs Cloud).
|
|
54
|
+
|
|
55
|
+
## [0.1.0] - 2026-02-26
|
|
56
|
+
|
|
57
|
+
### Added
|
|
58
|
+
- Deterministic **FastAPI** and **Express** generators from blueprint **IR** (JSON graph).
|
|
59
|
+
- **`archrad export`** CLI (`--ir`, `--target`, `--out`).
|
|
60
|
+
- **Structural OpenAPI** validation pass on generated bundles (warnings, no LLM repair).
|
|
61
|
+
- **Golden path**: `docker-compose.yml`, `Dockerfile`, `Makefile`, README section; container port **8080**; configurable **host** publish port (`--host-port` / `ARCHRAD_HOST_PORT`).
|
|
62
|
+
- Optional **localhost preflight** for host port (warn or `--strict-host-port`).
|
|
63
|
+
- Library API: `runDeterministicExport`, OpenAPI helpers, golden-layer helpers.
|
|
64
|
+
|
|
65
|
+
[Unreleased]: https://github.com/archradhq/arch-deterministic/compare/v0.1.0...HEAD
|
|
66
|
+
[0.1.0]: https://github.com/archradhq/arch-deterministic/releases/tag/v0.1.0
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Development usually happens in the **private InkByte monorepo** under `packages/deterministic`, so the server and package stay in sync.
|
|
4
|
+
|
|
5
|
+
When you change this package, follow the product monorepo checklist: **`docs/MONOREPO_OSS_DETERMINISTIC_ALIGNMENT.md`** (and run **`npm run test:deterministic`** from the **InkByte repo root**).
|
|
6
|
+
|
|
7
|
+
If you clone **only** this repository (`archradhq/arch-deterministic`):
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm ci
|
|
11
|
+
npm run build # required: there is no prepare script (see docs/ENGINEERING_NOTES.md)
|
|
12
|
+
npm test # runs tsc --noEmit then vitest
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
PRs: keep changes **free of product-specific** imports (no Firestore, no `server/` paths). Apache-2.0 — see `LICENSE`.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
Copyright 2026 ArchRad / InkByte contributors
|
|
6
|
+
|
|
7
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
you may not use this file except in compliance with the License.
|
|
9
|
+
You may obtain a copy of the License at
|
|
10
|
+
|
|
11
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
|
|
13
|
+
Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
See the License for the specific language governing permissions and
|
|
17
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
# @archrad/deterministic
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
<!-- GIFs: validate hero — `npm run build && vhs scripts/record-demo-validate.tape` → demo-validate.gif. Drift — `npm run record:demo:drift` → demo-drift.gif (terminal tape) or replay scripts + capture. For “edit → save → validate-drift” (IDE + terminal, skeptic-grade), see scripts/DEMO_GIF_STORYBOARD.md “Trust loop”. scripts/README_DEMO_RECORDING.md. package.json `files`. -->
|
|
6
|
+
|
|
7
|
+
**A deterministic compiler and linter for system architecture.**
|
|
8
|
+
|
|
9
|
+
**Validate your architecture before you write code.**
|
|
10
|
+
|
|
11
|
+
**For AI agents / IDE assistants:** see **`llms.txt`** in this package (llms.txt-style project summary for tools like Claude Code, Devin, etc.).
|
|
12
|
+
|
|
13
|
+
**Apache-2.0** — blueprint **IR** (JSON graph) → **FastAPI** or **Express** + **OpenAPI**, **Docker**, **Makefile** — **no LLM**, **no account**, **offline**.
|
|
14
|
+
|
|
15
|
+
**OSS positioning:** *Includes structural validation + basic architecture linting (rule-based, deterministic).*
|
|
16
|
+
|
|
17
|
+
> This is not only a generator: **`archrad validate`** treats your graph like source code — **IR-STRUCT-*** (shape/refs/cycles) and **IR-LINT-*** (light architecture heuristics: health routes, fan-out, sync chains, HTTP→DB coupling). Then **`archrad export`** compiles to runnable projects. After you have a tree on disk, **`archrad validate-drift`** compares it to a **fresh** export from the same IR (missing/changed files — **not** semantic code review). Generated **OpenAPI** gets a **document-shape** pass (parse + required fields — **not** Spectral-style spec lint).
|
|
18
|
+
|
|
19
|
+
**Open core (OSS):** **IR structural validation**, **basic architecture lint** (rule-based, deterministic), **OpenAPI document-shape** checks. **ArchRad Cloud** adds **policy / compliance**, deeper **architecture intelligence**, and **AI remediation** — see **`docs/STRUCTURAL_VS_SEMANTIC_VALIDATION.md`**.
|
|
20
|
+
|
|
21
|
+
### Concept, cold start, and strategy (honest scope)
|
|
22
|
+
|
|
23
|
+
The **core idea** is sound: **IR = what to build**, **codegen = how** — with compiler-style validation and deterministic output. **Adoption friction** is real: this repo is a **compiler with no bundled authoring UI** (JSON/graph in; you bring **ArchRad Cloud**, another tool, or hand-written IR). **Where the IR comes from** is intentionally **plural**: **manual** graph JSON/YAML today, plus **`archrad ingest openapi`** (and more ingestion surfaces over time — e.g. **IaC**). Treat **OpenAPI → IR** as a **starting lane**, not the whole roadmap: regenerate IR in CI, then **`archrad validate`** / **`archrad export`** — structural HTTP surface only, not full system semantics. Draft language for “drift / where does IR come from?” threads: **`scripts/SOCIAL_POST_DRIFT_AND_INGESTION.md`**. **Export is one-way** (no built-in round-trip from edited code back to IR)—best thought of as **scaffold + contract validation**, not full lifecycle architecture sync unless you own that workflow. Many teams will still treat the OSS layer as a **trust and CI artifact** that makes the Cloud/AI path auditable. Read **`docs/CONCEPT_ADOPTION_AND_LIMITS.md`** for the full framing and future directions (e.g. lightweight YAML/IDE ergonomics).
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## How it works (architecture)
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
IR (nodes/edges) → validateIrStructural (IR-STRUCT-*) → errors block export
|
|
31
|
+
↓
|
|
32
|
+
validateIrLint (IR-LINT-*) → warnings (CI: --fail-on-warning / --max-warnings)
|
|
33
|
+
↓
|
|
34
|
+
pythonFastAPI | nodeExpress generators
|
|
35
|
+
↓
|
|
36
|
+
openapi.yaml + app code + package metadata
|
|
37
|
+
↓
|
|
38
|
+
golden layer (Dockerfile, docker-compose.yml, Makefile, README; host→container e.g. 8080:8080)
|
|
39
|
+
↓
|
|
40
|
+
validateOpenApiInBundleStructural(openapi.yaml) → document-shape warnings (not full API lint)
|
|
41
|
+
↓
|
|
42
|
+
{ files, openApiStructuralWarnings, irStructuralFindings, irLintFindings }
|
|
43
|
+
|
|
44
|
+
Optional CI: archrad validate-drift → re-export IR in-memory, diff vs existing ./out → DRIFT-MISSING / DRIFT-MODIFIED (thin deterministic gate)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Validation levels (quick contract)
|
|
48
|
+
|
|
49
|
+
1. **JSON Schema validation** — IR document shape vs `schemas/archrad-ir-graph-v1.schema.json` (editor/CI; optional at runtime).
|
|
50
|
+
2. **IR structural validation** — `validateIrStructural`: arrays, ids, HTTP `config`, edge refs, cycles (`IR-STRUCT-*`). Uses an internal **normalized** graph (see **`docs/IR_CONTRACT.md`**).
|
|
51
|
+
3. **Export-time generated OpenAPI structural validation** — Parse + required fields on the **generated** `openapi.yaml` (document shape, not Spectral).
|
|
52
|
+
|
|
53
|
+
**Architecture lint** (`IR-LINT-*`) sits after structural checks: rule visitors on the parsed graph (heuristics, not schema).
|
|
54
|
+
|
|
55
|
+
### Validation layers (naming)
|
|
56
|
+
|
|
57
|
+
| Layer (OSS) | What it is | Codes |
|
|
58
|
+
|-------------|------------|--------|
|
|
59
|
+
| **IR structural validation** | Graph well-formedness: ids, edges, cycles, HTTP path/method | `IR-STRUCT-*` |
|
|
60
|
+
| **Architecture lint (basic)** | Deterministic heuristics only (no AI, no org policy) | `IR-LINT-*` |
|
|
61
|
+
| **OpenAPI structural validation** (document shape) | Parse + required top-level OpenAPI fields on **generated** spec | *(string warnings, not IR codes)* |
|
|
62
|
+
|
|
63
|
+
| Layer (Cloud — not this package) | Examples |
|
|
64
|
+
|----------------------------------|----------|
|
|
65
|
+
| **Policy engine** | SOC2, org rules, entitlement |
|
|
66
|
+
| **Architecture intelligence** | Deeper NFR / cost / security reasoning |
|
|
67
|
+
| **AI remediation** | Repair loops, suggested edits |
|
|
68
|
+
|
|
69
|
+
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`**).
|
|
71
|
+
3. **Generators** → `openapi.yaml`, handlers, deps.
|
|
72
|
+
4. **Golden path** → `make run` / `docker compose up --build`.
|
|
73
|
+
5. **OpenAPI document shape** on the bundle — **not** [Spectral](https://github.com/stoplightio/spectral)-level lint. Issues → **`openApiStructuralWarnings`**.
|
|
74
|
+
|
|
75
|
+
**IR contract:** **`schemas/archrad-ir-graph-v1.schema.json`**. **Parser boundary + normalized shapes:** **`docs/IR_CONTRACT.md`** (`normalizeIrGraph` → `materializeNormalizedGraph`).
|
|
76
|
+
|
|
77
|
+
**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
|
+
|
|
79
|
+
### Codegen vs validation (retry, timeouts, policy)
|
|
80
|
+
|
|
81
|
+
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.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Ways to use it
|
|
86
|
+
|
|
87
|
+
| Mode | Best for | Example |
|
|
88
|
+
|------|-----------|---------|
|
|
89
|
+
| **CLI** | Quick local scaffolding, CI, “no Node project” usage | `archrad export --ir graph.json --target python --out ./out` |
|
|
90
|
+
| **YAML → IR** | Author graphs in YAML, emit JSON for validate/export | `archrad yaml-to-ir -y graph.yaml -o graph.json` |
|
|
91
|
+
| **OpenAPI → IR** | Derive HTTP nodes from OpenAPI 3.x (same IR shape as YAML path); **ArchRad Cloud** merge uses the same library | `archrad ingest openapi --spec openapi.yaml -o graph.json` |
|
|
92
|
+
| **CLI validate** | CI / pre-commit: IR structural + architecture lint, no codegen | `archrad validate --ir graph.json` |
|
|
93
|
+
| **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
|
+
| **Library** (`@archrad/deterministic`) | IDPs / pipelines | `runDeterministicExport` → files + findings; **`runValidateDrift`** / **`runDriftCheckAgainstFiles`** for drift |
|
|
95
|
+
|
|
96
|
+
### CLI
|
|
97
|
+
|
|
98
|
+
**Input is structured IR (JSON), not natural language.** There is no `archrad export --prompt "..."`. You pass a **graph file** (nodes/edges) like **`fixtures/minimal-graph.json`**. The **npm README GIF** uses **`fixtures/demo-direct-db-violation.json`** → **`fixtures/demo-direct-db-layered.json`**: **failure-first** **`IR-LINT-DIRECT-DB-ACCESS-002`**, fix on the graph, then a **clean** validate (no codegen in the clip). For a graph that hits **many** lint rules at once (stress test), use **`fixtures/ecommerce-with-warnings.json`**. For a **golden payment + retry** graph (edge `config.retry.maxAttempts` + payment node `config.retryPolicy`, both `3`), use **`fixtures/payment-retry-demo.json`** — export shows **`retry_policy`** / retry helpers in **`app/main.py`**. Record that path with **`scripts/record-demo-payment-retry.tape`** (→ **`demo-payment-retry.gif`**). **Drift clip:** **`scripts/record-demo-drift.tape`** / **`npm run record:demo:drift`** (→ **`demo-drift.gif`**) — export, **`tail`** before/after a one-line tamper on **`out/app/main.py`**, then **`validate-drift`**; if **VHS** fails on your machine, run **`scripts/run-demo-drift-sequence.ps1`** or **`.sh`** while screen-capturing (see **`scripts/README_DEMO_RECORDING.md`**). See **`scripts/DEMO_GIF_STORYBOARD.md`** for all tapes. **CLI:** `--target python` is the FastAPI bundle; there is no separate `fastapi` target name. To go from **plain English → IR**, use **ArchRad Cloud** or your own LLM step; this package only does **IR → files**.
|
|
99
|
+
|
|
100
|
+
**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
|
+
|
|
102
|
+
```bash
|
|
103
|
+
archrad ingest openapi --spec ./openapi.yaml --out ./graph.json
|
|
104
|
+
archrad validate --ir ./graph.json
|
|
105
|
+
archrad export --ir ./graph.json --target python --out ./out
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**YAML → JSON (lighter authoring):** edit **`fixtures/minimal-graph.yaml`** (or your own file) and compile to IR JSON, then validate or export:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
archrad yaml-to-ir --yaml fixtures/minimal-graph.yaml --out ./graph.json
|
|
112
|
+
archrad validate --ir ./graph.json
|
|
113
|
+
# or pipe: archrad yaml-to-ir -y fixtures/minimal-graph.yaml | archrad validate --ir /dev/stdin # on Unix; on Windows use --out then validate
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
YAML must have either top-level **`graph:`** (object) or top-level **`nodes:`** (array); bare graphs are wrapped as `{ "graph": { ... } }` automatically.
|
|
117
|
+
|
|
118
|
+
After `npm run build` (required after `npm ci`; there is no `prepare` hook — see **`docs/ENGINEERING_NOTES.md`**):
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
node dist/cli.js export --ir fixtures/minimal-graph.json --target python --out ./my-api
|
|
122
|
+
node dist/cli.js yaml-to-ir --yaml fixtures/minimal-graph.yaml --out /tmp/ir.json
|
|
123
|
+
# After global install / npx:
|
|
124
|
+
archrad export --ir ./graph.json --target node --out ./my-express-api
|
|
125
|
+
|
|
126
|
+
# Validate IR (structural + architecture lint). Pretty output; exit 1 on structural errors by default:
|
|
127
|
+
node dist/cli.js validate --ir fixtures/minimal-graph.json
|
|
128
|
+
# Machine-readable + CI gates:
|
|
129
|
+
archrad validate --ir ./graph.json --json
|
|
130
|
+
archrad validate --ir ./graph.json --fail-on-warning
|
|
131
|
+
archrad validate --ir ./graph.json --max-warnings 0
|
|
132
|
+
# Structural only (skip IR-LINT-*):
|
|
133
|
+
archrad validate --ir ./graph.json --skip-lint
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**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.
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
archrad export -i ./graph.json -t python -o ./out
|
|
140
|
+
# …edit files under ./out…
|
|
141
|
+
archrad validate-drift -i ./graph.json -t python -o ./out --skip-host-port-check
|
|
142
|
+
# CI-friendly:
|
|
143
|
+
archrad validate-drift -i ./graph.json -t python -o ./out --skip-host-port-check --json
|
|
144
|
+
# Fail if the tree has extra files not in the reference export:
|
|
145
|
+
archrad validate-drift -i ./graph.json -t python -o ./out --strict-extra
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
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`**).
|
|
149
|
+
|
|
150
|
+
#### Example: validate architecture
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
archrad validate --ir fixtures/minimal-graph.json
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Example output (stderr):
|
|
157
|
+
|
|
158
|
+
```text
|
|
159
|
+
archrad validate:
|
|
160
|
+
⚠️ IR-LINT-NO-HEALTHCHECK-003: No HTTP node exposes a typical health/readiness path (...)
|
|
161
|
+
Fix: Add a GET route such as /health for orchestrators and load balancers.
|
|
162
|
+
Suggestion: Expose liveness vs readiness separately if your platform distinguishes them.
|
|
163
|
+
Impact: Weaker deploy/rollback safety and harder operations automation.
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Structural errors look like **`❌ IR-STRUCT-...`** with **`Fix:`** lines. Use **`--json`** to consume findings in GitHub Actions or other CI.
|
|
167
|
+
|
|
168
|
+
- **`--ir`** — JSON: `{ "graph": { "nodes", "edges", "metadata" } }` or a raw graph (CLI wraps it).
|
|
169
|
+
- **`--target`** — `python` \| `node` \| `nodejs`
|
|
170
|
+
- **`--out`** — output directory (created if needed)
|
|
171
|
+
- **`--host-port <n>`** — host port Docker publishes (default **8080**; container still listens on **8080** inside). Same as env **`ARCHRAD_HOST_PORT`**.
|
|
172
|
+
- **`--skip-host-port-check`** — don’t probe `127.0.0.1` before export.
|
|
173
|
+
- **`--strict-host-port`** — **exit with error** if the host port appears **in use** (CI-friendly).
|
|
174
|
+
- **`--danger-skip-ir-structural-validation`** — **UNSAFE:** skip **`validateIrStructural`** before export (never in CI). **Parse/normalize failures** (invalid root, empty graph) are still detected via **`validateIrLint`** and **block** export with **`IR-STRUCT-*`** in **`irStructuralFindings`**. A hidden **`--skip-ir-structural-validation`** remains as a deprecated alias.
|
|
175
|
+
- **`--skip-ir-lint`** — skip **`validateIrLint`** during export.
|
|
176
|
+
- **`--fail-on-warning`** / **`--max-warnings <n>`** — if set, **no files are written** when IR structural + lint findings violate the policy (same semantics as **`validate`**).
|
|
177
|
+
|
|
178
|
+
By default, if **8080** (or your `--host-port`) looks **busy** on localhost, the CLI **warns** so you can change the port before `docker compose` fails with a bind error.
|
|
179
|
+
|
|
180
|
+
**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.
|
|
181
|
+
|
|
182
|
+
### Validate the package as a developer
|
|
183
|
+
|
|
184
|
+
1. `cd packages/deterministic && npm ci && npm run build && npm test`
|
|
185
|
+
2. `node dist/cli.js export --ir fixtures/minimal-graph.json --target python --out ./tmp-out`
|
|
186
|
+
3. `cd tmp-out && make run` then `curl` the URL shown in the generated **README** (port matches `--host-port` if you set it).
|
|
187
|
+
4. Optional: `node dist/cli.js export ... --host-port 18080` if **8080** is already taken.
|
|
188
|
+
|
|
189
|
+
### Library
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
import {
|
|
193
|
+
runDeterministicExport,
|
|
194
|
+
runValidateDrift,
|
|
195
|
+
validateIrStructural,
|
|
196
|
+
validateIrLint,
|
|
197
|
+
sortFindings,
|
|
198
|
+
shouldFailFromFindings,
|
|
199
|
+
} from '@archrad/deterministic';
|
|
200
|
+
|
|
201
|
+
const { files, openApiStructuralWarnings, irStructuralFindings, irLintFindings } =
|
|
202
|
+
await runDeterministicExport(ir, 'python', {
|
|
203
|
+
hostPort: 8080,
|
|
204
|
+
skipIrLint: false, // default
|
|
205
|
+
});
|
|
206
|
+
// Structural errors → empty files (unless skipIrStructuralValidation). Lint is non-blocking for export unless you check policy in your pipeline.
|
|
207
|
+
|
|
208
|
+
const drift = await runValidateDrift(ir, 'python', '/path/to/existing-export', {
|
|
209
|
+
skipIrLint: false, // set true to match CLI --skip-ir-lint on reference export
|
|
210
|
+
});
|
|
211
|
+
// drift.ok, drift.driftFindings, drift.exportResult — same core semantics as CLI validate-drift (CLI also probes host port before calling the library)
|
|
212
|
+
|
|
213
|
+
const all = sortFindings([...validateIrStructural(ir), ...validateIrLint(ir)]);
|
|
214
|
+
if (shouldFailFromFindings(all, { failOnWarning: true })) {
|
|
215
|
+
/* gate your CI */
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Optional: `isLocalHostPortFree` / `normalizeGoldenHostPort` from the same package if you want your own preflight.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Golden path (~60 seconds)
|
|
224
|
+
|
|
225
|
+
From the package root (after build):
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
node dist/cli.js export --ir fixtures/minimal-graph.json --target python --out ./out
|
|
229
|
+
cd ./out
|
|
230
|
+
make run
|
|
231
|
+
# In another terminal, once the API is up:
|
|
232
|
+
curl -sS -X POST http://localhost:8080/signup -H "Content-Type: application/json" -d '{}'
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
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.
|
|
236
|
+
|
|
237
|
+
**Helper script** (prints the same flow; use when recording a terminal GIF):
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
bash scripts/golden-path-demo.sh
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
See **`scripts/README_DEMO_RECORDING.md`** for **VHS / asciinema / ttyrec** tips, **When VHS fails**, **drift** replay scripts, and the **trust loop** (IDE edit + terminal **`validate-drift`**). The hero GIF at the top is **`demo-validate.gif`**; **`demo-drift.gif`** and the **trust-loop** storyboard live in **`scripts/DEMO_GIF_STORYBOARD.md`**.
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Open source vs ArchRad Cloud
|
|
248
|
+
|
|
249
|
+
**This repository is only the deterministic engine** — local, offline, no phone-home.
|
|
250
|
+
|
|
251
|
+
| Here (OSS) | ArchRad Cloud (commercial product) |
|
|
252
|
+
|------------|-------------------------------------|
|
|
253
|
+
| IR **structural** + **architecture lint** (`validate`, `IR-STRUCT-*`, `IR-LINT-*`), compiler (`export`), **`validate-drift`** (on-disk vs fresh export), OpenAPI **document-shape** warnings, golden Docker/Makefile | **Policy engine**, deeper **architecture intelligence**, **AI remediation**, richer **drift / sync** UX in the builder |
|
|
254
|
+
| `archrad` CLI forever, no account required for this package | Auth, orgs, **quotas**, billing |
|
|
255
|
+
| No proprietary **LLM** orchestration or “repair” loops | LLM generation, repair, multi-model routing |
|
|
256
|
+
| No Git sync, no enterprise policy injection in this repo | Git push, governance, compliance dashboards |
|
|
257
|
+
|
|
258
|
+
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.
|
|
259
|
+
|
|
260
|
+
**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.
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## Monorepo vs public OSS repo
|
|
265
|
+
|
|
266
|
+
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).
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## Publishing the public OSS repo
|
|
271
|
+
|
|
272
|
+
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.
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## Contributing
|
|
277
|
+
|
|
278
|
+
See **`CONTRIBUTING.md`**.
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## License
|
|
283
|
+
|
|
284
|
+
Apache-2.0 — see **`LICENSE`**.
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Security policy
|
|
2
|
+
|
|
3
|
+
## Supported versions
|
|
4
|
+
|
|
5
|
+
We publish **`@archrad/deterministic`** on npm from the [`archradhq/arch-deterministic`](https://github.com/archradhq/arch-deterministic) repository. Security fixes are applied on the **latest** minor release line when practical; use the newest version when possible.
|
|
6
|
+
|
|
7
|
+
## Reporting a vulnerability
|
|
8
|
+
|
|
9
|
+
**Please do not** open a public GitHub issue for undisclosed security problems.
|
|
10
|
+
|
|
11
|
+
Instead:
|
|
12
|
+
|
|
13
|
+
1. Prefer **GitHub private vulnerability reporting** on [`archradhq/arch-deterministic`](https://github.com/archradhq/arch-deterministic/security) if enabled; otherwise email the **repository maintainers** with:
|
|
14
|
+
- A short description and impact
|
|
15
|
+
- Steps to reproduce (if safe to share)
|
|
16
|
+
- Affected versions / environments (Node, OS) if known
|
|
17
|
+
|
|
18
|
+
2. We aim to acknowledge within **a few business days** and coordinate disclosure and a fix release.
|
|
19
|
+
|
|
20
|
+
## Scope
|
|
21
|
+
|
|
22
|
+
This package is a **local** CLI and library: **IR JSON → project files**. It does not ship a network service by default. Reports about **generated application code** (e.g. a user’s Docker image) are usually out of scope unless the issue is in **this package’s** templates or logic.
|
|
23
|
+
|
|
24
|
+
## Dependency advisories
|
|
25
|
+
|
|
26
|
+
Run `npm audit` in your project after installing. We use **Dependabot** on the GitHub repo for dependency update PRs.
|
package/biome.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
|
3
|
+
"vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true },
|
|
4
|
+
"files": {
|
|
5
|
+
"ignore": ["dist", "node_modules", "*.json", "fixtures", "schemas"]
|
|
6
|
+
},
|
|
7
|
+
"formatter": {
|
|
8
|
+
"enabled": false
|
|
9
|
+
},
|
|
10
|
+
"organizeImports": { "enabled": false },
|
|
11
|
+
"linter": {
|
|
12
|
+
"enabled": true,
|
|
13
|
+
"rules": {
|
|
14
|
+
"recommended": false,
|
|
15
|
+
"suspicious": {
|
|
16
|
+
"noDebugger": "error"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"javascript": {
|
|
21
|
+
"formatter": {
|
|
22
|
+
"quoteStyle": "single"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human-oriented CLI formatting for IR findings (structural + architecture lint).
|
|
3
|
+
*/
|
|
4
|
+
import type { IrStructuralFinding } from './ir-structural.js';
|
|
5
|
+
/** Pretty multi-line output for terminal / CI logs */
|
|
6
|
+
export declare function formatFindingLines(f: IrStructuralFinding): string[];
|
|
7
|
+
/**
|
|
8
|
+
* Pretty-print findings. By default groups **IR structural (IR-STRUCT-*)** and **architecture lint (IR-LINT-*)** so both are obvious in CI logs.
|
|
9
|
+
*/
|
|
10
|
+
export declare function printFindingsPretty(findings: IrStructuralFinding[], header?: string, options?: {
|
|
11
|
+
groupByLayer?: boolean;
|
|
12
|
+
}): void;
|
|
13
|
+
export type ValidationExitPolicy = {
|
|
14
|
+
/** When true, any warning fails the run (CI gate). */
|
|
15
|
+
failOnWarning?: boolean;
|
|
16
|
+
/** Fail when warning count is strictly greater than this (undefined = no limit) */
|
|
17
|
+
maxWarnings?: number;
|
|
18
|
+
};
|
|
19
|
+
export declare function countBySeverity(findings: IrStructuralFinding[], sev: IrStructuralFinding['severity']): number;
|
|
20
|
+
/** true = exit with failure */
|
|
21
|
+
export declare function shouldFailFromFindings(findings: IrStructuralFinding[], policy: ValidationExitPolicy): boolean;
|
|
22
|
+
export declare function sortFindings(findings: IrStructuralFinding[]): IrStructuralFinding[];
|
|
23
|
+
//# sourceMappingURL=cli-findings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-findings.d.ts","sourceRoot":"","sources":["../src/cli-findings.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAc9D,sDAAsD;AACtD,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,mBAAmB,GAAG,MAAM,EAAE,CAYnE;AAWD;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,mBAAmB,EAAE,EAC/B,MAAM,CAAC,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAE,GACnC,IAAI,CAuBN;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,sDAAsD;IACtD,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,mFAAmF;IACnF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,wBAAgB,eAAe,CAAC,QAAQ,EAAE,mBAAmB,EAAE,EAAE,GAAG,EAAE,mBAAmB,CAAC,UAAU,CAAC,GAAG,MAAM,CAE7G;AAED,+BAA+B;AAC/B,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,mBAAmB,EAAE,EAAE,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAM7G;AAID,wBAAgB,YAAY,CAAC,QAAQ,EAAE,mBAAmB,EAAE,GAAG,mBAAmB,EAAE,CAKnF"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human-oriented CLI formatting for IR findings (structural + architecture lint).
|
|
3
|
+
*/
|
|
4
|
+
const ICON = {
|
|
5
|
+
error: '❌',
|
|
6
|
+
warning: '⚠️',
|
|
7
|
+
info: 'ℹ️',
|
|
8
|
+
};
|
|
9
|
+
function ttyColor() {
|
|
10
|
+
const noColor = process.env.NO_COLOR != null && process.env.NO_COLOR !== '';
|
|
11
|
+
if (noColor || !process.stderr.isTTY)
|
|
12
|
+
return { red: '', reset: '' };
|
|
13
|
+
return { red: '\u001b[31m', reset: '\u001b[0m' };
|
|
14
|
+
}
|
|
15
|
+
/** Pretty multi-line output for terminal / CI logs */
|
|
16
|
+
export function formatFindingLines(f) {
|
|
17
|
+
const icon = ICON[f.severity] ?? '•';
|
|
18
|
+
const { red, reset } = ttyColor();
|
|
19
|
+
const head = f.code.startsWith('IR-LINT-') && red
|
|
20
|
+
? `${red}${icon} ${f.code}: ${f.message}${reset}`
|
|
21
|
+
: `${icon} ${f.code}: ${f.message}`;
|
|
22
|
+
const lines = [head];
|
|
23
|
+
if (f.fixHint)
|
|
24
|
+
lines.push(` Fix: ${f.fixHint}`);
|
|
25
|
+
if (f.suggestion)
|
|
26
|
+
lines.push(` Suggestion: ${f.suggestion}`);
|
|
27
|
+
if (f.impact)
|
|
28
|
+
lines.push(` Impact: ${f.impact}`);
|
|
29
|
+
return lines;
|
|
30
|
+
}
|
|
31
|
+
function printFindingBlock(findings) {
|
|
32
|
+
for (const f of findings) {
|
|
33
|
+
for (const line of formatFindingLines(f)) {
|
|
34
|
+
console.error(line);
|
|
35
|
+
}
|
|
36
|
+
console.error('');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Pretty-print findings. By default groups **IR structural (IR-STRUCT-*)** and **architecture lint (IR-LINT-*)** so both are obvious in CI logs.
|
|
41
|
+
*/
|
|
42
|
+
export function printFindingsPretty(findings, header, options) {
|
|
43
|
+
if (!findings.length)
|
|
44
|
+
return;
|
|
45
|
+
if (header)
|
|
46
|
+
console.error(header);
|
|
47
|
+
const group = options?.groupByLayer !== false;
|
|
48
|
+
if (!group) {
|
|
49
|
+
printFindingBlock(findings);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const structural = findings.filter((f) => f.code.startsWith('IR-STRUCT-'));
|
|
53
|
+
const lint = findings.filter((f) => f.code.startsWith('IR-LINT-'));
|
|
54
|
+
const other = findings.filter((f) => !f.code.startsWith('IR-STRUCT-') && !f.code.startsWith('IR-LINT-'));
|
|
55
|
+
if (structural.length) {
|
|
56
|
+
console.error('IR structural (IR-STRUCT-*):');
|
|
57
|
+
printFindingBlock(structural);
|
|
58
|
+
}
|
|
59
|
+
if (lint.length) {
|
|
60
|
+
console.error('Architecture lint (IR-LINT-*):');
|
|
61
|
+
printFindingBlock(lint);
|
|
62
|
+
}
|
|
63
|
+
if (other.length) {
|
|
64
|
+
console.error('Other findings:');
|
|
65
|
+
printFindingBlock(other);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export function countBySeverity(findings, sev) {
|
|
69
|
+
return findings.filter((f) => f.severity === sev).length;
|
|
70
|
+
}
|
|
71
|
+
/** true = exit with failure */
|
|
72
|
+
export function shouldFailFromFindings(findings, policy) {
|
|
73
|
+
if (findings.some((f) => f.severity === 'error'))
|
|
74
|
+
return true;
|
|
75
|
+
const w = countBySeverity(findings, 'warning');
|
|
76
|
+
if (Boolean(policy.failOnWarning) && w > 0)
|
|
77
|
+
return true;
|
|
78
|
+
if (policy.maxWarnings != null && w > policy.maxWarnings)
|
|
79
|
+
return true;
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
const SEV_ORDER = { error: 0, warning: 1, info: 2 };
|
|
83
|
+
export function sortFindings(findings) {
|
|
84
|
+
return [...findings].sort((a, b) => {
|
|
85
|
+
const d = (SEV_ORDER[a.severity] ?? 9) - (SEV_ORDER[b.severity] ?? 9);
|
|
86
|
+
return d !== 0 ? d : a.code.localeCompare(b.code);
|
|
87
|
+
});
|
|
88
|
+
}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;GAGG"}
|