arkgate 4.2.1 → 4.3.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +59 -3
  2. package/README.md +19 -6
  3. package/bin/ark-mcp-runtime.mjs +64 -0
  4. package/bin/ark.mjs +55 -1
  5. package/bin/lib/adapter-contract.mjs +88 -5
  6. package/bin/lib/agent-projection-command.mjs +396 -0
  7. package/bin/lib/agent-projection.mjs +319 -0
  8. package/bin/lib/agent-skills-package.mjs +266 -0
  9. package/bin/lib/baseline-key.mjs +32 -0
  10. package/bin/lib/ci-and-commands.mjs +44 -0
  11. package/bin/lib/diagnostic-catalog.mjs +155 -0
  12. package/bin/lib/status-command.mjs +369 -0
  13. package/bin/lib/status-manifest.mjs +394 -0
  14. package/dist/eslint/index.cjs +3 -3
  15. package/dist/eslint/index.js +3 -3
  16. package/dist/index.cjs +46 -11
  17. package/dist/index.d.ts +729 -6
  18. package/dist/index.js +46 -11
  19. package/docs/README.md +6 -6
  20. package/docs/agent-guide.md +112 -14
  21. package/docs/configuration.md +7 -0
  22. package/docs/develop.md +8 -0
  23. package/docs/diagnostics.md +606 -0
  24. package/docs/package-surface.md +18 -8
  25. package/docs/product-voice.md +45 -0
  26. package/docs/use.md +23 -0
  27. package/package.json +7 -1
  28. package/schemas/ark.analysis-result.schema.json +14 -1
  29. package/schemas/ark.status-manifest.schema.json +244 -0
  30. package/server.json +2 -2
  31. package/templates/agent-skills/README.md +59 -0
  32. package/templates/agent-skills/ark-adopt/SKILL.md +171 -0
  33. package/templates/agent-skills/ark-architect/SKILL.md +175 -0
  34. package/templates/agent-skills/ark-autopilot/SKILL.md +242 -0
  35. package/templates/agent-skills/ark-contract/SKILL.md +136 -0
  36. package/templates/agent-skills/ark-coverage/SKILL.md +167 -0
  37. package/templates/agent-skills/ark-explain/SKILL.md +210 -0
  38. package/templates/agent-skills/ark-explore/SKILL.md +377 -0
  39. package/templates/agent-skills/ark-fix/SKILL.md +185 -0
  40. package/templates/agent-skills/ark-loop/SKILL.md +180 -0
  41. package/templates/agent-skills/ark-place/SKILL.md +162 -0
  42. package/templates/agent-skills/ark-runtime/SKILL.md +120 -0
  43. package/templates/agent-skills/ark-think/SKILL.md +133 -0
  44. package/templates/agent-skills/ark-upgrade/SKILL.md +218 -0
package/CHANGELOG.md CHANGED
@@ -5,12 +5,68 @@ in the immutable pre-2.0 archive linked below.
5
5
 
6
6
  ## Unreleased
7
7
 
