arkgate 4.8.10 → 4.8.11

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
@@ -3,6 +3,38 @@
3
3
  All notable changes to ArkGate (`arkgate`; formerly `ark-runtime-kernel`) are documented here or
4
4
  in the immutable pre-2.0 archive linked below.
5
5
 
6
+ ## 4.8.11 — 2026-09-02
7
+
8
+ **Patch** over **4.8.10**. Nested `--config` with `--root` stays inside that
9
+ tree. Library Shape splits `adapterContract` and `agentProjection` so the AI
10
+ edits one concern at a time. **Write. Check. Ship.** **No required config migration.**
11
+ No `schemaVersion` bump. Does not close `K01` / `Z09`. No new skill names.
12
+
13
+ **Status: prepared** (tree candidate; npm `latest` remains **4.8.10** until the
14
+ signed-tag provenance workflow succeeds).
15
+
16
+ ### Fixed
17
+ - Nested `--config` is a file path: `ark-check --root examples/app --config
18
+ examples/app/ark.config.json` no longer treats the nested config as a
19
+ walk-up filename and no longer scans the invoker `cwd` (`include` is relative
20
+ to `--root`). Basename walk-up still applies when `--config` is
21
+ `ark.config.json`.
22
+ - `doctorResidentWarm` CI budget ceiling is 680ms (~12% headroom after the
23
+ adapterContract split). Still far below one-shot doctor.
24
+
25
+ ### Changed
26
+ - Domain Shape: `adapterContract` types/schema and finding-ref helpers are
27
+ their own modules; the public facade stays under the god-module floors.
28
+ Callers still import `src/domain/adapterContract.ts`. CLI pure helpers follow
29
+ the DF03 split.
30
+ - Domain Shape: `agentProjection` types/markers, body/meta formatters, and
31
+ merge/stamp are their own modules; the public facade re-exports. Callers still
32
+ import `src/domain/agentProjection.ts`. Generated CLI children stay in
33
+ DomainModel so the facade does not import Tooling.
34
+ - Dev-tree pins: `browserslist` 4.28.8 (GHSA-73wf-gq98-2v4g) and `fast-uri`
35
+ 3.1.7 (GHSA-5jgf-p345-68v8 and siblings). These are lock/override pins, not a
36
+ consumer API change.
37
+
6
38
  ## 4.8.10 — 2026-09-02
7
39
 
8
40
  **Patch** over **4.8.9**. Amarilla first-aggregate sensors. Incomplete analysis
@@ -10,8 +42,7 @@ never looks green: an enforced sensor that cannot see its scope is not a pass.
10
42
  **Write. Check. Ship.** **No required config migration.** No `schemaVersion` bump.
11
43
  Does not close `K01` / `Z09`. No new skill names.
12
44
 
13
- **Status: prepared** (tree candidate; npm `latest` remains **4.8.9** until the
14
- signed-tag provenance workflow succeeds).
45
+ **Status: published** (on npm `latest`; see `docs/releases/4.8.10.md`).
15
46
 
16
47
  ### Added
17
48
  - `arkgate report` / `ark report` on the existing dual bins: draft an upstream GitHub issue for
package/README.md CHANGED
@@ -37,7 +37,7 @@ Works with Cursor, Claude, Codex, and Grok.
37
37
 
38
38
  </div>
39
39
 
40
- > **ArkGate 4.8.10** is prepared on this tree; npm `latest` remains **4.8.9**.
40
+ > **ArkGate 4.8.11** is prepared on this tree; npm `latest` remains **4.8.10**.
41
41
  > Write. Check. Ship. Adopted = required GitHub
42
42
  > status running `arkgate-check --strict-merge`, or an explicit `advisory-only` stance.
43
43
  > Status is compact (`arkgate-check --doctor`; `--all` for Details). Optional **ArkRun**
@@ -45,7 +45,7 @@ Works with Cursor, Claude, Codex, and Grok.
45
45
  > (`arkgate/order`) stops the agent rewriting the few slow product decisions as CRUD;
46
46
  > later pattern change is `proposeRelease` then `apply`.
47
47
  > `@arkgate/runtime` is deprecated.
