arkgate 4.2.1 → 4.4.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 (63) hide show
  1. package/CHANGELOG.md +85 -3
  2. package/README.md +24 -8
  3. package/bin/ark-check-runtime.mjs +16 -1
  4. package/bin/ark-mcp-runtime.mjs +64 -0
  5. package/bin/ark.mjs +55 -1
  6. package/bin/lib/adapter-contract.mjs +88 -5
  7. package/bin/lib/agent-projection-command.mjs +396 -0
  8. package/bin/lib/agent-projection.mjs +319 -0
  9. package/bin/lib/agent-skills-package.mjs +266 -0
  10. package/bin/lib/baseline-key.mjs +32 -0
  11. package/bin/lib/ci-and-commands.mjs +55 -5
  12. package/bin/lib/diagnostic-catalog.mjs +155 -0
  13. package/bin/lib/doctor-plan.mjs +25 -0
  14. package/bin/lib/html-report-advisories.mjs +33 -0
  15. package/bin/lib/html-report-depth.mjs +24 -0
  16. package/bin/lib/improvement-compass-doctor.mjs +106 -0
  17. package/bin/lib/improvement-compass.mjs +630 -0
  18. package/bin/lib/status-command.mjs +369 -0
  19. package/bin/lib/status-manifest.mjs +431 -0
  20. package/dist/eslint/index.cjs +3 -3
  21. package/dist/eslint/index.js +3 -3
  22. package/dist/index.cjs +46 -11
  23. package/dist/index.d.ts +886 -6
  24. package/dist/index.js +46 -11
  25. package/docs/README.md +9 -8
  26. package/docs/agent-guide.md +128 -14
  27. package/docs/configuration.md +7 -0
  28. package/docs/develop.md +12 -1
  29. package/docs/diagnostics.md +606 -0
  30. package/docs/package-surface.md +44 -31
  31. package/docs/product-voice.md +71 -0
  32. package/docs/use.md +60 -1
  33. package/package.json +7 -1
  34. package/schemas/ark.analysis-result.schema.json +14 -1
  35. package/schemas/ark.status-manifest.schema.json +270 -0
  36. package/server.json +2 -2
  37. package/templates/agent-skills/README.md +59 -0
  38. package/templates/agent-skills/ark-adopt/SKILL.md +191 -0
  39. package/templates/agent-skills/ark-architect/SKILL.md +195 -0
  40. package/templates/agent-skills/ark-autopilot/SKILL.md +262 -0
  41. package/templates/agent-skills/ark-contract/SKILL.md +156 -0
  42. package/templates/agent-skills/ark-coverage/SKILL.md +187 -0
  43. package/templates/agent-skills/ark-explain/SKILL.md +230 -0
  44. package/templates/agent-skills/ark-explore/SKILL.md +397 -0
  45. package/templates/agent-skills/ark-fix/SKILL.md +205 -0
  46. package/templates/agent-skills/ark-loop/SKILL.md +200 -0
  47. package/templates/agent-skills/ark-place/SKILL.md +182 -0
  48. package/templates/agent-skills/ark-runtime/SKILL.md +127 -0
  49. package/templates/agent-skills/ark-think/SKILL.md +153 -0
  50. package/templates/agent-skills/ark-upgrade/SKILL.md +238 -0
  51. package/templates/skills/ark-adopt.md +20 -0
  52. package/templates/skills/ark-architect.md +21 -1
  53. package/templates/skills/ark-autopilot.md +25 -5
  54. package/templates/skills/ark-contract.md +20 -0
  55. package/templates/skills/ark-coverage.md +20 -0
  56. package/templates/skills/ark-explain.md +20 -0
  57. package/templates/skills/ark-explore.md +23 -3
  58. package/templates/skills/ark-fix.md +22 -2
  59. package/templates/skills/ark-loop.md +22 -2
  60. package/templates/skills/ark-place.md +20 -0
  61. package/templates/skills/ark-runtime.md +7 -0
  62. package/templates/skills/ark-think.md +20 -0
  63. package/templates/skills/ark-upgrade.md +20 -0
