diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c501d33..66c78fc 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -90,3 +90,23 @@ jobs: exit 0 fi pnpm run test:e2e + + structural-gate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: Install sentrux + env: + SENTRUX_VERSION: v0.5.7 + run: | + curl -fsSL \ + "https://github.com/sentrux/sentrux/releases/download/${SENTRUX_VERSION}/sentrux-linux-x86_64" \ + -o /usr/local/bin/sentrux + chmod +x /usr/local/bin/sentrux + sentrux --version + + - name: Structural regression gate + run: sentrux gate diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000..7257c1f --- /dev/null +++ b/.mcp.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "sentrux": { + "command": "sentrux", + "args": ["mcp"] + } + } +} diff --git a/.sentrux/baseline.json b/.sentrux/baseline.json new file mode 100644 index 0000000..cff7b1c --- /dev/null +++ b/.sentrux/baseline.json @@ -0,0 +1,12 @@ +{ + "timestamp": 1778026327.988928, + "quality_signal": 0.5194591550342765, + "coupling_score": 0.41732283464566927, + "cycle_count": 1, + "god_file_count": 0, + "hotspot_count": 0, + "complex_fn_count": 9, + "max_depth": 7, + "total_import_edges": 127, + "cross_module_edges": 55 +} \ No newline at end of file diff --git a/.sentrux/rules.toml b/.sentrux/rules.toml new file mode 100644 index 0000000..9e261c1 --- /dev/null +++ b/.sentrux/rules.toml @@ -0,0 +1,45 @@ +# Architectural rules enforced by `sentrux check` and `sentrux gate`. +# Run from repo root: `sentrux check` + +[constraints] +max_cycles = 0 +max_coupling = "B" +max_cc = 15 +no_god_files = true + +# Layers: lower `order` = higher in the stack (may depend on higher-order +# layers but not the other way around). Verified empirically — sentrux +# flags "order N must not depend on order N+M" regardless of docs. + +[[layers]] +name = "inner-loop" +paths = ["packages/agent/src/inner-loop/*"] +order = 0 + +[[layers]] +name = "lib" +paths = ["packages/agent/src/lib/*"] +order = 1 + +[[layers]] +name = "api-shape-helpers" +paths = ["packages/agent/src/api-shape-helpers/*"] +order = 2 + +# Boundaries: explicit bans backing up the layer ordering. +# `from` must not depend on `to`. + +[[boundaries]] +from = "packages/agent/src/api-shape-helpers/*" +to = "packages/agent/src/lib/*" +reason = "api-shape-helpers holds leaf type definitions; it must not depend on lib." + +[[boundaries]] +from = "packages/agent/src/api-shape-helpers/*" +to = "packages/agent/src/inner-loop/*" +reason = "api-shape-helpers is a leaf layer; it must not depend on anything above it." + +[[boundaries]] +from = "packages/agent/src/lib/*" +to = "packages/agent/src/inner-loop/*" +reason = "lib is a utility layer; it must not depend on the inner-loop entry point."