8
- ## 4.2.1 — 2026-08-08 (prepared)
8
+ ## 4.3.0 — 2026-08-09 (prepared)
9
+
10
+ **Minor** over 4.2.1. Phase ACS — agent contract surface: diagnostic code catalog, unified
11
+ status snapshot, version-matched agent projection, Agent Skills packaging of the existing 13
12
+ skills, stable finding refs, and maintainer placement A/B eval. **No required config migration.**
13
+ Codex remains advisory at write time. Skills and AGENTS projection never enforce. Z09 / RB-11
14
+ remain open. **Status: prepared** (`arkgate@4.3.0` not published yet; **4.2.1** remains on npm
15
+ `latest` until publication).
16
+
17
+ ### Added
18
+
19
+ - **Diagnostic code catalog (ACS02):** closed public `ruleId` vocabulary with why/fix text and
20
+ docs anchors (`docs/diagnostics.md#RULE_ID`). Domain source `src/domain/diagnosticCatalog.ts`,
21
+ generated CLI mirror `bin/lib/diagnostic-catalog.mjs`, root exports (`DIAGNOSTIC_CATALOG`,
22
+ `getDiagnosticCatalogEntry`, `diagnosticDocsPath`, …). Remediation/parity fixtures forbid
23
+ unknown production codes. No new rule semantics — cataloguing only.
24
+ - **Unified status manifest (ACS03):** `ark status --json` (+ MCP `ark_status`) returns one
25
+ session/project snapshot — identity binding, honest write-path activation, last-check summary,
26
+ ArkRules residual counts, primary next action. Domain `src/domain/statusManifest.ts`, generated
27
+ pure `bin/lib/status-manifest.mjs`, schema `arkgate/schema/status-manifest`, root exports
28
+ (`buildStatusManifest`, `ARK_STATUS_MANIFEST_SCHEMA`, …). Never prompts; `CI=1` forces JSON.
29
+ Optional `--expected-root` / `--expected-project-id` for matched vs stale identity. Not a score.
30
+ - **Version-matched agent projection (ACS04):** `ark agents-md` regenerates a managed
31
+ AGENTS.md projection block stamped with the installed package version + contract summary
32
+ (layers, diagnostic short list). Install/upgrade templates (`agentInstructions` /
33
+ `compactAgentInstructions`) embed the same block. Content-identity merge preserves customized
34
+ regions outside markers. Explicitly **non-authoritative** — enforcement remains ark-check /
35
+ hooks / CI; projection is never a gate input. Domain `src/domain/agentProjection.ts`, generated
36
+ pure `bin/lib/agent-projection.mjs`, root exports (`buildAgentProjectionBlock`,
37
+ `mergeAgentProjectionDocument`, …). Drift: `ark agents-md --check`.
38
+ - **Agent Skills packaging (ACS05):** the existing **13** `/ark-*` skills ship in an Agent
39
+ Skills–compatible layout at `templates/agent-skills/<name>/SKILL.md` (1:1 with flat
40
+ `templates/skills/*.md`). Ecosystem install: `npx skills add ./node_modules/arkgate/templates/agent-skills`
41
+ (or the GitHub tree). Ark install path unchanged (`--install-agent-gates` / `--skills-only`).
42
+ Domain `src/domain/agentSkillsPackage.ts` (frozen `ARK_SKILL_NAMES`, frontmatter/layout
43
+ validation), generated pure `bin/lib/agent-skills-package.mjs`, `npm run generate:agent-skills` /
44
+ `check:agent-skills`. **No new skill names.** Skills remain non-enforcing.
45
+ - **Stable finding refs (ACS06):** analysis-result schema **`1.5`** — every factory-emitted
46
+ diagnostic (CLI JSON, MCP, opt-in `ARK_REPAIR_JSON` repair payload) includes `findingRef`
47
+ (`fnv1a-…`), baseline-compatible `targetKey`, and `docsCodePath`. `targetKey` is exactly the
48
+ baseline (occurrence) freeze key so multi-turn agent loops never orphan baselined debt.
49
+ Domain helpers on `adapterContract` / `baselineKey`; multi-turn fixture
50
+ `tests/fixtures/finding-refs/multi-turn-stability.json`.
51
+ - **Maintainer placement A/B eval (ACS07):** under `eval/placement-ab/`, fixture pairs compare
52
+ agent placement **with** Ark gates+skills (`ark-place` / `ark-architect` markers) vs
53
+ **without**. CI-safe dry mode (`npm run eval:placement-ab`) measures real `ark-check` on both
54
+ arms; results path `eval/placement-ab-report.json` + template
55
+ `eval/placement-ab/results/RESULTS.template.json`. Live mode optional and never fails when no
56
+ agent/API key is present. **Not a product score.**
57
+
58
+ ### Docs / claims (ACS08)
59
+
60
+ - Claims matrix refreshed for 4.3.0 agent-contract statements (**0 Contradicted** on public
61
+ lanes). Release notes at `docs/releases/4.3.0.md` (**Status: prepared**). Public lanes name
62
+ the prepared candidate while npm `latest` remains **4.2.1** until Trusted Publishing.
63
+
64
+ ## 4.2.1 — 2026-08-08 (published)
9
65
 