package/CHANGELOG.md CHANGED
@@ -5,12 +5,94 @@ 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.4.0 — 2026-08-09
9
+
10
+ **Minor** over 4.3.0. Improvement compass (architecture lenses as a `notAScore` projection of
11
+ existing sensors), doctor/HTML residual coaching, deeper skills + compact router for vibe-coder
12
+ and full-AI workflows, and product-only public docs. **No required config migration.** Codex remains
13
+ advisory at write time. Skills and AGENTS projection never enforce. **Status: prepared in tree —
14
+ not published on npm until OIDC publish + `npm view` succeed** (see `docs/releases/4.4.0.md`).
15
+
16
+ ### Added
17
+
18
+ - **Improvement compass:** doctor human section + `doctor.improvementCompass` JSON — closed set of
19
+ architecture **lenses** (SoC, DIP, domain alignment, …) with status `ok` / `residual` /
20
+ `not-instrumented` / `out-of-scope`, evidence refs, optional next action, capped `topResidual`,
21
+ always **`notAScore: true`**. Projected from existing smells, walls, cohesion, ArkRules, and
22
+ design-weak signals. Residual lenses never flip `valid`, strict-merge, or `goal.met`.
23
+ Out-of-scope locked for scalability, app security tooling, and full resilience. Type-only
24
+ placement debt (`failsStrict: false`) maps to **modularity** residual only (not coupling).
25
+ - **Optional status slice:** thin `improvementCompass.topResidual` on the status manifest schema for
26
+ Tooling pass-through — **`ark status` does not compute lenses yet**; agents should read doctor.
27
+ - **HTML report:** advisory section `data-advisory="improvementCompass"` (report parity with doctor).
28
+ - **Skills + compact router:** same **13** skill names deepened for vibe-coder outcomes — compass
29
+ preflight, anti false-done, AI-easy placement cues, out-of-scope honesty, Completion **Compass**
30
+ bullet. Compact start router surfaces residual lenses and forbids “done” on green edges alone.
31
+ - **Docs:** use / develop / agent-guide describe compass + Align/Stabilize/Shape in product language.
32
+
33
+ **Freezes held:** no principle scores/ranks; no new skill names; no new ArkRules sensors; no LLM
34
+ verdict; projection never enforces.
35
+
36
+ ## 4.3.0 — 2026-08-09
37
+
38
+ **Minor** over 4.2.1. Agent contract surface: diagnostic code catalog, unified status snapshot,
39
+ version-matched agent projection, Agent Skills packaging of the existing 13 skills, stable finding
40
+ refs, and maintainer placement A/B eval. **No required config migration.** Codex remains advisory
41
+ at write time. Skills and AGENTS projection never enforce. **Status: published**
42
+ (`arkgate@4.3.0` on npm `latest`).
43
+
44
+ ### Added
45
+
46
+ - **Diagnostic code catalog (ACS02):** closed public `ruleId` vocabulary with why/fix text and
47
+ docs anchors (`docs/diagnostics.md#RULE_ID`). Domain source `src/domain/diagnosticCatalog.ts`,
48
+ generated CLI mirror `bin/lib/diagnostic-catalog.mjs`, root exports (`DIAGNOSTIC_CATALOG`,
49
+ `getDiagnosticCatalogEntry`, `diagnosticDocsPath`, …). Remediation/parity fixtures forbid
50
+ unknown production codes. No new rule semantics — cataloguing only.
51
+ - **Unified status manifest (ACS03):** `ark status --json` (+ MCP `ark_status`) returns one
52
+ session/project snapshot — identity binding, honest write-path activation, last-check summary,
53
+ ArkRules residual counts, primary next action. Domain `src/domain/statusManifest.ts`, generated
54
+ pure `bin/lib/status-manifest.mjs`, schema `arkgate/schema/status-manifest`, root exports
55
+ (`buildStatusManifest`, `ARK_STATUS_MANIFEST_SCHEMA`, …). Never prompts; `CI=1` forces JSON.
56
+ Optional `--expected-root` / `--expected-project-id` for matched vs stale identity. Not a score.
57
+ - **Version-matched agent projection (ACS04):** `ark agents-md` regenerates a managed
58
+ AGENTS.md projection block stamped with the installed package version + contract summary
59
+ (layers, diagnostic short list). Install/upgrade templates (`agentInstructions` /
60
+ `compactAgentInstructions`) embed the same block. Content-identity merge preserves customized
61
+ regions outside markers. Explicitly **non-authoritative** — enforcement remains ark-check /
62
+ hooks / CI; projection is never a gate input. Domain `src/domain/agentProjection.ts`, generated
63
+ pure `bin/lib/agent-projection.mjs`, root exports (`buildAgentProjectionBlock`,
64
+ `mergeAgentProjectionDocument`, …). Drift: `ark agents-md --check`.
65
+ - **Agent Skills packaging (ACS05):** the existing **13** `/ark-*` skills ship in an Agent
66
+ Skills–compatible layout at `templates/agent-skills/<name>/SKILL.md` (1:1 with flat
67
+ `templates/skills/*.md`). Ecosystem install: `npx skills add ./node_modules/arkgate/templates/agent-skills`
68
+ (or the GitHub tree). Ark install path unchanged (`--install-agent-gates` / `--skills-only`).
69
+ Domain `src/domain/agentSkillsPackage.ts` (frozen `ARK_SKILL_NAMES`, frontmatter/layout
70
+ validation), generated pure `bin/lib/agent-skills-package.mjs`, `npm run generate:agent-skills` /
71
+ `check:agent-skills`. **No new skill names.** Skills remain non-enforcing.
72
+ - **Stable finding refs (ACS06):** analysis-result schema **`1.5`** — every factory-emitted
73
+ diagnostic (CLI JSON, MCP, opt-in `ARK_REPAIR_JSON` repair payload) includes `findingRef`
74
+ (`fnv1a-…`), baseline-compatible `targetKey`, and `docsCodePath`. `targetKey` is exactly the
75
+ baseline (occurrence) freeze key so multi-turn agent loops never orphan baselined debt.
76
+ Domain helpers on `adapterContract` / `baselineKey`; multi-turn fixture
77
+ `tests/fixtures/finding-refs/multi-turn-stability.json`.
78
+ - **Maintainer placement A/B eval (ACS07):** under `eval/placement-ab/`, fixture pairs compare
79
+ agent placement **with** Ark gates+skills (`ark-place` / `ark-architect` markers) vs
80
+ **without**. CI-safe dry mode (`npm run eval:placement-ab`) measures real `ark-check` on both
81
+ arms; results path `eval/placement-ab-report.json` + template
82
+ `eval/placement-ab/results/RESULTS.template.json`. Live mode optional and never fails when no
83
+ agent/API key is present. **Not a product score.**
84
+
85
+ ### Docs / claims (ACS08)
86
+
87
+ - Claims matrix refreshed for 4.3.0 agent-contract statements (**0 Contradicted** on public
88
+ lanes). Release notes at `docs/releases/4.3.0.md` (**Status: published**). npm `latest` is **4.3.0**.
89
+
90
+ ## 4.2.1 — 2026-08-08 (published)
9
91
 
