arkgate 4.8.26 → 4.8.27

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.
Files changed (61) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +2 -2
  3. package/bin/ark-layer-match.mjs +23 -0
  4. package/bin/ark-mcp-runtime.mjs +45 -4
  5. package/bin/ark-mcp.mjs +9 -3
  6. package/bin/ark.mjs +27 -1
  7. package/bin/lib/analysis-engine.mjs +10 -10
  8. package/bin/lib/arkrules-sensors.mjs +9 -4
  9. package/bin/lib/class-source-scan.mjs +1 -1
  10. package/bin/lib/config-contract.mjs +5 -0
  11. package/bin/lib/prepare-change.mjs +10 -6
  12. package/bin/lib/prepare-write.mjs +7 -0
  13. package/bin/lib/remediation.mjs +84 -0
  14. package/bin/lib/resolved-candidate-facts.mjs +19 -2
  15. package/bin/lib/snippet-analysis.mjs +82 -1
  16. package/dist/chunk-A5EIY3HI.cjs +3 -3
  17. package/dist/chunk-H5I3GARV.js +16 -0
  18. package/dist/chunk-QQ4SJEJ7.cjs +16 -0
  19. package/dist/chunk-SVSQVGFJ.js +3 -3
  20. package/dist/{configTypes-GPvvdleM.d.cts → configTypes-D1oxws6x.d.cts} +6 -0
  21. package/dist/{configTypes-GPvvdleM.d.ts → configTypes-D1oxws6x.d.ts} +6 -0
  22. package/dist/eslint/index.cjs +11 -12
  23. package/dist/eslint/index.d.cts +7 -1
  24. package/dist/eslint/index.d.ts +7 -1
  25. package/dist/eslint/index.js +11 -11
  26. package/dist/index.cjs +32 -32
  27. package/dist/index.d.cts +13 -2
  28. package/dist/index.d.ts +13 -2
  29. package/dist/index.js +32 -32
  30. package/dist/inspectorListen-6DQVJ4DC.js +2 -1
  31. package/dist/inspectorListen-KGMIQ2TF.cjs +2 -1
  32. package/dist/nestjs/index.cjs +1 -1
  33. package/dist/nestjs/index.d.cts +1 -1
  34. package/dist/nestjs/index.d.ts +1 -1
  35. package/dist/nestjs/index.js +1 -1
  36. package/dist/order/index.cjs +1 -1
  37. package/dist/order/index.js +1 -1
  38. package/dist/runtime/index.cjs +10 -10
  39. package/dist/runtime/index.d.cts +4 -3
  40. package/dist/runtime/index.d.ts +4 -3
  41. package/dist/runtime/index.js +10 -10
  42. package/dist/{types-CnauMQQL.d.cts → types-DBiqlOWZ.d.cts} +6 -0
  43. package/dist/{types-CnauMQQL.d.ts → types-DBiqlOWZ.d.ts} +6 -0
  44. package/docs/README.md +2 -1
  45. package/docs/agent-guide.md +3 -2
  46. package/docs/ai-gates.md +1 -1
  47. package/docs/configuration.md +11 -0
  48. package/docs/package-surface.md +2 -1
  49. package/docs/product-voice.md +1 -1
  50. package/package.json +1 -1
  51. package/schemas/ark.config.schema.json +10 -0
  52. package/server.json +2 -2
  53. package/templates/agent-skills/README.md +1 -1
  54. package/templates/agent-skills/ark-adopt/SKILL.md +6 -0
  55. package/templates/agent-skills/ark-autopilot/SKILL.md +1 -1
  56. package/templates/agent-skills/ark-place/SKILL.md +4 -0
  57. package/templates/skills/ark-adopt.md +6 -0
  58. package/templates/skills/ark-autopilot.md +1 -1
  59. package/templates/skills/ark-place.md +4 -0
  60. package/dist/chunk-G3JRBJGH.js +0 -16
  61. package/dist/chunk-R3AJ5RSZ.cjs +0 -16
package/CHANGELOG.md CHANGED
@@ -5,6 +5,23 @@ in the immutable pre-2.0 archive linked below.
5
5
 
6
6
  ## Unreleased
7
7
 
