arkgate 3.0.5 → 3.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 CHANGED
@@ -4,7 +4,53 @@ All notable changes to ArkGate (`arkgate`; formerly `ark-runtime-kernel`) are do
4
4
 
5
5
  ## Unreleased
6
6
 
7
- No changes are scheduled after 3.0.5.
7
+ ## 3.1.0 — 2026-07-15
8
+
9
+ Deterministic change integrity. **No breaking** CLI or `ark.config.json` changes. **No gate
10
+ weaken.**
11
+
12
+ ### Added
13
+
14
+ - **T01 policy-transition guard:** the public analysis API and generated CLI engine classify
15
+ `ark.config.json` changes as `strengthening`, `neutral`, `judgment-required`, or `weakening`.
16
+ `--strict-merge` compares the Git merge-base contract when available; explicit
17
+ `--policy-base` / `--policy-base-ref` inputs are also supported.
18
+ - **Hash-bound acknowledgement:** weakening and judgment-required findings fail until
19
+ `--policy-ack` supplies the exact base/candidate policy hashes, complete finding-id set, and a
20
+ non-empty reason. Any later contract edit invalidates the acknowledgement.
21
+ - **CI base provenance:** generated workflows fetch full history and the composite Action passes
22
+ the PR/push base SHA through `ARK_POLICY_BASE_REF`.
23
+ - **MCP parity:** `ark_policy_delta` exposes the same read-only classifier for explicit base and
24
+ candidate contracts and returns blocking transitions as tool errors.
25
+ - **T02 atomic change preflight:** public `preflightChange(...)`, CLI
26
+ `ark preflight --changes <change-set.json>`, and MCP `ark_prepare_change` evaluate one complete
27
+ create/update/delete candidate without writing. Schema `1.0` includes per-file content
28
+ fingerprints plus policy, compiler, base-tree, and candidate-tree fingerprints.
29
+ - **Batch safety:** duplicate normalized paths, stale delete targets, lexical root escapes, and
30
+ symlink escapes fail closed; cross-file forbidden edges and cycles are reported before commit.
31
+ - **T03 optional architecture change map:** strict schema `1.0` describes canonical operations,
32
+ resolved layers, and local edges. CLI/MCP preflight binds its deterministic hash; no map is
33
+ installed by default. Both published schema subpaths are parity-checked with the Domain contract.
34
+ - **T04 honest structural convergence:** map-enabled preflight compares the explicit complete
35
+ candidate with the current supplied base through the shared analysis IR. Stable findings separate
36
+ satisfied, missing, contradictory, and unplanned file/edge work; structural drift rejects the
37
+ batch without writes, while every result states behavioral completion was not evaluated.
38
+ - **T05 actionable, context-independent enforcement:** blocking diagnostics expose one deterministic
39
+ `nextAction` across JSON and human output. Doctor and hook repair JSON separate supported,
40
+ installed, active, and bypassable enforcement with evidence and operation coverage; MCP-only and
41
+ locally unverifiable required-status state remain labeled honestly.
42
+ - **Complete-patch hook parity:** governed Codex `ApplyPatch` create/update/delete sets use the same
43
+ atomic preflight as CLI/MCP before per-file safety checks, catching batch-only edges and cycles.
44
+ Codex remains advisory/bypassable at the host level.
45
+ - **Fixed Phase T evaluation:** `npm run eval:change-integrity` proves identical no-context hashes and
46
+ verdicts, CLI/MCP/hook/final diagnostic parity, one concise casual denial, prewritten feature
47
+ acceptance, and strict Ark green without a live LLM or required planning file.
48
+ ### Fixed
49
+
50
+ - **Compatibility/release:** analysis-result `1.1` preserves `1.0` TypeScript values; first-push
51
+ zero SHAs and resumable npm release assets are handled safely.
52
+
53
+ Release note: `docs/releases/3.1.0.md`.
8
54
 
9
55
  ## 3.0.5 — 2026-07-14
10
56
 
package/README.md CHANGED
@@ -136,7 +136,7 @@ npx arkgate-check --install-agent-gates --force
136
136
  npx arkgate-check --doctor