10
92
  **Patch** over 4.2.0. Next.js **16.3** field compatibility: root `proxy.ts` (Next 16 network-boundary
11
93
  rename of middleware) is scanned and classified; eval fixtures and release-surface pins track
12
94
  `next@16.3.0`. **No required config migration.** Codex remains advisory at write time.
13
- **Status: prepared** (`arkgate@4.2.1` not published yet).
95
+ **Status: published** (`arkgate@4.2.1`; superseded by 4.3.0 on `latest`).
14
96
 
15
97
  ### Fixed
16
98
 
@@ -39,7 +121,7 @@ handshake, and project-bound `ark_manifest`; cross-project/config paths fail bef
39
121
  ArkRules analysis; Codex setup distinguishes files configured on disk from a runtime observed
40
122
  after restart. **No required config migration.** Legacy MCP calls remain callable but explicitly
41
123
  non-authoritative until bound. Codex remains advisory at write time. **Status: published**
42
- (`arkgate@4.2.0` on npm `latest`).
124
+ (`arkgate@4.2.0`; superseded by 4.2.1 on `latest`).
43
125
 
44
126
  ### Added
45
127
 
package/README.md CHANGED
@@ -16,9 +16,8 @@ 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 on npm `latest`. Tree is preparing **4.4.0** (improvement compass) — see [4.4.0 notes](docs/releases/4.4.0.md) (Status: prepared until publish).
20
+ > [4.4.0 notes](docs/releases/4.4.0.md) · [4.3.0](docs/releases/4.3.0.md) · [4.2.1](docs/releases/4.2.1.md) · [Docs hub](docs/README.md) · [Product voice](docs/product-voice.md)
22
21
 
