arkgate 2.12.0 → 2.13.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 +83 -0
- package/README.md +55 -31
- package/bin/ark-check.mjs +95 -36
- package/bin/ark-mcp.mjs +11 -5
- package/bin/ark-shared.mjs +88 -56
- package/bin/ark.mjs +45 -10
- package/bin/lib/agent-gates.mjs +12 -0
- package/bin/lib/architecture-scan.mjs +8 -0
- package/bin/lib/ci-and-commands.mjs +9 -3
- package/bin/lib/codex-home.mjs +7 -0
- package/bin/lib/config-contract.mjs +331 -0
- package/bin/lib/doctor-plan.mjs +43 -16
- package/bin/lib/enforcement-profiles.mjs +97 -0
- package/bin/lib/host-support-matrix.mjs +77 -0
- package/bin/lib/install-migrate.mjs +45 -14
- package/bin/lib/mcp-adoption.mjs +35 -3
- package/bin/lib/open-html.mjs +75 -0
- package/bin/lib/presets.mjs +3 -2
- package/bin/lib/safety-diagnostics.mjs +31 -11
- package/bin/lib/skill-install.mjs +64 -0
- package/bin/lib/ts-resolve.mjs +2 -1
- package/bin/lib/weakest-link.mjs +417 -0
- package/bin/lib/write-path-capabilities.mjs +182 -0
- package/bin/lib/write-path-detect.mjs +62 -99
- package/dist/configContract-iBLxx5Tz.d.cts +53 -0
- package/dist/configContract-iBLxx5Tz.d.ts +53 -0
- package/dist/eslint/index.cjs +375 -13
- package/dist/eslint/index.cjs.map +1 -1
- package/dist/eslint/index.d.cts +30 -20
- package/dist/eslint/index.d.ts +30 -20
- package/dist/eslint/index.js +375 -13
- package/dist/eslint/index.js.map +1 -1
- package/dist/index.cjs +723 -61
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +95 -5
- package/dist/index.d.ts +95 -5
- package/dist/index.js +716 -61
- package/dist/index.js.map +1 -1
- package/dist/nestjs/index.cjs +150 -42
- package/dist/nestjs/index.cjs.map +1 -1
- package/dist/nestjs/index.d.cts +2 -1
- package/dist/nestjs/index.d.ts +2 -1
- package/dist/nestjs/index.js +150 -42
- package/dist/nestjs/index.js.map +1 -1
- package/dist/runtime/index.cjs +723 -61
- package/dist/runtime/index.cjs.map +1 -1
- package/dist/runtime/index.d.cts +3 -2
- package/dist/runtime/index.d.ts +3 -2
- package/dist/runtime/index.js +716 -61
- package/dist/runtime/index.js.map +1 -1
- package/dist/{types-BZ17b9i5.d.cts → types-BxBwnBpC.d.cts} +9 -36
- package/dist/{types-BZ17b9i5.d.ts → types-Wcs_l1_J.d.ts} +9 -36
- package/docs/agent-guide.md +32 -20
- package/docs/ai-gates.md +53 -18
- package/docs/configuration.md +97 -0
- package/docs/enthusiast/README.md +3 -3
- package/docs/enthusiast/how-to-agent-gates.md +7 -3
- package/docs/migrate-from-ark-runtime-kernel.md +3 -0
- package/docs/package-surface.md +14 -9
- package/docs/production-hardening.md +15 -2
- package/docs/threat-model.md +65 -0
- package/docs/typescript-support.md +3 -3
- package/package.json +15 -2
- package/schemas/ark.config.schema.json +750 -0
- package/server.json +2 -2
- package/templates/hooks/pre-commit-ark +37 -0
- package/templates/skills/ark-coverage.md +2 -2
- package/templates/skills/ark-runtime.md +8 -5
- package/templates/skills/ark-upgrade.md +36 -16
- package/tests/fixtures/ts-consumer/ark.config.json +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,89 @@ All notable changes to ArkGate (`arkgate`; formerly `ark-runtime-kernel`) are do
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 2.13.0 — 2026-07-11
|
|
8
|
+
|
|
9
|
+
- Added the stable, deterministic analysis IR and public in-memory API: `loadContract`,
|
|
10
|
+
`analyzeProject`, `analyzeChange`, and `explainViolation`.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **Temp-worktree release verification:** Codex multi-project fixtures now keep the simulated
|
|
15
|
+
primary project outside temp-root policy even when the repository itself is checked out under
|
|
16
|
+
`/tmp`, so the release confidence gate is reproducible without weakening fail-closed temp MCP
|
|
17
|
+
rewrites.
|
|
18
|
+
- **Test and Codex-home isolation:** Vitest now redirects `CODEX_HOME` to a disposable test home,
|
|
19
|
+
so direct helper calls and spawned CLIs cannot rewrite the developer's real Codex config. Temp
|
|
20
|
+
project installs also recognize an explicitly exported default `~/.codex` as the real home and
|
|
21
|
+
skip implicit MCP rewiring unless `--codex-home` is requested.
|
|
22
|
+
- **Workflow retry boundary:** `RetryPolicy` now retries only `step.execute` failures and
|
|
23
|
+
timeouts. A snapshot-store or completion-audit failure after a successful effect is terminal,
|
|
24
|
+
enters compensation, and never executes the completed effect again.
|
|
25
|
+
- **Scanner bypass corpus:** forbidden-global checks now use single-file TypeScript symbols, so
|
|
26
|
+
local `fetch` / `Date` bindings do not false-positive while ambient aliases and
|
|
27
|
+
`globalThis.Date.now()` remain violations. CLI, AICodeGate, and ESLint share the verdict.
|
|
28
|
+
TypeScript `import x = require('...')` now creates a dependency edge, and direct
|
|
29
|
+
`require(expr)` emits `DYNAMIC_REQUIRE_NOT_ALLOWLISTED` (strict profiles fail unless the file
|
|
30
|
+
is reviewed in `dynamicImportAllowlist`). The scan cache is versioned past the old semantics.
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- **Versioned configuration contract:** `ark.config.json` now carries `$schema` and
|
|
35
|
+
`schemaVersion: "1.0"`. One canonical loader validates and migrates the contract for CLI, MCP,
|
|
36
|
+
and ESLint with path-specific diagnostics and a fail-closed unknown-key policy. The generated
|
|
37
|
+
JSON Schema ships at the stable `arkgate/schema` subpath; unversioned configs from the previous
|
|
38
|
+
major migrate deterministically in memory.
|
|
39
|
+
- **Active-host enforcement capabilities:** doctor and adoption checks now project
|
|
40
|
+
`hard-write`, `advisory-write`, `merge-gate`, and `repair-payload` from the active host only,
|
|
41
|
+
with evidence paths and a separate repo-wide inventory. Claude/Grok hooks can no longer make
|
|
42
|
+
Codex, Cursor, or an unknown host appear hard-enforced; human doctor output names the host and
|
|
43
|
+
separates advisory MCP checks from the shared CI check and its external required-status policy.
|
|
44
|
+
- **Host-compatible enforcement profiles:** generated CI now uses `--strict-merge`, while
|
|
45
|
+
`--strict` remains a compatibility alias; neither depends on an editor hook. The optional
|
|
46
|
+
`--require-write-hook <host>` check verifies Claude/Grok explicitly, reports Cursor/Codex as
|
|
47
|
+
advisory-write plus the shared CI check only, and makes `ark start` reject impossible,
|
|
48
|
+
mismatched, or preserved-incompatible requests before writing project files.
|
|
49
|
+
- **Executable regression confidence gate:** `npm run test:confidence` now combines the existing
|
|
50
|
+
broad Vitest coverage thresholds with real Stryker mutation testing over write-path detection,
|
|
51
|
+
dependency extraction, forbidden-global detection, baseline keys, and workflow retry logic.
|
|
52
|
+
CI and both npm release paths invoke the same gate; mutation score fails below 90%.
|
|
53
|
+
- **Q2 repair dogfood closed:** deny → `ARK_REPAIR_JSON`/`autoPatch` → host re-inject →
|
|
54
|
+
revalidation allow proven via shipped `bin/ark-mcp.mjs` (Claude/Grok hooks already
|
|
55
|
+
`--hook-repair`; `doctor.writePath.mode = repair`).
|
|
56
|
+
- **Q3 weakest-link sensors:** `bin/lib/weakest-link.mjs` + doctor adoption gaps
|
|
57
|
+
(`enforcement-ci-*`, config drift, pre-commit missing); maintained
|
|
58
|
+
`templates/hooks/pre-commit-ark`; optional `ARK_DOCTOR_GITHUB=1` branch-protection
|
|
59
|
+
report (honest unavailable / not-protected — never fake green).
|
|
60
|
+
- **Q5 scale bench:** `scripts/ark-scale-bench.mjs` / `npm run bench:scale` (real
|
|
61
|
+
ark-check cold/warm p50/p95 on generated trees).
|
|
62
|
+
- **Q6 module budgets:** `scripts/check-module-budgets.mjs` / `npm run check:module-budgets`.
|
|
63
|
+
- **Q8 fault-injection tests:** compensation failure audit, cancellation-ignoring timeout,
|
|
64
|
+
outbox retry attempts + clear (durability boundary).
|
|
65
|
+
- **Q9 threat model + package allowlist:** `docs/threat-model.md`,
|
|
66
|
+
`scripts/verify-package-files.mjs` / `npm run check:package-files`.
|
|
67
|
+
|
|
68
|
+
### Changed
|
|
69
|
+
|
|
70
|
+
- **Product identity retained:** ArkGate, `arkgate`, the `arkgate*` / `ark*` commands,
|
|
71
|
+
`ark.config.json`, `ark://`, `ARK_*`, the existing GitHub repository, and `arkgate.online` remain
|
|
72
|
+
canonical. The unpublished local rename experiment was reversed before any external cutover.
|
|
73
|
+
- **Truthful host support matrix and runtime status:** one capability-backed matrix now drives
|
|
74
|
+
README and generated `AGENTS.md` guarantees for Claude, Grok, Cursor, and Codex. Doctor exposes
|
|
75
|
+
both the supported host profile and repository evidence; public docs distinguish hard local
|
|
76
|
+
hooks, advisory MCP, CI checks, and required-status merge blocking. The optional runtime/Nest
|
|
77
|
+
surface is explicitly experimental and is not required for architecture-gate adoption.
|
|
78
|
+
- **Active host vs deferred Codex on upgrade/doctor:** `/ark-upgrade` greens the
|
|
79
|
+
**session host** first; Codex `$CODEX_HOME` prompts/MCP multi-project debt is
|
|
80
|
+
**deferred** when the session host is **known and not Codex** (Grok/Claude/Cursor).
|
|
81
|
+
Unknown host (CI/plain shell) keeps original severity. Doctor marks deferred gaps
|
|
82
|
+
`deferred: true` (severity `info`), prefixes the message, and omits them from Top
|
|
83
|
+
actions. Temp/upgrade MCP `--root` stays urgent (fail-closed). New helpers:
|
|
84
|
+
`detectActiveAgentHost`, `codexConcernIsActive` (do not treat `CODEX_HOME` alone
|
|
85
|
+
as Codex). `ark-check` advisory for stale Codex-home skills notes the deferral.
|
|
86
|
+
Completion contract adds **Active host** / **Deferred hosts**.
|
|
87
|
+
- **ROADMAP Track Q:** Q2 `done`; Q3/Q5/Q6/Q8/Q9 `doing` with residual external/DoD
|
|
88
|
+
items listed; Q4/Q7/Q10 remain `todo` (no false complete).
|
|
89
|
+
|
|
7
90
|
## 2.12.0 — 2026-07-10
|
|
8
91
|
|
|
9
92
|
### Fixed
|
package/README.md
CHANGED
|
@@ -43,15 +43,19 @@ commands above).
|
|
|
43
43
|
|
|
44
44
|
## What it is (30 seconds)
|
|
45
45
|
|
|
46
|
-
**ArkGate** = a machine-readable architecture file (`ark.config.json`) enforced
|
|
47
|
-
|
|
46
|
+
**ArkGate** = a machine-readable architecture file (`ark.config.json`) enforced at CI, with
|
|
47
|
+
host-specific protection while an agent writes:
|
|
48
|
+
|
|
49
|
+
**Name note:** this is the TypeScript architecture-enforcement package published as `arkgate`.
|
|
50
|
+
It is not affiliated with the separate Archgate CLI project.
|
|
48
51
|
|
|
49
52
|
| When | Tool |
|
|
50
53
|
|------|------|
|
|
51
|
-
| **While the AI writes** |
|
|
52
|
-
| **Before merge** | `arkgate-check` CI |
|
|
54
|
+
| **While the AI writes** | Hard PreToolUse block on Claude/Grok; advisory MCP on Cursor/Codex |
|
|
55
|
+
| **Before merge** | `arkgate-check` CI check; merge blocking requires it as a required status |
|
|
53
56
|
|
|
54
|
-
Optional later: runtime kernel (`createArkKernel`) if you want
|
|
57
|
+
Optional later: the **experimental** runtime kernel (`createArkKernel`) if you want to evaluate
|
|
58
|
+
event/intent governance. It is not required for gate adoption.
|
|
55
59
|
|
|
56
60
|
It is **not** a web framework, ORM, or job runner.
|
|
57
61
|
|
|
@@ -82,7 +86,7 @@ mode you configure:
|
|
|
82
86
|
|-------|--------|-----------|
|
|
83
87
|
| **Suggest** | New/thin project | Finish `start` + autopilot |
|
|
84
88
|
| **Adapt** | Not fully protected yet | Keep autopilot / adopt until clean |
|
|
85
|
-
| **Enforce** |
|
|
89
|
+
| **Enforce** | Contract coverage is honest and checked edges are clean | Keep the host-appropriate write path; require the CI status if it must block merges |
|
|
86
90
|
|
|
87
91
|
You **arrive** at Enforce. You never “turn on Enforce”.
|
|
88
92
|
|
|
@@ -143,7 +147,7 @@ npx arkgate-check --coverage
|
|
|
143
147
|
|
|
144
148
|
## Other skills (only when you need them)
|
|
145
149
|
|
|
146
|
-
Install once: `npx arkgate-check --install-agent-gates`
|
|
150
|
+
Install once: `npx arkgate-check --install-agent-gates`
|
|
147
151
|
(`--tools claude,cursor,codex,grok` to pick hosts.)
|
|
148
152
|
|
|
149
153
|
**Default is always `/ark-autopilot`.** The rest are escapes, not a second curriculum.
|
|
@@ -155,8 +159,9 @@ otherwise they **fall back to sequential**.
|
|
|
155
159
|
|
|
156
160
|
**Write path (Track W):** Prefer MCP **`ark_prepare_write`** when you have a snippet (place +
|
|
157
161
|
constrain + validate + optional `autoPatch` + `judgmentBrief`). PreToolUse hooks with
|
|
158
|
-
`--hook-repair` emit machine-readable repair payloads on deny (still hard block;
|
|
159
|
-
write).
|
|
162
|
+
`--hook-repair` on Claude/Grok emit machine-readable repair payloads on deny (still hard block;
|
|
163
|
+
never silent write). Cursor/Codex MCP calls remain advisory. See
|
|
164
|
+
[docs/ai-gates.md](docs/ai-gates.md).
|
|
160
165
|
|
|
161
166
|
| Need | Skill |
|
|
162
167
|
|------|--------|
|
|
@@ -170,11 +175,24 @@ write). See [docs/ai-gates.md](docs/ai-gates.md).
|
|
|
170
175
|
| Edit `ark.config.json` safely | `/ark-contract` |
|
|
171
176
|
| Plain-language tour of the report | `/ark-explain` |
|
|
172
177
|
| Deep coverage + opportunities audit | `/ark-coverage` |
|
|
173
|
-
|
|
|
174
|
-
| Bump ArkGate + refresh
|
|
178
|
+
| Evaluate the experimental runtime against hand-rolled bus/outbox (TS) | `/ark-runtime` |
|
|
179
|
+
| Bump ArkGate + refresh active host (defer Codex when not on Codex) | `/ark-upgrade` |
|
|
180
|
+
|
|
181
|
+
### Host enforcement support
|
|
182
|
+
|
|
183
|
+
<!-- arkgate-host-support:start -->
|
|
184
|
+
| Host | Local write boundary | MCP validation | CI / merge path | Repair payload |
|
|
185
|
+
|------|----------------------|----------------|-----------------|----------------|
|
|
186
|
+
| Claude Code | Hard block for PreToolUse `Write` / `Edit` / `MultiEdit` | Advisory; the agent must call it | Available `arkgate-check --strict-merge` check | Emitted on hook deny; host must re-inject |
|
|
187
|
+
| Grok Build | Hard block for PreToolUse `write` / `search_replace` (plus aliases) | Advisory; the agent must call it | Available `arkgate-check --strict-merge` check | Emitted on hook deny; host must re-inject |
|
|
188
|
+
| Cursor | No hard hook; MCP/rules are advisory | Advisory; the agent must call it | Available `arkgate-check --strict-merge` check | No hard-boundary payload |
|
|
189
|
+
| OpenAI Codex | No hard hook; MCP/rules are advisory | Advisory; the agent must call it | Available `arkgate-check --strict-merge` check | No hard-boundary payload |
|
|
190
|
+
|
|
191
|
+
This table describes the supported profile **after its files are installed and the host loads/trusts them**. A hard local boundary covers only the listed hook operations; alternate tools, direct filesystem writes, and human edits still rely on CI. MCP validation is advisory because the agent must call it. The CI check blocks a merge only when the repository makes that status required. Repair payloads never write code silently: the host must re-inject the candidate and ArkGate revalidates it. Run `arkgate-check --doctor` for the evidence actually detected in the current repository.
|
|
192
|
+
<!-- arkgate-host-support:end -->
|
|
193
|
+
|
|
194
|
+
Detailed setup: [docs/ai-gates.md](docs/ai-gates.md).
|
|
175
195
|
|
|
176
|
-
Hosts with full MCP/hooks: **Claude Code**, **Cursor**, **Codex**, **Grok Build**.
|
|
177
|
-
More: [docs/ai-gates.md](docs/ai-gates.md). Health: **`npx arkgate-check --doctor`**.
|
|
178
196
|
---
|
|
179
197
|
|
|
180
198
|
## How it works (short)
|
|
@@ -182,19 +200,24 @@ More: [docs/ai-gates.md](docs/ai-gates.md). Health: **`npx arkgate-check --docto
|
|
|
182
200
|
```
|
|
183
201
|
ark.config.json
|
|
184
202
|
│
|
|
185
|
-
├─► Write
|
|
186
|
-
├─► CI
|
|
187
|
-
└─► Runtime kernel
|
|
203
|
+
├─► Write path (arkgate-mcp) — hard hook or advisory MCP, by host
|
|
204
|
+
├─► CI check (arkgate-check) — merge block only when status is required
|
|
205
|
+
└─► Runtime kernel — experimental opt-in; gates do not need it
|
|
188
206
|
```
|
|
189
207
|
|
|
190
208
|
- **Presets:** hexagonal, layered, feature-sliced, monorepo, ui-surface, vertical-slice, ddd-bounded-contexts (+ aliases clean-architecture / onion-architecture). Layers start optional; doctor suggests tightening populated cores. Cross-slice / cross-context bans use optional `peerIsolation` rules.
|
|
209
|
+
- **Versioned config:** generated contracts include `$schema` + `schemaVersion`; CLI, MCP, and
|
|
210
|
+
ESLint validate through the same loader. Unknown keys fail with their JSON path. See the
|
|
211
|
+
[configuration and editor guide](docs/configuration.md).
|
|
191
212
|
- **Frameworks:** Nest / Next / express / library layouts get sensible globs on init so day-one coverage is real.
|
|
192
213
|
- **Brownfield:** baseline ratchet, refuse to freeze a wrong contract, `/ark-adopt` for mature trees.
|
|
193
214
|
- **Agents:** skills install into Claude / Cursor / Codex / Grok; `ark start` freezes **day-zero origin** under `.ark/reports/` **before** agent docs/CI templates.
|
|
194
215
|
- **Write protocol (2.10 / Track W):** mechanical-safe **autoPatch** on the write gate (`import type`); MCP **`ark_prepare_write`** (place + validate + patch + judgmentBrief); opt-in hook **`--hook-repair`** (`ARK_REPAIR_JSON`); doctor **`writePath`** (repair vs reject-only); loop-cost eval (`npm run eval:loop-cost`). Port-proof inject is **judgment** (arity change), not silent auto-apply.
|
|
195
|
-
- **Fail-closed CI (2.11):** `--strict` combines config coverage,
|
|
196
|
-
|
|
197
|
-
|
|
216
|
+
- **Fail-closed CI (2.11):** `--strict-merge` combines config coverage, shared gate-file
|
|
217
|
+
presence, and bypass diagnostics for dynamic imports, TypeScript suppressions, explicit `any`
|
|
218
|
+
casts, InMemory runtime defaults, and disabled peer isolation. `--strict` is a compatibility
|
|
219
|
+
alias. Neither requires an editor hook; use `--require-write-hook claude|grok` when that local
|
|
220
|
+
guarantee is part of the check.
|
|
198
221
|
- **Trust / coverage (2.12):** package unit-test floors on the broad product surface
|
|
199
222
|
(statements/lines **≥80%**, branches/functions **≥85%**; enforcement-critical modules **≥95%**
|
|
200
223
|
branch). Explore dual-plan + day-zero origin first (see above). Roadmap next: Q2 repair dogfood matrix.
|
|
@@ -205,7 +228,7 @@ ark.config.json
|
|
|
205
228
|
| | ArkGate | Typical boundary linter |
|
|
206
229
|
|--|:---:|:---:|
|
|
207
230
|
| CI import rules | ✅ | ✅ |
|
|
208
|
-
|
|
|
231
|
+
| Hard-block supported-host AI writes before they land | ✅ (Claude/Grok hooks) | ❌ |
|
|
209
232
|
| Contract agents can read (`ark://manifest`) | ✅ | ❌ |
|
|
210
233
|
| Placement tools (`ark_place`, …) | ✅ | ❌ |
|
|
211
234
|
| Honest governed % + adoption path | ✅ | ❌ |
|
|
@@ -225,7 +248,7 @@ npx arkgate-check --doctor --json # machine-readable doctor.adoption
|
|
|
225
248
|
npx arkgate-check --strict # fail-closed CI + installed-gate/safety checks
|
|
226
249
|
npx arkgate-check --plan # safe-to-auto-fix vs your call
|
|
227
250
|
npx arkgate-check --coverage # Governed: N%
|
|
228
|
-
npx arkgate-check --report ark-report.html # showcase HTML
|
|
251
|
+
npx arkgate-check --report ark-report.html # showcase HTML (opens in browser on local TTY; --no-open to skip)
|
|
229
252
|
npx arkgate-check --baseline # only NEW violations fail
|
|
230
253
|
npx arkgate upgrade # package + gates/skills + MCP/Codex normalize
|
|
231
254
|
```
|
|
@@ -239,10 +262,11 @@ CI (example):
|
|
|
239
262
|
|
|
240
263
|
---
|
|
241
264
|
|
|
242
|
-
## Optional
|
|
265
|
+
## Optional experimental runtime kernel
|
|
243
266
|
|
|
244
|
-
Gates need **no app code changes**.
|
|
245
|
-
|
|
267
|
+
Gates need **no app code changes**. The runtime API is currently **experimental** and is not a
|
|
268
|
+
production-readiness claim. If you want to evaluate runtime intent/event contracts, use the
|
|
269
|
+
opt-in subpath (preferred):
|
|
246
270
|
|
|
247
271
|
```ts
|
|
248
272
|
import { createStrictArkKernelFromConfig } from 'arkgate/runtime';
|
|
@@ -250,7 +274,7 @@ import { createStrictArkKernelFromConfig } from 'arkgate/runtime';
|
|
|
250
274
|
```
|
|
251
275
|
|
|
252
276
|
Root `import { … } from 'arkgate'` still re-exports kernel symbols for compatibility
|
|
253
|
-
in this major;
|
|
277
|
+
in this major; use `arkgate/runtime` when evaluating the experimental surface.
|
|
254
278
|
|
|
255
279
|
NestJS: `arkgate/nestjs` (optional peer `@nestjs/common`).
|
|
256
280
|
|
|
@@ -269,7 +293,7 @@ for real systems. Details: [docs/production-hardening.md](docs/production-harden
|
|
|
269
293
|
| Audience | Link |
|
|
270
294
|
|----------|------|
|
|
271
295
|
| New builders (plain language) | [docs/enthusiast/](docs/enthusiast/README.md) |
|
|
272
|
-
| **Package surface (stable vs
|
|
296
|
+
| **Package surface (stable vs experimental)** | [docs/package-surface.md](docs/package-surface.md) |
|
|
273
297
|
| Wire Claude / Cursor / Codex / Grok + **ESLint (CI-parity)** | [docs/ai-gates.md](docs/ai-gates.md) |
|
|
274
298
|
| **TypeScript 5 / 6 / 7 support** | [docs/typescript-support.md](docs/typescript-support.md) |
|
|
275
299
|
| Migrate from `ark-runtime-kernel` | [docs/migrate-from-ark-runtime-kernel.md](docs/migrate-from-ark-runtime-kernel.md) |
|
|
@@ -290,11 +314,11 @@ npm run typecheck
|
|
|
290
314
|
npm run check:architecture # Ark gates itself
|
|
291
315
|
```
|
|
292
316
|
|
|
293
|
-
**Website:** [arkgate.online](https://www.arkgate.online/)
|
|
294
|
-
**npm:** [`arkgate`](https://www.npmjs.com/package/arkgate) · formerly `ark-runtime-kernel`
|
|
295
|
-
**Product:** **ArkGate** — architecture co-pilot / gate for AI TypeScript (not a runtime kernel).
|
|
296
|
-
CLI: `arkgate` · `arkgate-check` · `arkgate-mcp` (aliases `ark` / `ark-check` / `ark-mcp` still work for one major).
|
|
297
|
-
MCP registry: `io.github.pedroknigge/arkgate`.
|
|
317
|
+
**Website:** [arkgate.online](https://www.arkgate.online/)
|
|
318
|
+
**npm:** [`arkgate`](https://www.npmjs.com/package/arkgate) · formerly `ark-runtime-kernel`
|
|
319
|
+
**Product:** **ArkGate** — architecture co-pilot / gate for AI TypeScript (not a runtime kernel).
|
|
320
|
+
CLI: `arkgate` · `arkgate-check` · `arkgate-mcp` (aliases `ark` / `ark-check` / `ark-mcp` still work for one major).
|
|
321
|
+
MCP registry: `io.github.pedroknigge/arkgate`.
|
|
298
322
|
**Source:** [github.com/pedroknigge/arkgate](https://github.com/pedroknigge/arkgate)
|
|
299
323
|
|
|
300
324
|
Node ≥ 18 · **MIT**.
|
package/bin/ark-check.mjs
CHANGED
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
loadTypeScript,
|
|
34
34
|
detectSkillGaps,
|
|
35
35
|
detectCodexHomeGap,
|
|
36
|
+
detectActiveAgentHost,
|
|
36
37
|
missingGates,
|
|
37
38
|
staleRunnerGateFiles,
|
|
38
39
|
brokenMcpGateFiles,
|
|
@@ -56,6 +57,7 @@ import {
|
|
|
56
57
|
reportsDir,
|
|
57
58
|
readJsonSafe,
|
|
58
59
|
} from './lib/html-report.mjs';
|
|
60
|
+
import { shouldOpenHtmlReport, openHtmlInBrowser } from './lib/open-html.mjs';
|
|
59
61
|
import {
|
|
60
62
|
computeCoverage,
|
|
61
63
|
runCoverage,
|
|
@@ -81,6 +83,7 @@ import {
|
|
|
81
83
|
import {
|
|
82
84
|
ARCHITECTURE_PRESETS,
|
|
83
85
|
} from './lib/presets.mjs';
|
|
86
|
+
import { loadArkConfigContract, parseArkConfigJson } from './lib/config-contract.mjs';
|
|
84
87
|
|
|
85
88
|
import {
|
|
86
89
|
collectGovernedFiles,
|
|
@@ -91,6 +94,7 @@ import {
|
|
|
91
94
|
configWarning,
|
|
92
95
|
} from './lib/config-warnings.mjs';
|
|
93
96
|
import { runArchitectureScan } from './lib/architecture-scan.mjs';
|
|
97
|
+
import { validateHardWriteRequest } from './lib/enforcement-profiles.mjs';
|
|
94
98
|
|
|
95
99
|
|
|
96
100
|
function parseArgs(argv) {
|
|
@@ -101,9 +105,9 @@ function parseArgs(argv) {
|
|
|
101
105
|
printConfig: undefined,
|
|
102
106
|
tsconfig: undefined,
|
|
103
107
|
json: false,
|
|
104
|
-
strict: false,
|
|
105
108
|
strictConfig: false,
|
|
106
109
|
requireGates: false,
|
|
110
|
+
requireWriteHook: undefined,
|
|
107
111
|
init: false,
|
|
108
112
|
installAgentGates: false,
|
|
109
113
|
tools: undefined,
|
|
@@ -122,6 +126,8 @@ function parseArgs(argv) {
|
|
|
122
126
|
applyPolicyPack: undefined,
|
|
123
127
|
watch: false,
|
|
124
128
|
beginner: false,
|
|
129
|
+
openReport: false,
|
|
130
|
+
noOpenReport: false,
|
|
125
131
|
version: false,
|
|
126
132
|
help: false,
|
|
127
133
|
};
|
|
@@ -135,13 +141,15 @@ function parseArgs(argv) {
|
|
|
135
141
|
for (let i = 2; i < argv.length; i += 1) {
|
|
136
142
|
const arg = argv[i];
|
|
137
143
|
if (arg === '--json') args.json = true;
|
|
138
|
-
else if (arg === '--strict') {
|
|
139
|
-
args.strict = true;
|
|
144
|
+
else if (arg === '--strict' || arg === '--strict-merge') {
|
|
140
145
|
args.strictConfig = true;
|
|
141
146
|
args.requireGates = true;
|
|
142
147
|
}
|
|
143
148
|
else if (arg === '--strict-config') args.strictConfig = true;
|
|
144
149
|
else if (arg === '--require-gates') args.requireGates = true;
|
|
150
|
+
else if (arg === '--require-write-hook') {
|
|
151
|
+
args.requireWriteHook = requireValue(arg, i++).trim().toLowerCase();
|
|
152
|
+
}
|
|
145
153
|
else if (arg === '--init') args.init = true;
|
|
146
154
|
else if (arg === '--preset') args.preset = requireValue(arg, i++);
|
|
147
155
|
else if (arg === '--install-agent-gates') args.installAgentGates = true;
|
|
@@ -183,6 +191,8 @@ function parseArgs(argv) {
|
|
|
183
191
|
}
|
|
184
192
|
else if (arg === '--reset-origin') args.resetOrigin = true;
|
|
185
193
|
else if (arg === '--no-archive') args.noArchive = true;
|
|
194
|
+
else if (arg === '--open') args.openReport = true;
|
|
195
|
+
else if (arg === '--no-open') args.noOpenReport = true;
|
|
186
196
|
else if (arg === '--baseline' || arg === '--update-baseline') {
|
|
187
197
|
if (arg === '--update-baseline') args.updateBaseline = true;
|
|
188
198
|
// optional path value: consume the next arg only when it isn't another flag
|
|
@@ -214,7 +224,7 @@ function usage() {
|
|
|
214
224
|
return [
|
|
215
225
|
'Usage: arkgate-check | ark-check (identical bins; product name ArkGate)',
|
|
216
226
|
' ark-check --version',
|
|
217
|
-
' ark-check --root <project> --config <ark.config.json> [--manifest <ark.manifest.json>] [--tsconfig <tsconfig.json>] [--strict | --strict-config] [--require-gates] [--json] [--baseline [file]] [--report [file.html]] [--no-cache]',
|
|
227
|
+
' ark-check --root <project> --config <ark.config.json> [--manifest <ark.manifest.json>] [--tsconfig <tsconfig.json>] [--strict-merge | --strict | --strict-config] [--require-gates] [--require-write-hook <host>] [--json] [--baseline [file]] [--report [file.html]] [--no-cache]',
|
|
218
228
|
' ark-check --coverage [--json] per-layer file counts + full unclassified list (report only, exit 0)',
|
|
219
229
|
' ark-check --plan [--json] classified remediation plan (mechanical-safe / judgment / deferred) + goal; report only',
|
|
220
230
|
' ark-check --recommend [--json] [--write-plan] application-shape plan; --write-plan emits ark-adoption-plan.json',
|
|
@@ -224,10 +234,11 @@ function usage() {
|
|
|
224
234
|
' ark-check --adopt-contract [--write] expand include + UI patterns from ungoverned dirs (contract adopt)',
|
|
225
235
|
' ark-check --ratchet-cores when raw graph is green (0 violations; baseline ignored), set optional:false on populated cores only (writes ark.config.json)',
|
|
226
236
|
' ark-check --watch re-run the check when governed files change (debounced)',
|
|
227
|
-
' ark-check --report [file.html] [--beginner] [--reset-origin] [--no-archive]',
|
|
237
|
+
' ark-check --report [file.html] [--beginner] [--reset-origin] [--no-archive] [--open|--no-open]',
|
|
228
238
|
' HTML report + snapshots under .ark/reports/ (origin once, latest each run, history JSON)',
|
|
239
|
+
' Best-effort open in browser (local TTY). No-op if open fails. --no-open / ARK_NO_OPEN_REPORT=1 to skip; --open forces open.',
|
|
229
240
|
' ark-check --init [--preset hexagonal|layered|feature-sliced|monorepo|ui-surface|vertical-slice|ddd-bounded-contexts|clean-architecture|onion-architecture] [--force]',
|
|
230
|
-
' ark-check --install-agent-gates [--tools claude,cursor,codex,grok] [--skills-only] [--codex-home] [--force]',
|
|
241
|
+
' ark-check --install-agent-gates [--tools claude,cursor,codex,grok] [--require-write-hook <host>] [--skills-only] [--codex-home] [--force]',
|
|
231
242
|
' ark-check --update-baseline [file] freeze current violations (default .ark-baseline.json)',
|
|
232
243
|
' ark-check --print-config eleven-layer',
|
|
233
244
|
'',
|
|
@@ -267,8 +278,12 @@ function usage() {
|
|
|
267
278
|
'',
|
|
268
279
|
'Config warnings are advisory by default and are included in JSON output.',
|
|
269
280
|
'Use --strict-config to make config warnings fail the check.',
|
|
270
|
-
'Use --strict for the fail-closed CI profile: --strict-config + --require-gates',
|
|
271
|
-
'plus the security diagnostics surfaced by doctor.',
|
|
281
|
+
'Use --strict-merge for the fail-closed CI profile: --strict-config + --require-gates',
|
|
282
|
+
'plus the security diagnostics surfaced by doctor. --strict is a compatibility alias.',
|
|
283
|
+
'This merge profile never depends on an editor/agent hook.',
|
|
284
|
+
'Add --require-write-hook claude|grok to validate a hard local write boundary for that',
|
|
285
|
+
'specific host. Cursor and Codex expose advisory MCP tools plus the shared CI check;',
|
|
286
|
+
'merge blocking requires repository policy to make that status required.',
|
|
272
287
|
'',
|
|
273
288
|
'--require-gates fails the check when AGENTS.md, .mcp.json, or the generated CI',
|
|
274
289
|
'workflow is missing, so "installed but never configured" is a red CI. Combine it',
|
|
@@ -276,7 +291,8 @@ function usage() {
|
|
|
276
291
|
'',
|
|
277
292
|
'--install-agent-gates writes AGENTS.md, .mcp.json, and the CI workflow for every',
|
|
278
293
|
'project, plus tool-specific templates. Known tools: claude, cursor, codex, grok',
|
|
279
|
-
'(
|
|
294
|
+
'(Claude/Grok hard-write hooks; Cursor/Codex advisory MCP; shared CI check for all) and',
|
|
295
|
+
'windsurf, cline, copilot, kiro, roo, continue, gemini',
|
|
280
296
|
'(instruction-tier rule files derived from the same contract).',
|
|
281
297
|
'It also installs the /ark-* skills shipped in templates/skills/ into each',
|
|
282
298
|
'detected tool\'s command location (.claude/skills/, .cursor/commands/,',
|
|
@@ -290,7 +306,7 @@ function usage() {
|
|
|
290
306
|
'Pass --tools to pick which tool configs to write; otherwise they are auto-detected',
|
|
291
307
|
'from their config directories (.claude/, .cursor/, .codex/, .grok/, .windsurf/,',
|
|
292
308
|
'.clinerules/, .kiro/, .roo/, .continue/, .gemini/; copilot is explicit-only).',
|
|
293
|
-
'claude+cursor+codex are written when nothing is detected.',
|
|
309
|
+
'claude+cursor+codex+grok are written when nothing is detected.',
|
|
294
310
|
'',
|
|
295
311
|
'Generate a starter 11-layer config:',
|
|
296
312
|
' ark-check --print-config eleven-layer > ark.config.json',
|
|
@@ -305,25 +321,12 @@ function readConfig(root, configPath) {
|
|
|
305
321
|
? configPath
|
|
306
322
|
: path.join(root, configPath);
|
|
307
323
|
if (!fs.existsSync(fullPath)) {
|
|
308
|
-
return
|
|
309
|
-
include: ['src'],
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
};
|
|
324
|
+
return loadArkConfigContract(
|
|
325
|
+
{ include: ['src'], layers: [], rules: DEFAULT_RULES },
|
|
326
|
+
fullPath
|
|
327
|
+
).config;
|
|
313
328
|
}
|
|
314
|
-
|
|
315
|
-
return {
|
|
316
|
-
include: raw.include ?? ['src'],
|
|
317
|
-
layers: raw.layers ?? [],
|
|
318
|
-
rules: raw.rules ?? DEFAULT_RULES,
|
|
319
|
-
...(raw.exclude ? { exclude: raw.exclude } : {}),
|
|
320
|
-
...(raw.excludeGenerated !== undefined ? { excludeGenerated: raw.excludeGenerated } : {}),
|
|
321
|
-
...(raw.cyclePolicy ? { cyclePolicy: raw.cyclePolicy } : {}),
|
|
322
|
-
...(raw.dynamicImportAllowlist
|
|
323
|
-
? { dynamicImportAllowlist: raw.dynamicImportAllowlist }
|
|
324
|
-
: {}),
|
|
325
|
-
...(raw.safety ? { safety: raw.safety } : {}),
|
|
326
|
-
};
|
|
329
|
+
return parseArkConfigJson(fs.readFileSync(fullPath, 'utf8'), fullPath).config;
|
|
327
330
|
}
|
|
328
331
|
|
|
329
332
|
/**
|
|
@@ -976,16 +979,41 @@ async function main() {
|
|
|
976
979
|
return;
|
|
977
980
|
}
|
|
978
981
|
|
|
979
|
-
if (args.requireGates) {
|
|
980
|
-
|
|
981
|
-
if (args.
|
|
982
|
-
|
|
982
|
+
if (args.requireGates || args.requireWriteHook) {
|
|
983
|
+
let writeRequest = null;
|
|
984
|
+
if (args.requireWriteHook) {
|
|
985
|
+
writeRequest = validateHardWriteRequest({
|
|
986
|
+
root: args.root,
|
|
987
|
+
host: args.requireWriteHook,
|
|
988
|
+
tools: [args.requireWriteHook],
|
|
989
|
+
force: true,
|
|
990
|
+
});
|
|
991
|
+
if (!writeRequest.ok) {
|
|
992
|
+
const payload = {
|
|
993
|
+
ok: false,
|
|
994
|
+
error: 'unsupported-enforcement-profile',
|
|
995
|
+
message: writeRequest.error,
|
|
996
|
+
};
|
|
997
|
+
if (args.json) console.log(JSON.stringify(payload, null, 2));
|
|
998
|
+
else console.error(writeRequest.error);
|
|
999
|
+
process.exitCode = 2;
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
const missing = args.requireGates ? missingGates(args.root) : [];
|
|
1005
|
+
if (
|
|
1006
|
+
writeRequest?.host &&
|
|
1007
|
+
!detectWritePathCapabilities(args.root, writeRequest.host).capabilities['hard-write']
|
|
1008
|
+
) {
|
|
1009
|
+
missing.push(`${writeRequest.host} hard-write hook`);
|
|
983
1010
|
}
|
|
984
1011
|
if (missing.length > 0) {
|
|
985
1012
|
const payload = {
|
|
986
1013
|
ok: false,
|
|
987
1014
|
error: 'missing-gates',
|
|
988
1015
|
missing,
|
|
1016
|
+
...(writeRequest?.host ? { writeHost: writeRequest.host } : {}),
|
|
989
1017
|
};
|
|
990
1018
|
if (args.json) {
|
|
991
1019
|
console.log(JSON.stringify(payload, null, 2));
|
|
@@ -994,7 +1022,12 @@ async function main() {
|
|
|
994
1022
|
for (const relativePath of missing) {
|
|
995
1023
|
console.error(` - ${relativePath}`);
|
|
996
1024
|
}
|
|
997
|
-
|
|
1025
|
+
const installArgs = writeRequest?.host
|
|
1026
|
+
? `--install-agent-gates --tools ${writeRequest.host} --require-write-hook ${writeRequest.host}`
|
|
1027
|
+
: '--install-agent-gates';
|
|
1028
|
+
console.error(
|
|
1029
|
+
`\nRun \`${arkCommand(args.root, 'ark', 'init')}\` (or \`${arkCommand(args.root, 'ark-check', installArgs)}\`) to configure enforcement.`
|
|
1030
|
+
);
|
|
998
1031
|
}
|
|
999
1032
|
process.exitCode = 1;
|
|
1000
1033
|
return;
|
|
@@ -1004,7 +1037,12 @@ async function main() {
|
|
|
1004
1037
|
// When --require-gates is the only intent (no config/architecture run needed),
|
|
1005
1038
|
// callers still get a clear signal from the exit code and the human-mode line.
|
|
1006
1039
|
if (!args.json) {
|
|
1007
|
-
|
|
1040
|
+
if (args.requireGates) {
|
|
1041
|
+
console.log('Ark gates present (merge profile): ' + REQUIRED_GATE_FILES.join(', '));
|
|
1042
|
+
}
|
|
1043
|
+
if (writeRequest?.host) {
|
|
1044
|
+
console.log(`Ark hard-write hook present for ${writeRequest.host}.`);
|
|
1045
|
+
}
|
|
1008
1046
|
}
|
|
1009
1047
|
}
|
|
1010
1048
|
|
|
@@ -1277,6 +1315,18 @@ async function main() {
|
|
|
1277
1315
|
}
|
|
1278
1316
|
}
|
|
1279
1317
|
}
|
|
1318
|
+
|
|
1319
|
+
// Best-effort: open the report in the default browser. If it opens, fine;
|
|
1320
|
+
// if not (headless, no GUI, spawn error), do nothing — never fail the check.
|
|
1321
|
+
// Skipped in CI / Vitest / ARK_NO_OPEN_REPORT; --open / --no-open override.
|
|
1322
|
+
if (
|
|
1323
|
+
shouldOpenHtmlReport({
|
|
1324
|
+
force: Boolean(args.openReport),
|
|
1325
|
+
noOpen: Boolean(args.noOpenReport) || Boolean(args.json),
|
|
1326
|
+
})
|
|
1327
|
+
) {
|
|
1328
|
+
openHtmlInBrowser(reportPath);
|
|
1329
|
+
}
|
|
1280
1330
|
}
|
|
1281
1331
|
|
|
1282
1332
|
if (args.json) {
|
|
@@ -1385,10 +1435,19 @@ async function main() {
|
|
|
1385
1435
|
const parts = [];
|
|
1386
1436
|
if (codexHomeGap.missing > 0) parts.push(`${codexHomeGap.missing} missing`);
|
|
1387
1437
|
if (codexHomeGap.stale > 0) parts.push(`${codexHomeGap.stale} outdated`);
|
|
1438
|
+
// Advisory always; when session host is known and not Codex, say so so
|
|
1439
|
+
// /ark-upgrade does not chase home prompts as Incomplete.
|
|
1440
|
+
const activeHost = detectActiveAgentHost();
|
|
1441
|
+
const deferredNote =
|
|
1442
|
+
activeHost != null && activeHost !== 'codex'
|
|
1443
|
+
? ' Deferred unless you use Codex — not a blocker for Grok/Claude/Cursor. '
|
|
1444
|
+
: ' ';
|
|
1388
1445
|
console.log(
|
|
1389
1446
|
color.dim(
|
|
1390
|
-
`/ark-* skills in ${codexPromptsDir()} are behind this Ark (${parts.join(', ')})
|
|
1391
|
-
|
|
1447
|
+
`/ark-* skills in ${codexPromptsDir()} are behind this Ark (${parts.join(', ')}).` +
|
|
1448
|
+
deferredNote +
|
|
1449
|
+
`Codex loads them from $CODEX_HOME/prompts, not the repo. ` +
|
|
1450
|
+
`When using Codex: ${arkCommand(root, 'ark-check', '--install-agent-gates --skills-only --codex-home --force')}`
|
|
1392
1451
|
)
|
|
1393
1452
|
);
|
|
1394
1453
|
}
|
package/bin/ark-mcp.mjs
CHANGED
|
@@ -58,6 +58,7 @@ import {
|
|
|
58
58
|
import { createImportTargetResolver } from './lib/import-resolve.mjs';
|
|
59
59
|
import { validateWithAutoPatch, resolveImportFileAbs } from './lib/auto-patch.mjs';
|
|
60
60
|
import { composePrepareWrite } from './lib/prepare-write.mjs';
|
|
61
|
+
import { loadArkConfigContract } from './lib/config-contract.mjs';
|
|
61
62
|
|
|
62
63
|
const arkCheckBin = fileURLToPath(new URL('./ark-check.mjs', import.meta.url));
|
|
63
64
|
|
|
@@ -120,6 +121,11 @@ function readJson(file, { required } = {}) {
|
|
|
120
121
|
}
|
|
121
122
|
}
|
|
122
123
|
|
|
124
|
+
function readArkConfig(file, { required } = {}) {
|
|
125
|
+
const raw = readJson(file, { required });
|
|
126
|
+
return raw === undefined ? undefined : loadArkConfigContract(raw, file).config;
|
|
127
|
+
}
|
|
128
|
+
|
|
123
129
|
function resolveInRoot(root, maybePath) {
|
|
124
130
|
if (!maybePath) return undefined;
|
|
125
131
|
return path.isAbsolute(maybePath) ? maybePath : path.join(root, maybePath);
|
|
@@ -460,11 +466,11 @@ async function main() {
|
|
|
460
466
|
const ts = await loadOptionalTypeScript();
|
|
461
467
|
|
|
462
468
|
const config =
|
|
463
|
-
(configPath ?
|
|
464
|
-
|
|
465
|
-
layers: [],
|
|
466
|
-
|
|
467
|
-
|
|
469
|
+
(configPath ? readArkConfig(configPath, { required: args.configExplicit }) : undefined) ??
|
|
470
|
+
loadArkConfigContract(
|
|
471
|
+
{ include: ['src'], layers: [], rules: DEFAULT_RULES },
|
|
472
|
+
configPath ?? 'ark.config.json'
|
|
473
|
+
).config;
|
|
468
474
|
if (!config.layers || config.layers.length === 0) {
|
|
469
475
|
process.stderr.write(
|
|
470
476
|
'[ark-mcp] warning: no layers configured — file→layer inference from config patterns ' +
|