48
- > [4.8.10 prepared](docs/releases/4.8.10.md) · [4.8.9 published](docs/releases/4.8.9.md) · [4.8.8](docs/releases/4.8.8.md) · [4.8.7](docs/releases/4.8.7.md) · [4.8.6](docs/releases/4.8.6.md) · [4.8.5](docs/releases/4.8.5.md) · [4.8.4](docs/releases/4.8.4.md) · [4.8.3](docs/releases/4.8.3.md) · [4.8.2](docs/releases/4.8.2.md) · [4.8.1](docs/releases/4.8.1.md) · [4.8.0](docs/releases/4.8.0.md) · [Docs hub](docs/README.md) · [Voice](docs/product-voice.md)
48
+ > [4.8.11 prepared](docs/releases/4.8.11.md) · [4.8.10 published](docs/releases/4.8.10.md) · [4.8.9](docs/releases/4.8.9.md) · [4.8.8](docs/releases/4.8.8.md) · [4.8.7](docs/releases/4.8.7.md) · [4.8.6](docs/releases/4.8.6.md) · [4.8.5](docs/releases/4.8.5.md) · [4.8.4](docs/releases/4.8.4.md) · [4.8.3](docs/releases/4.8.3.md) · [4.8.2](docs/releases/4.8.2.md) · [4.8.1](docs/releases/4.8.1.md) · [4.8.0](docs/releases/4.8.0.md) · [Docs hub](docs/README.md) · [Voice](docs/product-voice.md)
49
49
 
50
50
  ---
51
51
 
@@ -342,8 +342,9 @@ Rename the three keys. Compact starters leave it off. Details:
342
342
  | Config · package surface · TS | [configuration](docs/configuration.md) · [package-surface](docs/package-surface.md) · [typescript-support](docs/typescript-support.md) |
343
343
  | Brownfield | [docs/brownfield-adoption.md](docs/brownfield-adoption.md) |
344
344
  | Security | [SECURITY.md](SECURITY.md) |
345
- | Prepared (4.8.10; not published) | [docs/releases/4.8.10.md](docs/releases/4.8.10.md) · [CHANGELOG](CHANGELOG.md) |
346
- | Current published (4.8.9 on npm `latest`) | [docs/releases/4.8.9.md](docs/releases/4.8.9.md) · [CHANGELOG](CHANGELOG.md) |
345
+ | Prepared (4.8.11; not published) | [docs/releases/4.8.11.md](docs/releases/4.8.11.md) · [CHANGELOG](CHANGELOG.md) |
346
+ | Current published (4.8.10 on npm `latest`) | [docs/releases/4.8.10.md](docs/releases/4.8.10.md) · [CHANGELOG](CHANGELOG.md) |
347
+ | Prior published (4.8.9) | [docs/releases/4.8.9.md](docs/releases/4.8.9.md) · [CHANGELOG](CHANGELOG.md) |
347
348
  | Prior published (4.8.7) | [docs/releases/4.8.7.md](docs/releases/4.8.7.md) · [CHANGELOG](CHANGELOG.md) |
348
349
  | Prior published (4.8.6) | [docs/releases/4.8.6.md](docs/releases/4.8.6.md) |
349
350
  | Prior published (4.8.5) | [docs/releases/4.8.5.md](docs/releases/4.8.5.md) |