23
22
  ---
24
23
 
@@ -44,10 +43,13 @@ npx arkgate-check --doctor # control plane: status light + primary next a
44
43
  ```
45
44
 
46
45
  That is the product. Doctor is the control plane — when stuck, do **primary next action #1**.
46
+ Doctor also shows an **improvement compass** (architecture lenses such as separation of concerns and
47
+ dependency inversion — **not a score**). Residual lenses mean Shape work may remain even when edges
48
+ are green. Details: [use.md — Improvement compass](docs/use.md#improvement-compass-not-a-score).
47
49
 
48
50
  ```text
49
- start → doctor → day-to-day (place + gate)
50
- ↘ optional /ark-autopilot after skill pack
51
+ start → doctor (+ compass) → day-to-day (place + gate)
52
+ ↘ optional /ark-autopilot after skill pack
51
53
  ```
52
54
 
53
55
  Aliases `ark` / `ark-check` / `ark-mcp` still work. npm / pnpm / yarn. No install lifecycle scripts.
@@ -79,6 +81,15 @@ Details: [configuration](docs/configuration.md#arkrules-intra-layer-opt-in) · [
79
81
 
80
82
  **Name note:** npm package `arkgate` — not affiliated with the separate Archgate CLI project.
81
83
 
84
+ ### When not to adopt
85
+
86
+ ArkGate is overkill for small trees with **no AI agents** and **no multi-layer boundaries**, for
87
+ single-developer hobby CRUDs under no integration pressure, and for teams that will not maintain
88
+ `ark.config.json` or a **required** CI status running `arkgate-check --strict-merge`. In those
89
+ cases stay with a boundary linter alone (see [Why not only ESLint / Nx / cruiser?](#why-not-only-eslint--nx--cruiser)).
90
+ Anyone path: [docs/use.md — When not to adopt](docs/use.md#when-not-to-adopt). Limits of a green
91
+ check: [4.3.0 — What ArkGate is / isn't](docs/releases/4.3.0.md#what-arkgate-is--isnt).
92
+
82
93
  ---
83
94
 
84
95
  ## Status lights (not settings)
@@ -158,14 +169,17 @@ expectation.
158
169
 
159
170
  ```bash
160
171
  npx arkgate start --apply
172
+ npx arkgate status --json # session/project snapshot (identity, activation, last check)
161
173
  npx arkgate-check --doctor
162
174
  npx arkgate-check --plan
163
175
  npx arkgate-check --coverage
164
176
  npx arkgate-check --strict-merge # CI / required status
165
177
  npx arkgate-check --install-agent-gates --tools claude,cursor,codex,grok
