arkgate 4.3.0 → 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 (51) hide show
  1. package/CHANGELOG.md +36 -10
  2. package/README.md +10 -7
  3. package/bin/ark-check-runtime.mjs +16 -1
  4. package/bin/lib/agent-projection.mjs +1 -1
  5. package/bin/lib/ci-and-commands.mjs +11 -5
  6. package/bin/lib/doctor-plan.mjs +25 -0
  7. package/bin/lib/html-report-advisories.mjs +33 -0
  8. package/bin/lib/html-report-depth.mjs +24 -0
  9. package/bin/lib/improvement-compass-doctor.mjs +106 -0
  10. package/bin/lib/improvement-compass.mjs +630 -0
  11. package/bin/lib/status-manifest.mjs +38 -1
  12. package/dist/index.cjs +33 -33
  13. package/dist/index.d.ts +159 -2
  14. package/dist/index.js +33 -33
  15. package/docs/README.md +6 -5
  16. package/docs/agent-guide.md +20 -4
  17. package/docs/develop.md +6 -3
  18. package/docs/diagnostics.md +1 -1
  19. package/docs/package-surface.md +37 -34
  20. package/docs/product-voice.md +26 -0
  21. package/docs/use.md +37 -1
  22. package/package.json +1 -1
  23. package/schemas/ark.status-manifest.schema.json +26 -0
  24. package/server.json +2 -2
  25. package/templates/agent-skills/README.md +2 -2
  26. package/templates/agent-skills/ark-adopt/SKILL.md +20 -0
  27. package/templates/agent-skills/ark-architect/SKILL.md +21 -1
  28. package/templates/agent-skills/ark-autopilot/SKILL.md +25 -5
  29. package/templates/agent-skills/ark-contract/SKILL.md +20 -0
  30. package/templates/agent-skills/ark-coverage/SKILL.md +20 -0
  31. package/templates/agent-skills/ark-explain/SKILL.md +20 -0
  32. package/templates/agent-skills/ark-explore/SKILL.md +23 -3
  33. package/templates/agent-skills/ark-fix/SKILL.md +22 -2
  34. package/templates/agent-skills/ark-loop/SKILL.md +22 -2
  35. package/templates/agent-skills/ark-place/SKILL.md +20 -0
  36. package/templates/agent-skills/ark-runtime/SKILL.md +7 -0
  37. package/templates/agent-skills/ark-think/SKILL.md +20 -0
  38. package/templates/agent-skills/ark-upgrade/SKILL.md +20 -0
  39. package/templates/skills/ark-adopt.md +20 -0
  40. package/templates/skills/ark-architect.md +21 -1
  41. package/templates/skills/ark-autopilot.md +25 -5
  42. package/templates/skills/ark-contract.md +20 -0
  43. package/templates/skills/ark-coverage.md +20 -0
  44. package/templates/skills/ark-explain.md +20 -0
  45. package/templates/skills/ark-explore.md +23 -3
  46. package/templates/skills/ark-fix.md +22 -2
  47. package/templates/skills/ark-loop.md +22 -2
  48. package/templates/skills/ark-place.md +20 -0
  49. package/templates/skills/ark-runtime.md +7 -0
  50. package/templates/skills/ark-think.md +20 -0
  51. package/templates/skills/ark-upgrade.md +20 -0
package/CHANGELOG.md CHANGED
@@ -5,14 +5,41 @@ in the immutable pre-2.0 archive linked below.
5
5
 
6
6
  ## Unreleased
7
7
 
