arkgate 4.6.4 → 4.6.5

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 (49) hide show
  1. package/CHANGELOG.md +39 -5
  2. package/README.md +6 -5
  3. package/bin/ark-check-runtime.mjs +98 -14
  4. package/bin/ark-mcp-runtime.mjs +18 -30
  5. package/bin/lib/adapter-contract.mjs +13 -9
  6. package/bin/lib/agent-projection-command.mjs +18 -0
  7. package/bin/lib/agent-projection.mjs +2 -2
  8. package/bin/lib/analysis-engine.mjs +5 -5
  9. package/bin/lib/ci-and-commands.mjs +3 -3
  10. package/bin/lib/ci-merge-boundary.mjs +89 -0
  11. package/bin/lib/config-contract.mjs +2 -0
  12. package/bin/lib/diagnostic-catalog.mjs +5 -4
  13. package/bin/lib/doctor-next-actions.mjs +17 -5
  14. package/bin/lib/doctor-plan.mjs +14 -0
  15. package/bin/lib/enforcement-honesty.mjs +2 -0
  16. package/bin/lib/graph-blind.mjs +15 -6
  17. package/bin/lib/install-migrate.mjs +10 -0
  18. package/bin/lib/invariant-coverage.mjs +6 -2
  19. package/bin/lib/managed-upgrade.mjs +8 -3
  20. package/bin/lib/presets.mjs +22 -0
  21. package/bin/lib/remediation.mjs +74 -10
  22. package/bin/lib/skill-install.mjs +2 -0
  23. package/bin/lib/snippet-analysis.mjs +40 -8
  24. package/dist/{configTypes-B8uIcLaG.d.ts → configTypes-l6XiwiC1.d.ts} +7 -0
  25. package/dist/eslint/index.cjs +3 -3
  26. package/dist/eslint/index.d.ts +1 -1
  27. package/dist/eslint/index.js +3 -3
  28. package/dist/index.cjs +26 -26
  29. package/dist/index.d.ts +11 -2
  30. package/dist/index.js +29 -29
  31. package/docs/README.md +3 -2
  32. package/docs/agent-guide.md +10 -0
  33. package/docs/brownfield-adoption.md +7 -1
  34. package/docs/configuration.md +2 -1
  35. package/docs/develop.md +4 -2
  36. package/docs/diagnostics.md +15 -5
  37. package/docs/package-surface.md +3 -2
  38. package/package.json +1 -1
  39. package/schemas/ark.config.schema.json +6 -0
  40. package/server.json +2 -2
  41. package/templates/agent-skills/README.md +1 -1
  42. package/templates/agent-skills/ark-adopt/SKILL.md +7 -0
  43. package/templates/agent-skills/ark-explore/SKILL.md +6 -0
  44. package/templates/agent-skills/ark-place/SKILL.md +11 -4
  45. package/templates/agent-skills/ark-upgrade/SKILL.md +9 -2
  46. package/templates/skills/ark-adopt.md +7 -0
  47. package/templates/skills/ark-explore.md +6 -0
  48. package/templates/skills/ark-place.md +11 -4
  49. package/templates/skills/ark-upgrade.md +9 -2
package/CHANGELOG.md CHANGED
@@ -5,6 +5,40 @@ in the immutable pre-2.0 archive linked below.
5
5
 
6
6
  ## Unreleased
7
7
 