10
66
  **Patch** over 4.2.0. Next.js **16.3** field compatibility: root `proxy.ts` (Next 16 network-boundary
11
67
  rename of middleware) is scanned and classified; eval fixtures and release-surface pins track
12
68
  `next@16.3.0`. **No required config migration.** Codex remains advisory at write time.
13
- **Status: prepared** (`arkgate@4.2.1` not published yet).
69
+ **Status: published** (`arkgate@4.2.1` on npm `latest`).
14
70
 
15
71
  ### Fixed
16
72
 
@@ -39,7 +95,7 @@ handshake, and project-bound `ark_manifest`; cross-project/config paths fail bef
39
95
  ArkRules analysis; Codex setup distinguishes files configured on disk from a runtime observed
40
96
  after restart. **No required config migration.** Legacy MCP calls remain callable but explicitly
41
97
  non-authoritative until bound. Codex remains advisory at write time. **Status: published**
42
- (`arkgate@4.2.0` on npm `latest`).
98
+ (`arkgate@4.2.0`; superseded by 4.2.1 on `latest`).
43
99
 
44
100
  ### Added
45
101
 
package/README.md CHANGED
@@ -16,9 +16,9 @@ and makes sure a “green” check means something real.
16
16
 
17
17
  </div>
18
18
 
19
- > **ArkGate 4.2.1** is prepared (Next.js 16.3 root `proxy.ts` scan + fixture pins);
20
- > **4.2.0** remains on npm `latest` until publication.
21
- > [4.2.1 candidate](docs/releases/4.2.1.md) · [4.2.0](docs/releases/4.2.0.md) · [4.1.1](docs/releases/4.1.1.md) · [Docs hub](docs/README.md) · [Product voice](docs/product-voice.md)
19
+ > **ArkGate 4.3.0** is prepared (agent contract surface: catalog, status, projection, skills packaging, finding refs);
20
+ > **4.2.1** remains on npm `latest` until publication.
21
+ > [4.3.0 candidate](docs/releases/4.3.0.md) · [4.2.1](docs/releases/4.2.1.md) · [4.2.0](docs/releases/4.2.0.md) · [Docs hub](docs/README.md) · [Product voice](docs/product-voice.md)
22
22
 
23
23
  ---
24
24
 