8
+ ## 4.8.27 — 2026-10-14
9
+
10
+ **Patch** over **4.8.26**. First-principles enforcement, public layer entrypoints, and remediation blueprints (P0–P5). **Write. Check. Ship.** **No required config migration.** No `schemaVersion` bump. Does not close `K01` / `Z09`. This mother `ark.config.json` still does **not** turn `arkOrder` on.
11
+
12
+ **Status: released** (tag `v4.8.27`). Run `npm view arkgate@4.8.27 version` to confirm (#314).
13
+
14
+ ### Added
15
+ - `layers[].entrypoints` public surface contract enforced across `ark-check`, `arkgate/eslint`, `AICodeGate`, and `ark-mcp` at the `findDeniedEdgeDecision` waist, with policy-delta classification and config warnings.
16
+ - `ark_place` and `ark_prepare_write` project `entrypoints` and `targetEntrypoints` so agents know which public barrel/entrypoint to import before writing.
17
+ - Deterministic TypeScript remediation `blueprint` (`judgmentBrief.blueprint`) on rejections for `LAYER_IMPORT_VIOLATION` (including `entrypoints`), `FORBIDDEN_GLOBAL`, `CAPABILITY_VIOLATION`, `ARKORDER_*`, and `ARKRUN_*`.
18
+ - `ark init --baseline [path]` freezes existing violations into `.ark-baseline.json` via `--write-baseline` and wires `--baseline` into `--strict-merge` during brownfield onboarding.
19
+ - Immediate 2-hop value cycle detection (`A -> B -> A`) in single-file `Write`/`Edit` hooks and `ark_prepare_write` when `cyclePolicy` is `"strict"`, plus warm resident `ApplyPatch` / `apply_patch` evaluation (`1500ms` window) and incremental `ResolverIngestCache`.
20
+
21
+ ### Fixed
22
+ - `arkRuleSensors` masks comments and strings before evaluating `aggregate-guards-invariants` and `immutable-value-object` method bodies, and recognizes structural `if (...) throw` guard clauses.
23
+ - Enabled `treeshake: true` in `tsup.config.ts` and `packages/runtime/tsup.config.ts`, shrinking compiled runtime bundles.
24
+
8
25
  ## 4.8.26 — 2026-10-13
9
26
 
10
27
  **Patch** over **4.8.25**. #353 and #354. **Write. Check. Ship.** **No required config migration.** No `schemaVersion` bump. Does not close `K01` / `Z09`. This mother `ark.config.json` still does **not** turn `arkOrder` on.
package/README.md CHANGED
@@ -25,11 +25,11 @@ Design the boundary before you implement. Put a small interface in front of the
25
25
 
26
26
  ## Try it
27
27
 
28
- One command. Preview only — no files change. Current release is **4.8.26**
28
+ One command. Preview only — no files change. Current release is **4.8.27**
29
29
  (check npm `latest` with `npm view arkgate version`):
30
30
 
31
31
  ```bash
32
- npx arkgate@4.8.26 start
32
+ npx arkgate@4.8.27 start
33
33
  ```
34
34
 
35
35
  ## What a red result looks like
@@ -1858,6 +1858,29 @@ export function findDeniedEdgeDecision(rules, from, to, options) {
1858
1858
  continue;
1859
1859
  return { rule };
1860
1860
  }
1861
+ if (from !== to &&
1862
+ typeof options?.toPath === 'string' &&
1863
+ options.toPath.length > 0 &&
1864
+ Array.isArray(options?.layers)) {
1865
+ const targetLayer = options.layers.find((layer) => layer.name === to);
1866
+ const entrypoints = Array.isArray(targetLayer?.entrypoints)
1867
+ ? targetLayer.entrypoints.filter((entry) => typeof entry === 'string' && entry.length > 0)
1868
+ : [];
1869
+ if (entrypoints.length > 0) {
1870
+ const normTo = String(options.toPath).split(/[/\\]/).join('/');
1871
+ const matchesEntrypoint = entrypoints.some((pattern) => globToRegExp(pattern).test(normTo));
1872
+ if (!matchesEntrypoint) {
1873
+ return {
1874
+ rule: {
1875
+ from,
1876
+ to,
1877
+ allowed: false,
1878
+ message: `${from} may only import ${to} via its declared entrypoints (${entrypoints.join(', ')}); deep import "${normTo}" bypasses the public surface.`,
1879
+ },
1880
+ };
1881
+ }
1882
+ }
1883
+ }
1861
1884
  return undefined;
1862
1885
  }