8
+ ## 4.6.5 — 2026-08-19
9
+
10
+ **Patch** over **4.6.4**. Adoption, placement, doctor, upgrade, and write-path honesty for
11
+ existing Next.js trees and multi-host teams. **No required config migration.**
12
+
13
+ **Status: current** (shipping as `arkgate@4.6.5`; see `docs/releases/4.6.5.md`).
14
+
15
+ ### Changed
16
+
17
+ - **Adopt starter:** existing trees get SharedKernel (types/constants), CompositionRoot (wiring),
18
+ and `src/**/domain/**`. Flattened `src/lib/**` is not dumped into Application. Adopt writes
19
+ `.ark/golden-pattern.json` (load-bearing for place).
20
+ - **Place:** `filePath` is required (fail-closed). Never invents `components/*.tsx` or defaults
21
+ to Presentation.
22
+ - **LAYER_IMPORT nextAction:** branches by import kind — constants/types → Domain/SharedKernel;
23
+ kernel/events from Persistence → do not emit; port only for a real use-case.
24
+ - **Reserved empty globs:** `reserved` / `allowEmpty` so `--strict-config` does not fail on
25
+ future houses. Typo warning only if the glob is not reserved.
26
+ - **Parse / lexical:** `ANALYSIS_PARSE_INCOMPLETE` includes the TypeScript line + message.
27
+ Contract `exclude` paths skip the write hook. Incremental mid-edit parse does not deny.
28
+ `LEXICAL_EVIDENCE_INCOMPLETE` hook deny does not tell the agent to call `ark_prepare_change`.
29
+ - **WritePath / CI honesty:** `.ark/ci-merge-boundary.json` — hook configured-not-fired,
30
+ per-host writePath (Claude hard vs Cursor soft), CI present-but-not-required, GitHub Free
31
+ cannot require. Hook green is not tree green.
32
+ - **Upgrade:** preview default is hosts keep (union `--tools` with existing). Apply installs
33
+ the bumped package unless `--no-install`. Projection writes AGENTS.md and CLAUDE.md.
34
+ Prefer project `.agents/skills`; home must not duplicate the catalog.
35
+ - **Doctor JSON:** stable envelope `{ schemaVersion, envelope: "doctor", ok, doctor }`.
36
+ ENFORCE + empty plan A → Shape, not reinstall gates. Distinguishes installed vs stale skills.
37
+ - **Graph scan:** threshold scales with included file count (floor 2500, cap 8000) so a
38
+ ~3300-file Next.js tree is not deferred.
39
+ - **INVARIANT_UNCOVERED:** `never-had-tests` (adopt residual) vs `tests-disappeared` (regression).
40
+ - **CLI-first:** identity handshake is optional when the CLI already resolved the root.
41
+
8
42
  ## 4.6.4 — 2026-08-18
9
43
 
10
44
  **Patch** over **4.6.3**. `ark upgrade` now tells Codex users how to activate the local
@@ -12,7 +46,7 @@ in the immutable pre-2.0 archive linked below.
12
46
  trust the exact hook definition, and verify `doctor.writePath` after a governed patch.
13
47
  **No required config migration.**
14
48
 
15
- **Status: prepared** (not yet on npm `latest`; see `docs/releases/4.6.4.md`).
49
+ **Status: published** (on npm `latest` until 4.6.5 lands; see `docs/releases/4.6.4.md`).
16
50
 
17
51
  ### Changed
18
52
 
@@ -829,8 +863,8 @@ Not a rewrite of the analysis engine. No plan-B silent codemod. No fake hard wri
829
863
 
830
864
  ## 3.8.3 — 2026-07-22
831
865
 
832
- Corrective **patch** over 3.8.2 from multi-repo field evidence (PROPIA pnpm workspace upgrade;
833
- Amarilla greenfield `start`). **No required config migration.** Z09 / residual `RB-11` remain open.
866
+ Corrective **patch** over 3.8.2 from multi-repo field evidence (pnpm workspace upgrade;
867
+ greenfield `start`). **No required config migration.** Z09 / residual `RB-11` remain open.
834
868
 
835
869
  ### Fixed
836
870
 