137
137
  ```
138
138
 
139
- Full checklist (CI, MCP, Codex, imports): **[docs/migrate-from-ark-runtime-kernel.md](docs/migrate-from-ark-runtime-kernel.md)**.
139
+ Full checklist (CI, MCP, Codex, imports): **[migrate-from-ark-runtime-kernel.md](https://github.com/pedroknigge/arkgate/blob/main/docs/migrate-from-ark-runtime-kernel.md)**.
140
140
 
141
141
  ---
142
142
 
@@ -199,6 +199,20 @@ constrain + validate + optional `autoPatch` + `judgmentBrief`). PreToolUse hooks
199
199
  never silent write). Cursor/Codex MCP calls remain advisory. See
200
200
  [docs/ai-gates.md](docs/ai-gates.md).
201
201
 
202
+ For a complete multi-file architecture-source candidate, use MCP **`ark_prepare_change`** or
203
+ `ark preflight --changes change-set.json --json`. Creates, updates, and deletes are evaluated as
204
+ one read-only graph, so an edge or cycle that appears only across the batch is rejected before any
205
+ project file is written. With `--change-map map.json` (or MCP `changeMap`), the same verdict also
206
+ classifies planned structure as satisfied, missing, contradictory, or unplanned. This is structural
207
+ convergence only: behavioral completion is always reported as not evaluated.
208
+
209
+ Every blocking diagnostic carries stable rule/location/evidence fields plus one deterministic
210
+ `nextAction`; human CLI/hook text prints that same action. A complete Codex `ApplyPatch` payload is
211
+ reconstructed and sent through the same atomic engine before per-file safety checks. Codex remains
212
+ honestly bypassable/advisory because not every Code Mode write dispatches the project hook. The
213
+ verdict depends only on the explicit contract and candidate—not `AGENTS.md`, skills, injected prose,
214
+ or an LLM.
215
+
202
216
  | Need | Skill | Not |
203
217
  |------|--------|-----|
204
218
  | Only the apply loop for plan **A** (edges) | `/ark-loop` | empty A + design residual → explore |
@@ -245,12 +259,16 @@ ark.config.json
245
259
 
246
260
  - **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.
247
261
  - **Versioned config:** generated contracts include `$schema` + `schemaVersion`; CLI, MCP, and
248
- ESLint validate through the same loader. Unknown keys fail with their JSON path. See the
249
- [configuration and editor guide](docs/configuration.md).
262
+ ESLint validate through the same loader. Unknown keys fail with their JSON path. Strict merge
263
+ also compares the contract transition and blocks unacknowledged weakening with hashes and stable
264
+ finding ids. See the [configuration and editor guide](docs/configuration.md).
250
265
  - **Frameworks:** Nest / Next / express / library layouts get sensible globs on init so day-one coverage is real.
251
266
  - **Brownfield:** baseline ratchet, refuse to freeze a wrong contract, `/ark-adopt` for mature trees.
252
267
  - **Agents:** `ark start` asks for (or detects) one active host and writes one compact router, not copied skill packs, in at most five project files and 25 KB. Use `ark-check --install-agent-gates --skills-only --tools <host>` later when you explicitly want the full `/ark-*` skill set. Reports are opt-in with `ark-check --report`.
253
268
  - **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.
269
+ - **Enforcement ladder (Phase T):** doctor JSON exposes `writePath.enforcementLadder` with separate
270
+ `supported`, `installed`, `active`, `bypassable`, evidence, operation coverage, and required-status
271
+ honesty. Hook repair JSON carries the operation-scoped ladder; MCP alone remains advisory.
254
272
  - **Fail-closed CI (2.11):** `--strict-merge` combines config coverage, shared gate-file
255
273
  presence, and bypass diagnostics for dynamic imports, TypeScript suppressions, explicit `any`
256
274
  casts, InMemory runtime defaults, and disabled peer isolation. `--strict` is a compatibility
@@ -288,12 +306,14 @@ npx arkgate start --tools codex --apply # select the host explicitly
288
306
  npx arkgate start --install --apply # also add arkgate to package.json (explicit only)
289
307
  npx arkgate start --remove-host codex # preview compact-host removal; add --apply to confirm
290
308
  npx arkgate-check --doctor # health + Adoption gaps (not just fitness)
291
- npx arkgate-check --doctor --json # machine-readable doctor.adoption
309
+ npx arkgate-check --doctor --json # adoption + explicit writePath.enforcementLadder
292
310
  npx arkgate-check --strict # fail-closed CI + installed-gate/safety checks
293
311
  npx arkgate-check --plan # safe-to-auto-fix vs your call
294
312
  npx arkgate-check --coverage # Governed: N%
295
313
  npx arkgate-check --report ark-report.html # showcase HTML (opens in browser on local TTY; --no-open to skip)
296
314
  npx arkgate-check --baseline # only NEW violations fail
315
+ npx arkgate preflight --changes changes.json --json # atomic read-only batch verdict
316
+ npx arkgate preflight --changes changes.json --change-map map.json --json # intent hash + structural convergence
297
317
  npx arkgate upgrade # package + gates/skills + MCP/Codex normalize
298
318
  ```
