arkgate 4.6.3 → 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.
- package/CHANGELOG.md +64 -5
- package/README.md +7 -5
- package/bin/ark-check-runtime.mjs +98 -14
- package/bin/ark-mcp-runtime.mjs +54 -33
- package/bin/ark.mjs +1 -0
- package/bin/lib/adapter-contract.mjs +13 -9
- package/bin/lib/agent-projection-command.mjs +18 -0
- package/bin/lib/agent-projection.mjs +2 -2
- package/bin/lib/analysis-engine.mjs +5 -5
- package/bin/lib/ci-and-commands.mjs +3 -3
- package/bin/lib/ci-merge-boundary.mjs +89 -0
- package/bin/lib/config-contract.mjs +2 -0
- package/bin/lib/diagnostic-catalog.mjs +5 -4
- package/bin/lib/doctor-next-actions.mjs +17 -5
- package/bin/lib/doctor-plan.mjs +14 -0
- package/bin/lib/enforcement-honesty.mjs +2 -0
- package/bin/lib/graph-blind.mjs +15 -6
- package/bin/lib/install-migrate.mjs +10 -0
- package/bin/lib/invariant-coverage.mjs +6 -2
- package/bin/lib/managed-upgrade.mjs +9 -4
- package/bin/lib/presets.mjs +22 -0
- package/bin/lib/remediation.mjs +74 -10
- package/bin/lib/skill-install.mjs +2 -0
- package/bin/lib/snippet-analysis.mjs +40 -8
- package/bin/lib/upgrade-command.mjs +37 -4
- package/bin/lib/upgrade-whats-new.mjs +19 -1
- package/dist/{configTypes-B8uIcLaG.d.ts → configTypes-l6XiwiC1.d.ts} +7 -0
- package/dist/eslint/index.cjs +3 -3
- package/dist/eslint/index.d.ts +1 -1
- package/dist/eslint/index.js +3 -3
- package/dist/index.cjs +26 -26
- package/dist/index.d.ts +11 -2
- package/dist/index.js +29 -29
- package/docs/README.md +5 -4
- package/docs/agent-guide.md +12 -1
- package/docs/ai-gates.md +5 -0
- package/docs/brownfield-adoption.md +7 -1
- package/docs/configuration.md +2 -1
- package/docs/develop.md +5 -2
- package/docs/diagnostics.md +15 -5
- package/docs/package-surface.md +6 -5
- package/docs/threat-model.md +1 -1
- package/package.json +1 -1
- package/schemas/ark.config.schema.json +6 -0
- package/server.json +2 -2
- package/templates/agent-skills/README.md +1 -1
- package/templates/agent-skills/ark-adopt/SKILL.md +7 -0
- package/templates/agent-skills/ark-explore/SKILL.md +6 -0
- package/templates/agent-skills/ark-place/SKILL.md +11 -4
- package/templates/agent-skills/ark-upgrade/SKILL.md +22 -4
- package/templates/skills/ark-adopt.md +7 -0
- package/templates/skills/ark-explore.md +6 -0
- package/templates/skills/ark-place.md +11 -4
- package/templates/skills/ark-upgrade.md +22 -4
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,64 @@ 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
|
+
|
|
42
|
+
## 4.6.4 — 2026-08-18
|
|
43
|
+
|
|
44
|
+
**Patch** over **4.6.3**. `ark upgrade` now tells Codex users how to activate the local
|
|
45
|
+
`apply_patch` boundary after upgrading: refresh the project hook, restart Codex/local Desktop,
|
|
46
|
+
trust the exact hook definition, and verify `doctor.writePath` after a governed patch.
|
|
47
|
+
**No required config migration.**
|
|
48
|
+
|
|
49
|
+
**Status: published** (on npm `latest` until 4.6.5 lands; see `docs/releases/4.6.4.md`).
|
|
50
|
+
|
|
51
|
+
### Changed
|
|
52
|
+
|
|
53
|
+
- **Upgrade JSON:** `whatsNew.items` includes stable id `codex-hard-write` with the exact
|
|
54
|
+
`--install-agent-gates --tools codex --force` command and evidence to inspect.
|
|
55
|
+
- **Upgrade human output:** **Suggested improvements** prints the same refresh/restart/trust/check
|
|
56
|
+
path on preview and apply, including nothing-to-apply previews.
|
|
57
|
+
- **Upgrade skill:** flat and Agent Skills guidance tells Codex to exercise a governed
|
|
58
|
+
`apply_patch` and keeps hosted/specialized/shell/direct/incomplete/human paths CI-backed.
|
|
59
|
+
- **Stale MCP fail-closed:** when process version no longer matches the project install,
|
|
60
|
+
`ark_identity` reports non-authoritative evidence and project tools return
|
|
61
|
+
`PROCESS_PACKAGE_STALE` until restart/retarget.
|
|
62
|
+
- **Global CLI handoff:** a modern stale global `ark upgrade` delegates the original invocation
|
|
63
|
+
to the project-local `node_modules/arkgate/bin/ark.mjs` instead of managing from the wrong PATH
|
|
64
|
+
version. Pre-4.6.4 globals still require one `npx arkgate upgrade` entry.
|
|
65
|
+
|
|
8
66
|
## 4.6.3 — 2026-08-18
|
|
9
67
|
|
|
10
68
|
**Patch** over **4.6.2**. Codex CLI and local ChatGPT Desktop/App Server now get a
|
|
@@ -12,7 +70,8 @@ runtime-proven pre-write block for complete `apply_patch` calls. ArkGate accepts
|
|
|
12
70
|
`tool_input.command` payload, while incomplete, hosted, specialized, shell/direct, and human
|
|
13
71
|
write paths remain CI-backed. **No required config migration.**
|
|
14
72
|
|
|
15
|
-
**Status:
|
|
73
|
+
**Status: published** (npm `latest` from signed tag `v4.6.3`; OIDC run `32167523804`;
|
|
74
|
+
see `docs/releases/4.6.3.md`).
|
|
16
75
|
|
|
17
76
|
### Changed
|
|
18
77
|
|
|
@@ -804,8 +863,8 @@ Not a rewrite of the analysis engine. No plan-B silent codemod. No fake hard wri
|
|
|
804
863
|
|
|
805
864
|
## 3.8.3 — 2026-07-22
|
|
806
865
|
|
|
807
|
-
Corrective **patch** over 3.8.2 from multi-repo field evidence (
|
|
808
|
-
|
|
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.
|
|
809
868
|
|
|
810
869
|
### Fixed
|
|
811
870
|
|
|
@@ -832,7 +891,7 @@ Amarilla greenfield `start`). **No required config migration.** Z09 / residual `
|
|
|
832
891
|
|
|
833
892
|
## 3.8.2 — 2026-07-22
|
|
834
893
|
|
|
835
|
-
Corrective **patch** over 3.8.1 from
|
|
894
|
+
Corrective **patch** over 3.8.1 from field Next.js evidence. Aligns doctor skill freshness with
|
|
836
895
|
managed upgrade content identity, hardens upgrade preview honesty, clarifies doctor writePath
|
|
837
896
|
inventory vs this-invocation, ships the Y06 pure-layer opt-in advisory, and treats complete-catalog
|
|
838
897
|
Codex leftover prompts as safe-to-delete. **No required config migration.** Z09 / residual `RB-11`
|
|
@@ -856,7 +915,7 @@ remain open.
|
|
|
856
915
|
|
|
857
916
|
- **Y06 pure-layer opt-in nudge:** when a golden pattern names pure modules and no layer sets
|
|
858
917
|
`pure: true`, doctor emits one advisory line (`doctor.pureLayerOptIn`). Never a blocker; never
|
|
859
|
-
auto-writes `pure: true`. Promoted from field evidence (
|
|
918
|
+
auto-writes `pure: true`. Promoted from field evidence (a Next.js app).
|
|
860
919
|
|
|
861
920
|
## 3.8.1 — 2026-07-22
|
|
862
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.
|
|
20
|
-
>
|
|
21
|
-
> [4.6.
|
|
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,10 @@ 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.
|
|
220
|
-
|
|
|
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) |
|
|
222
|
+
| Prior (4.6.2) | [docs/releases/4.6.2.md](docs/releases/4.6.2.md) |
|
|
221
223
|
| Prior (4.6.1) | [docs/releases/4.6.1.md](docs/releases/4.6.1.md) |
|
|
222
224
|
| Prior (4.6.0) | [docs/releases/4.6.0.md](docs/releases/4.6.0.md) |
|
|
223
225
|
| Prior (4.5.7) | [docs/releases/4.5.7.md](docs/releases/4.5.7.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
|
-
|
|
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
|
-
(
|
|
713
|
+
(dir === 'lib' || dir.endsWith('/lib'))
|
|
650
714
|
) {
|
|
651
715
|
continue;
|
|
652
716
|
}
|
|
653
|
-
|
|
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)
|
|
656
|
-
|
|
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
|
|
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
|
-
|
|
662
|
-
|
|
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`));
|
package/bin/ark-mcp-runtime.mjs
CHANGED
|
@@ -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
|
-
|
|
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;
|
|
@@ -1960,11 +1968,14 @@ export async function runArkMcp({ hookInput } = {}) {
|
|
|
1960
1968
|
}
|
|
1961
1969
|
|
|
1962
1970
|
function contextFor(binding) {
|
|
1971
|
+
const processPackage = processPackageHonesty();
|
|
1963
1972
|
return {
|
|
1964
1973
|
projectIdentity,
|
|
1965
1974
|
binding,
|
|
1966
|
-
authoritative
|
|
1967
|
-
|
|
1975
|
+
// A correctly bound project is still non-authoritative when this long-lived
|
|
1976
|
+
// process loaded a different package version than the project now resolves.
|
|
1977
|
+
authoritative: binding.authoritative && !processPackage.processStale,
|
|
1978
|
+
processPackage,
|
|
1968
1979
|
};
|
|
1969
1980
|
}
|
|
1970
1981
|
|
|
@@ -2041,13 +2052,36 @@ export async function runArkMcp({ hookInput } = {}) {
|
|
|
2041
2052
|
);
|
|
2042
2053
|
}
|
|
2043
2054
|
|
|
2055
|
+
function staleProcessFailureResult(binding) {
|
|
2056
|
+
const processPackage = processPackageHonesty();
|
|
2057
|
+
return withProjectContext(
|
|
2058
|
+
{
|
|
2059
|
+
content: [
|
|
2060
|
+
{
|
|
2061
|
+
type: 'text',
|
|
2062
|
+
text: JSON.stringify({
|
|
2063
|
+
ok: false,
|
|
2064
|
+
error: {
|
|
2065
|
+
code: 'PROCESS_PACKAGE_STALE',
|
|
2066
|
+
message: processPackage.nextAction,
|
|
2067
|
+
},
|
|
2068
|
+
}),
|
|
2069
|
+
},
|
|
2070
|
+
],
|
|
2071
|
+
isError: true,
|
|
2072
|
+
},
|
|
2073
|
+
binding
|
|
2074
|
+
);
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2044
2077
|
const TOOLS = [
|
|
2045
2078
|
{
|
|
2046
2079
|
name: 'ark_identity',
|
|
2047
2080
|
description:
|
|
2048
2081
|
'First call. Prove this MCP process is the right project: pass project.expectedRoot ' +
|
|
2049
2082
|
'(exact absolute root) and reuse the returned projectId. Do this before any other Ark tool. ' +
|
|
2050
|
-
'A missing
|
|
2083
|
+
'A missing/unmatched root or processPackage.processStale means restart/retarget the host ' +
|
|
2084
|
+
'and use the project-local CLI until identity and package versions align.',
|
|
2051
2085
|
inputSchema: { type: 'object', properties: {} },
|
|
2052
2086
|
},
|
|
2053
2087
|
{
|
|
@@ -2150,11 +2184,10 @@ export async function runArkMcp({ hookInput } = {}) {
|
|
|
2150
2184
|
{
|
|
2151
2185
|
name: 'ark_place',
|
|
2152
2186
|
description:
|
|
2153
|
-
'Place a file in the architecture:
|
|
2154
|
-
'Returns layer, mayImport / mustNotImport, forbiddenGlobals, and
|
|
2155
|
-
'(
|
|
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). ' +
|
|
2156
2190
|
'Call BEFORE writing a new file. ' +
|
|
2157
|
-
'If only description is given, returns a conventional path proposal under a governed layer. ' +
|
|
2158
2191
|
'Prefer ark_prepare_write when you already have the source snippet (place+validate+autoPatch in one call).',
|
|
2159
2192
|
inputSchema: {
|
|
2160
2193
|
type: 'object',
|
|
@@ -2166,7 +2199,7 @@ export async function runArkMcp({ hookInput } = {}) {
|
|
|
2166
2199
|
description: {
|
|
2167
2200
|
type: 'string',
|
|
2168
2201
|
description:
|
|
2169
|
-
'What you are building
|
|
2202
|
+
'What you are building. Does not invent a path — pass filePath. Without filePath the tool fail-closes.',
|
|
2170
2203
|
},
|
|
2171
2204
|
},
|
|
2172
2205
|
},
|
|
@@ -2615,31 +2648,12 @@ export async function runArkMcp({ hookInput } = {}) {
|
|
|
2615
2648
|
const golden = loadGoldenPattern(args.root);
|
|
2616
2649
|
const withGolden = (placement) => attachGoldenToPlacement(placement, golden);
|
|
2617
2650
|
|
|
2618
|
-
if (
|
|
2619
|
-
const slug = description
|
|
2620
|
-
.trim()
|
|
2621
|
-
.toLowerCase()
|
|
2622
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
2623
|
-
.replace(/^-|-$/g, '')
|
|
2624
|
-
.slice(0, 48) || 'component';
|
|
2625
|
-
const proposedPath = `src/components/${slug}.tsx`;
|
|
2626
|
-
const layerName = inferLayer(proposedPath, config, args.root) || 'PresentationAdapters';
|
|
2627
|
-
return withGolden({
|
|
2628
|
-
filePath: proposedPath,
|
|
2629
|
-
proposed: true,
|
|
2630
|
-
description: description.trim(),
|
|
2631
|
-
layer: layerName,
|
|
2632
|
-
governed: Boolean(inferLayer(proposedPath, config, args.root)),
|
|
2633
|
-
note:
|
|
2634
|
-
'filePath was omitted — proposed a conventional path from description. ' +
|
|
2635
|
-
'Pass filePath explicitly for authoritative placement.',
|
|
2636
|
-
});
|
|
2637
|
-
}
|
|
2638
|
-
if (typeof filePath !== 'string' || !filePath) {
|
|
2651
|
+
if (typeof filePath !== 'string' || !filePath.trim()) {
|
|
2639
2652
|
return {
|
|
2640
2653
|
error:
|
|
2641
|
-
'
|
|
2642
|
-
'Example: { "filePath": "src/
|
|
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,
|
|
2643
2657
|
};
|
|
2644
2658
|
}
|
|
2645
2659
|
const layerName = inferLayer(filePath, config, args.root);
|
|
@@ -3005,6 +3019,13 @@ export async function runArkMcp({ hookInput } = {}) {
|
|
|
3005
3019
|
reply(id, bindingFailureResult(binding));
|
|
3006
3020
|
return;
|
|
3007
3021
|
}
|
|
3022
|
+
// Keep ark_identity available so the host can diagnose the stale process,
|
|
3023
|
+
// but fail every project tool closed until the MCP server restarts on the
|
|
3024
|
+
// version installed for this root.
|
|
3025
|
+
if (params?.name !== 'ark_identity' && processPackageHonesty().processStale) {
|
|
3026
|
+
reply(id, staleProcessFailureResult(binding));
|
|
3027
|
+
return;
|
|
3028
|
+
}
|
|
3008
3029
|
try {
|
|
3009
3030
|
reply(id, withProjectContext(handler(params, binding), binding));
|
|
3010
3031
|
} catch (error) {
|
package/bin/ark.mjs
CHANGED
|
@@ -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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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.
|
|
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';
|