1863
1886
  /**
@@ -100,6 +100,7 @@ import {
100
100
  } from './lib/mcp-hook-payload.mjs';
101
101
  import {
102
102
  canonicalizeCandidateChanges,
103
+ createResolverIngestCache,
103
104
  resolvedCompilerInputPaths,
104
105
  resolvedInputIdentities,
105
106
  } from './lib/resolved-candidate-facts.mjs';
@@ -415,8 +416,16 @@ function snippetContext({
415
416
  classification,
416
417
  arkRulesSnapshot,
417
418
  surface,
419
+ resolveImportTarget,
418
420
  }) {
419
- const base = { layer, filePath, ...(surface ? { surface } : {}) };
421
+ const base = {
422
+ root,
423
+ layer,
424
+ filePath,
425
+ cyclePolicy: config?.cyclePolicy ?? 'strict',
426
+ ...(typeof resolveImportTarget === 'function' ? { resolveImportTarget } : {}),
427
+ ...(surface ? { surface } : {}),
428
+ };
420
429
  const arkRulesOn = (arkRulesSnapshot?.arkRules?.structure?.length ?? 0) > 0;
421
430
  const contractErrors = arkRulesSnapshot?.errors ?? [];
422
431
  if (!config?.arkRun && !config?.arkOrder && !arkRulesOn && contractErrors.length === 0) {
@@ -597,9 +606,13 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
597
606
  return;
598
607
  }
599
608
  const patchChangeSet = classifyChangeSet(patchWrites.map((change) => String(change.path)));
609
+ const effectiveContractSession =
610
+ typeof attemptContext?.contractSession === 'boolean'
611
+ ? attemptContext.contractSession
612
+ : contractSessionFrom({});
600
613
  const patchLawGate = evaluateTeamGate({
601
614
  changeSet: patchChangeSet,
602
- contractSession: contractSessionFrom({}),
615
+ contractSession: effectiveContractSession,
603
616
  });
604
617
  if (patchLawGate.deny && patchChangeSet.mixed) {
605
618
  const message = patchLawGate.message;
@@ -737,6 +750,8 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
737
750
  ts,
738
751
  tsconfig: args.tsconfig,
739
752
  manifest: args.projectManifest,
753
+ ingestCache: args.residentIngestCache,
754
+ contractSession: effectiveContractSession,
740
755
  });
741
756
  } catch (error) {
742
757
  const message = error instanceof Error ? error.message : String(error);
@@ -920,6 +935,7 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
920
935
  relFile: normalizedRel,
921
936
  arkRulesSnapshot: args.arkRulesSnapshot,
922
937
  surface: 'hook',
938
+ resolveImportTarget: args.resolveImportTarget,
923
939
  });
924
940
  const validateOnce = (src) =>
925
941
  validateSnippetAnalysis({ gate, ts, source: src, context });
@@ -1224,7 +1240,10 @@ function captureResidentHook(payload, gate, config, args, ts, request) {
1224
1240
  failOnNewSmells: request.failOnNewSmells === true,
1225
1241
  },
1226
1242
  ts,
1227
- { grokHookEvent: request.grokHookEvent === true },
1243
+ {
1244
+ grokHookEvent: request.grokHookEvent === true,
1245
+ contractSession: request.contractSession === true,
1246
+ },
1228
1247
  {
1229
1248
  stdout: (value) => {
1230
1249
  stdout += value;
@@ -1632,6 +1651,10 @@ export async function runArkMcp({ hookInput, hookRoot } = {}) {
1632
1651
  .filter((layer) => layer.name && layer.mayImportInfrastructure === true)
1633
1652
  .map((layer) => layer.name);
1634
1653
 
1654
+ const importTargetResolver = createImportTargetResolver(ts, args.root, config);
1655
+ args.resolveImportTarget = importTargetResolver;
1656
+ args.residentIngestCache = createResolverIngestCache(config);
1657
+
1635
1658
  const gate = ark.createAICodeGate({
1636
1659
  architectureProfile: profile,
1637
1660
  intents,
@@ -1641,10 +1664,11 @@ export async function runArkMcp({ hookInput, hookRoot } = {}) {
1641
1664
  capabilityWalls,
1642
1665
  infrastructureLayers,
1643
1666
  // Contract-first: one resolve step yields layer + relPath for rules + peerIsolation.
1644
- resolveImportTarget: createImportTargetResolver(ts, args.root, config),
1667
+ resolveImportTarget: importTargetResolver,
1645
1668
  architectureLayers: configLayers.map((layer) => ({
1646
1669
  name: layer.name,
1647
1670
  patterns: layer.patterns,
1671
+ ...(Array.isArray(layer.entrypoints) ? { entrypoints: layer.entrypoints } : {}),
1648
1672
  })),
1649
1673
  allowNonLiteralDynamicImport: (filePath) => {
1650
1674
  if (!filePath || !Array.isArray(config.dynamicImportAllowlist)) return false;
@@ -2624,6 +2648,7 @@ export async function runArkMcp({ hookInput, hookRoot } = {}) {
2624
2648
  layer,
2625
2649
  arkRulesSnapshot: effectiveArkRulesSnapshot,
2626
2650
  surface: 'mcp',
2651
+ resolveImportTarget: args.resolveImportTarget,
2627
2652
  });
2628
2653
  const validateOnce = (src) => validateSnippetAnalysis({ gate, ts, source: src, context });
2629
2654
  // W1: attempt mechanical-safe single-file autoPatch (import type), re-validate or discard.
@@ -2874,6 +2899,16 @@ export async function runArkMcp({ hookInput, hookRoot } = {}) {
2874
2899
  rules.some((rule) => !rule.allowed && rule.from === layerName && rule.to === to)
2875
2900
  );
2876
2901
  const mayImport = otherNames.filter((name) => !mustNotImport.includes(name));
2902
+ const targetEntrypoints = Object.fromEntries(
2903
+ configLayers
2904
+ .filter(
2905
+ (layer) =>
2906
+ mayImport.includes(layer.name) &&
2907
+ Array.isArray(layer.entrypoints) &&
2908
+ layer.entrypoints.length > 0
2909
+ )
2910
+ .map((layer) => [layer.name, layer.entrypoints])
2911
+ );
2877
2912
  const sliceCatalog = [
2878
2913
  ...(args.arkRulesSnapshot?.arkRules?.structure ?? []),
2879
2914
  ...(args.arkRulesSnapshot?.arkRules?.invariants ?? []),
@@ -2886,6 +2921,10 @@ export async function runArkMcp({ hookInput, hookRoot } = {}) {
2886
2921
  ...placementDescriptionFields(layerMeta),
2887
2922
  forbiddenGlobals: layerMeta?.forbiddenGlobals ?? [],
2888
2923
  ...(layerMeta?.mayImportInfrastructure ? { mayImportInfrastructure: true } : {}),
2924
+ ...(Array.isArray(layerMeta?.entrypoints) && layerMeta.entrypoints.length > 0
2925
+ ? { entrypoints: layerMeta.entrypoints }
2926
+ : {}),
2927
+ ...(Object.keys(targetEntrypoints).length > 0 ? { targetEntrypoints } : {}),
2889
2928
  ...(sliceRules.length > 0 ? { sliceRules } : {}),
2890
2929
  mayImport,
2891
2930
  mustNotImport,
@@ -2946,6 +2985,7 @@ export async function runArkMcp({ hookInput, hookRoot } = {}) {
2946
2985
  layer,
2947
2986
  arkRulesSnapshot: effectiveArkRulesSnapshot,
2948
2987
  surface: 'mcp',
2988
+ resolveImportTarget: args.resolveImportTarget,
2949
2989
  });
2950
2990
  const validateOnce = (src) => validateSnippetAnalysis({ gate, ts, source: src, context });
2951
2991
  const result = composePrepareWrite({
@@ -2998,6 +3038,7 @@ export async function runArkMcp({ hookInput, hookRoot } = {}) {
2998
3038
  ts,
2999
3039
  tsconfig: args.tsconfig,
3000
3040
  manifest: projectManifest,
3041
+ ingestCache: args.residentIngestCache,
3001
3042
  });
3002
3043
  return validityToolResult(result);
3003
3044
  } catch (error) {
package/bin/ark-mcp.mjs CHANGED
@@ -44,8 +44,7 @@ function launcherArgs(argv) {
44
44
  }
45
45
 
46
46
  function residentEligiblePayload(payload) {
47
- const toolName = payload?.tool_name ?? payload?.toolName;
48
- return !['ApplyPatch', 'apply_patch'].includes(toolName);
47
+ return Boolean(payload && typeof payload === 'object');
49
48
  }
50
49
 
51
50
  async function tryResidentHook(args, hookInput) {
@@ -58,6 +57,8 @@ async function tryResidentHook(args, hookInput) {
58
57
  return { status: 0, stdout: '', stderr: '' };
59
58
  }
60
59
  if (!residentEligiblePayload(payload)) return null;
60
+ const toolName = payload?.tool_name ?? payload?.toolName;
61
+ const isPatch = toolName === 'ApplyPatch' || toolName === 'apply_patch';
61
62
  const endpoint = residentHookEndpoint({
62
63
  root: args.root,
63
64
  config: args.config,
@@ -70,7 +71,9 @@ async function tryResidentHook(args, hookInput) {
70
71
  socket: endpoint.socket,
71
72
  timeoutMs: Number.isFinite(configuredTimeout) && configuredTimeout > 0
72
73
  ? configuredTimeout
73
- : 75,
74
+ : isPatch
75
+ ? 1500
76
+ : 75,
74
77
  request: {
75
78
  protocolVersion: RESIDENT_HOOK_PROTOCOL_VERSION,
76
79
  kind: 'hook',
@@ -81,6 +84,9 @@ async function tryResidentHook(args, hookInput) {
81
84
  hookRepair: args.hookRepair,
82
85
  failOnNewSmells: args.failOnNewSmells,
83
86
  grokHookEvent: Boolean(process.env.GROK_HOOK_EVENT),
87
+ contractSession: /^(?:1|true|yes|on)$/i.test(
88
+ String(process.env.ARK_CONTRACT_SESSION ?? '').trim()
89
+ ),
84
90
  payload,
85
91
  },
86
92
  });
package/bin/ark.mjs CHANGED
@@ -98,7 +98,7 @@ function freezeDayZeroOrigin(root) {
98
98
  /** Commands that read a non-default contract file via --config. */