@@ -857,7 +891,7 @@ Amarilla greenfield `start`). **No required config migration.** Z09 / residual `
857
891
 
858
892
  ## 3.8.2 — 2026-07-22
859
893
 
860
- Corrective **patch** over 3.8.1 from PREDIAL WEB field evidence. Aligns doctor skill freshness with
894
+ Corrective **patch** over 3.8.1 from field Next.js evidence. Aligns doctor skill freshness with
861
895
  managed upgrade content identity, hardens upgrade preview honesty, clarifies doctor writePath
862
896
  inventory vs this-invocation, ships the Y06 pure-layer opt-in advisory, and treats complete-catalog
863
897
  Codex leftover prompts as safe-to-delete. **No required config migration.** Z09 / residual `RB-11`
@@ -881,7 +915,7 @@ remain open.
881
915
 
882
916
  - **Y06 pure-layer opt-in nudge:** when a golden pattern names pure modules and no layer sets
883
917
  `pure: true`, doctor emits one advisory line (`doctor.pureLayerOptIn`). Never a blocker; never
884
- auto-writes `pure: true`. Promoted from field evidence (PREDIAL WEB).
918
+ auto-writes `pure: true`. Promoted from field evidence (a Next.js app).
885
919
 
886
920
  ## 3.8.1 — 2026-07-22
887
921
 
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.6.3** is on npm `latest` Codex CLI/local Desktop hard-block complete trusted `apply_patch`.
20
- > **4.6.4 is prepared** `ark upgrade` tells Codex how to refresh, trust, restart, and verify it.
21
- > [4.6.4 notes](docs/releases/4.6.4.md) · [4.6.3](docs/releases/4.6.3.md) · [4.6.2](docs/releases/4.6.2.md) · [4.6.1](docs/releases/4.6.1.md) · [Docs hub](docs/README.md) · [Product voice](docs/product-voice.md)
19
+ > **ArkGate 4.6.5** is current (on npm after this release). Adopt/place honesty for existing Next.js
20
+ > trees, stable doctor JSON, writePath/CI honesty file, hosts-keep upgrade, CLAUDE.md projection.
21
+ > [4.6.5 notes](docs/releases/4.6.5.md) · [4.6.4](docs/releases/4.6.4.md) · [4.6.3](docs/releases/4.6.3.md) · [4.6.2](docs/releases/4.6.2.md) · [4.6.1](docs/releases/4.6.1.md) · [4.6.0](docs/releases/4.6.0.md) · [Docs hub](docs/README.md) · [Product voice](docs/product-voice.md)
22
22
 
23
23
  ---
24
24
 
@@ -216,8 +216,9 @@ for real systems. Details: [docs/production-hardening.md](docs/production-harden
216
216
  | Config · package surface · TS | [configuration](docs/configuration.md) · [package-surface](docs/package-surface.md) · [typescript-support](docs/typescript-support.md) |
217
217
  | Brownfield | [docs/brownfield-adoption.md](docs/brownfield-adoption.md) |
218
218
  | Security | [SECURITY.md](SECURITY.md) |
219
- | Current release (4.6.3 on npm `latest`) | [docs/releases/4.6.3.md](docs/releases/4.6.3.md) · [CHANGELOG](CHANGELOG.md) |
220
- | Prepared patch (4.6.4) | [docs/releases/4.6.4.md](docs/releases/4.6.4.md) |
219
+ | Current release (4.6.5) | [docs/releases/4.6.5.md](docs/releases/4.6.5.md) · [CHANGELOG](CHANGELOG.md) |
220
+ | Prior published (4.6.4 on npm `latest` until 4.6.5 lands) | [docs/releases/4.6.4.md](docs/releases/4.6.4.md) |
221
+ | Prior published (4.6.3) | [docs/releases/4.6.3.md](docs/releases/4.6.3.md) |
221
222
  | Prior (4.6.2) | [docs/releases/4.6.2.md](docs/releases/4.6.2.md) |
222
223
  | Prior (4.6.1) | [docs/releases/4.6.1.md](docs/releases/4.6.1.md) |
223
224
  | Prior (4.6.0) | [docs/releases/4.6.0.md](docs/releases/4.6.0.md) |
@@ -87,9 +87,11 @@ import {
87
87
  import {
88
88
  ARCHITECTURE_PRESETS,
89
89
  APPLICATION_LIB_ORCHESTRATION_PATTERNS,
90
+ COMPOSITION_ROOT_PATH_PATTERNS,
90
91
  DOMAIN_PATH_PATTERNS,
91
92
  NEXT_API_APPLICATION_PATTERNS,
92
93
  PERSISTENCE_PATH_PATTERNS,
94
+ SHARED_KERNEL_PATH_PATTERNS,
93
95
  retrofitP0aApiApplicationPatterns,
94
96
  withDefaultArkRules,
95
97
  writeArkRulesTemplates,
@@ -573,6 +575,60 @@ function runSuggestInclude(args) {
573
575
  console.log(color.dim(payload.note));
574
576
  }
575
577
 
578
+ const MATURE_LAYER_RULES = Object.freeze([
579
+ { from: 'SharedKernel', to: 'DomainModel', allowed: false },
580
+ { from: 'SharedKernel', to: 'ApplicationOrchestration', allowed: false },
581
+ { from: 'SharedKernel', to: 'PresentationAdapters', allowed: false },
582
+ { from: 'SharedKernel', to: 'PersistenceAdapters', allowed: false },
583
+ { from: 'SharedKernel', to: 'CompositionRoot', allowed: false },
584
+ { from: 'DomainModel', to: 'CompositionRoot', allowed: false },
585
+ { from: 'PresentationAdapters', to: 'CompositionRoot', allowed: false },
586
+ { from: 'PersistenceAdapters', to: 'CompositionRoot', allowed: false },
587
+ ]);
588
+
589
+ function ensureMatureAdoptionLayers(layers, byLayer) {
590
+ const names = new Set(layers.map((layer) => layer.name));
591
+ const next = [...layers];
592
+ if (!names.has('SharedKernel')) {
593
+ next.push({
594
+ name: 'SharedKernel',
595
+ patterns: [...(byLayer.get('SharedKernel') ?? SHARED_KERNEL_PATH_PATTERNS)],
596
+ reserved: true,
597
+ allowEmpty: true,
598
+ description: 'Shared types and constants. Persistence and Presentation may import this; it imports nothing else.',
599
+ });
600
+ }
601
+ if (!names.has('CompositionRoot')) {
602
+ next.push({
603
+ name: 'CompositionRoot',
604
+ patterns: [...(byLayer.get('CompositionRoot') ?? COMPOSITION_ROOT_PATH_PATTERNS)],
605
+ reserved: true,
606
+ allowEmpty: true,
607
+ description: 'DI / bootstrap wiring. May import Domain and Persistence; Domain must not import this.',
608
+ });
609
+ }
610
+ return next;
611
+ }
612
+
613
+ function writeAdoptGoldenPattern(root) {
614
+ const dir = path.join(root, '.ark');
615
+ const dest = path.join(dir, 'golden-pattern.json');
616
+ if (fs.existsSync(dest)) return { wrote: false, path: '.ark/golden-pattern.json' };
617
+ fs.mkdirSync(dir, { recursive: true });
618
+ const golden = {
619
+ schemaVersion: '1',
620
+ name: 'feature-folders',
621
+ norm:
622
+ 'New modules live under src/<feature>/{domain,application,composition,infrastructure}. ' +
623
+ 'Types and constants go to SharedKernel. Wiring goes to CompositionRoot. ' +
624
+ 'Presentation never imports Domain. Persistence never imports Application.',
625
+ newCodeHome: 'src',
626
+ examplePath: 'src/example/domain/model.ts',
627
+ };
628
+ fs.writeFileSync(dest, `${JSON.stringify(golden, null, 2)}\n`);
629
+ return { wrote: true, path: '.ark/golden-pattern.json' };
630
+ }
631
+
576
632
  /**
577
633
  * Contract-adopt: expand include + layer patterns from ungoverned proposals.
578
634
  * Read-only unless --write. Does not weaken rules or baseline violations.
@@ -632,7 +688,9 @@ function runAdoptContract(args) {
632
688
  ...APPLICATION_LIB_ORCHESTRATION_PATTERNS,
633
689
  'api/**',
634
690
  ];
635
- const domainPatterns = [...DOMAIN_PATH_PATTERNS];
691
+ const domainPatterns = [...DOMAIN_PATH_PATTERNS, 'src/**/domain/**'];
692
+ const sharedKernelPatterns = [...SHARED_KERNEL_PATH_PATTERNS];
693
+ const compositionRootPatterns = [...COMPOSITION_ROOT_PATH_PATTERNS];
636
694
 
637
695
  // Build pattern additions from unclassified suggestions (path-aware).
638
696
  const byLayer = new Map([
@@ -640,33 +698,45 @@ function runAdoptContract(args) {
640
698
  ['PersistenceAdapters', [...persistencePatterns]],
641
699
  ['ApplicationOrchestration', [...applicationPatterns]],
642
700
  ['DomainModel', [...domainPatterns]],
701
+ ['SharedKernel', [...sharedKernelPatterns]],
702
+ ['CompositionRoot', [...compositionRootPatterns]],
643
703
  ]);
644
704
  for (const suggestion of cov.suggestions ?? []) {
645
705
  if (suggestion.unrecognized || !suggestion.layer) continue;
646
- // Never treat bare lib as Presentation solely.
706
+ const dir = String(suggestion.dir || '');
707
+ // Never dump bare lib/ into Presentation or Application.
708
+ if (dir === 'lib' || dir === 'src/lib' || dir.endsWith('/lib')) {
709
+ continue;
710
+ }
647
711
  if (
648
712
  suggestion.layer === 'PresentationAdapters' &&
649
- (suggestion.dir === 'lib' || suggestion.dir.endsWith('/lib'))
713
+ (dir === 'lib' || dir.endsWith('/lib'))
650
714
  ) {
651
715
  continue;
652
716
  }
653
- const list = byLayer.get(suggestion.layer) ?? [];
717
+ let layerName = suggestion.layer;
718
+ if (/(^|\/)(types|constants|shared)(\/|$)/i.test(dir)) layerName = 'SharedKernel';
719
+ else if (/(^|\/)(composition|factories|bootstrap)(\/|$)/i.test(dir)) layerName = 'CompositionRoot';
720
+ else if (/(^|\/)domain(\/|$)/i.test(dir)) layerName = 'DomainModel';
721
+ const list = byLayer.get(layerName) ?? [];
654
722
  const glob = suggestion.dir === '.' ? null : `${suggestion.dir}/**`;
655
- if (glob && !list.includes(glob)) list.push(glob);
656
- byLayer.set(suggestion.layer, list);
723
+ if (glob && !list.includes(glob) && glob !== 'src/lib/**' && glob !== 'lib/**') {
724
+ list.push(glob);
725
+ }
726
+ byLayer.set(layerName, list);
657
727
  }
658
728
 
659
- const layers = (config.layers || []).map((layer) => {
729
+ const stripLibVacuum = (patterns) =>
730
+ (patterns || []).filter((p) => p !== '**/lib/**' && p !== 'lib/**' && p !== 'src/lib/**');
731
+
732
+ let layers = (config.layers || []).map((layer) => {
660
733
  const extras = byLayer.get(layer.name);
661
- if (!extras?.length) return layer;
662
- // Strip accidental bare lib/** if a previous adopt wrote it into Presentation.
663
- const cleaned = (layer.patterns || []).filter((p) => {
664
- if (layer.name !== 'PresentationAdapters') return true;
665
- return p !== '**/lib/**' && p !== 'lib/**' && p !== 'src/lib/**';
666
- });
734
+ const cleaned = stripLibVacuum(layer.patterns || []);
735
+ if (!extras?.length) return { ...layer, patterns: cleaned };
667
736
  const patterns = [...new Set([...cleaned, ...extras])];
668
737
  return { ...layer, patterns };
669
738
  });
739
+ layers = ensureMatureAdoptionLayers(layers, byLayer);
670
740
  // If no PresentationAdapters layer, leave layers as-is (don't invent full profile).
671
741
  const proposal = {
672
742
  ok: true,
@@ -681,6 +751,10 @@ function runAdoptContract(args) {
681
751
  persistencePatterns,
682
752
  applicationPatterns,
683
753
  domainPatterns,
754
+ sharedKernelPatterns,
755
+ compositionRootPatterns,
756
+ proposedLayers: ['SharedKernel', 'CompositionRoot', 'DomainModel'],
757
+ goldenPattern: '.ark/golden-pattern.json',
684
758
  totalFiles: cov.totalFiles,
685
759
  governedPercent: cov.governed.percent,
686
760
  unclassified: cov.unclassified.count,
@@ -698,12 +772,21 @@ function runAdoptContract(args) {
698
772
  }
699
773
 
700
774
  if (args.write) {
775
+ const existingRules = Array.isArray(config.rules) ? config.rules : [];
776
+ const rules = [...existingRules];
777
+ for (const rule of MATURE_LAYER_RULES) {
778
+ if (!rules.some((r) => r.from === rule.from && r.to === rule.to && r.allowed === rule.allowed)) {
779
+ rules.push(rule);
780
+ }
781
+ }
701
782
  const next = {
702
783
  ...config,
703
784
  include: proposal.after.include,
704
785
  layers,
786
+ rules,
705
787
  };
706
788
  fs.writeFileSync(configPath, `${JSON.stringify(next, null, 2)}\n`);
789
+ proposal.golden = writeAdoptGoldenPattern(root);
707
790
  proposal.wrote = true;
708
791
  }
709
792
 
@@ -720,7 +803,8 @@ function runAdoptContract(args) {
720
803
  );
721
804
  console.log(` presentation patterns += ${uiPatterns.join(', ')}`);
722
805
  console.log(` persistence patterns += (data clients / db / auth — never bare lib→Presentation)`);
723
- console.log(` application patterns += Next/Vercel API shells`);
806
+ console.log(` application patterns += Next/Vercel API shells (never bare src/lib/**)`);
807
+ console.log(` shared kernel += types/constants; composition root += wiring`);
724
808
  if (proposal.wrote) {
725
809
  console.log(color.green(` wrote ${path.relative(root, configPath) || args.config}`));
726
810
  console.log(color.dim(` Next: ${arkCommand(root, 'ark-check', '--coverage')} then --plan`));
@@ -21,6 +21,7 @@ import {
21
21
  detectWorkspaces,
22
22
  detectTsPackageRoots,
23
23
  resolveIncludeRoots,
24
+ isScanExcludedRelative,
24
25
  } from './ark-shared.mjs';
25
26
  import { effectiveCapabilityDeny, stableSerialize } from './lib/analysis-engine.mjs';
26
27
  import { createImportTargetResolver } from './lib/import-resolve.mjs';
@@ -866,6 +867,11 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
866
867
  emitHostAllow(output, { antigravityStyle, cursorStyle });
867
868
  return;
868
869
  }
870
+ const normalizedRel = rel.split(path.sep).join('/');
871
+ if (isScanExcludedRelative(normalizedRel, config)) {
872
+ emitHostAllow(output, { antigravityStyle, cursorStyle });
873
+ return;
874
+ }
869
875
 
870
876
  const source = proposedSource(toolName, toolInput);
871
877
  if (typeof source !== 'string') {
@@ -897,7 +903,6 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
897
903
  autoPatch: null,
898
904
  };
899
905
  })();
900
- const normalizedRel = rel.split(path.sep).join('/');
901
906
  const designDelta = args.failOnNewSmells && layer
902
907
  ? evaluateWriteDesignDelta({
903
908
  root: args.root,
@@ -930,7 +935,10 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
930
935
  // New file: nothing pre-exists, every violation is new.
931
936
  }
932
937
  const newViolations = (result.violations ?? []).filter((violation) => {
933
- if (String(violation.ruleId ?? violation.code).startsWith('ANALYSIS_')) return true;
938
+ const rule = String(violation.ruleId ?? violation.code);
939
+ // Incremental mid-edit parse errors are normal for agents — do not deny solely on them.
940
+ if (rule === 'ANALYSIS_PARSE_INCOMPLETE') return false;
941
+ if (rule.startsWith('ANALYSIS_')) return true;
934
942
  const key = violationKey(violation);
935
943
  const remaining = existingCounts.get(key) ?? 0;
936
944
  if (remaining === 0) return true;
@@ -2176,11 +2184,10 @@ export async function runArkMcp({ hookInput } = {}) {
2176
2184
  {
2177
2185
  name: 'ark_place',
2178
2186
  description:
2179
- 'Place a file in the architecture: pass filePath (preferred) and/or description. ' +
2180
- 'Returns layer, mayImport / mustNotImport, forbiddenGlobals, and optional goldenPattern ' +
2181
- '(advisory for NEW code when .ark/golden-pattern.json exists — never clears design-weak). ' +
2187
+ 'Place a file in the architecture: filePath is required (fail-closed without it — never invents components/*.tsx or defaults to Presentation). ' +
2188
+ 'Returns layer, mayImport / mustNotImport, forbiddenGlobals, and goldenPattern ' +
2189
+ '(load-bearing for NEW code when .ark/golden-pattern.json exists — adopt generates it). ' +
2182
2190
  'Call BEFORE writing a new file. ' +
2183
- 'If only description is given, returns a conventional path proposal under a governed layer. ' +
2184
2191
  'Prefer ark_prepare_write when you already have the source snippet (place+validate+autoPatch in one call).',
2185
2192
  inputSchema: {
2186
2193
  type: 'object',
@@ -2192,7 +2199,7 @@ export async function runArkMcp({ hookInput } = {}) {
2192
2199
  description: {
2193
2200
  type: 'string',
2194
2201
  description:
2195
- 'What you are building (e.g. "Remotion caption overlay"). Used when filePath is omitted to propose a path.',
2202
+ 'What you are building. Does not invent a path — pass filePath. Without filePath the tool fail-closes.',
2196
2203
  },
2197
2204
  },
2198
2205
  },
@@ -2641,31 +2648,12 @@ export async function runArkMcp({ hookInput } = {}) {
2641
2648
  const golden = loadGoldenPattern(args.root);
2642
2649
  const withGolden = (placement) => attachGoldenToPlacement(placement, golden);
2643
2650
 
2644
- if ((typeof filePath !== 'string' || !filePath) && typeof description === 'string' && description.trim()) {
2645
- const slug = description
2646
- .trim()
2647
- .toLowerCase()
2648
- .replace(/[^a-z0-9]+/g, '-')
2649
- .replace(/^-|-$/g, '')
2650
- .slice(0, 48) || 'component';
2651
- const proposedPath = `src/components/${slug}.tsx`;
2652
- const layerName = inferLayer(proposedPath, config, args.root) || 'PresentationAdapters';
2653
- return withGolden({
2654
- filePath: proposedPath,
2655
- proposed: true,
2656
- description: description.trim(),
2657
- layer: layerName,
2658
- governed: Boolean(inferLayer(proposedPath, config, args.root)),
2659
- note:
2660
- 'filePath was omitted — proposed a conventional path from description. ' +
2661
- 'Pass filePath explicitly for authoritative placement.',
2662
- });
2663
- }
2664
- if (typeof filePath !== 'string' || !filePath) {
2651
+ if (typeof filePath !== 'string' || !filePath.trim()) {
2665
2652
  return {
2666
2653
  error:
2667
- 'Needs filePath and/or description. ' +
2668
- 'Example: { "filePath": "src/components/Foo.tsx" } or { "description": "caption overlay UI component" }.',
2654
+ 'ark_place requires filePath. Fail-closed: will not invent a path (never default to Presentation or components/*.tsx). ' +
2655
+ 'Example: { "filePath": "src/lib/repositories/orders-repository.ts" }.',
2656
+ failClosed: true,
2669
2657
  };
2670
2658
  }
2671
2659
  const layerName = inferLayer(filePath, config, args.root);
@@ -8,6 +8,8 @@
8
8
  * Pure CLI helper (bin/lib/adapter-contract.mjs). Zero Node I/O.
9
9
  */