@@ -1066,6 +1066,12 @@ function applyConfigRootWalkUp(args) {
1066
1066
  args.configRoot = effective.configRoot;
1067
1067
  args.configFound = effective.configFound;
1068
1068
  args.writeRoot = effective.writeRoot;
1069
+ // Nested relative --config (`examples/app/ark.config.json`) must stay a resolved
1070
+ // file path under --root. Leaving the relative string makes readConfig join it
1071
+ // again and miss; walk-up used to latch the parent instead.
1072
+ if (effective.configFound && typeof effective.configPath === 'string') {
1073
+ args.config = effective.configPath;
1074
+ }
1069
1075
  if (effective.walkedUp && effective.root !== path.resolve(args.root)) {
1070
1076
  // Read paths (or write + --follow-config-root): adopt discovered config root.
1071
1077
  args.root = effective.root;
package/bin/ark-check.mjs CHANGED
@@ -41,6 +41,9 @@ function residentArgs(argv) {
41
41
  // Same monorepo walk-up as full runtime (NEW-MONOREPO-CWD-WALKUP).
42
42
  const effective = resolveEffectiveProjectRoot(args.root, { configName: args.config });
43
43
  args.configRoot = effective.configRoot;
44
+ if (effective.configFound && typeof effective.configPath === 'string') {
45
+ args.config = effective.configPath;
46
+ }
44
47
  if (effective.walkedUp) {
45
48
  args.root = effective.root;
46
49
  args.configWalkedUp = true;
@@ -0,0 +1,137 @@
1
+ /**
2
+ * GENERATED FILE — do not edit by hand.
3
+ *
4
+ * Canonical algorithm: src/domain/adapterContractTypes.ts
5
+ * Regenerate: node scripts/generate-cli-pure.mjs
6
+ * Drift check: node scripts/generate-cli-pure.mjs --check
7
+ *
8
+ * Pure CLI helper (bin/lib/adapter-contract-types.mjs). Zero Node I/O.
9
+ */
10
+
11
+ /**
12
+ * 1.5 adds stable finding refs on every factory-emitted diagnostic (ACS06):
13
+ * `findingRef`, `targetKey` (baseline-compatible), `docsCodePath`.
14
+ * 1.4 added optional evidence.arkruleId + evidence.arkruleSource (ADR 0012 / AR03).
15
+ */
16
+ export const ARK_ANALYSIS_RESULT_SCHEMA_VERSION = '1.5';
17
+ /** Repo-relative diagnostics docs path (parity with ACS02 diagnostic catalog). */
18
+ export const ADAPTER_DIAGNOSTIC_DOCS_RELATIVE_PATH = 'docs/diagnostics.md';
19
+ export const ARK_ANALYSIS_RESULT_SCHEMA = {
20
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
21
+ $id: 'https://unpkg.com/arkgate@3/schemas/ark.analysis-result.schema.json',
22
+ title: 'ArkGate analysis result',
23
+ type: 'object',
24
+ additionalProperties: false,
25
+ required: [
26
+ 'schemaVersion',
27
+ 'mode',
28
+ 'valid',
29
+ 'completeness',
30
+ 'completenessReasons',
31
+ 'diagnostics',
32
+ ],
33
+ allOf: [
34
+ {
35
+ if: {
36
+ properties: { completeness: { enum: ['partial', 'unavailable'] } },
37
+ required: ['completeness'],
38
+ },
39
+ then: { properties: { valid: { const: false } } },
40
+ },
41
+ {
42
+ if: {
43
+ properties: {
44
+ mode: { const: 'resolved-candidate-facts' },
45
+ completeness: { enum: ['complete', 'partial'] },
46
+ },
47
+ required: ['mode', 'completeness'],
48
+ },
49
+ then: {
50
+ required: ['policyHash', 'resolverIdentity', 'factsHash', 'candidateTreeHash'],
51
+ },
52
+ },
53
+ {
54
+ if: {
55
+ properties: { completeness: { const: 'complete' } },
56
+ required: ['completeness'],
57
+ },
58
+ then: { properties: { completenessReasons: { maxItems: 0 } } },
59
+ else: { properties: { completenessReasons: { minItems: 1 } } },
60
+ },
61
+ ],
62
+ properties: {
63
+ schemaVersion: { const: ARK_ANALYSIS_RESULT_SCHEMA_VERSION },
64
+ mode: { enum: ['lexical-compatibility', 'resolved-candidate-facts'] },
65
+ valid: { type: 'boolean' },
66
+ completeness: { enum: ['complete', 'partial', 'unavailable'] },
67
+ completenessReasons: {
68
+ type: 'array',
69
+ items: {
70
+ type: 'object',
71
+ additionalProperties: false,
72
+ required: ['code', 'message'],
73
+ properties: {
74
+ code: { type: 'string', minLength: 1 },
75
+ message: { type: 'string', minLength: 1 },
76
+ file: { type: 'string', minLength: 1 },
77
+ },
78
+ },
79
+ },
80
+ policyHash: { type: 'string', minLength: 1 },
81
+ resolverIdentity: { type: 'string', minLength: 1 },
82
+ factsHash: { type: 'string', minLength: 1 },
83
+ candidateTreeHash: { type: 'string', minLength: 1 },
84
+ diagnostics: {
85
+ type: 'array',
86
+ items: {
87
+ type: 'object',
88
+ additionalProperties: false,
89
+ required: ['ruleId', 'severity', 'message', 'location', 'evidence'],
90
+ properties: {
91
+ ruleId: { type: 'string', minLength: 1 },
92
+ severity: { enum: ['error', 'warning'] },
93
+ message: { type: 'string', minLength: 1 },
94
+ location: {
95
+ type: 'object',
96
+ additionalProperties: false,
97
+ required: ['file', 'line', 'column'],
98
+ properties: {
99
+ file: { type: 'string', minLength: 1 },
100
+ line: { type: 'integer', minimum: 1 },
101
+ column: { type: 'integer', minimum: 1 },
102
+ },
103
+ },
104
+ evidence: {
105
+ type: 'object',
106
+ additionalProperties: false,
107
+ properties: {
108
+ target: { type: 'string' },
109
+ fromLayer: { type: 'string' },
110
+ toLayer: { type: 'string' },
111
+ typeOnly: { type: 'boolean' },
112
+ targetTypeOnlyExports: { type: 'boolean' },
113
+ sourcePureTypeModule: { type: 'boolean' },
114
+ namedBindingsTypeOnly: { type: 'boolean' },
115
+ portProofEligible: { type: 'boolean' },
116
+ peerIsolation: { type: 'boolean' },
117
+ capability: { type: 'string', minLength: 1 },
118
+ edgeKind: { type: 'string', minLength: 1 },
119
+ arkruleId: { type: 'string', minLength: 1 },
120
+ arkruleSource: { type: 'string', minLength: 1 },
121
+ },
122
+ },
123
+ nextAction: { type: 'string', minLength: 1 },
124
+ /** ACS06 — compact multi-turn id; always derived from targetKey when emitted. */
125
+ findingRef: { type: 'string', minLength: 1, pattern: '^fnv1a-[0-9a-f]{8}$' },
126
+ /**
127
+ * ACS06 — baseline-compatible freeze identity
128
+ * (`ruleId|file|from|to|target` with optional `#N` occurrence suffix).
129
+ */
130
+ targetKey: { type: 'string', minLength: 1 },
131
+ /** ACS06 — package-relative diagnostics anchor path. */
132
+ docsCodePath: { type: 'string', minLength: 1 },
133
+ },
134
+ },
135
+ },
136
+ },
137
+ };
@@ -9,73 +9,16 @@
9
9
  */
10
10
 
11
11
  import { deterministicNextAction } from './remediation.mjs';
12
- /** Versioned public result contract shared by every ArkGate enforcement adapter. */
13
- /**
14
- * 1.5 adds stable finding refs on every factory-emitted diagnostic (ACS06):
15
- * `findingRef`, `targetKey` (baseline-compatible), `docsCodePath`.
16
- * 1.4 added optional evidence.arkruleId + evidence.arkruleSource (ADR 0012 / AR03).
17
- */
18
- export const ARK_ANALYSIS_RESULT_SCHEMA_VERSION = '1.5';
19
- /** Repo-relative diagnostics docs path (parity with ACS02 diagnostic catalog). */
20
- export const ADAPTER_DIAGNOSTIC_DOCS_RELATIVE_PATH = 'docs/diagnostics.md';
12
+ import { ARK_ANALYSIS_RESULT_SCHEMA_VERSION, } from './adapter-contract-types.mjs';
13
+ import { adapterDocsCodePath, adapterFindingOccurrenceTargetKeys, adapterFindingRefFromTargetKey, adapterFindingTargetKey, } from './adapter-finding-refs.mjs';
14
+ export { ADAPTER_DIAGNOSTIC_DOCS_RELATIVE_PATH, ARK_ANALYSIS_RESULT_SCHEMA, ARK_ANALYSIS_RESULT_SCHEMA_VERSION, } from './adapter-contract-types.mjs';
15
+ export { adapterDocsCodePath, adapterFindingOccurrenceTargetKeys, adapterFindingRefFromTargetKey, adapterFindingTargetKey, } from './adapter-finding-refs.mjs';
21
16
  function text(value) {
22
17
  return typeof value === 'string' && value.length > 0 ? value : undefined;
23
18
  }
24
19
  function positiveInteger(value, fallback) {
25
20
  return Number.isInteger(value) && Number(value) > 0 ? Number(value) : fallback;
26
21
  }
27
- /**
28
- * Baseline-compatible target key for a violation input.
29
- * Field order and empty-string fallbacks **must** match `baselineKey` in
30
- * `baselineKey.ts` — parity tests guard this so finding refs never orphan freezes.
31
- *
32
- * Note: uses raw ruleId/file strings (including empty) the same way baseline does;
33
- * display `ruleId` / `location.file` may still normalize to ARK_UNKNOWN / `<unknown>`.
34
- */
35
- export function adapterFindingTargetKey(violation) {
36
- const ruleId = typeof violation.ruleId === 'string'
37
- ? violation.ruleId
38
- : typeof violation.code === 'string'
39
- ? violation.code
40
- : undefined;
41
- const file = typeof violation.file === 'string' ? violation.file : undefined;
42
- const fromLayer = typeof violation.fromLayer === 'string' ? violation.fromLayer : undefined;
43
- const toLayer = typeof violation.toLayer === 'string' ? violation.toLayer : undefined;
44
- const target = typeof violation.target === 'string' ? violation.target : undefined;
45
- return [
46
- ruleId,
47
- file,
48
- fromLayer ?? '',
49
- toLayer ?? '',
50
- target ?? '',
51
- ].join('|');
52
- }
53
- /**
54
- * Occurrence-aware target keys for a violation list (parity with baselineOccurrenceKeys).
55
- * First occurrence keeps the historical base key; duplicates get `#N`.
56
- */
57
- export function adapterFindingOccurrenceTargetKeys(violations) {
58
- const counts = new Map();
59
- return violations.map((violation) => {
60
- const base = adapterFindingTargetKey(violation);
61
- const occurrence = (counts.get(base) ?? 0) + 1;
62
- counts.set(base, occurrence);
63
- return occurrence === 1 ? base : `${base}#${occurrence}`;
64
- });
65
- }
66
- /** FNV-1a finding ref from a baseline-compatible targetKey (not a security hash). */
67
- export function adapterFindingRefFromTargetKey(targetKey) {
68
- let hash = 0x811c9dc5;
69
- for (let index = 0; index < targetKey.length; index += 1) {
70
- hash ^= targetKey.charCodeAt(index);
71
- hash = Math.imul(hash, 0x01000193);
72
- }
73
- return `fnv1a-${(hash >>> 0).toString(16).padStart(8, '0')}`;
74
- }
75
- /** Package-relative docs path with fragment for a public ruleId. */
76
- export function adapterDocsCodePath(ruleId) {
77
- return `${ADAPTER_DIAGNOSTIC_DOCS_RELATIVE_PATH}#${ruleId}`;
78
- }
79
22
  function nextActionForDiagnostic(ruleId, evidence, violation) {
80
23
  return deterministicNextAction({
81
24
  ruleId,
@@ -240,122 +183,3 @@ export function createAdapterResult(input) {
240
183
  }
241
184
  return { ...base, mode, valid: false, completeness, ...evidence };
242
185
  }
243
- export const ARK_ANALYSIS_RESULT_SCHEMA = {
244
- $schema: 'https://json-schema.org/draft/2020-12/schema',
245
- $id: 'https://unpkg.com/arkgate@3/schemas/ark.analysis-result.schema.json',
246
- title: 'ArkGate analysis result',
247
- type: 'object',
248
- additionalProperties: false,
249
- required: [
250
- 'schemaVersion',
251
- 'mode',
252
- 'valid',
253
- 'completeness',
254
- 'completenessReasons',
255
- 'diagnostics',
256
- ],
257
- allOf: [
258
- {
259
- if: {
260
- properties: { completeness: { enum: ['partial', 'unavailable'] } },
261
- required: ['completeness'],
262
- },
263
- then: { properties: { valid: { const: false } } },
264
- },
265
- {
266
- if: {
267
- properties: {
268
- mode: { const: 'resolved-candidate-facts' },
269
- completeness: { enum: ['complete', 'partial'] },
270
- },
271
- required: ['mode', 'completeness'],
272
- },
273
- then: {
274
- required: ['policyHash', 'resolverIdentity', 'factsHash', 'candidateTreeHash'],
275
- },
276
- },
277
- {
278
- if: {
279
- properties: { completeness: { const: 'complete' } },
280
- required: ['completeness'],
281
- },
282
- then: { properties: { completenessReasons: { maxItems: 0 } } },
283
- else: { properties: { completenessReasons: { minItems: 1 } } },
284
- },
285
- ],
286
- properties: {
287
- schemaVersion: { const: ARK_ANALYSIS_RESULT_SCHEMA_VERSION },
288
- mode: { enum: ['lexical-compatibility', 'resolved-candidate-facts'] },
289
- valid: { type: 'boolean' },
290
- completeness: { enum: ['complete', 'partial', 'unavailable'] },
291
- completenessReasons: {
292
- type: 'array',
293
- items: {
294
- type: 'object',
295
- additionalProperties: false,
296
- required: ['code', 'message'],
297
- properties: {
298
- code: { type: 'string', minLength: 1 },
299
- message: { type: 'string', minLength: 1 },
300
- file: { type: 'string', minLength: 1 },
301
- },
302
- },
303
- },
304
- policyHash: { type: 'string', minLength: 1 },
305
- resolverIdentity: { type: 'string', minLength: 1 },
306
- factsHash: { type: 'string', minLength: 1 },
307
- candidateTreeHash: { type: 'string', minLength: 1 },
308
- diagnostics: {
309
- type: 'array',
310
- items: {
311
- type: 'object',
312
- additionalProperties: false,
313
- required: ['ruleId', 'severity', 'message', 'location', 'evidence'],
314
- properties: {
315
- ruleId: { type: 'string', minLength: 1 },
316
- severity: { enum: ['error', 'warning'] },
317
- message: { type: 'string', minLength: 1 },
318
- location: {
319
- type: 'object',
320
- additionalProperties: false,
321
- required: ['file', 'line', 'column'],
322
- properties: {
323
- file: { type: 'string', minLength: 1 },
324
- line: { type: 'integer', minimum: 1 },
325
- column: { type: 'integer', minimum: 1 },
326
- },
327
- },
328
- evidence: {
329
- type: 'object',
330
- additionalProperties: false,
331
- properties: {
332
- target: { type: 'string' },
333
- fromLayer: { type: 'string' },
334
- toLayer: { type: 'string' },
335
- typeOnly: { type: 'boolean' },
336
- targetTypeOnlyExports: { type: 'boolean' },
337
- sourcePureTypeModule: { type: 'boolean' },
338
- namedBindingsTypeOnly: { type: 'boolean' },
339
- portProofEligible: { type: 'boolean' },
340
- peerIsolation: { type: 'boolean' },
341
- capability: { type: 'string', minLength: 1 },
342
- edgeKind: { type: 'string', minLength: 1 },
343
- arkruleId: { type: 'string', minLength: 1 },
344
- arkruleSource: { type: 'string', minLength: 1 },
345
- },
346
- },
347
- nextAction: { type: 'string', minLength: 1 },
348
- /** ACS06 — compact multi-turn id; always derived from targetKey when emitted. */
349
- findingRef: { type: 'string', minLength: 1, pattern: '^fnv1a-[0-9a-f]{8}$' },
350
- /**
351
- * ACS06 — baseline-compatible freeze identity
352
- * (`ruleId|file|from|to|target` with optional `#N` occurrence suffix).
353
- */
354
- targetKey: { type: 'string', minLength: 1 },
355
- /** ACS06 — package-relative diagnostics anchor path. */
356
- docsCodePath: { type: 'string', minLength: 1 },
357
- },
358
- },
359
- },
360
- },
361
- };
@@ -0,0 +1,63 @@
1
+ /**
2
+ * GENERATED FILE — do not edit by hand.
3
+ *
4
+ * Canonical algorithm: src/domain/adapterFindingRefs.ts
5
+ * Regenerate: node scripts/generate-cli-pure.mjs
6
+ * Drift check: node scripts/generate-cli-pure.mjs --check
7
+ *
8
+ * Pure CLI helper (bin/lib/adapter-finding-refs.mjs). Zero Node I/O.
9
+ */
10
+
11
+ import { ADAPTER_DIAGNOSTIC_DOCS_RELATIVE_PATH, } from './adapter-contract-types.mjs';
12
+ /**
13
+ * Baseline-compatible target key for a violation input.
14
+ * Field order and empty-string fallbacks **must** match `baselineKey` in
15
+ * `baselineKey.ts` — parity tests guard this so finding refs never orphan freezes.
16
+ *
17
+ * Note: uses raw ruleId/file strings (including empty) the same way baseline does;
18
+ * display `ruleId` / `location.file` may still normalize to ARK_UNKNOWN / `<unknown>`.
19
+ */
20
+ export function adapterFindingTargetKey(violation) {
21
+ const ruleId = typeof violation.ruleId === 'string'
22
+ ? violation.ruleId
23
+ : typeof violation.code === 'string'
24
+ ? violation.code
25
+ : undefined;
26
+ const file = typeof violation.file === 'string' ? violation.file : undefined;
27
+ const fromLayer = typeof violation.fromLayer === 'string' ? violation.fromLayer : undefined;
28
+ const toLayer = typeof violation.toLayer === 'string' ? violation.toLayer : undefined;
29
+ const target = typeof violation.target === 'string' ? violation.target : undefined;
30
+ return [
31
+ ruleId,
32
+ file,
33
+ fromLayer ?? '',
34
+ toLayer ?? '',
35
+ target ?? '',
36
+ ].join('|');
37
+ }
38
+ /**
39
+ * Occurrence-aware target keys for a violation list (parity with baselineOccurrenceKeys).
40
+ * First occurrence keeps the historical base key; duplicates get `#N`.
41
+ */
42
+ export function adapterFindingOccurrenceTargetKeys(violations) {
43
+ const counts = new Map();
44
+ return violations.map((violation) => {
45
+ const base = adapterFindingTargetKey(violation);
46
+ const occurrence = (counts.get(base) ?? 0) + 1;
47
+ counts.set(base, occurrence);
48
+ return occurrence === 1 ? base : `${base}#${occurrence}`;
49
+ });
50
+ }
51
+ /** FNV-1a finding ref from a baseline-compatible targetKey (not a security hash). */
52
+ export function adapterFindingRefFromTargetKey(targetKey) {
53
+ let hash = 0x811c9dc5;
54
+ for (let index = 0; index < targetKey.length; index += 1) {
55
+ hash ^= targetKey.charCodeAt(index);
56
+ hash = Math.imul(hash, 0x01000193);
57
+ }
58
+ return `fnv1a-${(hash >>> 0).toString(16).padStart(8, '0')}`;
59
+ }
60
+ /** Package-relative docs path with fragment for a public ruleId. */
61
+ export function adapterDocsCodePath(ruleId) {
62
+ return `${ADAPTER_DIAGNOSTIC_DOCS_RELATIVE_PATH}#${ruleId}`;
63
+ }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * GENERATED FILE — do not edit by hand.
3
+ *
4
+ * Canonical algorithm: src/domain/agentProjectionFormatters.ts
5
+ * Regenerate: node scripts/generate-cli-pure.mjs
6
+ * Drift check: node scripts/generate-cli-pure.mjs --check
7
+ *
8
+ * Pure CLI helper (bin/lib/agent-projection-formatters.mjs). Zero Node I/O.
9
+ */
10
+
11
+ import { AGENT_PROJECTION_END_MARKER, AGENT_PROJECTION_ENFORCEMENT_SURFACES, AGENT_PROJECTION_NON_ENFORCEMENT_LABEL, ARK_AGENT_PROJECTION_SCHEMA_VERSION, } from './agent-projection-types.mjs';
12
+ /** FNV-1a identity — portable, no Node crypto (same family as stableHash). */
13
+ export function agentProjectionContentIdentity(body) {
14
+ const normalized = String(body ?? '').replace(/\r\n/g, '\n');
15
+ let hash = 0x811c9dc5;
16
+ for (let index = 0; index < normalized.length; index += 1) {
17
+ hash ^= normalized.charCodeAt(index);
18
+ hash = Math.imul(hash, 0x01000193);
19
+ }
20
+ return `fnv1a-${(hash >>> 0).toString(16).padStart(8, '0')}`;
21
+ }
22
+ export function normalizeNewlines(text) {
23
+ return String(text ?? '').replace(/\r\n/g, '\n');
24
+ }
25
+ export function ensureTrailingNewline(text) {
26
+ const normalized = normalizeNewlines(text);
27
+ return normalized.endsWith('\n') ? normalized : `${normalized}\n`;
28
+ }
29
+ export function safeVersion(version) {
30
+ if (typeof version !== 'string' || version.trim().length === 0)
31
+ return 'unknown';
32
+ // Avoid breaking HTML comments / markdown with control chars.
33
+ return version.trim().replace(/[>\s]/g, '');
34
+ }
35
+ function resolveProfile(profile) {
36
+ return profile === 'compact' ? 'compact' : 'full';
37
+ }
38
+ /**
39
+ * Build the managed begin marker line (includes version + nonAuthoritative stamp).
40
+ */
41
+ export function buildAgentProjectionBeginMarker(facts) {
42
+ const version = safeVersion(facts.arkgateVersion);
43
+ const schema = typeof facts.schemaVersion === 'string' && facts.schemaVersion.trim()
44
+ ? facts.schemaVersion.trim()
45
+ : ARK_AGENT_PROJECTION_SCHEMA_VERSION;
46
+ return `<!-- arkgate:agent-projection:begin schema=${schema} arkgateVersion=${version} nonAuthoritative=true -->`;
47
+ }
48
+ /**
49
+ * Layer placement rows for the projection (compact markdown table).
50
+ */
51
+ export function formatAgentProjectionLayers(layers) {
52
+ if (!Array.isArray(layers) || layers.length === 0) {
53
+ return '_No project layers loaded — read `ark.config.json` or run `ark start` / `ark_manifest`._';
54
+ }
55
+ const rows = layers
56
+ .map((layer) => {
57
+ const name = layer.name?.trim() || 'Unknown';
58
+ const patternList = layer.patterns ?? [];
59
+ const prefixList = layer.intentPrefixes ?? [];
60
+ const patterns = patternList.map((pattern) => `\`${pattern}\``).join(', ') || '—';
61
+ const prefixes = prefixList.map((prefix) => `\`${prefix}\``).join(', ') || '—';
62
+ return `| ${name} | ${patterns} | ${prefixes} |`;
63
+ })
64
+ .join('\n');
65
+ return `| Layer | Patterns | Intent prefixes |
66
+ |-------|----------|-----------------|
67
+ ${rows}`;
68
+ }
69
+ /**
70
+ * Catalog short-list bullets (ruleId + title). Empty list → pointer only.
71
+ */
72
+ export function formatAgentProjectionCatalogShortList(entries, docsPath) {
73
+ const path = docsPath.trim() || 'docs/diagnostics.md';
74
+ if (!Array.isArray(entries) || entries.length === 0) {
75
+ return `Full public codes: \`${path}\` (and package \`DIAGNOSTIC_CATALOG\`).`;
76
+ }
77
+ const lines = entries
78
+ .filter((entry) => entry && typeof entry.ruleId === 'string' && entry.ruleId.length > 0)
79
+ .map((entry) => {
80
+ const title = typeof entry.title === 'string' && entry.title.trim() ? entry.title.trim() : entry.ruleId;
81
+ return `- \`${entry.ruleId}\` — ${title}`;
82
+ });
83
+ return `${lines.join('\n')}
84
+
85
+ Full catalog: \`${path}\` (\`#RULE_ID\` anchors).`;
86
+ }
87
+ /**
88
+ * Projection **body** only (no begin/end markers). Used for content-identity.
89
+ */
90
+ export function buildAgentProjectionBody(facts) {
91
+ const version = safeVersion(facts.arkgateVersion);
92
+ const profile = resolveProfile(facts.profile);
93
+ const checkCommand = typeof facts.checkCommand === 'string' && facts.checkCommand.trim()
94
+ ? facts.checkCommand.trim()
95
+ : 'ark-check --strict-config';
96
+ const docsPath = typeof facts.diagnosticsDocsPath === 'string' && facts.diagnosticsDocsPath.trim()
97
+ ? facts.diagnosticsDocsPath.trim()
98
+ : 'docs/diagnostics.md';
99
+ const hostRaw = typeof facts.host === 'string' ? facts.host.trim().toLowerCase() : '';
100
+ const host = hostRaw && hostRaw !== 'unknown' ? hostRaw : null;
101
+ const layers = Array.isArray(facts.layers) ? facts.layers : [];
102
+ const catalog = Array.isArray(facts.catalogShortList) ? facts.catalogShortList : [];
103
+ const lines = [
104
+ '## ArkGate agent contract projection',
105
+ '',
106
+ AGENT_PROJECTION_NON_ENFORCEMENT_LABEL,
107
+ '',
108
+ `- **arkgateVersion:** \`${version}\` (must match the installed package; regenerate with \`ark agents-md --write\` after upgrade)`,
109
+ `- **projectionSchema:** \`${ARK_AGENT_PROJECTION_SCHEMA_VERSION}\``,
110
+ `- **profile:** \`${profile}\`${host ? ` · **host:** \`${host}\`` : ''}`,
111
+ `- **after edits:** \`${checkCommand}\``,
112
+ '',
113
+ ];
114
+ if (profile === 'compact') {
115
+ lines.push('### Primary path', '', '1. Run doctor (`ark-check --doctor`) — what is wrong and what to do first. Prefer the project-local CLI; do not wait on MCP “still connecting”.', '2. Name leftover work in plain language; never “done” on green imports alone while leftover design work remains.', '3. Identity handshake is optional when the CLI already resolved the project root. Call `ark_identity` only when using MCP evidence.', '4. Read the rules file with `ark_manifest` (same expectation) or the local `ark.config.json`. `ark://manifest` is compatibility-only / unverified.', '5. Place files inside configured layers; validate; run the check command above on violations — fix the import, do not weaken the rules file.', '6. Single door: illegal imports → fix; leftover design work → map then one small refactor with user OK.', '', '### Layers (summary)', '', formatAgentProjectionLayers(layers), '');
116
+ }
117
+ else {
118
+ lines.push('### Contract layers', '', formatAgentProjectionLayers(layers), '', 'When creating a **new** kind of code that no layer covers, update `ark.config.json` first (`/ark-adopt`), then place the file.', '', '### Diagnostic codes (short list)', '', formatAgentProjectionCatalogShortList(catalog, docsPath), '', '### Session truth', '', '- Machine snapshot: `ark status --json` (or MCP `ark_status`) — identity, activation honesty, last check, residual counts. **Not a score.**', '- Authoritative contract: local `ark.config.json` / CLI, or `ark_manifest` after a matched `ark_identity` handshake. Identity is optional when CLI already resolved the root.', '- Host docs: the same projection schema is merged into `AGENTS.md` and `CLAUDE.md` (`ark agents-md --write`).', '');
119
+ }
120
+ lines.push('### Enforcement surfaces (authoritative)', '', AGENT_PROJECTION_ENFORCEMENT_SURFACES.map((surface) => `- \`${surface}\``).join('\n'), '');
121
+ return lines.join('\n').replace(/\n{3,}/g, '\n\n').trimEnd() + '\n';
122
+ }
123
+ /**
124
+ * Full managed block: begin marker + body + end marker.
125
+ */
126
+ export function buildAgentProjectionBlock(facts) {
127
+ const body = buildAgentProjectionBody(facts);
128
+ const begin = buildAgentProjectionBeginMarker({
129
+ arkgateVersion: facts.arkgateVersion,
130
+ schemaVersion: ARK_AGENT_PROJECTION_SCHEMA_VERSION,
131
+ });
132
+ return `${begin}\n${body}${AGENT_PROJECTION_END_MARKER}\n`;
133
+ }
134
+ /**
135
+ * Machine meta for CLI `--json` / tests (never a gate input).
136
+ */
137
+ export function buildAgentProjectionMeta(facts) {
138
+ const body = buildAgentProjectionBody(facts);
139
+ const layers = Array.isArray(facts.layers) ? facts.layers : [];
140
+ const catalog = Array.isArray(facts.catalogShortList) ? facts.catalogShortList : [];
141
+ return {
142
+ schemaVersion: ARK_AGENT_PROJECTION_SCHEMA_VERSION,
143
+ arkgateVersion: safeVersion(facts.arkgateVersion),
144
+ nonAuthoritative: true,
145
+ enforcementSurfaces: [...AGENT_PROJECTION_ENFORCEMENT_SURFACES],
146
+ contentIdentity: agentProjectionContentIdentity(body),
147
+ layerCount: layers.length,
148
+ catalogCodeCount: catalog.filter((entry) => entry?.ruleId).length,
149
+ profile: resolveProfile(facts.profile),
150
+ };
151
+ }