99
99
  const CONFIG_FLAG_COMMANDS = new Set(['preflight', 'status', 'agents-md', 'agent-projection']);
100
100
 
101
- function parseArgs(argv) {
101
+ export function parseArgs(argv) {
102
102
  const args = {
103
103
  command: undefined,
104
104
  root: process.cwd(),
@@ -119,6 +119,7 @@ function parseArgs(argv) {
119
119
  requireWriteHook: undefined,
120
120
  expectedRoot: undefined,
121
121
  expectedProjectId: undefined,
122
+ baseline: undefined,
122
123
  write: false,
123
124
  check: false,
124
125
  stdout: false,
@@ -144,6 +145,15 @@ function parseArgs(argv) {
144
145
  else if (arg === '--yes' || arg === '-y') args.yes = true;
145
146
  else if (arg === '--force') args.force = true;
146
147
  else if (arg === '--no-strict') args.strict = false;
148
+ else if (arg === '--baseline') {
149
+ const next = argv[i + 1];
150
+ if (next !== undefined && !next.startsWith('-')) {
151
+ args.baseline = next;
152
+ i += 1;
153
+ } else {
154
+ args.baseline = '.ark-baseline.json';
155
+ }
156
+ }
147
157
  else if (arg === '--install') {
148
158
  args.install = true;
149
159
  args.installExplicit = true;
@@ -369,11 +379,27 @@ async function init(args) {
369
379
  if (status !== 0) return status;
370
380
  }
371
381
 
382
+ if (args.baseline) {
383
+ const baselineAbs = path.isAbsolute(args.baseline)
384
+ ? args.baseline
385
+ : path.join(root, args.baseline);
386
+ if (!fs.existsSync(baselineAbs) || args.force) {
387
+ const baselineStatus = runArkCheck(
388
+ ['--root', root, '--config', 'ark.config.json', '--write-baseline', args.baseline],
389
+ { cwd: root }
390
+ );
391
+ if (baselineStatus !== 0) return baselineStatus;
392
+ }
393
+ }
394
+
372
395
  const runStrict =
373
396
  args.strict &&
374
397
  (nonInteractive || (await askYesNo(rl, 'Run strict architecture check now?', true)));
375
398
  if (runStrict) {
376
399
  const strictArgs = ['--root', root, '--config', 'ark.config.json', '--strict-merge'];
400
+ if (args.baseline) {
401
+ strictArgs.push('--baseline', args.baseline);
402
+ }
377
403
  if (args.requireWriteHook) {
378
404
  strictArgs.push('--require-write-hook', args.requireWriteHook);
379
405
  }