10
10
 
11
+ import { layerImportNextAction } from './remediation.mjs';
12
+ /** Versioned public result contract shared by every ArkGate enforcement adapter. */
11
13
  /**
12
14
  * 1.5 adds stable finding refs on every factory-emitted diagnostic (ACS06):
13
15
  * `findingRef`, `targetKey` (baseline-compatible), `docsCodePath`.
@@ -76,15 +78,17 @@ export function adapterDocsCodePath(ruleId) {
76
78
  }
77
79
  function nextActionForDiagnostic(ruleId, evidence, violation) {
78
80
  if (ruleId === 'LAYER_IMPORT_VIOLATION') {
79
- if (evidence.typeOnly ||
80
- violation.targetTypeOnlyExports === true ||
81
- violation.namedBindingsTypeOnly === true) {
82
- return 'Move the referenced type to a mutually allowed layer, use `import type`, then preflight again.';
83
- }
84
- if (violation.peerIsolation === true) {
85
- return 'Extract the shared dependency to a shared layer, test at the public interface, then preflight again.';
86
- }
87
- return `Define a port in ${evidence.fromLayer ?? 'the source layer'}, inject the ${evidence.toLayer ?? 'outer-layer'} implementation, test at the public interface, then preflight again.`;
81
+ return layerImportNextAction({
82
+ ruleId,
83
+ typeOnly: evidence.typeOnly === true,
84
+ targetTypeOnlyExports: violation.targetTypeOnlyExports === true,
85
+ namedBindingsTypeOnly: violation.namedBindingsTypeOnly === true,
86
+ peerIsolation: violation.peerIsolation === true,
87
+ portProofEligible: violation.portProofEligible === true,
88
+ fromLayer: text(evidence.fromLayer) ?? undefined,
89
+ toLayer: text(evidence.toLayer) ?? undefined,
90
+ target: text(evidence.target) ?? text(violation.target) ?? undefined,
91
+ });
88
92
  }
89
93
  if (ruleId === 'FORBIDDEN_GLOBAL') {
90
94
  return `Inject ${evidence.target ?? 'the capability'} through a port, test at the public interface, then preflight again.`;
@@ -261,6 +261,14 @@ export function runAgentProjectionCommand(args = {}) {
261
261
  profile: args.profile,
262
262
  arkgateVersion: args.arkgateVersion,
263
263
  });
264
+ const claudePlan = planAgentProjectionRefresh({
265
+ root: args.root,
266
+ config: args.config,
267
+ host: args.host || 'claude',
268
+ profile: args.profile,
269
+ arkgateVersion: args.arkgateVersion,
270
+ targetRelativePath: 'CLAUDE.md',
271
+ });
264
272
 
265
273
  if (stdoutOnly) {
266
274
  if (asJson) {
@@ -337,8 +345,10 @@ export function runAgentProjectionCommand(args = {}) {
337
345
  }
338
346
 
339
347
  let applyResult = { wrote: false, action: plan.action, path: plan.path };
348
+ let claudeApply = { wrote: false, action: claudePlan.action, path: claudePlan.path };
340
349
  if (doWrite) {
341
350
  applyResult = applyAgentProjectionRefresh(plan, { write: true });
351
+ claudeApply = applyAgentProjectionRefresh(claudePlan, { write: true });
342
352
  }
343
353
 
344
354
  if (asJson) {
@@ -349,6 +359,14 @@ export function runAgentProjectionCommand(args = {}) {
349
359
  arkgateVersion: plan.packageVersion,
350
360
  nonAuthoritative: true,
351
361
  path: plan.relativePath,
362
+ hosts: {
363
+ agentsMd: { path: plan.relativePath, wrote: applyResult.wrote, action: applyResult.action },
364
+ claudeMd: {
365
+ path: claudePlan.relativePath,
366
+ wrote: claudeApply.wrote,
367
+ action: claudeApply.action,
368
+ },
369
+ },
352
370
  action: applyResult.action,
353
371
  wrote: applyResult.wrote,
354
372
  wouldWrite: plan.wouldWrite,
@@ -148,10 +148,10 @@ export function buildAgentProjectionBody(facts) {
148
148
  '',
149
149
  ];
150
150
  if (profile === 'compact') {
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 leftover work in plain language; never “done” on green imports alone while leftover design work remains.', '3. Call `ark_identity` with `project.expectedRoot` at the exact project root; reuse root + `projectId` on Ark MCP calls.', '4. Read architecture config 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: import-rule debt → fix; leftover design work / residual shape lenses → map then guided apply with user OK.', '', '### Contract layers (summary)', '', formatAgentProjectionLayers(layers), '');
151
+ lines.push('### Primary path', '', '1. Run doctor (`ark-check --doctor`) — status light + primary next action. Prefer the project-local CLI; do not wait on MCP “still connecting”.', '2. Read the improvement compass (not a score). Name leftover work in plain language; never “done” on green imports alone while leftover design work remains.', '3. Identity handshake is optional when the CLI already resolved the project root. Call `ark_identity` only when using MCP evidence.', '4. Read architecture config with `ark_manifest` (same expectation) or the local `ark.config.json`. `ark://manifest` is compatibility-only / unverified.', '5. Place files inside configured layers; validate; run the check command above on violations — fix architecture, do not weaken the gate.', '6. Single door: import-rule debt → fix; leftover design work / residual shape lenses → map then guided apply with user OK.', '', '### Contract layers (summary)', '', formatAgentProjectionLayers(layers), '');
152
152
  }
153
153
  else {
154
- lines.push('### Contract layers', '', formatAgentProjectionLayers(layers), '', 'When creating a **new** kind of code that no layer covers, update `ark.config.json` first (`/ark-adopt`), then place the file.', '', '### Diagnostic codes (short list)', '', formatAgentProjectionCatalogShortList(catalog, docsPath), '', '### Session truth', '', '- Machine snapshot: `ark status --json` (or MCP `ark_status`) — identity, activation honesty, last check, residual counts. **Not a score.**', '- Authoritative contract: `ark_manifest` after a matched `ark_identity` handshake.', '');
154
+ lines.push('### Contract layers', '', formatAgentProjectionLayers(layers), '', 'When creating a **new** kind of code that no layer covers, update `ark.config.json` first (`/ark-adopt`), then place the file.', '', '### Diagnostic codes (short list)', '', formatAgentProjectionCatalogShortList(catalog, docsPath), '', '### Session truth', '', '- Machine snapshot: `ark status --json` (or MCP `ark_status`) — identity, activation honesty, last check, residual counts. **Not a score.**', '- Authoritative contract: local `ark.config.json` / CLI, or `ark_manifest` after a matched `ark_identity` handshake. Identity is optional when CLI already resolved the root.', '- Host docs: the same projection schema is merged into `AGENTS.md` and `CLAUDE.md` (`ark agents-md --write`).', '');
155
155
  }
156
156
  lines.push('### Enforcement surfaces (authoritative)', '', AGENT_PROJECTION_ENFORCEMENT_SURFACES.map((surface) => `- \`${surface}\``).join('\n'), '');
157
157
  return lines.join('\n').replace(/\n{3,}/g, '\n\n').trimEnd() + '\n';