@@ -79,6 +79,15 @@ Details: [configuration](docs/configuration.md#arkrules-intra-layer-opt-in) · [
79
79
 
80
80
  **Name note:** npm package `arkgate` — not affiliated with the separate Archgate CLI project.
81
81
 
82
+ ### When not to adopt
83
+
84
+ ArkGate is overkill for small trees with **no AI agents** and **no multi-layer boundaries**, for
85
+ single-developer hobby CRUDs under no integration pressure, and for teams that will not maintain
86
+ `ark.config.json` or a **required** CI status running `arkgate-check --strict-merge`. In those
87
+ cases stay with a boundary linter alone (see [Why not only ESLint / Nx / cruiser?](#why-not-only-eslint--nx--cruiser)).
88
+ Anyone path: [docs/use.md — When not to adopt](docs/use.md#when-not-to-adopt). Limits of a green
89
+ check: [4.3.0 — What ArkGate is / isn't](docs/releases/4.3.0.md#what-arkgate-is--isnt).
90
+
82
91
  ---
83
92
 
84
93
  ## Status lights (not settings)
@@ -158,14 +167,17 @@ expectation.
158
167
 
159
168
  ```bash
160
169
  npx arkgate start --apply
170
+ npx arkgate status --json # session/project snapshot (identity, activation, last check)
161
171
  npx arkgate-check --doctor
162
172
  npx arkgate-check --plan
163
173
  npx arkgate-check --coverage
164
174
  npx arkgate-check --strict-merge # CI / required status
165
175
  npx arkgate-check --install-agent-gates --tools claude,cursor,codex,grok
176
+ # optional: same 13 skills via Agent Skills ecosystem (no new names)
177
+ # npx skills add ./node_modules/arkgate/templates/agent-skills
166
178
  ```
167
179
 
168
- More: [docs/develop.md](docs/develop.md) · enthusiast track: [docs/enthusiast/](docs/enthusiast/README.md)
180
+ More: [docs/develop.md](docs/develop.md) · skills install: [docs/agent-guide.md](docs/agent-guide.md#install-skills-ark-and-ecosystem) · enthusiast track: [docs/enthusiast/](docs/enthusiast/README.md)
169
181
 
170
182
  ---
171
183
 
@@ -194,8 +206,9 @@ for real systems. Details: [docs/production-hardening.md](docs/production-harden
194
206
  | Config · package surface · TS | [configuration](docs/configuration.md) · [package-surface](docs/package-surface.md) · [typescript-support](docs/typescript-support.md) |
195
207
  | Brownfield | [docs/brownfield-adoption.md](docs/brownfield-adoption.md) |
196
208
  | Security | [SECURITY.md](SECURITY.md) |
197
- | Prepared candidate (4.2.1) | [docs/releases/4.2.1.md](docs/releases/4.2.1.md) · [CHANGELOG](CHANGELOG.md) |
198
- | Current published (4.2.0) | [docs/releases/4.2.0.md](docs/releases/4.2.0.md) |
209
+ | Prepared candidate (4.3.0) | [docs/releases/4.3.0.md](docs/releases/4.3.0.md) · [CHANGELOG](CHANGELOG.md) |
210
+ | Current published (4.2.1) | [docs/releases/4.2.1.md](docs/releases/4.2.1.md) |
211
+ | Previous (4.2.0) | [docs/releases/4.2.0.md](docs/releases/4.2.0.md) |
199
212
  | Previous (4.1.1) | [docs/releases/4.1.1.md](docs/releases/4.1.1.md) |
200
213
  | Previous (4.1.0) | [docs/releases/4.1.0.md](docs/releases/4.1.0.md) |
201
214
  | Previous patch (4.0.1) | [docs/releases/4.0.1.md](docs/releases/4.0.1.md) |
@@ -36,6 +36,8 @@ import {
36
36
  createProjectId,
37
37
  createProjectIdentity,
38
38
  } from './lib/project-identity.mjs';
39
+ import { buildProjectStatusManifest } from './lib/status-command.mjs';
40
+ import { ARK_STATUS_MANIFEST_SCHEMA } from './lib/status-manifest.mjs';
39
41
 
40
42
  function arkRulesCatalogForManifest(snapshot) {
41
43
  if (snapshot?.errors?.length || !snapshot?.arkRules) return {};
@@ -2143,6 +2145,25 @@ export async function runArkMcp({ hookInput } = {}) {
2143
2145
  '— never a numeric score. Same plane as ark-check --rules-inventory.',
2144
2146
  inputSchema: { type: 'object', properties: {} },
2145
2147
  },
2148
+ {
2149
+ name: 'ark_status',
2150
+ description:
2151
+ 'Unified session/project status manifest (ACS03): project identity binding, honest write-path ' +
2152
+ 'activation, last-check summary, ArkRules residual counts, and primary next action. Same ' +
2153
+ 'envelope as `ark status --json`. Never prompts; never a numeric score. Prefer after ' +
2154
+ 'ark_identity so project.expectedRoot is bound.',
2155
+ inputSchema: { type: 'object', properties: {} },
2156
+ outputSchema: {
2157
+ type: 'object',
2158
+ additionalProperties: true,
2159
+ properties: {
2160
+ status: ARK_STATUS_MANIFEST_SCHEMA,
2161
+ projectIdentity: projectIdentityOutputSchema,
2162
+ binding: PROJECT_BINDING_SCHEMA,
2163
+ authoritative: { type: 'boolean' },
2164
+ },
2165
+ },
2166
+ },
2146
2167
  ];
2147
2168
 
2148
2169
  for (const tool of TOOLS) {
@@ -2664,6 +2685,48 @@ export async function runArkMcp({ hookInput } = {}) {
2664
2685
  }
2665
2686
  }
2666
2687
 
2688
+ function runStatusTool(_params, binding) {
2689
+ try {
2690
+ const status = buildProjectStatusManifest({
2691
+ root: args.root,
2692
+ config: path.basename(configPath) === 'ark.config.json' ? 'ark.config.json' : configPath,
2693
+ expectedRoot: binding?.expectedRoot ?? _params?.arguments?.project?.expectedRoot,
2694
+ expectedProjectId:
2695
+ binding?.expectedProjectId ?? _params?.arguments?.project?.expectedProjectId,
2696
+ arkgateVersion: ark.version,
2697
+ });
2698
+ // Prefer MCP binding status when the tool framework already evaluated expectation.
2699
+ if (binding && typeof binding.status === 'string') {
2700
+ status.projectIdentity.binding = binding.status;
2701
+ status.projectIdentity.authoritative = binding.authoritative === true;
2702
+ if (binding.code) status.projectIdentity.code = binding.code;
2703
+ if (binding.message) status.projectIdentity.message = binding.message;
2704
+ if (binding.status === 'mismatch') {
2705
+ status.nextAction = {
2706
+ id: 'rebind-project-identity',
2707
+ summary:
2708
+ binding.message ||
2709
+ 'Project expectation does not match this MCP process — re-run ark_identity with the correct root.',
2710
+ };
2711
+ }
2712
+ }
2713
+ const payload = {
2714
+ ok: binding?.status !== 'mismatch',
2715
+ status,
2716
+ };
2717
+ return {
2718
+ content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }],
2719
+ structuredContent: payload,
2720
+ isError: binding?.status === 'mismatch',
2721
+ };
2722
+ } catch (error) {
2723
+ return {
2724
+ content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }],
2725
+ isError: true,
2726
+ };
2727
+ }
2728
+ }
2729
+
2667
2730
  function runRulesInventoryTool() {
2668
2731
  try {
2669
2732
  const governed = collectGovernedFiles(args.root, config);
@@ -2772,6 +2835,7 @@ export async function runArkMcp({ hookInput } = {}) {
2772
2835
  ark_recommend: runRecommendTool,
2773
2836
  ark_suggest_include: runSuggestIncludeTool,
2774
2837
  ark_rules_inventory: runRulesInventoryTool,
2838
+ ark_status: runStatusTool,
2775
2839
  };
2776
2840
 
2777
2841
  const send = (msg) => process.stdout.write(`${JSON.stringify(msg)}\n`);
package/bin/ark.mjs CHANGED
@@ -33,6 +33,8 @@ import {
33
33
  readChangeSetFile,
34
34
  renderChangePreflight,
35
35
  } from './lib/prepare-change.mjs';
36
+ import { runStatusCommand } from './lib/status-command.mjs';
37
+ import { runAgentProjectionCommand } from './lib/agent-projection-command.mjs';
36
38
 
37
39
  const here = path.dirname(fileURLToPath(import.meta.url));
38
40
  const arkCheck = path.join(here, 'ark-check.mjs');
@@ -81,6 +83,11 @@ function parseArgs(argv) {
81
83
  skipPackageManager: false,
82
84
  removeHost: undefined,
83
85
  requireWriteHook: undefined,
86
+ expectedRoot: undefined,
87
+ expectedProjectId: undefined,
88
+ write: false,
89
+ check: false,
90
+ stdout: false,
84
91
  help: false,
85
92
  version: false,
86
93
  };
@@ -127,6 +134,11 @@ function parseArgs(argv) {
127
134
  else if (arg === '--require-write-hook') {
128
135
  args.requireWriteHook = requireValue(arg, i++).trim().toLowerCase();
129
136
  }
137
+ else if (arg === '--expected-root') args.expectedRoot = path.resolve(requireValue(arg, i++));
138
+ else if (arg === '--expected-project-id') args.expectedProjectId = requireValue(arg, i++);
139
+ else if (arg === '--write') args.write = true;
140
+ else if (arg === '--check') args.check = true;
141
+ else if (arg === '--stdout') args.stdout = true;
130
142
  else if (arg === '--help' || arg === '-h' || arg === 'help') args.help = true;
131
143
  else if (arg === '--version' || arg === '-V') args.version = true;
132
144
  else if (!arg.startsWith('-') && args.command === undefined) args.command = arg;
@@ -143,6 +155,10 @@ function usage() {
143
155
  [--archetype <playbook-id>] [--tools <list>] [--require-write-hook <host>] [--yes] [--force] [--no-strict]
144
156
  ark upgrade [--root <project>] [--tools <list>] [--apply] [--plan-digest <sha256>] [--accept-conflicts] [--json] [--no-install] [--no-strict]
145
157
  ark preflight --changes <change-set.json> [--change-map <map.json>] [--root <project>] [--config ark.config.json] [--manifest <manifest.json>] [--tsconfig <tsconfig.json>] [--json]
158
+ ark status [--root <project>] [--config ark.config.json] [--json]
159
+ [--expected-root <abs>] [--expected-project-id sha256:…] [--tools <host>]
160
+ ark agents-md [--root <project>] [--config ark.config.json] [--write] [--check] [--stdout] [--json]
161
+ [--tools <host>]
146
162
 
147
163
  Commands:
148
164
  start New here? Analyze and preview the complete setup. Read-only unless --apply.
@@ -152,6 +168,13 @@ Commands:
152
168
  --apply --no-install applies those exact bytes and verifies them.
153
169
  (alias: ark update)
154
170
  preflight Validate one atomic create/update/delete set without writing project files.
171
+ status Unified session/project manifest (identity, activation, last check, rules).
172
+ Never prompts. Prefer --json for agents; CI=1 forces JSON.
173
+ agents-md Version-matched agent contract projection (ACS04). Stamps package version +
174
+ contract summary into a managed AGENTS.md block. Non-authoritative — not a
175
+ gate input. Preview by default; --write merges without clobbering outside
176
+ regions; --check fails on version drift; --stdout prints the block only.
177
+ (aliases: agents-md, agent-projection)
155
178
 
156
179
  Options:
157
180
  --yes Non-interactive defaults: create config if needed, install gate templates, run strict check.
@@ -164,7 +187,12 @@ Options:
164
187
  --accept-conflicts
165
188
  Allow upgrade to recreate deleted managed assets or replace recorded conflicts.
166
189
  --plan-digest Digest emitted by an upgrade preview; required to apply managed bytes.
167
- --json Emit the start/upgrade preview as deterministic machine-readable JSON.
190
+ --json Emit the start/upgrade/status/agents-md preview as deterministic machine-readable JSON.
191
+ --write For agents-md: merge the version-matched projection into AGENTS.md.
192
+ --check For agents-md: exit 1 when projection stamp drifts from package version.
193
+ --stdout For agents-md: print the projection block only (no file write).
194
+ --expected-root / --expected-project-id
195
+ Optional project expectation for status (MCP-compatible binding check).
168
196
  --preset Start from a named architecture preset instead of detection.
169
197
  --archetype Application shape from templates/architecture-playbook.json (maps to the matching preset).
170
198
  Valid ids: crud-product, api-backend, frontend-surface, library-sdk, cli-utility,
@@ -900,6 +928,32 @@ async function main() {
900
928
  }
901
929
  }
902
930
 
931
+ if (args.command === 'status') {
932
+ return runStatusCommand({
933
+ root: args.root,
934
+ config: args.config,
935
+ json: args.json,
936
+ expectedRoot: args.expectedRoot,
937
+ expectedProjectId: args.expectedProjectId,
938
+ host: args.tools,
939
+ arkgateVersion: cliVersion(),
940
+ });
941
+ }
942
+
943
+ if (args.command === 'agents-md' || args.command === 'agent-projection') {
944
+ return runAgentProjectionCommand({
945
+ root: args.root,
946
+ config: args.config,
947
+ json: args.json,
948
+ write: args.write,
949
+ apply: args.apply,
950
+ check: args.check,
951
+ stdout: args.stdout,
952
+ host: args.tools,
953
+ arkgateVersion: cliVersion(),
954
+ });
955
+ }
956
+
903
957
  console.error(`Unknown command: ${args.command}`);
904
958
  console.error(usage());
905
959
  return 2;
@@ -8,14 +8,72 @@
8
8
  * Pure CLI helper (bin/lib/adapter-contract.mjs). Zero Node I/O.
9
9
  */
10
10
 
11
- /** 1.4 adds optional evidence.arkruleId + evidence.arkruleSource (ADR 0012 / AR03). */
12
- export const ARK_ANALYSIS_RESULT_SCHEMA_VERSION = '1.4';
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';
13
19
  function text(value) {
14
20
  return typeof value === 'string' && value.length > 0 ? value : undefined;
15
21
  }
16
22
  function positiveInteger(value, fallback) {
17
23
  return Number.isInteger(value) && Number(value) > 0 ? Number(value) : fallback;
18
24
  }
25
+ /**
26
+ * Baseline-compatible target key for a violation input.
27
+ * Field order and empty-string fallbacks **must** match `baselineKey` in
28
+ * `baselineKey.ts` — parity tests guard this so finding refs never orphan freezes.
29
+ *
30
+ * Note: uses raw ruleId/file strings (including empty) the same way baseline does;
31
+ * display `ruleId` / `location.file` may still normalize to ARK_UNKNOWN / `<unknown>`.
32
+ */
33
+ export function adapterFindingTargetKey(violation) {
34
+ const ruleId = typeof violation.ruleId === 'string'
35
+ ? violation.ruleId
36
+ : typeof violation.code === 'string'
37
+ ? violation.code
38
+ : undefined;
39
+ const file = typeof violation.file === 'string' ? violation.file : undefined;
40
+ const fromLayer = typeof violation.fromLayer === 'string' ? violation.fromLayer : undefined;
41
+ const toLayer = typeof violation.toLayer === 'string' ? violation.toLayer : undefined;
42
+ const target = typeof violation.target === 'string' ? violation.target : undefined;
43
+ return [
44
+ ruleId,
45
+ file,
46
+ fromLayer ?? '',
47
+ toLayer ?? '',
48
+ target ?? '',
49
+ ].join('|');
50
+ }
51
+ /**
52
+ * Occurrence-aware target keys for a violation list (parity with baselineOccurrenceKeys).
53
+ * First occurrence keeps the historical base key; duplicates get `#N`.
54
+ */
55
+ export function adapterFindingOccurrenceTargetKeys(violations) {
56
+ const counts = new Map();
57
+ return violations.map((violation) => {
58
+ const base = adapterFindingTargetKey(violation);
59
+ const occurrence = (counts.get(base) ?? 0) + 1;
60
+ counts.set(base, occurrence);
61
+ return occurrence === 1 ? base : `${base}#${occurrence}`;
62
+ });
63
+ }
64
+ /** FNV-1a finding ref from a baseline-compatible targetKey (not a security hash). */
65
+ export function adapterFindingRefFromTargetKey(targetKey) {
66
+ let hash = 0x811c9dc5;
67
+ for (let index = 0; index < targetKey.length; index += 1) {
68
+ hash ^= targetKey.charCodeAt(index);
69
+ hash = Math.imul(hash, 0x01000193);
70
+ }
71
+ return `fnv1a-${(hash >>> 0).toString(16).padStart(8, '0')}`;
72
+ }
73
+ /** Package-relative docs path with fragment for a public ruleId. */
74
+ export function adapterDocsCodePath(ruleId) {
75
+ return `${ADAPTER_DIAGNOSTIC_DOCS_RELATIVE_PATH}#${ruleId}`;
76
+ }
19
77
  function nextActionForDiagnostic(ruleId, evidence, violation) {
20
78
  if (ruleId === 'LAYER_IMPORT_VIOLATION') {
21
79
  if (evidence.typeOnly ||
@@ -51,7 +109,12 @@ function nextActionForDiagnostic(ruleId, evidence, violation) {
51
109
  }
52
110
  return `Resolve ${ruleId} without weakening ark.config.json, then run Ark again.`;
53
111
  }
54
- export function toAdapterDiagnostic(violation, fallbackSeverity = 'error') {
112
+ export function toAdapterDiagnostic(violation, fallbackSeverity = 'error',
113
+ /**
114
+ * Optional precomputed baseline-compatible targetKey (e.g. occurrence-aware from
115
+ * `adapterFindingOccurrenceTargetKeys`). When omitted, uses the first-occurrence key.
116
+ */
117
+ targetKeyOverride) {
55
118
  const ruleId = text(violation.ruleId) ?? text(violation.code) ?? 'ARK_UNKNOWN';
56
119
  // Type-only placement debt (failsStrict:false / typeOnly non-peer) is warning severity.
57
120
  const severity = violation.severity === 'warning' ||
@@ -84,6 +147,8 @@ export function toAdapterDiagnostic(violation, fallbackSeverity = 'error') {
84
147
  ...(text(violation.arkruleId) ? { arkruleId: text(violation.arkruleId) } : {}),
85
148
  ...(text(violation.arkruleSource) ? { arkruleSource: text(violation.arkruleSource) } : {}),
86
149
  };
150
+ const targetKey = targetKeyOverride ?? adapterFindingTargetKey(violation);
151
+ const findingRef = adapterFindingRefFromTargetKey(targetKey);
87
152
  return {
88
153
  ruleId,
89
154
  severity,
@@ -95,6 +160,9 @@ export function toAdapterDiagnostic(violation, fallbackSeverity = 'error') {
95
160
  },
96
161
  evidence,
97
162
  nextAction: text(violation.nextAction) ?? nextActionForDiagnostic(ruleId, evidence, violation),
163
+ findingRef,
164
+ targetKey,
165
+ docsCodePath: adapterDocsCodePath(ruleId),
98
166
  };
99
167
  }
100
168
  export function createAdapterResult(input) {
@@ -133,10 +201,16 @@ export function createAdapterResult(input) {
133
201
  }
134
202
  }
135
203
  }
204
+ // Occurrence-aware targetKeys (violations and warnings counted separately) so
205
+ // list identity matches baselineOccurrenceKeys on each stream — ACS06 freeze parity.
206
+ const violationList = input.violations ?? [];
207
+ const warningList = input.warnings ?? [];
208
+ const violationTargetKeys = adapterFindingOccurrenceTargetKeys(violationList);
209
+ const warningTargetKeys = adapterFindingOccurrenceTargetKeys(warningList);
136
210
  const diagnostics = [
137
211
  // toAdapterDiagnostic maps failsStrict:false / typeOnly non-peer → warning severity.
138
- ...(input.violations ?? []).map((item) => toAdapterDiagnostic(item, 'error')),
139
- ...(input.warnings ?? []).map((item) => toAdapterDiagnostic(item, 'warning')),
212
+ ...violationList.map((item, index) => toAdapterDiagnostic(item, 'error', violationTargetKeys[index])),
213
+ ...warningList.map((item, index) => toAdapterDiagnostic(item, 'warning', warningTargetKeys[index])),
140
214
  ];
141
215
  const base = {
142
216
  schemaVersion: ARK_ANALYSIS_RESULT_SCHEMA_VERSION,
@@ -286,6 +360,15 @@ export const ARK_ANALYSIS_RESULT_SCHEMA = {
286
360
  },
287
361
  },
288
362
  nextAction: { type: 'string', minLength: 1 },
363
+ /** ACS06 — compact multi-turn id; always derived from targetKey when emitted. */
364
+ findingRef: { type: 'string', minLength: 1, pattern: '^fnv1a-[0-9a-f]{8}$' },
365
+ /**
366
+ * ACS06 — baseline-compatible freeze identity
367
+ * (`ruleId|file|from|to|target` with optional `#N` occurrence suffix).
368
+ */
369
+ targetKey: { type: 'string', minLength: 1 },
370
+ /** ACS06 — package-relative diagnostics anchor path. */
371
+ docsCodePath: { type: 'string', minLength: 1 },
289
372
  },
290
373
  },
291
374
  },