8
- ## 4.3.0 — 2026-08-09 (prepared)
8
+ ## 4.4.0 — 2026-08-09
9
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).
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`).
16
43
 
17
44
  ### Added
18
45
 
@@ -58,15 +85,14 @@ remain open. **Status: prepared** (`arkgate@4.3.0` not published yet; **4.2.1**
58
85
  ### Docs / claims (ACS08)
59
86
 
60
87
  - 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.
88
+ lanes). Release notes at `docs/releases/4.3.0.md` (**Status: published**). npm `latest` is **4.3.0**.
63
89
 
64
90
  ## 4.2.1 — 2026-08-08 (published)
65
91
 
66
92
  **Patch** over 4.2.0. Next.js **16.3** field compatibility: root `proxy.ts` (Next 16 network-boundary
67
93
  rename of middleware) is scanned and classified; eval fixtures and release-surface pins track
68
94
  `next@16.3.0`. **No required config migration.** Codex remains advisory at write time.
69
- **Status: published** (`arkgate@4.2.1` on npm `latest`).
95
+ **Status: published** (`arkgate@4.2.1`; superseded by 4.3.0 on `latest`).
70
96
 
71
97
  ### Fixed
72
98
 
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.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)
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.
@@ -206,8 +208,9 @@ for real systems. Details: [docs/production-hardening.md](docs/production-harden
206
208
  | Config · package surface · TS | [configuration](docs/configuration.md) · [package-surface](docs/package-surface.md) · [typescript-support](docs/typescript-support.md) |
207
209
  | Brownfield | [docs/brownfield-adoption.md](docs/brownfield-adoption.md) |
208
210
  | Security | [SECURITY.md](SECURITY.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
+ | 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) |
211
214
  | Previous (4.2.0) | [docs/releases/4.2.0.md](docs/releases/4.2.0.md) |
212
215
  | Previous (4.1.1) | [docs/releases/4.1.1.md](docs/releases/4.1.1.md) |
213
216
  | Previous (4.1.0) | [docs/releases/4.1.0.md](docs/releases/4.1.0.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)
@@ -148,7 +148,7 @@ export function buildAgentProjectionBody(facts) {
148
148
  '',
149
149
  ];
150
150
  if (profile === 'compact') {
151
- lines.push('### Primary path', '', '1. Call `ark_identity` with `project.expectedRoot` at the exact project root; reuse root + `projectId` on Ark MCP calls.', '2. Read the contract with `ark_manifest` (same expectation). `ark://manifest` is compatibility-only / unverified.', '3. Place files inside configured layers; validate; run the check command above on violations — fix architecture, do not weaken the gate.', '', '### Contract layers (summary)', '', formatAgentProjectionLayers(layers), '');
151
+ lines.push('### Primary path', '', '1. Run doctor (`ark-check --doctor`) — status light + primary next action.', '2. Read the improvement compass (not a score). Name residual lenses in plain language; never “done” on green edges alone while residual remains.', '3. Call `ark_identity` with `project.expectedRoot` at the exact project root; reuse root + `projectId` on Ark MCP calls.', '4. Read the contract with `ark_manifest` (same expectation). `ark://manifest` is compatibility-only / unverified.', '5. Place files inside configured layers; validate; run the check command above on violations — fix architecture, do not weaken the gate.', '6. Single door: edges debt → fix; design-weak / residual shape lenses → map then guided apply with user OK.', '', '### Contract layers (summary)', '', formatAgentProjectionLayers(layers), '');
152
152
  }