178
+ # optional: same 13 skills via Agent Skills ecosystem (no new names)
179
+ # npx skills add ./node_modules/arkgate/templates/agent-skills
166
180
  ```
167
181
 
168
- More: [docs/develop.md](docs/develop.md) · enthusiast track: [docs/enthusiast/](docs/enthusiast/README.md)
182
+ 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
183
 
170
184
  ---
171
185
 
@@ -194,8 +208,10 @@ for real systems. Details: [docs/production-hardening.md](docs/production-harden
194
208
  | Config · package surface · TS | [configuration](docs/configuration.md) · [package-surface](docs/package-surface.md) · [typescript-support](docs/typescript-support.md) |
195
209
  | Brownfield | [docs/brownfield-adoption.md](docs/brownfield-adoption.md) |
196
210
  | 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) |
211
+ | Current release (4.3.0 on npm `latest`) | [docs/releases/4.3.0.md](docs/releases/4.3.0.md) · [CHANGELOG](CHANGELOG.md) |
212
+ | Next prepare (4.4.0) | [docs/releases/4.4.0.md](docs/releases/4.4.0.md) (prepared — not published until npm verify) |
213
+ | Prior (4.2.1) | [docs/releases/4.2.1.md](docs/releases/4.2.1.md) |
214
+ | Previous (4.2.0) | [docs/releases/4.2.0.md](docs/releases/4.2.0.md) |
199
215
  | Previous (4.1.1) | [docs/releases/4.1.1.md](docs/releases/4.1.1.md) |
200
216
  | Previous (4.1.0) | [docs/releases/4.1.0.md](docs/releases/4.1.0.md) |
201
217
  | Previous patch (4.0.1) | [docs/releases/4.0.1.md](docs/releases/4.0.1.md) |
@@ -1576,6 +1576,14 @@ async function main() {
1576
1576
  : readJsonSafe(path.join(reportsDir(root), 'origin.json'));
1577
1577
  // Pass the same baseline split as doctor so productHonesty dirty-freeze matches.
1578
1578
  const reportBaseline = readBaseline(root, args.baseline || '.ark-baseline.json');
1579
+ // Doctor parity for improvement compass: stale keys = baseline keys not in current occurrence set.
1580
+ // Only when analysis is complete — partial scans under-count current keys and inflate false stale residual
1581
+ // (same gate as doctor-plan: baselineStale: analysisComplete ? staleBaseline : null).
1582
+ const reportOccurrenceKeys = baselineOccurrenceKeys(violations);
1583
+ const reportCurrentKeys = new Set(reportOccurrenceKeys);
1584
+ const reportBaselineStale = reportBaseline.exists
1585
+ ? [...reportBaseline.keys].filter((key) => !reportCurrentKeys.has(key)).length
1586
+ : 0;
1579
1587
  const { adoption: adoptionForReport, designDepth } = buildReportDepthPayload(
1580
1588
  root,
1581
1589
  config,
@@ -1588,6 +1596,7 @@ async function main() {
1588
1596
  frozenKeys: reportBaseline.exists ? reportBaseline.keys.size : 0,
1589
1597
  activeCount: activeViolations.length,
1590
1598
  activeBlockingCount: blockingViolations.length,
1599
+ baselineStale: analysisComplete ? reportBaselineStale : null,
1591
1600
  }
1592
1601
  );
1593
1602
  const reportPayload = {
@@ -1608,7 +1617,13 @@ async function main() {
1608
1617
  originJustCreated: !existingOrigin,
1609
1618
  adoption: adoptionForReport,
1610
1619
  designDepth,
1611
- advisories: computeDoctorAdvisories(root, config, coverage, rules, files, ts, parseHealth),
1620
+ advisories: {
1621
+ ...computeDoctorAdvisories(root, config, coverage, rules, files, ts, parseHealth),
1622
+ // Doctor parity: always emit improvement compass when doctor would (reportParity).
1623
+ ...(designDepth?.improvementCompass
1624
+ ? { improvementCompass: designDepth.improvementCompass }
1625
+ : {}),
1626
+ },
1612
1627
  };
1613
1628
  const html = args.beginner
1614
1629
  ? renderBeginnerHtmlReport(reportPayload)
@@ -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
  },