@archrad/deterministic 0.1.4 → 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.
@@ -1,8 +1,10 @@
1
1
  // Deterministic Python FastAPI exporter
2
2
  // Produces a map of filename -> content given an IR (plan graph) and options.
3
3
  import { getEdgeConfig, generateRetryCode, generateCircuitBreakerCode } from './edgeConfigCodeGenerator.js';
4
+ import { MAX_UNTRUSTED_STRING_LEN, stripLeadingTrailingHyphens } from './stringEdgeStrip.js';
4
5
  function safeId(id) {
5
- return String(id || '').replace(/[^A-Za-z0-9_\-]/g, '-').replace(/^-+|-+$/g, '').toLowerCase() || 'node';
6
+ const raw = String(id || '').slice(0, MAX_UNTRUSTED_STRING_LEN);
7
+ return stripLeadingTrailingHyphens(raw.replace(/[^A-Za-z0-9_\-]/g, '-')).toLowerCase() || 'node';
6
8
  }
7
9
  function handlerNameFor(n) {
8
10
  if (n && n.config && n.config.name)
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Static, deterministic remediation text for built-in finding codes (IR-STRUCT-*, IR-LINT-*, DRIFT-*).
3
+ * Used by MCP `archrad_suggest_fix` — not generated architecture; same hints the engine documents in findings.
4
+ */
5
+ export type StaticRuleGuidance = {
6
+ findingCode: string;
7
+ title: string;
8
+ remediation: string;
9
+ /** Canonical docs path (no analytics/query params). */
10
+ docsUrl: string;
11
+ };
12
+ /** Public OSS repo (subtree); `docs/` is at repo root in arch-deterministic. */
13
+ export declare const RULE_CODES_DOC_BASE = "https://github.com/archradhq/arch-deterministic/blob/main/docs/RULE_CODES.md";
14
+ /** GitHub heading anchor (must match markdown `## CODE` in docs/RULE_CODES.md). */
15
+ export declare function githubRuleCodeAnchor(code: string): string;
16
+ export declare function docsUrlForFindingCode(code: string): string;
17
+ export declare function listStaticRuleCodes(): string[];
18
+ export declare function getStaticRuleGuidance(findingCode: string): StaticRuleGuidance | null;
19
+ //# sourceMappingURL=static-rule-guidance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"static-rule-guidance.d.ts","sourceRoot":"","sources":["../src/static-rule-guidance.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,uDAAuD;IACvD,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,gFAAgF;AAChF,eAAO,MAAM,mBAAmB,iFACgD,CAAC;AAEjF,mFAAmF;AACnF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKzD;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE1D;AA2KD,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CAE9C;AAED,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CASpF"}
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Static, deterministic remediation text for built-in finding codes (IR-STRUCT-*, IR-LINT-*, DRIFT-*).
3
+ * Used by MCP `archrad_suggest_fix` — not generated architecture; same hints the engine documents in findings.
4
+ */
5
+ /** Public OSS repo (subtree); `docs/` is at repo root in arch-deterministic. */
6
+ export const RULE_CODES_DOC_BASE = 'https://github.com/archradhq/arch-deterministic/blob/main/docs/RULE_CODES.md';
7
+ /** GitHub heading anchor (must match markdown `## CODE` in docs/RULE_CODES.md). */
8
+ export function githubRuleCodeAnchor(code) {
9
+ return code
10
+ .toLowerCase()
11
+ .replace(/[^a-z0-9]+/g, '-')
12
+ .replace(/^-|-$/g, '');
13
+ }
14
+ export function docsUrlForFindingCode(code) {
15
+ return `${RULE_CODES_DOC_BASE}#${githubRuleCodeAnchor(code)}`;
16
+ }
17
+ /** Built-in codes with curated guidance. Org PolicyPack codes (e.g. ORG-*) are not listed here. */
18
+ const GUIDANCE = {
19
+ 'IR-LINT-DIRECT-DB-ACCESS-002': {
20
+ title: 'HTTP-like node connects directly to a datastore',
21
+ remediation: 'Introduce a service or domain layer between HTTP handlers and persistence: add intermediate nodes and edges so the API does not couple directly to a single DB node. This preserves testability, storage swaps, and invariant enforcement at a clear boundary.',
22
+ },
23
+ 'IR-LINT-HIGH-FANOUT-004': {
24
+ title: 'High outgoing dependency count',
25
+ remediation: 'Reduce fan-out: split responsibilities, add a facade, batch calls, or use async handoff (queues) so one node does not synchronously depend on many downstreams. High fan-out increases blast radius and latency under load.',
26
+ },
27
+ 'IR-LINT-SYNC-CHAIN-001': {
28
+ title: 'Long synchronous chain from HTTP entry',
29
+ remediation: 'Shorten the synchronous call graph or mark non-blocking hops as async: set `metadata.protocol` / edge metadata for async boundaries, or `config.async` where applicable, so depth reflects real execution. Deep sync chains amplify latency and failures.',
30
+ },
31
+ 'IR-LINT-NO-HEALTHCHECK-003': {
32
+ title: 'No typical health/readiness route on HTTP nodes',
33
+ remediation: 'Add at least one GET route such as `/health` or `/ready` on an HTTP node (or document a dedicated health node). Orchestrators and load balancers rely on these for safe deploys and rollbacks.',
34
+ },
35
+ 'IR-LINT-ISOLATED-NODE-005': {
36
+ title: 'Node has no incident edges',
37
+ remediation: 'Remove the orphan or connect it with edges so it participates in the architecture. Isolated nodes usually mean stale IR or a missing integration.',
38
+ },
39
+ 'IR-LINT-DUPLICATE-EDGE-006': {
40
+ title: 'Duplicate from→to edge',
41
+ remediation: 'Collapse duplicate edges or distinguish them with metadata if your model allows. Parallel duplicates clutter views and can double-count in generators.',
42
+ },
43
+ 'IR-LINT-HTTP-MISSING-NAME-007': {
44
+ title: 'HTTP-like node missing display name',
45
+ remediation: 'Set a short human-readable `name` on the node for docs, OpenAPI titles, and graph labels.',
46
+ },
47
+ 'IR-LINT-DATASTORE-NO-INCOMING-008': {
48
+ title: 'Datastore has no incoming edges',
49
+ remediation: 'Connect a service or data path to this datastore, or remove it if unused. Orphan persistence nodes misrepresent how data is written.',
50
+ },
51
+ 'IR-LINT-MULTIPLE-HTTP-ENTRIES-009': {
52
+ title: 'Multiple HTTP entry nodes without incoming edges',
53
+ remediation: 'Prefer a single API gateway or BFF unless multiple public surfaces are intentional and documented. Multiple entries duplicate auth, rate limits, and observability concerns.',
54
+ },
55
+ 'IR-LINT-MISSING-AUTH-010': {
56
+ title: 'HTTP entry missing auth coverage',
57
+ remediation: 'Add an auth boundary: connect an auth, oauth, jwt, or middleware node with an edge to or from this entry, or set `config.authRequired: false` for intentionally public endpoints (health, assets). Regulated environments expect a documented auth path for every public HTTP entry.',
58
+ },
59
+ 'IR-LINT-DEAD-NODE-011': {
60
+ title: 'Non-sink node with incoming edges but no outgoing edges',
61
+ remediation: 'Add an outgoing edge to a downstream consumer, or remove the node if it is obsolete. Dead-end non-sinks often indicate incomplete migrations or IR mistakes.',
62
+ },
63
+ 'IR-STRUCT-INVALID_ROOT': {
64
+ title: 'IR root is not a JSON object',
65
+ remediation: 'Pass a single JSON object: either `{ "graph": { "nodes": [], "edges": [] } }` or a graph object with a top-level `nodes` array.',
66
+ },
67
+ 'IR-STRUCT-NO_GRAPH': {
68
+ title: 'Missing graph shape',
69
+ remediation: 'Include `.graph` with `nodes` (and optional `edges`) or a top-level `nodes` array so the document describes a graph.',
70
+ },
71
+ 'IR-STRUCT-NODES_NOT_ARRAY': {
72
+ title: '`nodes` is not an array',
73
+ remediation: 'Set `nodes` to an array of node objects, each with a string `id` and a type/kind.',
74
+ },
75
+ 'IR-STRUCT-EDGES_NOT_ARRAY': {
76
+ title: '`edges` is present but not an array',
77
+ remediation: 'Set `edges` to an array of edge objects (or omit `edges` if there are no edges). Malformed `edges` is treated as empty with a warning.',
78
+ },
79
+ 'IR-STRUCT-EMPTY_GRAPH': {
80
+ title: 'Graph has no nodes',
81
+ remediation: 'Add at least one node before validation or export. An empty graph cannot generate a service.',
82
+ },
83
+ 'IR-STRUCT-NODE_INVALID': {
84
+ title: 'Node entry is not an object',
85
+ remediation: 'Each element of `nodes` must be a JSON object with at least `id` and type information.',
86
+ },
87
+ 'IR-STRUCT-NODE_NO_ID': {
88
+ title: 'Node missing non-empty id',
89
+ remediation: 'Assign a stable string `id` to every node. Ids are used for edges and code generation.',
90
+ },
91
+ 'IR-STRUCT-DUP_NODE_ID': {
92
+ title: 'Duplicate node id',
93
+ remediation: 'Ensure node ids are unique. Edges cannot reference duplicate ids unambiguously.',
94
+ },
95
+ 'IR-STRUCT-NODE_INVALID_CONFIG': {
96
+ title: 'Node `config` is not a plain object',
97
+ remediation: 'Use a plain object for `config` (e.g. `{ "url": "/api", "method": "GET" }`). Arrays and null are not valid.',
98
+ },
99
+ 'IR-STRUCT-HTTP_PATH': {
100
+ title: 'HTTP endpoint path invalid',
101
+ remediation: 'Set `config.url` or `config.route` to a non-empty path starting with `/`, e.g. `/users`.',
102
+ },
103
+ 'IR-STRUCT-HTTP_METHOD': {
104
+ title: 'HTTP method not supported',
105
+ remediation: 'Use GET, POST, PUT, PATCH, DELETE, HEAD, or OPTIONS in `config.method` (default may be applied as POST).',
106
+ },
107
+ 'IR-STRUCT-EDGE_INVALID': {
108
+ title: 'Edge is not an object',
109
+ remediation: 'Each edge must be an object with `from`/`to` (or `source`/`target`) referencing node ids.',
110
+ },
111
+ 'IR-STRUCT-EDGE_NO_ENDPOINTS': {
112
+ title: 'Edge missing endpoints',
113
+ remediation: 'Set both ends of the edge to existing node ids using `from` and `to` (or legacy `source`/`target`).',
114
+ },
115
+ 'IR-STRUCT-EDGE_AMBIGUOUS_FROM': {
116
+ title: 'Edge references duplicate source id',
117
+ remediation: 'Resolve duplicate node ids first; edges cannot point to an ambiguous source.',
118
+ },
119
+ 'IR-STRUCT-EDGE_UNKNOWN_FROM': {
120
+ title: 'Edge references unknown source node',
121
+ remediation: 'Add a node with the referenced id or correct the `from` endpoint.',
122
+ },
123
+ 'IR-STRUCT-EDGE_AMBIGUOUS_TO': {
124
+ title: 'Edge references duplicate target id',
125
+ remediation: 'Resolve duplicate node ids first; edges cannot point to an ambiguous target.',
126
+ },
127
+ 'IR-STRUCT-EDGE_UNKNOWN_TO': {
128
+ title: 'Edge references unknown target node',
129
+ remediation: 'Add a node with the referenced id or correct the `to` endpoint.',
130
+ },
131
+ 'IR-STRUCT-CYCLE': {
132
+ title: 'Directed cycle in the graph',
133
+ remediation: 'Remove or break cyclic edges unless your deployment explicitly allows synchronous loops. Cycles block layering and complicate codegen assumptions.',
134
+ },
135
+ 'DRIFT-MISSING': {
136
+ title: 'Exported file missing on disk',
137
+ remediation: 'Regenerate the export (`archrad export`) or restore the missing file so the tree matches the deterministic output for this IR.',
138
+ },
139
+ 'DRIFT-MODIFIED': {
140
+ title: 'File differs from deterministic export',
141
+ remediation: 'Revert manual edits to generated files or update the IR and re-export so the on-disk tree matches the compiler output.',
142
+ },
143
+ 'DRIFT-EXTRA': {
144
+ title: 'Extra file not in deterministic export',
145
+ remediation: 'Remove stray files from the export directory or add them to the model if they should be generated. Use `--strict-extra` semantics as documented for your CI gate.',
146
+ },
147
+ 'DRIFT-NO-EXPORT': {
148
+ title: 'No export produced for drift comparison',
149
+ remediation: 'Fix IR structural/lint errors blocking export, or verify `--target` and IR content so the exporter emits files.',
150
+ },
151
+ };
152
+ export function listStaticRuleCodes() {
153
+ return Object.keys(GUIDANCE).sort();
154
+ }
155
+ export function getStaticRuleGuidance(findingCode) {
156
+ const g = GUIDANCE[findingCode];
157
+ if (!g)
158
+ return null;
159
+ return {
160
+ findingCode,
161
+ title: g.title,
162
+ remediation: g.remediation,
163
+ docsUrl: docsUrlForFindingCode(findingCode),
164
+ };
165
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Linear-time trimming of repeated edge characters (avoids polynomial ReDoS on
3
+ * patterns like `/^-+|-+$/` when applied to uncontrolled strings).
4
+ */
5
+ export declare const MAX_UNTRUSTED_STRING_LEN = 8192;
6
+ export declare function stripLeadingTrailingHyphens(s: string, maxLen?: number): string;
7
+ export declare function stripLeadingTrailingUnderscores(s: string, maxLen?: number): string;
8
+ //# sourceMappingURL=stringEdgeStrip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stringEdgeStrip.d.ts","sourceRoot":"","sources":["../src/stringEdgeStrip.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAE7C,wBAAgB,2BAA2B,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,SAA2B,GAAG,MAAM,CAOhG;AAED,wBAAgB,+BAA+B,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,SAA2B,GAAG,MAAM,CAOpG"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Linear-time trimming of repeated edge characters (avoids polynomial ReDoS on
3
+ * patterns like `/^-+|-+$/` when applied to uncontrolled strings).
4
+ */
5
+ export const MAX_UNTRUSTED_STRING_LEN = 8192;
6
+ export function stripLeadingTrailingHyphens(s, maxLen = MAX_UNTRUSTED_STRING_LEN) {
7
+ const t = s.length <= maxLen ? s : s.slice(0, maxLen);
8
+ let i = 0;
9
+ let j = t.length;
10
+ while (i < j && t[i] === '-')
11
+ i++;
12
+ while (j > i && t[j - 1] === '-')
13
+ j--;
14
+ return t.slice(i, j);
15
+ }
16
+ export function stripLeadingTrailingUnderscores(s, maxLen = MAX_UNTRUSTED_STRING_LEN) {
17
+ const t = s.length <= maxLen ? s : s.slice(0, maxLen);
18
+ let i = 0;
19
+ let j = t.length;
20
+ while (i < j && t[i] === '_')
21
+ i++;
22
+ while (j > i && t[j - 1] === '_')
23
+ j--;
24
+ return t.slice(i, j);
25
+ }
package/docs/DRIFT.md ADDED
@@ -0,0 +1,52 @@
1
+ # Deterministic drift (`validate-drift`)
2
+
3
+ **Canonical OSS description** — versioned with **`@archrad/deterministic`**.
4
+ **Not** semantic “does this code match business intent?” — that class of analysis is out of scope for this layer (see **`STRUCTURAL_VS_SEMANTIC_VALIDATION.md`**).
5
+
6
+ ## What it is
7
+
8
+ **Drift** here means: you already have an **on-disk tree** (usually from `archrad export`), and you want to know whether it still matches what the **deterministic exporter would produce today** from the **same IR**.
9
+
10
+ The engine:
11
+
12
+ 1. Runs a **fresh export** from your IR in memory (same pipeline as `archrad export`).
13
+ 2. Compares the **expected file set + contents** to what is on disk (paths normalized, line endings normalized to `\n`).
14
+ 3. Emits **DRIFT-*** findings when something is missing, changed, or (optionally) extra.
15
+
16
+ So: **regen vs reality** — a thin gate for CI and pre-merge checks.
17
+
18
+ ## What it is not
19
+
20
+ - **Not** diffing IR to arbitrary hand-written code semantics.
21
+ - **Not** proving runtime behavior or security — use tests, review, and (where applicable) **ArchRad Cloud** governance.
22
+
23
+ ## CLI
24
+
25
+ ```bash
26
+ archrad validate-drift -i ./graph.json -t python -o ./out
27
+ ```
28
+
29
+ Use **`--json`** in CI. **`--strict-extra`** treats unexpected files in the output directory as findings. **`--skip-ir-lint`** / **`--policies`** follow the same semantics as **`export`** (see **`README.md`**).
30
+
31
+ ## Library
32
+
33
+ **`runValidateDrift`**, **`diffExpectedExportAgainstFiles`**, **`readDirectoryAsExportMap`** — see **`src/validate-drift.ts`**.
34
+
35
+ ## MCP
36
+
37
+ **`archrad_validate_drift`** — same semantics: IR (inline or **`irPath`**) + **`target`** + **`exportDir`**. See **`MCP.md`**.
38
+
39
+ ## DRIFT-* codes
40
+
41
+ | Code | Meaning |
42
+ |------|--------|
43
+ | **DRIFT-MISSING** | A file the exporter would emit is missing on disk. |
44
+ | **DRIFT-MODIFIED** | File exists but content differs from the deterministic export. |
45
+ | **DRIFT-EXTRA** | File exists on disk but is not in the reference export (with **`--strict-extra`**). |
46
+ | **DRIFT-NO-EXPORT** | Exporter produced no file map (often blocked by structural/lint errors or empty target output). |
47
+
48
+ Remediation text for each code (aligned with MCP **`archrad_suggest_fix`**) lives in **`RULE_CODES.md`** and **`src/static-rule-guidance.ts`**.
49
+
50
+ ## Product site
51
+
52
+ **archrad.com** may host narrative pages (e.g. `/docs/drift`) for onboarding; **definitions and CLI/MCP behavior** are maintained **here** in the OSS repo so they ship with the engine.
package/docs/MCP.md ADDED
@@ -0,0 +1,153 @@
1
+ # ArchRad MCP server — specification
2
+
3
+ **Status:** **0.1.x** — `archrad-mcp` ships in **`@archrad/deterministic`** (stdio MCP, same engine as CLI).
4
+ **Audience:** Engineering + launch narrative (Show HN / registry listing).
5
+
6
+ ## 1. Problem statement
7
+
8
+ | Mode | Behavior |
9
+ |------|------------|
10
+ | **Reactive** | CI runs `archrad` after code exists → catches drift late. |
11
+ | **Proactive** | An MCP server answers **before** edits land: “Is this edge allowed?”, “What does IR-LINT say about this sketch?” |
12
+
13
+ Agents optimize for *working code*; ArchRad supplies **deterministic constraints** so the loop can ask the engine *before* violating architecture.
14
+
15
+ ## 2. OSS vs product boundary
16
+
17
+ Aligned with **`STRUCTURAL_VS_SEMANTIC_VALIDATION.md`** (this folder) and your org’s OSS/product split docs.
18
+
19
+ | Surface | Ships in **OSS** (`@archrad/deterministic`, binary **`archrad-mcp`**) | Stays in **product / Cloud** |
20
+ |---------|------------------------------------------------------------------------|------------------------------|
21
+ | IR structural validation (`IR-STRUCT-*`) | Yes | — |
22
+ | Architecture lint (`IR-LINT-*`) + PolicyPack YAML from **local dir** | Yes | — |
23
+ | `validate_drift` vs on-disk export (local paths) | Yes | — |
24
+ | Static remediation text per **built-in** code (`archrad_suggest_fix`) | Yes | — |
25
+ | Org **`settings.archPolicyPacks`**, Firestore, membership | — | Yes |
26
+ | Semantic “is this business-correct?” reasoning | — | Yes (future assisted tools) |
27
+
28
+ **Rule of thumb:** If the tool only needs **IR JSON + local files**, it can live in the public MCP server. If it needs **tenant identity, billing, or org policy from Cloud**, expose a **separate** “ArchRad Cloud” MCP connector (private or authenticated) — do not move Cloud policy enforcement into OSS without an explicit product decision.
29
+
30
+ ## 3. Transport and packaging
31
+
32
+ - **Protocol:** [Model Context Protocol](https://modelcontextprotocol.io/) over **stdio** (default for Cursor, Claude Desktop, Copilot agent adapters).
33
+ - **Package:** **`@archrad/deterministic`** publishes two binaries: **`archrad`** (CLI) and **`archrad-mcp`** (MCP). One implementation backs CLI + MCP.
34
+ - **Registry:** Optional `server.json` / manifest for MCP Registry; document install for Cursor “Add MCP server”.
35
+
36
+ ## 4. IR payload size and `ir` vs `irPath`
37
+
38
+ | Concern | Guidance |
39
+ |---------|----------|
40
+ | **Inline `ir`** | Fine for small/medium graphs. Large JSON in a single tool call still stresses **host message limits** and **model context** — prefer **`irPath`** when the IR is big. |
41
+ | **`irPath`** | Absolute or relative path to a **single JSON file** (same shape as CLI `--ir`). Enforced **max file size 25 MiB** in the server (hard cap); if you exceed it, split validation or trim fixtures. |
42
+ | **Soft ceiling** | Below ~**5k–10k nodes**, inline JSON is usually workable if the host allows; above that, **`irPath` is recommended**. This is not a graph semantics limit — only practical transport/memory. |
43
+ | **Exactly one** | Provide **`ir`** **or** **`irPath`**, not both, not neither (for tools that need IR). |
44
+
45
+ **Privacy:** The OSS server does **not** add analytics or tracking parameters to tool responses. Optional product docs URLs use a stable path only (see **`archrad_suggest_fix`**).
46
+
47
+ ## 5. Local testing
48
+
49
+ ### 5.1 Smoke script (npm)
50
+
51
+ From the **`@archrad/deterministic`** package root (where **`package.json`** lives):
52
+
53
+ ```bash
54
+ npm run build
55
+ npm run smoke:mcp
56
+ ```
57
+
58
+ Exit code **0** means the MCP server spawned **`dist/mcp-server.js`**, listed tools, and successfully called **`archrad_suggest_fix`**.
59
+
60
+ ### 5.2 MCP Inspector (browser UI)
61
+
62
+ ```bash
63
+ npx @modelcontextprotocol/inspector node dist/mcp-server.js
64
+ ```
65
+
66
+ Open the URL Inspector prints (often **http://localhost:6274**). Under **Tools**, run **`archrad_list_rule_codes`** (no args), then **`archrad_validate_ir`** with **`irPath`** set to **`fixtures/minimal-graph.json`** (relative paths work if the process was started with **cwd** set to the package root).
67
+
68
+ ### 5.3 Cursor (MCP config + chat)
69
+
70
+ 1. **Build** so **`dist/mcp-server.js`** exists (`npm run build`).
71
+ 2. In Cursor **Settings → MCP**, add a server (exact JSON shape depends on Cursor version):
72
+ - **Command:** `node` (or full path to `node.exe` on Windows).
73
+ - **Args:** full path to **`dist/mcp-server.js`**.
74
+ - **Cwd (recommended):** the deterministic package root (directory containing **`package.json`**). Relative **`irPath`** values like **`fixtures/minimal-graph.json`** resolve from this directory.
75
+ 3. **Chat:** Cursor does not always show a “run tool” button for every server. Use **Agent** mode (or another mode that supports **tool use**) and ask explicitly, for example:
76
+
77
+ **List codes:**
78
+
79
+ > Use the MCP tool **`archrad_list_rule_codes`** (no arguments) and show me the raw JSON result.
80
+
81
+ **Validate via file:**
82
+
83
+ > Use the MCP tool **`archrad_validate_ir`** with **`irPath`** set to **`fixtures/minimal-graph.json`** (relative to the deterministic package). Show the full tool result.
84
+
85
+ If the model says it cannot find the tool, the MCP server failed to start or the configured server name does not match — check Cursor’s MCP panel for errors.
86
+
87
+ 4. **If `irPath` fails** with “file not found”, use the **absolute path** to **`fixtures/minimal-graph.json`**.
88
+
89
+ ### 5.4 Success criteria
90
+
91
+ - **`archrad_list_rule_codes`:** JSON with a **`codes`** array.
92
+ - **`archrad_validate_ir`:** JSON with **`irStructuralFindings`**, **`irLintFindings`**, **`combined`**, **`ok`** — not a connection or file error.
93
+
94
+ ## 6. Tools (0.1.5)
95
+
96
+ Tools are **idempotent** and **deterministic** where stated.
97
+
98
+ ### 6.1 Core
99
+
100
+ | Tool | Input | Output | Notes |
101
+ |------|--------|--------|-------|
102
+ | **`archrad_validate_ir`** | `ir` **or** `irPath`; optional `policiesDirectory` | `{ ok, irStructuralFindings, irLintFindings, combined }` | Same as CLI validate. |
103
+ | **`archrad_lint_summary`** | `ir` **or** `irPath`; optional `policiesDirectory` | Short summary + counts | Agent-friendly. |
104
+ | **`archrad_validate_drift`** | `ir` **or** `irPath`; `target`; `exportDir`; optional policies, `skipIrLint` | Drift + export findings | Same as CLI `validate-drift`. |
105
+ | **`archrad_policy_packs_load`** | `directory` or `files[]` | `{ ok, ruleCount }` or errors | Compiles packs; does not return visitor functions over MCP. |
106
+
107
+ ### 6.2 Static guidance (no generated architecture)
108
+
109
+ | Tool | Input | Output | Notes |
110
+ |------|--------|--------|-------|
111
+ | **`archrad_suggest_fix`** | `findingCode` (e.g. `IR-LINT-MISSING-AUTH-010`) | `{ ok, findingCode, title, remediation, docsUrl }` or `{ ok: false, error }` | **Curated text only** — not JSON Patch, not IR edits, not LLM output. **`docsUrl`** points to the **[`RULE_CODES.md`](https://github.com/archradhq/arch-deterministic/blob/main/docs/RULE_CODES.md)** section for that code on **GitHub** (canonical OSS; no query strings). Unknown built-in codes and **PolicyPack/org** ids return `ok: false` with a short explanation. |
112
+ | **`archrad_list_rule_codes`** | _(none)_ | `{ codes: string[] }` | Sorted list of built-in codes with static guidance. |
113
+
114
+ **Explicit non-goal:** **`archrad_suggest_fix` must not** return machine-generated graph edits or “patches” that invent services — that would be **generative** and would break the deterministic OSS contract.
115
+
116
+ ### 6.3 Explicit non-goals (MVP)
117
+
118
+ - No automatic **code** generation inside MCP (keep **`export`** as CLI/CI).
119
+ - No remote calls to ArchRad Cloud unless a **separate authenticated** server is defined.
120
+ - No **tracking** query parameters in MCP tool payloads.
121
+
122
+ ## 7. Resources (optional)
123
+
124
+ | Resource URI | Content |
125
+ |--------------|---------|
126
+ | `archrad://docs/ir-contract` | Pointer to bundled `IR_CONTRACT.md` snippet or link. |
127
+ | `archrad://schemas/ir-graph-v1` | JSON Schema for IR graph validation. |
128
+
129
+ ## 8. Security
130
+
131
+ - **Local-only by default:** IR and paths stay on the user machine; **no telemetry** in the OSS server unless explicitly documented elsewhere.
132
+ - **Path sandbox:** Validate `exportDir` / `irPath` against workspace roots if the host passes a `workspaceRoot` (host-dependent).
133
+ - **Cloud MCP (future):** OAuth or API keys; never embed product secrets in OSS.
134
+
135
+ ## 9. Launch narrative (copy bank)
136
+
137
+ - **One-liner:** *AI agents write code fast but drift from your architecture; ArchRad MCP gives them the same deterministic IR checks as CI, inside the agent loop.*
138
+ - **Show HN angle:** *Architectural conscience for Copilot / Cursor — IR-LINT before you merge.*
139
+
140
+ ## 10. Related repo tasks
141
+
142
+ - **Quality:** Keep graph/lint work responsive so MCP tools stay usable on mid-size IRs.
143
+
144
+ ## 11. Implementation checklist
145
+
146
+ 1. ~~Node MCP server (`@modelcontextprotocol/sdk`), stdio transport.~~
147
+ 2. ~~`archrad_validate_ir` + `archrad_validate_drift` + policy load + static `archrad_suggest_fix`.~~
148
+ 3. README + **`docs/MCP.md`**: Cursor config, `ir` / `irPath`, local testing (smoke, Inspector, chat prompts).
149
+ 4. Publish **`@archrad/deterministic`** to npm; subtree to public repo per release process.
150
+
151
+ ---
152
+
153
+ *This document aligns OSS MCP scope with product strategy; update when adding Cloud-only tools.*
@@ -0,0 +1,208 @@
1
+ # Built-in finding codes (IR-STRUCT, IR-LINT, DRIFT)
2
+
3
+ **Canonical OSS reference** — ships in **`@archrad/deterministic`**.
4
+ **MCP** **`archrad_suggest_fix`** returns **`title`**, **`remediation`**, and a **`docsUrl`** pointing at the matching section below. **PolicyPack / org** rules use custom ids (e.g. `ORG-*`) — not listed here.
5
+
6
+ **Product / marketing** copy may live on **archrad.com**; **deterministic semantics** are defined in this repo.
7
+
8
+ ---
9
+
10
+ ## DRIFT-EXTRA
11
+
12
+ Extra file not in deterministic export. **Remediation:** Remove stray files from the export directory or add them to the model if they should be generated. Use **`--strict-extra`** semantics as documented for your CI gate.
13
+
14
+ ---
15
+
16
+ ## DRIFT-MISSING
17
+
18
+ Exported file missing on disk. **Remediation:** Regenerate the export (**`archrad export`**) or restore the missing file so the tree matches the deterministic output for this IR.
19
+
20
+ ---
21
+
22
+ ## DRIFT-MODIFIED
23
+
24
+ File differs from deterministic export. **Remediation:** Revert manual edits to generated files or update the IR and re-export so the on-disk tree matches the compiler output.
25
+
26
+ ---
27
+
28
+ ## DRIFT-NO-EXPORT
29
+
30
+ No export produced for drift comparison. **Remediation:** Fix IR structural/lint errors blocking export, or verify **`--target`** and IR content so the exporter emits files.
31
+
32
+ ---
33
+
34
+ ## IR-LINT-DATASTORE-NO-INCOMING-008
35
+
36
+ Datastore has no incoming edges. **Remediation:** Connect a service or data path to this datastore, or remove it if unused.
37
+
38
+ ---
39
+
40
+ ## IR-LINT-DEAD-NODE-011
41
+
42
+ Non-sink node with incoming edges but no outgoing edges. **Remediation:** Add an outgoing edge to a downstream consumer, or remove the node if it is obsolete.
43
+
44
+ ---
45
+
46
+ ## IR-LINT-DIRECT-DB-ACCESS-002
47
+
48
+ HTTP-like node connects directly to a datastore. **Remediation:** Introduce a service or domain layer between HTTP handlers and persistence.
49
+
50
+ ---
51
+
52
+ ## IR-LINT-DUPLICATE-EDGE-006
53
+
54
+ Duplicate from→to edge. **Remediation:** Collapse duplicate edges or distinguish them with metadata if your model allows.
55
+
56
+ ---
57
+
58
+ ## IR-LINT-HIGH-FANOUT-004
59
+
60
+ High outgoing dependency count. **Remediation:** Reduce fan-out: split responsibilities, add a facade, batch calls, or use async handoff.
61
+
62
+ ---
63
+
64
+ ## IR-LINT-HTTP-MISSING-NAME-007
65
+
66
+ HTTP-like node missing display name. **Remediation:** Set a short human-readable **`name`** on the node.
67
+
68
+ ---
69
+
70
+ ## IR-LINT-ISOLATED-NODE-005
71
+
72
+ Node has no incident edges. **Remediation:** Remove the orphan or connect it with edges.
73
+
74
+ ---
75
+
76
+ ## IR-LINT-MISSING-AUTH-010
77
+
78
+ HTTP entry missing auth coverage. **Remediation:** Add an auth boundary (auth/middleware/oauth/jwt node or **`config.authRequired: false`** for public endpoints).
79
+
80
+ ---
81
+
82
+ ## IR-LINT-MULTIPLE-HTTP-ENTRIES-009
83
+
84
+ Multiple HTTP entry nodes without incoming edges. **Remediation:** Prefer a single API gateway or BFF unless multiple public surfaces are intentional.
85
+
86
+ ---
87
+
88
+ ## IR-LINT-NO-HEALTHCHECK-003
89
+
90
+ No typical health/readiness route on HTTP nodes. **Remediation:** Add a GET route such as **`/health`** or **`/ready`**.
91
+
92
+ ---
93
+
94
+ ## IR-LINT-SYNC-CHAIN-001
95
+
96
+ Long synchronous chain from HTTP entry. **Remediation:** Shorten the graph or mark non-blocking hops as async in edge metadata.
97
+
98
+ ---
99
+
100
+ ## IR-STRUCT-CYCLE
101
+
102
+ Directed cycle in the graph. **Remediation:** Remove or break cyclic edges unless your tooling explicitly allows execution loops.
103
+
104
+ ---
105
+
106
+ ## IR-STRUCT-DUP_NODE_ID
107
+
108
+ Duplicate node id. **Remediation:** Ensure node ids are unique.
109
+
110
+ ---
111
+
112
+ ## IR-STRUCT-EDGE_AMBIGUOUS_FROM
113
+
114
+ Edge references duplicate source id. **Remediation:** Resolve duplicate node ids first.
115
+
116
+ ---
117
+
118
+ ## IR-STRUCT-EDGE_AMBIGUOUS_TO
119
+
120
+ Edge references duplicate target id. **Remediation:** Resolve duplicate node ids first.
121
+
122
+ ---
123
+
124
+ ## IR-STRUCT-EDGE_INVALID
125
+
126
+ Edge is not an object. **Remediation:** Each edge must be an object with **`from`**/**`to`** (or **`source`**/**`target`**).
127
+
128
+ ---
129
+
130
+ ## IR-STRUCT-EDGE_NO_ENDPOINTS
131
+
132
+ Edge missing endpoints. **Remediation:** Set **`from`** and **`to`** to existing node ids.
133
+
134
+ ---
135
+
136
+ ## IR-STRUCT-EDGE_UNKNOWN_FROM
137
+
138
+ Edge references unknown source node. **Remediation:** Add a node with the referenced id or correct **`from`**.
139
+
140
+ ---
141
+
142
+ ## IR-STRUCT-EDGE_UNKNOWN_TO
143
+
144
+ Edge references unknown target node. **Remediation:** Add a node with the referenced id or correct **`to`**.
145
+
146
+ ---
147
+
148
+ ## IR-STRUCT-EDGES_NOT_ARRAY
149
+
150
+ **`edges`** is present but not an array. **Remediation:** Set **`edges`** to an array of edge objects (or omit **`edges`**).
151
+
152
+ ---
153
+
154
+ ## IR-STRUCT-EMPTY_GRAPH
155
+
156
+ Graph has no nodes. **Remediation:** Add at least one node before validation or export.
157
+
158
+ ---
159
+
160
+ ## IR-STRUCT-HTTP_METHOD
161
+
162
+ HTTP method not supported. **Remediation:** Use GET, POST, PUT, PATCH, DELETE, HEAD, or OPTIONS.
163
+
164
+ ---
165
+
166
+ ## IR-STRUCT-HTTP_PATH
167
+
168
+ HTTP endpoint path invalid. **Remediation:** Set **`config.url`** or **`config.route`** to a path starting with **`/`**.
169
+
170
+ ---
171
+
172
+ ## IR-STRUCT-INVALID_ROOT
173
+
174
+ IR root is not a JSON object. **Remediation:** Pass a single JSON object with **`graph`** or top-level **`nodes`**.
175
+
176
+ ---
177
+
178
+ ## IR-STRUCT-NO_GRAPH
179
+
180
+ Missing graph shape. **Remediation:** Include **`.graph`** with **`nodes`** or a top-level **`nodes`** array.
181
+
182
+ ---
183
+
184
+ ## IR-STRUCT-NODE_INVALID
185
+
186
+ Node entry is not an object. **Remediation:** Each **`nodes`** entry must be a JSON object with **`id`** and type information.
187
+
188
+ ---
189
+
190
+ ## IR-STRUCT-NODE_INVALID_CONFIG
191
+
192
+ Node **`config`** is not a plain object. **Remediation:** Use a plain object for **`config`**.
193
+
194
+ ---
195
+
196
+ ## IR-STRUCT-NODE_NO_ID
197
+
198
+ Node missing non-empty id. **Remediation:** Assign a stable string **`id`** to every node.
199
+
200
+ ---
201
+
202
+ ## IR-STRUCT-NODES_NOT_ARRAY
203
+
204
+ **`nodes`** is not an array. **Remediation:** Set **`nodes`** to an array of node objects.
205
+
206
+ ---
207
+
208
+ *Full strings in MCP/CLI mirror **`src/static-rule-guidance.ts`** (single implementation).*