153
153
  else {
154
154
  lines.push('### Contract layers', '', formatAgentProjectionLayers(layers), '', 'When creating a **new** kind of code that no layer covers, update `ark.config.json` first (`/ark-contract`), 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: `ark_manifest` after a matched `ark_identity` handshake.', '');
@@ -403,10 +403,16 @@ ${projectionBlock}
403
403
 
404
404
  **Primary path (do this):**
405
405
 
406
- 1. Status anytime: \`${doctorCmd}\` — one status light, one next action (control plane).
407
- 2. Before trusting MCP evidence: call \`ark_identity\` with \`project.expectedRoot\` set to this project's exact absolute root, then reuse that root plus the returned \`projectIdentity.projectId\` on every Ark MCP call. A descendant path is authoritative only with that matching id. Missing tool, non-\`matched\` binding, or wrong root means the process is stale: restart the host and use the local CLI meanwhile.
408
- 3. Day to day: call \`ark_manifest\` with the same project expectation; place new files with \`ark_place\`; validate after edits; run \`${checkCmd}\`. The \`ark://manifest\` resource is compatibility-only and always unverified/non-authoritative. On a gate deny, fix the architecture do not weaken the contract.
409
- 4. If MCP is unavailable: inspect \`ark.config.json\` and run \`${checkCmd}\`.
406
+ 1. Status anytime: \`${doctorCmd}\` — one status light, one primary next action (control plane).
407
+ 2. Read the **Improvement compass** section (not a score). Name residual lenses in plain language when present (SoC, DIP, domain, …). Out-of-scope lenses (performance, app security tooling, full resilience) stay honest do not invent Ark enforcement for them.
408
+ 3. Before trusting MCP evidence: call \`ark_identity\` with \`project.expectedRoot\` set to this project's exact absolute root, then reuse that root plus the returned \`projectIdentity.projectId\` on every Ark MCP call. A descendant path is authoritative only with that matching id. Missing tool, non-\`matched\` binding, or wrong root means the process is stale: restart the host and use the local CLI meanwhile.
409
+ 4. Day to day: call \`ark_manifest\` with the same project expectation; place new files with \`ark_place\`; validate after edits; run \`${checkCmd}\`. The \`ark://manifest\` resource is compatibility-only and always unverified/non-authoritative. On a gate deny, fix the architecture — do not weaken the contract.
410
+ 5. If MCP is unavailable: inspect \`ark.config.json\` and run \`${checkCmd}\`.
411
+
412
+ **Single door when residual remains:**
413
+ - **Edges debt** (import/capability violations) → fix with the gate / plan; skill pack only if doctor names a skill.
414
+ - **Design-weak / residual shape lenses** (compass residual while edges may look green) → map first, then guided apply with user OK — never “you’re done” on green edges alone.
415
+ - Empty plan A + residual lenses / design-weak → **not finished**.
410
416
 
411
417
  The selected host is \`${selectedHost}\`. Host registration and CI are installed with this file.
412
418
  This compact router is enough for normal feature work.
@@ -414,7 +420,7 @@ This compact router is enough for normal feature work.
414
420
  ## Expert depth (optional)
415
421
 
416
422
  Full \`/ark-*\` skills (including guided end-to-end \`/ark-autopilot\`) are **not** the default
417
- curriculum. Install them only when doctor top action #1 or a STOP handoff names a skill:
423
+ curriculum. Install them only when doctor top action #1, residual compass, or a STOP handoff names a skill:
418
424
 
419
425
  \`${installSkills}\`
420
426
  `;
@@ -62,6 +62,10 @@ import { computeDoctorAdvisories, printDoctorAdvisories } from './doctor-advisor
62
62
  import { ANALYSIS_COMPLETENESS, analysisIncompleteStatement, normalizeAnalysisCompleteness } from './analysis-completeness.mjs';
63
63
  import { designDeltaDoctorLines } from './design-delta.mjs';
64
64
  import { enforcementDoctorLines } from './enforcement-state.mjs';
65
+ import {
66
+ buildDoctorImprovementCompass,
67
+ printImprovementCompassSection,
68
+ } from './improvement-compass-doctor.mjs';
65
69
 
66
70
  const color = {
67
71
  green: (s) => `\x1b[32m${s}\x1b[0m`,
@@ -640,6 +644,23 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
640
644
  packageVersionTruth?.code === 'PACKAGE_PIN_SELF_HOST',
641
645
  });
642
646
 