299
319
 
@@ -314,7 +334,7 @@ separate experimental package:
314
334
 
315
335
  ```ts
316
336
  import { createStrictArkKernelFromConfig } from '@arkgate/runtime';
317
- // see docs/production-hardening.md and docs/package-surface.md
337
+ // see the repository production-hardening and package-surface guides
318
338
  ```
319
339
 
320
340
  The stable `arkgate` package does not bundle runtime implementation. The deprecated
@@ -328,7 +348,7 @@ The kernel’s default stores (`InMemoryEventBuffer`, `InMemoryAuditStore`,
328
348
  `InMemoryReadModelStore`, `InMemoryWorkflowStore`) are **reference in-memory only**:
329
349
  fine for tests, demos, and single-process local work — they **do not** survive restarts
330
350
  and are **not** production durability. Implement the store interfaces (or inject your own)
331
- for real systems. Details: [docs/production-hardening.md](docs/production-hardening.md).
351
+ for real systems. Details: [production-hardening.md](https://github.com/pedroknigge/arkgate/blob/main/docs/production-hardening.md).
332
352
 
333
353
  ---
334
354
 
@@ -340,12 +360,12 @@ for real systems. Details: [docs/production-hardening.md](docs/production-harden
340
360
  | **Package surface (stable vs experimental)** | [docs/package-surface.md](docs/package-surface.md) |
341
361
  | Wire Claude / Cursor / Codex / Grok + **ESLint (CI-parity)** | [docs/ai-gates.md](docs/ai-gates.md) |
342
362
  | **TypeScript 5 / 6 / 7 support** | [docs/typescript-support.md](docs/typescript-support.md) |
343
- | Migrate from `ark-runtime-kernel` | [docs/migrate-from-ark-runtime-kernel.md](docs/migrate-from-ark-runtime-kernel.md) |
363
+ | Migrate from `ark-runtime-kernel` | [docs/migrate-from-ark-runtime-kernel.md](https://github.com/pedroknigge/arkgate/blob/main/docs/migrate-from-ark-runtime-kernel.md) |
344
364
  | Messy existing repo | [docs/brownfield-adoption.md](docs/brownfield-adoption.md) |
345
365
  | Agent / MCP tools | [docs/agent-guide.md](docs/agent-guide.md) |
346
366
  | Demos | [docs/demos/](docs/demos/) |
347
367
  | Examples | [examples/](examples/README.md) |
348
- | Release 3.0.0 | [release notes and publication checklist](docs/releases/3.0.0.md) |
368
+ | Latest release (3.1.0) | [release notes](docs/releases/3.1.0.md) · [3.0.0 baseline](docs/releases/3.0.0.md) |
349
369
  | Roadmap | [ROADMAP.md](ROADMAP.md) · [Changelog](CHANGELOG.md) |
350
370
 
351
371
  ---
@@ -363,7 +383,7 @@ npm run check:architecture # Ark gates itself
363
383
  **npm:** [`arkgate`](https://www.npmjs.com/package/arkgate) · formerly `ark-runtime-kernel`
364
384
  **Product:** **ArkGate** — architecture co-pilot / gate for AI TypeScript (not a runtime kernel).
365
385
  CLI: `arkgate` · `arkgate-check` · `arkgate-mcp` (aliases `ark` / `ark-check` / `ark-mcp` still work for one major).
366
- MCP registry: `io.github.pedroknigge/arkgate`.
386
+ MCP registry: [`io.github.pedroknigge/arkgate`](https://registry.modelcontextprotocol.io/) (`server.json` @ package version).
367
387
  **Source:** [github.com/pedroknigge/arkgate](https://github.com/pedroknigge/arkgate)
368
388
 
369
389
  Node ≥ 18 · **MIT**.
package/bin/ark-check.mjs CHANGED
@@ -99,6 +99,7 @@ import {
99
99
  } from './lib/config-warnings.mjs';
100
100
  import { runArchitectureScan } from './lib/architecture-scan.mjs';
101
101
  import { validateHardWriteRequest } from './lib/enforcement-profiles.mjs';
102
+ import { analyzePolicyTransition } from './lib/policy-delta-io.mjs';
102
103
 
103
104
 
104
105
  function parseArgs(argv) {
@@ -110,6 +111,7 @@ function parseArgs(argv) {
110
111
  tsconfig: undefined,
111
112
  json: false,
112
113
  strictConfig: false,
114
+ strictMerge: false,
113
115
  requireGates: false,
114
116
  requireWriteHook: undefined,
115
117
  init: false,
@@ -119,6 +121,9 @@ function parseArgs(argv) {
119
121
  force: false,
120
122
  skillsOnly: false,
121
123
  baseline: undefined,
124
+ policyBase: undefined,
125
+ policyBaseRef: undefined,
126
+ policyAck: undefined,
122
127
  updateBaseline: false,
123
128
  noCache: false,
124
129
  coverage: false,
@@ -149,6 +154,7 @@ function parseArgs(argv) {
149
154
  else if (arg === '--strict' || arg === '--strict-merge') {
150
155
  args.strictConfig = true;
151
156
  args.requireGates = true;
157
+ args.strictMerge = true;
152
158
  }
153
159
  else if (arg === '--strict-config') args.strictConfig = true;
154
160
  else if (arg === '--require-gates') args.requireGates = true;
@@ -205,6 +211,9 @@ function parseArgs(argv) {
205
211
  const next = argv[i + 1];
206
212
  args.baseline = next && !next.startsWith('-') ? argv[++i] : '.ark-baseline.json';
207
213
  }
214
+ else if (arg === '--policy-base') args.policyBase = requireValue(arg, i++);
215
+ else if (arg === '--policy-base-ref') args.policyBaseRef = requireValue(arg, i++);
216
+ else if (arg === '--policy-ack') args.policyAck = requireValue(arg, i++);
208
217
  else if (arg === '--root') args.root = path.resolve(requireValue(arg, i++));
209
218
  else if (arg === '--config') args.config = requireValue(arg, i++);
210
219
  else if (arg === '--manifest') args.manifest = requireValue(arg, i++);
@@ -230,7 +239,7 @@ function usage() {
230
239
  return [
231
240
  'Usage: arkgate-check | ark-check (identical bins; product name ArkGate)',
232
241
  ' ark-check --version',
233
- ' 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]',
242
+ ' ark-check --root <project> --config <ark.config.json> [--manifest <ark.manifest.json>] [--tsconfig <tsconfig.json>] [--strict-merge | --strict | --strict-config] [--policy-base <file> | --policy-base-ref <git-ref>] [--policy-ack <file>] [--require-gates] [--require-write-hook <host>] [--json] [--baseline [file]] [--report [file.html]] [--no-cache]',
234
243
  ' ark-check --coverage [--json] per-layer file counts + full unclassified list (report only, exit 0)',
235
244
  ' ark-check --plan [--json] classified remediation plan (mechanical-safe / judgment / deferred) + goal; report only',
236
245
  ' ark-check --recommend [--json] [--write-plan] application-shape plan; --write-plan emits ark-adoption-plan.json',
@@ -287,6 +296,10 @@ function usage() {
287
296
  'Use --strict-merge for the fail-closed CI profile: --strict-config + --require-gates',
288
297
  'plus the security diagnostics surfaced by doctor. --strict is a compatibility alias.',
289
298
  'This merge profile never depends on an editor/agent hook.',
299
+ 'When a Git merge base is available, --strict-merge classifies the ark.config.json',
300
+ 'transition. Weakening or judgment-required findings fail unless --policy-ack names',
301
+ 'every finding and is bound to both policy hashes. Use --policy-base/--policy-base-ref',
302
+ 'for an explicit comparison; ARK_POLICY_BASE_REF is the CI environment equivalent.',
290
303
  'Add --require-write-hook claude|grok to validate a hard local write boundary for that',
291
304
  'specific host. Cursor and Codex expose advisory MCP tools plus the shared CI check;',
292
305
  'merge blocking requires repository policy to make that status required.',
@@ -1060,6 +1073,15 @@ async function main() {
1060
1073
 
1061
1074
  const root = args.root;
1062
1075
  const config = readConfig(root, args.config);
1076
+ const policyDelta = analyzePolicyTransition({
1077
+ root,
1078
+ configPath: args.config,
1079
+ candidateConfig: config,
1080
+ strictMerge: args.strictMerge,
1081
+ basePath: args.policyBase,
1082
+ baseRef: args.policyBaseRef,
1083
+ acknowledgementPath: args.policyAck,
1084
+ });
1063
1085
  const manifest = readManifest(root, args.manifest);
1064
1086
  const rules = manifest?.architecture?.rules ?? config.rules;
1065
1087
  const files = collectGovernedFiles(root, config);
@@ -1216,7 +1238,9 @@ async function main() {
1216
1238
  // Soft/advisory warnings (failsStrict === false) never fail --strict-config.
1217
1239
  const strictWarnings = warnings.filter((w) => w.failsStrict !== false);
1218
1240
  const ok =
1219
- activeViolations.length === 0 && (!args.strictConfig || strictWarnings.length === 0);
1241
+ activeViolations.length === 0 &&
1242
+ (!args.strictConfig || strictWarnings.length === 0) &&
1243
+ (policyDelta?.valid ?? true);
1220
1244
 
1221
1245
  if (args.plan) {
1222
1246
  const cov = computeCoverage(root, config, files, rules);
@@ -1384,6 +1408,7 @@ async function main() {
1384
1408
  }
1385
1409
  : {}),
1386
1410
  ...(codexRepoSkillGap ? { codexRepoSkillGap } : {}),
1411
+ ...(policyDelta ? { policyDelta } : {}),
1387
1412
  }, null, 2));
1388
1413
  } else {
1389
1414
  for (const warning of warnings) {
@@ -1402,7 +1427,22 @@ async function main() {
1402
1427
  )
1403
1428
  );
1404
1429
  }
1405
- if (activeViolations.length === 0) {
1430
+ if (policyDelta && !policyDelta.valid) {
1431
+ for (const finding of policyDelta.findings.filter(
1432
+ ({ classification }) =>
1433
+ classification === 'weakening' || classification === 'judgment-required'
1434
+ )) {
1435
+ console.error(
1436
+ `${color.red('policy')} ${finding.classification} ${finding.path}: ${finding.message}`
1437
+ );
1438
+ console.error(` Next: ${finding.nextAction}`);
1439
+ }
1440
+ console.error(
1441
+ `Policy transition blocked (${policyDelta.basePolicyHash} → ${policyDelta.candidatePolicyHash}). ` +
1442
+ 'Provide --policy-ack with the exact hashes, finding ids, and a non-empty reason.'
1443
+ );
1444
+ }
1445
+ if (activeViolations.length === 0 && (policyDelta?.valid ?? true)) {
1406
1446
  const advisoryOnly = warnings.length > 0 && strictWarnings.length === 0;
1407
1447
  if (warnings.length === 0) {
1408
1448
  console.log(`${color.green('✔')} Ark check passed.${baselineNote}`);
@@ -1421,7 +1461,9 @@ async function main() {
1421
1461
  }
1422
1462
  } else {
1423
1463
  console.error(
1424
- `${color.red('✖')} ${activeViolations.length} violation(s).${baselineNote}`
1464
+ activeViolations.length > 0
1465
+ ? `${color.red('✖')} ${activeViolations.length} violation(s).${baselineNote}`
1466
+ : `${color.red('✖')} Policy transition rejected.${baselineNote}`
1425
1467
  );
1426
1468
  }
1427
1469