647
+ // Improvement compass: projection only — never feeds ok/valid/goal.met.
648
+ const improvementCompass = buildDoctorImprovementCompass({
649
+ designSmells,
650
+ violations,
651
+ designWeak: designFitness.designWeak === true,
652
+ physicalCohesion: doctorAdvisories.physicalCohesion,
653
+ rulesUnderContract,
654
+ baselineExists: baseline.exists,
655
+ baselineStale: analysisComplete ? staleBaseline : null,
656
+ frozenResidual: baseline.exists ? baseline.keys.size : null,
657
+ dirtyBaselineRisk: productHonesty?.reasonIds?.includes?.('dirty-baseline') === true,
658
+ ungovernedDirCount: cov.suggestions?.length ?? 0,
659
+ emptyLayerCount: cov.emptyLayers?.length ?? 0,
660
+ goldenPatternPresent: goldenPattern.present === true,
661
+ arkRulesLoaded: rulesUnderContract?.active === true,
662
+ });
663
+
643
664
  if (asJson) {
644
665
  (options.writeJson ?? console.log)(
645
666
  JSON.stringify(
@@ -654,6 +675,8 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
654
675
  // Path-correct ENFORCE can still be design-weak (P02).
655
676
  designFitness,
656
677
  designSmells,
678
+ // Improvement compass (lenses; notAScore; never a gate input).
679
+ improvementCompass,
657
680
  ...(options.designDelta ? { designDelta: options.designDelta } : {}),
658
681
  // Q01: primary next action when Shape residual dominates (null if not design-weak).
659
682
  postGreenPath,
@@ -846,6 +869,8 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
846
869
  }
847
870
  }
848
871
 
872
+ printImprovementCompassSection(improvementCompass, { line, warn, ok, color });
873
+
849
874
  console.log('');
850
875
  console.log(color.bold('Design fitness'));
851
876
  if (designSmells.length === 0) {
@@ -10,6 +10,7 @@
10
10
  import { effectiveCapabilityDeny } from './analysis-engine.mjs';
11
11
  import { graphBlindSpotsHtml } from './graph-blind.mjs';
12
12
  import { formatRulesUnderContractHtml } from './rules-under-contract.mjs';
13
+ import { primaryImprovementCompassNextAction } from './improvement-compass.mjs';
13
14
 
14
15
  // htmlEscape is injected by the caller (html-report.mjs) — importing it back
15
16
  // would be a dependency cycle, and the repo's own gate blocks that. The
@@ -247,10 +248,42 @@ function rulesUnderContractHtml(section) {
247
248
  return formatRulesUnderContractHtml(section, esc);
248
249
  }
249
250
 
251
+ /** Improvement compass — advisory lenses only; never a score bar or gate input. */
252
+ function improvementCompassHtml(compass) {
253
+ if (!compass || compass.notAScore !== true || !Array.isArray(compass.lenses)) return '';
254
+ const residual = Array.isArray(compass.topResidual) ? compass.topResidual : [];
255
+ const residualLine =
256
+ residual.length === 0
257
+ ? '<p class="muted">Residual: none on instrumented lenses (not a score — green edges ≠ finished design).</p>'
258
+ : `<p><span class="tag warn">residual</span> ${residual
259
+ .map((id) => {
260
+ const lens = compass.lenses.find((l) => l.id === id);
261
+ return `<code>${esc(id)}</code>${lens?.summary ? ` — ${esc(lens.summary)}` : ''}`;
262
+ })
263
+ .join('<br/>')}</p>`;
264
+ const oos = compass.lenses
265
+ .filter((l) => l && l.status === 'out-of-scope')
266
+ .map((l) => `<code>${esc(l.id)}</code>`)
267
+ .join(' · ');
268
+ // Same primary next as doctor human: severity-ordered topResidual, not lens-id order.
269
+ const next = primaryImprovementCompassNextAction(compass);
270
+ const nextLine = next
271
+ ? `<p class="muted">Next: <code>${esc(next.ref)}</code> — ${esc(next.summary)}</p>`
272
+ : '';
273
+ return `
274
+ <section class="section card" data-advisory="improvementCompass">
275
+ <h2>Improvement compass <span class="muted">(not a score — projection only; never changes the verdict)</span></h2>
276
+ ${residualLine}
277
+ <p class="muted">Out of scope (honest): ${oos || 'scalability · resilience · security'}</p>
278
+ ${nextLine}
279
+ </section>`;
280
+ }
281
+
250
282
  export function renderAdvisorySections(advisories, escape) {
251
283
  if (!advisories || typeof advisories !== 'object') return '';
252
284
  if (typeof escape === 'function') esc = escape;
253
285
  return [
286
+ improvementCompassHtml(advisories.improvementCompass),
254
287
  contractHealthHtml(advisories.contractHealth),
255
288
  ambientStateHtml(advisories.ambientState),
256
289
  physicalCohesionHtml(advisories.physicalCohesion),
@@ -21,6 +21,8 @@ import {
21
21
  import { summarizeRulesUnderContract } from './rules-under-contract.mjs';
22
22
  import { readBaseline, baselineOccurrenceKeys } from './violations.mjs';
23
23
  import { describePackageVersionDualTruth } from './field-install.mjs';
24
+ import { buildDoctorImprovementCompass } from './improvement-compass-doctor.mjs';
25
+ import { computePhysicalCohesion } from './physical-cohesion.mjs';
24
26
 
25
27
  function esc(value) {
26
28
  return String(value)
@@ -43,6 +45,7 @@ function esc(value) {
43
45
  * frozenKeys?: number,
44
46
  * activeCount?: number,
45
47
  * activeBlockingCount?: number,
48
+ * baselineStale?: number | null,
46
49
  * }} [baselineSplit] same numbers doctor uses (do not recompute from active-only list)
47
50
  */
48
51
  export function buildReportDepthPayload(
@@ -155,6 +158,26 @@ export function buildReportDepthPayload(
155
158
  primaryNextAction: postGreenPath?.action ?? dualTruthNext,
156
159
  activeBlockingViolations: activeBlockingCount,
157
160
  });
161
+ // Doctor parity: same physical-cohesion + baseline stale facts as runDoctor.
162
+ const physicalCohesion = computePhysicalCohesion(root, files);
163
+ const baselineStale =
164
+ typeof baselineSplit.baselineStale === 'number' ? baselineSplit.baselineStale : null;
165
+ // Improvement compass — same projection as doctor; notAScore; never a gate input.
166
+ const improvementCompass = buildDoctorImprovementCompass({
167
+ designSmells,
168
+ violations: activeViolations,
169
+ designWeak: designFitness.designWeak === true,
170
+ physicalCohesion,
171
+ rulesUnderContract,
172
+ baselineExists: baseline.exists || frozenKeys > 0,
173
+ baselineStale,
174
+ frozenResidual: frozenKeys,
175
+ dirtyBaselineRisk: productHonesty?.reasonIds?.includes?.('dirty-baseline') === true,
176
+ ungovernedDirCount: coverage?.suggestions?.length ?? 0,
177
+ emptyLayerCount: coverage?.emptyLayers?.length ?? 0,
178
+ goldenPatternPresent: goldenPattern.present === true,
179
+ arkRulesLoaded: rulesUnderContract?.active === true,
180
+ });
158
181
  return {
159
182
  adoption,
160
183
  designDepth: {
@@ -166,6 +189,7 @@ export function buildReportDepthPayload(
166
189
  // P0-B / P1-M — folded into designDepth so --report stays a single payload.
167
190
  productHonesty,
168
191
  mergePlanes: rulesUnderContract?.mergePlanes ?? null,
192
+ improvementCompass,
169
193
  },
170
194
  };
171
195
  }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Doctor adapter for the Domain improvement compass (notAScore projection).
3
+ * Keeps doctor-plan.mjs inside its module budget; pure assembly only.
4
+ */
5
+ import {
6
+ buildImprovementCompass,
7
+ formatImprovementCompassDoctorLines,
8
+ primaryImprovementCompassNextAction,
9
+ } from './improvement-compass.mjs';
10
+
11
+ /**
12
+ * @param {{
13
+ * designSmells?: object[],
14
+ * violations?: object[],
15
+ * designWeak?: boolean,
16
+ * physicalCohesion?: { findings?: object[] } | null,
17
+ * rulesUnderContract?: object | null,
18
+ * baselineExists?: boolean,
19
+ * baselineStale?: number | null,
20
+ * frozenResidual?: number | null,
21
+ * dirtyBaselineRisk?: boolean,
22
+ * ungovernedDirCount?: number,
23
+ * emptyLayerCount?: number,
24
+ * goldenPatternPresent?: boolean,
25
+ * arkRulesLoaded?: boolean,
26
+ * }} input
27
+ */
28
+ export function buildDoctorImprovementCompass(input = {}) {
29
+ const violations = Array.isArray(input.violations) ? input.violations : [];
30
+ const ruleId = (v) => String(v?.ruleId ?? v?.code ?? '');
31
+
32
+ let cycleCount = 0;
33
+ let peerIsolationCount = 0;
34
+ let pureOrCapabilityResidual = 0;
35
+ let forbiddenGlobalResidual = 0;
36
+ let arkRulesStructureResidual = 0;
37
+
38
+ for (const v of violations) {
39
+ const id = ruleId(v).toUpperCase();
40
+ if (!id) continue;
41
+ if (id.includes('CYCLE') || id === 'CIRCULAR_DEPENDENCY') cycleCount += 1;
42
+ if (id.includes('PEER_ISOLATION')) peerIsolationCount += 1;
43
+ if (id === 'CAPABILITY_VIOLATION') pureOrCapabilityResidual += 1;
44
+ if (id === 'FORBIDDEN_GLOBAL' || id.startsWith('FORBIDDEN_')) forbiddenGlobalResidual += 1;
45
+ if (id.startsWith('ARKRULE_') || id === 'INVARIANT_UNCOVERED') arkRulesStructureResidual += 1;
46
+ }
47
+
48
+ const pcFindings = input.physicalCohesion?.findings;
49
+ const physicalCohesionFindingCount = Array.isArray(pcFindings) ? pcFindings.length : 0;
50
+
51
+ const arkRulesLoaded =
52
+ input.arkRulesLoaded === true ||
53
+ input.rulesUnderContract?.active === true ||
54
+ (typeof input.rulesUnderContract?.structureRules === 'number' &&
55
+ input.rulesUnderContract.structureRules > 0);
56
+
57
+ return buildImprovementCompass({
58
+ designSmells: Array.isArray(input.designSmells) ? input.designSmells : [],
59
+ violations: violations.map((v) => ({
60
+ ruleId: ruleId(v) || undefined,
61
+ message: typeof v?.message === 'string' ? v.message : undefined,
62
+ file: typeof v?.file === 'string' ? v.file : typeof v?.path === 'string' ? v.path : undefined,
63
+ fromLayer: v?.fromLayer,
64
+ toLayer: v?.toLayer,
65
+ failsStrict: v?.failsStrict,
66
+ typeOnly: v?.typeOnly === true || v?.namedBindingsTypeOnly === true || undefined,
67
+ })),
68
+ cycleCount,
69
+ peerIsolationCount,
70
+ physicalCohesionFindingCount,
71
+ arkRulesLoaded,
72
+ arkRulesStructureResidual,
73
+ designWeak: input.designWeak === true,
74
+ baselineExists: input.baselineExists === true,
75
+ baselineStale: input.baselineStale ?? null,
76
+ frozenResidual: input.frozenResidual ?? null,
77
+ dirtyBaselineRisk: input.dirtyBaselineRisk === true,
78
+ pureOrCapabilityResidual,
79
+ forbiddenGlobalResidual,
80
+ ungovernedDirCount: Number(input.ungovernedDirCount) || 0,
81
+ emptyLayerCount: Number(input.emptyLayerCount) || 0,
82
+ goldenPatternPresent: input.goldenPatternPresent === true,
83
+ // Doctor path is TypeScript-oriented (ArkGate product surface).
84
+ stackKind: 'typescript',
85
+ });
86
+ }
87
+
88
+ export {
89
+ formatImprovementCompassDoctorLines,
90
+ primaryImprovementCompassNextAction,
91
+ };
92
+
93
+ /**
94
+ * Human doctor section (never a score bar).
95
+ * @param {import('./improvement-compass.mjs').ImprovementCompass} compass
96
+ * @param {{ line: Function, warn: string, ok: string, color: { bold: Function } }} io
97
+ */
98
+ export function printImprovementCompassSection(compass, io) {
99
+ const { line, warn, ok, color } = io;
100
+ console.log('');
101
+ console.log(color.bold('Improvement compass (not a score)'));
102
+ const mark = compass.topResidual.length > 0 ? warn : ok;
103
+ for (const text of formatImprovementCompassDoctorLines(compass)) {
104
+ line(mark, text);
105
+ }
106
+ }