arkgate 2.10.0 → 2.12.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.
- package/CHANGELOG.md +107 -0
- package/README.md +21 -12
- package/SECURITY.md +3 -4
- package/bin/ark-check.mjs +41 -16
- package/bin/ark-mcp.mjs +54 -10
- package/bin/ark.mjs +87 -24
- package/bin/lib/agent-gates.mjs +68 -2090
- package/bin/lib/architecture-scan.mjs +4 -1
- package/bin/lib/baseline-key.mjs +17 -0
- package/bin/lib/ci-and-commands.mjs +386 -0
- package/bin/lib/config-warnings.mjs +22 -0
- package/bin/lib/core-layers.mjs +7 -0
- package/bin/lib/core-ratchet.mjs +3 -7
- package/bin/lib/deploy-path.mjs +205 -0
- package/bin/lib/doctor-plan.mjs +29 -5
- package/bin/lib/gate-files.mjs +223 -0
- package/bin/lib/hook-templates.mjs +99 -0
- package/bin/lib/install-migrate.mjs +442 -0
- package/bin/lib/mcp-adoption.mjs +423 -0
- package/bin/lib/presets.mjs +3 -0
- package/bin/lib/safety-diagnostics.mjs +263 -0
- package/bin/lib/scan-files.mjs +51 -6
- package/bin/lib/skill-install.mjs +259 -0
- package/bin/lib/typescript-host.mjs +88 -0
- package/bin/lib/violations.mjs +3 -3
- package/bin/lib/write-path-detect.mjs +138 -0
- package/dist/index.cjs +103 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.js +103 -8
- package/dist/index.js.map +1 -1
- package/dist/nestjs/index.cjs +18 -5
- package/dist/nestjs/index.cjs.map +1 -1
- package/dist/nestjs/index.d.cts +1 -1
- package/dist/nestjs/index.d.ts +1 -1
- package/dist/nestjs/index.js +18 -5
- package/dist/nestjs/index.js.map +1 -1
- package/dist/runtime/index.cjs +103 -8
- package/dist/runtime/index.cjs.map +1 -1
- package/dist/runtime/index.d.cts +1 -1
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +103 -8
- package/dist/runtime/index.js.map +1 -1
- package/dist/{types-D6Q8WHes.d.cts → types-BZ17b9i5.d.cts} +5 -1
- package/dist/{types-D6Q8WHes.d.ts → types-BZ17b9i5.d.ts} +5 -1
- package/docs/agent-guide.md +12 -2
- package/docs/ai-gates.md +20 -2
- package/docs/package-surface.md +10 -3
- package/docs/production-hardening.md +5 -0
- package/package.json +5 -2
- package/server.json +2 -2
- package/templates/skills/ark-autopilot.md +77 -45
- package/templates/skills/ark-explain.md +2 -1
- package/templates/skills/ark-explore.md +135 -34
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,113 @@ All notable changes to ArkGate (`arkgate`; formerly `ark-runtime-kernel`) are do
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 2.12.0 — 2026-07-10
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **Install agent gates on temp roots:** skip rewriting the developer's real
|
|
12
|
+
`~/.codex/config.toml` when the project root is a temp/upgrade scratch and
|
|
13
|
+
`CODEX_HOME` is unset. Home MCP wire failures no longer fail an otherwise
|
|
14
|
+
successful repo gate install (sandbox/EPERM). Explicit `CODEX_HOME` and
|
|
15
|
+
`--codex-home` still wire as before.
|
|
16
|
+
- **Q1 coverage floors (broad include, 80/85/95):** Vitest thresholds statements/lines **≥80%**,
|
|
17
|
+
branches/functions **≥85%** on the **full product unit surface** (`src/**` + `bin/lib/**` +
|
|
18
|
+
`bin/ark-shared.mjs`; only process-entry shells excluded — no cherry-picked enforcement-core
|
|
19
|
+
include). Per-path critical floors: write-path-detect / auto-patch / prepare-write /
|
|
20
|
+
safety-diagnostics / baseline-key / graph-cycles at **≥95%** branch. Real branch-driving tests
|
|
21
|
+
under `tests/unit/static-check/` (critical + surface/topup/seam suites). Two consecutive
|
|
22
|
+
green `npm run test:coverage` captures (stmts/lines **92.71%**, branches **85%**, functions
|
|
23
|
+
**94.76%**; critical modules all **≥95%** branch).
|
|
24
|
+
- **agent-gates modularization:** thin facade (`bin/lib/agent-gates.mjs` ~100 LOC) re-exports
|
|
25
|
+
`gate-files`, `skill-install`, `ci-and-commands`, `mcp-adoption`, `install-migrate`,
|
|
26
|
+
`typescript-host`, `hook-templates`, `write-path-detect`, plus field/codex helpers.
|
|
27
|
+
`detectDeployPathQuality` extracted to `bin/lib/deploy-path.mjs` so `mcp-adoption.mjs` stays
|
|
28
|
+
under the 600 LOC module budget. Import hygiene on extract modules; `loadTypeScript` uses
|
|
29
|
+
`__arkCheckCli` for the nested arkgate TypeScript fallback.
|
|
30
|
+
- **Deny→repair CI proof:** `tests/unit/static-check/writePathDetect.test.ts` drives
|
|
31
|
+
shipped `bin/ark-mcp.mjs --hook --hook-repair` and asserts `ARK_REPAIR_JSON` /
|
|
32
|
+
`ARK_AUTOPATCH_JSON` on deny (exit 2); reject-only without repair flag still supported.
|
|
33
|
+
- **Dogfood write path repair:** local Claude/Grok hooks use `--hook-repair`; doctor
|
|
34
|
+
reports `writePath.mode = repair` on this tree.
|
|
35
|
+
- **Self-hosted AGENTS.md:** `--install-agent-gates --force` no longer overwrites library
|
|
36
|
+
mother-repo Identity (`skipped-self-hosted`).
|
|
37
|
+
- **hexagonal-order-api:** `safety.allowInMemory` for ephemeral demo kernel; prefer
|
|
38
|
+
`arkgate/runtime` imports; `npm run check` green under `--strict-config`.
|
|
39
|
+
- **multi-app / monorepo rules:** deny App→Persistence, Presentation→Domain, and
|
|
40
|
+
Persistence→Presentation (parity with crud-product starter).
|
|
41
|
+
- **Generated CI Node default lags local npm (again):** when a project had no
|
|
42
|
+
`.nvmrc` / `engines.node`, the Ark architecture gate workflow defaulted to
|
|
43
|
+
Node 22. Lockfiles written on Node 24/26 then failed `npm ci` with
|
|
44
|
+
"Missing: … from lock file" before `ark-check` ran — CI green, Ark red.
|
|
45
|
+
Detection order is now `.nvmrc` / `.node-version` → `engines.node` → **highest
|
|
46
|
+
`node-version` from sibling workflows** (excludes `ark-check.yml` so a stale
|
|
47
|
+
gate cannot re-pin itself) → default **24**. Refresh existing gates with
|
|
48
|
+
`ark-check --install-agent-gates --force` (or edit `node-version` in
|
|
49
|
+
`.github/workflows/ark-check.yml`).
|
|
50
|
+
|
|
51
|
+
### Changed
|
|
52
|
+
|
|
53
|
+
- **Hook templates extracted** to `bin/lib/hook-templates.mjs` (agent-gates seam).
|
|
54
|
+
- **Write-path detect extracted** to `bin/lib/write-path-detect.mjs` (doctor W5; re-exported
|
|
55
|
+
from agent-gates).
|
|
56
|
+
- **Coverage thresholds** raised to Q1 floors on the broad include set: statements/lines **≥80**,
|
|
57
|
+
branches/functions **≥85**, critical write/safety modules **≥95%** branch (see Fixed above).
|
|
58
|
+
- **`/ark-explore` skill:** decision-grade recon — field path (run starters/checks),
|
|
59
|
+
installed hooks vs install templates, coupling via fan-in/exports (not LOC alone),
|
|
60
|
+
ranked “así te lo re-soluciono” rows only when residual changes action; ENFORCE /
|
|
61
|
+
empty plan treated as baseline, not the story. **v2.1:** output modes (recon vs
|
|
62
|
+
dual-plan seed, no multi-week roadmaps by default); path-correct vs design-correct
|
|
63
|
+
+ semantic false-green; success signals and kill-switches on bets (anti-vanity).
|
|
64
|
+
- **`/ark-autopilot` skill:** explore-first (decision-grade), **dual plan** —
|
|
65
|
+
A remediation from `--plan` + B pattern/evolution bets (never auto-apply B as
|
|
66
|
+
mechanical-safe); empty plan no longer means “healthy” without explore/B.
|
|
67
|
+
- **Day-zero origin first:** `ark start` / `ark init` freeze `.ark/reports/origin.*`
|
|
68
|
+
immediately after `ark.config.json` exists and **before** agent docs, skills, and CI
|
|
69
|
+
templates. Later `--report` still shows evolution vs that snapshot.
|
|
70
|
+
|
|
71
|
+
## 2.11.0 — 2026-07-10
|
|
72
|
+
|
|
73
|
+
Fail-closed enforcement hardening: `--strict` now combines contract coverage, installed-gate
|
|
74
|
+
checks, write-hook presence, and bypass diagnostics in one CI profile. The GitHub Action runs
|
|
75
|
+
the exact checked-out revision by default, and runtime workflows cancel timed-out work
|
|
76
|
+
cooperatively.
|
|
77
|
+
|
|
78
|
+
### Added
|
|
79
|
+
|
|
80
|
+
- **Strict CI profile:** `arkgate-check --strict` enables strict config validation, requires
|
|
81
|
+
generated gates plus a PreToolUse write hook, and fails on configured safety diagnostics.
|
|
82
|
+
- **Bypass diagnostics:** new `dynamicImportAllowlist` and `safety` config fields detect
|
|
83
|
+
non-literal dynamic imports, TypeScript suppression directives, explicit `any` casts,
|
|
84
|
+
production InMemory defaults, and disabled or omitted peer isolation. `--doctor --json`
|
|
85
|
+
exposes the same evidence under `doctor.safety`.
|
|
86
|
+
- **Release-quality CI:** JavaScript syntax validation, enforced coverage thresholds, and a
|
|
87
|
+
Node 18/20/22/24 compatibility matrix now run before merge.
|
|
88
|
+
|
|
89
|
+
### Changed
|
|
90
|
+
|
|
91
|
+
- **Pinned GitHub Action execution:** `uses: pedroknigge/arkgate@<tag-or-SHA>` now runs that
|
|
92
|
+
checked-out ArkGate source. The `version` input remains available only as an explicit exact
|
|
93
|
+
npm compatibility override.
|
|
94
|
+
- **Complete MCP contract:** `ark://manifest` exposes every configured file layer separately
|
|
95
|
+
from runtime intent layers, plus reviewed dynamic-import and safety policy.
|
|
96
|
+
- **Workflow cancellation contract:** workflow steps receive an `AbortSignal` as their third
|
|
97
|
+
argument. `timeoutMs` aborts that signal, clears the active step, and rejects duplicate step
|
|
98
|
+
names before execution can corrupt compensation order.
|
|
99
|
+
- **Filesystem confinement:** source scans follow internal symlinks once and reject symlinks
|
|
100
|
+
that escape the project root.
|
|
101
|
+
|
|
102
|
+
### Fixed
|
|
103
|
+
|
|
104
|
+
- **Baseline duplicate honesty:** repeated violations now receive stable per-occurrence keys,
|
|
105
|
+
so adding a second identical violation is new debt instead of being hidden by one baseline
|
|
106
|
+
entry.
|
|
107
|
+
- **Write-hook duplicate honesty:** proposed writes compare violation counts, preventing a new
|
|
108
|
+
duplicate from being mistaken for an already-existing violation.
|
|
109
|
+
- **CLI argument safety:** unknown flags and missing flag values fail with usage guidance
|
|
110
|
+
instead of silently weakening enforcement or throwing an internal error.
|
|
111
|
+
- **Action gate detection:** repositories using the ArkGate composite Action satisfy the CI
|
|
112
|
+
gate check without needing a separate literal `ark-check` command.
|
|
113
|
+
|
|
7
114
|
## 2.10.0 — 2026-07-10
|
|
8
115
|
|
|
9
116
|
Track W — **Constrained write → verified repair**: write-boundary autoPatch, prepare_write,
|
package/README.md
CHANGED
|
@@ -23,8 +23,8 @@ and makes sure a “green” check means something real.
|
|
|
23
23
|
If you remember nothing else:
|
|
24
24
|
|
|
25
25
|
```text
|
|
26
|
-
1. npx arkgate start ←
|
|
27
|
-
2. /ark-autopilot ←
|
|
26
|
+
1. npx arkgate start ← walk tree → contract → **day-zero origin** → gates
|
|
27
|
+
2. /ark-autopilot ← explore first, dual plan, safe fixes, leave gates on
|
|
28
28
|
3. npx arkgate-check --doctor ← “where am I?” anytime (one status screen)
|
|
29
29
|
```
|
|
30
30
|
|
|
@@ -106,7 +106,7 @@ Full checklist (CI, MCP, Codex, imports): **[docs/migrate-from-ark-runtime-kerne
|
|
|
106
106
|
|
|
107
107
|
```bash
|
|
108
108
|
npm install -D arkgate typescript
|
|
109
|
-
npx arkgate start #
|
|
109
|
+
npx arkgate start # contract → day-zero origin → gates + plan
|
|
110
110
|
# in agent:
|
|
111
111
|
# /ark-autopilot
|
|
112
112
|
npx arkgate-check --doctor # status light + next action
|
|
@@ -117,11 +117,13 @@ Aliases `ark` / `ark-check` / `ark-mcp` still work. **npm / pnpm / yarn**. No in
|
|
|
117
117
|
<details>
|
|
118
118
|
<summary>What <code>/ark-autopilot</code> does under the hood (optional detail)</summary>
|
|
119
119
|
|
|
120
|
-
1. Setup if needed (`ark start`).
|
|
121
|
-
2.
|
|
122
|
-
3.
|
|
123
|
-
4.
|
|
124
|
-
5. Gates on +
|
|
120
|
+
1. Setup if needed (`ark start` — contract, then **day-zero origin**, then gates).
|
|
121
|
+
2. **Explore pass** (decision-grade map of *this* product; field path when demos exist).
|
|
122
|
+
3. **Dual plan:** **A** remediation from `--plan` (mechanical-safe only by default); **B** pattern/evolution bets (never auto-applied as mechanical-safe). Empty plan ≠ “healthy” without explore/B.
|
|
123
|
+
4. Apply A → re-check; judgment only when you ask for full apply.
|
|
124
|
+
5. Gates on + latest report (evolution vs frozen origin).
|
|
125
|
+
|
|
126
|
+
Standalone recon without applying: `/ark-explore`.
|
|
125
127
|
|
|
126
128
|
</details>
|
|
127
129
|
|
|
@@ -188,8 +190,14 @@ ark.config.json
|
|
|
188
190
|
- **Presets:** hexagonal, layered, feature-sliced, monorepo, ui-surface, vertical-slice, ddd-bounded-contexts (+ aliases clean-architecture / onion-architecture). Layers start optional; doctor suggests tightening populated cores. Cross-slice / cross-context bans use optional `peerIsolation` rules.
|
|
189
191
|
- **Frameworks:** Nest / Next / express / library layouts get sensible globs on init so day-one coverage is real.
|
|
190
192
|
- **Brownfield:** baseline ratchet, refuse to freeze a wrong contract, `/ark-adopt` for mature trees.
|
|
191
|
-
- **Agents:** skills install into Claude / Cursor / Codex / Grok; `ark start` freezes
|
|
193
|
+
- **Agents:** skills install into Claude / Cursor / Codex / Grok; `ark start` freezes **day-zero origin** under `.ark/reports/` **before** agent docs/CI templates.
|
|
192
194
|
- **Write protocol (2.10 / Track W):** mechanical-safe **autoPatch** on the write gate (`import type`); MCP **`ark_prepare_write`** (place + validate + patch + judgmentBrief); opt-in hook **`--hook-repair`** (`ARK_REPAIR_JSON`); doctor **`writePath`** (repair vs reject-only); loop-cost eval (`npm run eval:loop-cost`). Port-proof inject is **judgment** (arity change), not silent auto-apply.
|
|
195
|
+
- **Fail-closed CI (2.11):** `--strict` combines config coverage, required CI/MCP/write gates,
|
|
196
|
+
PreToolUse hook presence, and bypass diagnostics for dynamic imports, TypeScript suppressions,
|
|
197
|
+
explicit `any` casts, InMemory runtime defaults, and disabled peer isolation.
|
|
198
|
+
- **Trust / coverage (2.12):** package unit-test floors on the broad product surface
|
|
199
|
+
(statements/lines **≥80%**, branches/functions **≥85%**; enforcement-critical modules **≥95%**
|
|
200
|
+
branch). Explore dual-plan + day-zero origin first (see above). Roadmap next: Q2 repair dogfood matrix.
|
|
193
201
|
- **TypeScript:** project compilers 5.x / 6.x / 7.x — gate falls back to a nested JS-API TypeScript when TS 7’s main export is version-only ([docs/typescript-support.md](docs/typescript-support.md)).
|
|
194
202
|
|
|
195
203
|
### Why not only ESLint / dependency-cruiser / Nx?
|
|
@@ -211,9 +219,10 @@ ark.config.json
|
|
|
211
219
|
## Common commands
|
|
212
220
|
|
|
213
221
|
```bash
|
|
214
|
-
npx arkgate start # guided setup
|
|
222
|
+
npx arkgate start # guided setup: contract → origin → gates → plan
|
|
215
223
|
npx arkgate-check --doctor # health + Adoption gaps (not just fitness)
|
|
216
224
|
npx arkgate-check --doctor --json # machine-readable doctor.adoption
|
|
225
|
+
npx arkgate-check --strict # fail-closed CI + installed-gate/safety checks
|
|
217
226
|
npx arkgate-check --plan # safe-to-auto-fix vs your call
|
|
218
227
|
npx arkgate-check --coverage # Governed: N%
|
|
219
228
|
npx arkgate-check --report ark-report.html # showcase HTML + Adoption card + origin/latest
|
|
@@ -224,8 +233,8 @@ npx arkgate upgrade # package + gates/skills + MCP/Codex n
|
|
|
224
233
|
CI (example):
|
|
225
234
|
|
|
226
235
|
```yaml
|
|
227
|
-
- run: npx arkgate-check --root . --config ark.config.json --strict
|
|
228
|
-
# or: uses: pedroknigge/arkgate
|
|
236
|
+
- run: npx arkgate-check --root . --config ark.config.json --strict
|
|
237
|
+
# or: uses: pedroknigge/arkgate@<tag-or-SHA> # runs that checked-out revision
|
|
229
238
|
```
|
|
230
239
|
|
|
231
240
|
---
|
package/SECURITY.md
CHANGED
|
@@ -28,10 +28,9 @@ a private security contact without including exploit details.
|
|
|
28
28
|
ArkGate releases are GitHub-first:
|
|
29
29
|
|
|
30
30
|
1. Changes land on GitHub and must pass CI plus the dedicated security workflow.
|
|
31
|
-
2. A GitHub Release is created from
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
wired). Signed tags can later require `ARK_REQUIRE_SIGNED_RELEASE_TAG=true`.
|
|
31
|
+
2. A GitHub Release is created from a signed `vX.Y.Z` tag (`git tag -s`). The publish
|
|
32
|
+
workflow verifies the signature and fails closed for unsigned tags. The local override
|
|
33
|
+
exists only for explicit emergency use and is not enabled in CI.
|
|
35
34
|
3. The manual `Publish npm` workflow verifies the tag, requires the GitHub Release to
|
|
36
35
|
exist, reruns release checks, publishes npm with provenance, and uploads a SHA-256
|
|
37
36
|
checksum for the npm tarball to the GitHub Release.
|
package/bin/ark-check.mjs
CHANGED
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
arkPackageVersion,
|
|
44
44
|
REQUIRED_GATE_FILES,
|
|
45
45
|
codexPromptsDir,
|
|
46
|
+
detectWritePathCapabilities,
|
|
46
47
|
} from './lib/agent-gates.mjs';
|
|
47
48
|
import { syncBaselineIntoCheckSurfaces } from './lib/field-install.mjs';
|
|
48
49
|
import {
|
|
@@ -64,6 +65,7 @@ import {
|
|
|
64
65
|
import { runRatchetCores } from './lib/core-ratchet.mjs';
|
|
65
66
|
import {
|
|
66
67
|
baselineKey,
|
|
68
|
+
baselineOccurrenceKeys,
|
|
67
69
|
readBaseline,
|
|
68
70
|
summarizeViolations,
|
|
69
71
|
writeBaseline,
|
|
@@ -99,6 +101,7 @@ function parseArgs(argv) {
|
|
|
99
101
|
printConfig: undefined,
|
|
100
102
|
tsconfig: undefined,
|
|
101
103
|
json: false,
|
|
104
|
+
strict: false,
|
|
102
105
|
strictConfig: false,
|
|
103
106
|
requireGates: false,
|
|
104
107
|
init: false,
|
|
@@ -122,13 +125,25 @@ function parseArgs(argv) {
|
|
|
122
125
|
version: false,
|
|
123
126
|
help: false,
|
|
124
127
|
};
|
|
128
|
+
const requireValue = (flag, index) => {
|
|
129
|
+
const value = argv[index + 1];
|
|
130
|
+
if (value === undefined || value.startsWith('-')) {
|
|
131
|
+
throw new Error(`Missing value for ${flag}. Run ark-check --help for usage.`);
|
|
132
|
+
}
|
|
133
|
+
return value;
|
|
134
|
+
};
|
|
125
135
|
for (let i = 2; i < argv.length; i += 1) {
|
|
126
136
|
const arg = argv[i];
|
|
127
137
|
if (arg === '--json') args.json = true;
|
|
138
|
+
else if (arg === '--strict') {
|
|
139
|
+
args.strict = true;
|
|
140
|
+
args.strictConfig = true;
|
|
141
|
+
args.requireGates = true;
|
|
142
|
+
}
|
|
128
143
|
else if (arg === '--strict-config') args.strictConfig = true;
|
|
129
144
|
else if (arg === '--require-gates') args.requireGates = true;
|
|
130
145
|
else if (arg === '--init') args.init = true;
|
|
131
|
-
else if (arg === '--preset') args.preset =
|
|
146
|
+
else if (arg === '--preset') args.preset = requireValue(arg, i++);
|
|
132
147
|
else if (arg === '--install-agent-gates') args.installAgentGates = true;
|
|
133
148
|
else if (arg === '--tools') {
|
|
134
149
|
// Consume the next arg only when it isn't another flag (same rule as --baseline),
|
|
@@ -152,7 +167,7 @@ function parseArgs(argv) {
|
|
|
152
167
|
else if (arg === '--recommend') args.recommend = true;
|
|
153
168
|
else if (arg === '--write-plan') args.writePlan = true;
|
|
154
169
|
else if (arg === '--list-policy-packs') args.listPolicyPacks = true;
|
|
155
|
-
else if (arg === '--apply-policy-pack') args.applyPolicyPack =
|
|
170
|
+
else if (arg === '--apply-policy-pack') args.applyPolicyPack = requireValue(arg, i++);
|
|
156
171
|
else if (arg === '--suggest-include') args.suggestInclude = true;
|
|
157
172
|
else if (arg === '--adopt-contract') args.adoptContract = true;
|
|
158
173
|
else if (arg === '--ratchet-cores') args.ratchetCores = true;
|
|
@@ -174,13 +189,14 @@ function parseArgs(argv) {
|
|
|
174
189
|
const next = argv[i + 1];
|
|
175
190
|
args.baseline = next && !next.startsWith('-') ? argv[++i] : '.ark-baseline.json';
|
|
176
191
|
}
|
|
177
|
-
else if (arg === '--root') args.root = path.resolve(
|
|
178
|
-
else if (arg === '--config') args.config =
|
|
179
|
-
else if (arg === '--manifest') args.manifest =
|
|
180
|
-
else if (arg === '--print-config') args.printConfig =
|
|
181
|
-
else if (arg === '--tsconfig') args.tsconfig =
|
|
192
|
+
else if (arg === '--root') args.root = path.resolve(requireValue(arg, i++));
|
|
193
|
+
else if (arg === '--config') args.config = requireValue(arg, i++);
|
|
194
|
+
else if (arg === '--manifest') args.manifest = requireValue(arg, i++);
|
|
195
|
+
else if (arg === '--print-config') args.printConfig = requireValue(arg, i++);
|
|
196
|
+
else if (arg === '--tsconfig') args.tsconfig = requireValue(arg, i++);
|
|
182
197
|
else if (arg === '--help' || arg === '-h') args.help = true;
|
|
183
198
|
else if (arg === '--version' || arg === '-V') args.version = true;
|
|
199
|
+
else throw new Error(`Unknown argument: ${arg}. Run ark-check --help for usage.`);
|
|
184
200
|
}
|
|
185
201
|
return args;
|
|
186
202
|
}
|
|
@@ -198,7 +214,7 @@ function usage() {
|
|
|
198
214
|
return [
|
|
199
215
|
'Usage: arkgate-check | ark-check (identical bins; product name ArkGate)',
|
|
200
216
|
' ark-check --version',
|
|
201
|
-
' ark-check --root <project> --config <ark.config.json> [--manifest <ark.manifest.json>] [--tsconfig <tsconfig.json>] [--strict-config] [--require-gates] [--json] [--baseline [file]] [--report [file.html]] [--no-cache]',
|
|
217
|
+
' ark-check --root <project> --config <ark.config.json> [--manifest <ark.manifest.json>] [--tsconfig <tsconfig.json>] [--strict | --strict-config] [--require-gates] [--json] [--baseline [file]] [--report [file.html]] [--no-cache]',
|
|
202
218
|
' ark-check --coverage [--json] per-layer file counts + full unclassified list (report only, exit 0)',
|
|
203
219
|
' ark-check --plan [--json] classified remediation plan (mechanical-safe / judgment / deferred) + goal; report only',
|
|
204
220
|
' ark-check --recommend [--json] [--write-plan] application-shape plan; --write-plan emits ark-adoption-plan.json',
|
|
@@ -251,6 +267,8 @@ function usage() {
|
|
|
251
267
|
'',
|
|
252
268
|
'Config warnings are advisory by default and are included in JSON output.',
|
|
253
269
|
'Use --strict-config to make config warnings fail the check.',
|
|
270
|
+
'Use --strict for the fail-closed CI profile: --strict-config + --require-gates',
|
|
271
|
+
'plus the security diagnostics surfaced by doctor.',
|
|
254
272
|
'',
|
|
255
273
|
'--require-gates fails the check when AGENTS.md, .mcp.json, or the generated CI',
|
|
256
274
|
'workflow is missing, so "installed but never configured" is a red CI. Combine it',
|
|
@@ -301,6 +319,10 @@ function readConfig(root, configPath) {
|
|
|
301
319
|
...(raw.exclude ? { exclude: raw.exclude } : {}),
|
|
302
320
|
...(raw.excludeGenerated !== undefined ? { excludeGenerated: raw.excludeGenerated } : {}),
|
|
303
321
|
...(raw.cyclePolicy ? { cyclePolicy: raw.cyclePolicy } : {}),
|
|
322
|
+
...(raw.dynamicImportAllowlist
|
|
323
|
+
? { dynamicImportAllowlist: raw.dynamicImportAllowlist }
|
|
324
|
+
: {}),
|
|
325
|
+
...(raw.safety ? { safety: raw.safety } : {}),
|
|
304
326
|
};
|
|
305
327
|
}
|
|
306
328
|
|
|
@@ -316,7 +338,7 @@ function detectConfig(root) {
|
|
|
316
338
|
|
|
317
339
|
for (const entry of DEFAULT_INTENT_PREFIXES) {
|
|
318
340
|
const directories = (DEFAULT_LAYER_DIRECTORIES[entry.layer] ?? []).filter(
|
|
319
|
-
(directory) => walk(path.join(root, srcDir, directory)).length > 0
|
|
341
|
+
(directory) => walk(path.join(root, srcDir, directory), [], { root }).length > 0
|
|
320
342
|
);
|
|
321
343
|
if (directories.length === 0) continue;
|
|
322
344
|
layers.push({
|
|
@@ -784,7 +806,7 @@ function runInit(args) {
|
|
|
784
806
|
}
|
|
785
807
|
// The starter profile only governs src/. Existing source elsewhere would make the
|
|
786
808
|
// gate silently green, so surface it instead of pretending the project is covered.
|
|
787
|
-
const outside = walk(args.root)
|
|
809
|
+
const outside = walk(args.root, [], { root: args.root })
|
|
788
810
|
.map((file) => normalize(path.relative(args.root, file)))
|
|
789
811
|
.filter((rel) => !rel.startsWith('src/') && !rel.split('/').some((s) => s.startsWith('.')));
|
|
790
812
|
if (outside.length > 0) {
|
|
@@ -956,6 +978,9 @@ async function main() {
|
|
|
956
978
|
|
|
957
979
|
if (args.requireGates) {
|
|
958
980
|
const missing = missingGates(args.root);
|
|
981
|
+
if (args.strict && !detectWritePathCapabilities(args.root).hookPresent) {
|
|
982
|
+
missing.push('PreToolUse write hook');
|
|
983
|
+
}
|
|
959
984
|
if (missing.length > 0) {
|
|
960
985
|
const payload = {
|
|
961
986
|
ok: false,
|
|
@@ -1032,7 +1057,7 @@ async function main() {
|
|
|
1032
1057
|
);
|
|
1033
1058
|
}
|
|
1034
1059
|
|
|
1035
|
-
const { violations, warnings } = runArchitectureScan({
|
|
1060
|
+
const { violations, warnings, safety } = runArchitectureScan({
|
|
1036
1061
|
root,
|
|
1037
1062
|
config,
|
|
1038
1063
|
manifest,
|
|
@@ -1046,6 +1071,7 @@ async function main() {
|
|
|
1046
1071
|
runDoctor(root, config, files, rules, violations, args.json, {
|
|
1047
1072
|
configPath: path.isAbsolute(args.config) ? args.config : path.join(root, args.config),
|
|
1048
1073
|
configMissing: !fs.existsSync(path.isAbsolute(args.config) ? args.config : path.join(root, args.config)),
|
|
1074
|
+
safety,
|
|
1049
1075
|
});
|
|
1050
1076
|
return;
|
|
1051
1077
|
}
|
|
@@ -1118,11 +1144,10 @@ async function main() {
|
|
|
1118
1144
|
if (args.baseline) {
|
|
1119
1145
|
const baseline = readBaseline(root, args.baseline);
|
|
1120
1146
|
if (baseline.exists) {
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
);
|
|
1125
|
-
const currentKeys = new Set(violations.map(baselineKey));
|
|
1147
|
+
const occurrenceKeys = baselineOccurrenceKeys(violations);
|
|
1148
|
+
suppressed = violations.filter((_, index) => baseline.keys.has(occurrenceKeys[index]));
|
|
1149
|
+
activeViolations = violations.filter((_, index) => !baseline.keys.has(occurrenceKeys[index]));
|
|
1150
|
+
const currentKeys = new Set(occurrenceKeys);
|
|
1126
1151
|
staleBaselineKeys = [...baseline.keys].filter((key) => !currentKeys.has(key)).length;
|
|
1127
1152
|
} else {
|
|
1128
1153
|
warnings.push(
|
package/bin/ark-mcp.mjs
CHANGED
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
DEFAULT_LAYER_DIRECTORIES,
|
|
49
49
|
DEFAULT_RULES,
|
|
50
50
|
arkCommand,
|
|
51
|
+
globToRegExp,
|
|
51
52
|
layerForFile,
|
|
52
53
|
shouldShowNewHereNudge,
|
|
53
54
|
detectWorkspaces,
|
|
@@ -273,18 +274,23 @@ function runHook(gate, config, args, ts) {
|
|
|
273
274
|
// line numbers (edits shift them); simpler than full baselineKey (no file/layer fields
|
|
274
275
|
// needed — this file is fixed).
|
|
275
276
|
const violationKey = (violation) => `${violation.ruleId}|${violation.target ?? violation.message}`;
|
|
276
|
-
let
|
|
277
|
+
let existingCounts = new Map();
|
|
277
278
|
try {
|
|
278
279
|
const current = fs.readFileSync(filePath, 'utf8');
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
280
|
+
for (const violation of gate.validate(current, { layer, filePath }).violations) {
|
|
281
|
+
const key = violationKey(violation);
|
|
282
|
+
existingCounts.set(key, (existingCounts.get(key) ?? 0) + 1);
|
|
283
|
+
}
|
|
282
284
|
} catch {
|
|
283
285
|
// New file: nothing pre-exists, every violation is new.
|
|
284
286
|
}
|
|
285
|
-
const newViolations = (result.violations ?? []).filter(
|
|
286
|
-
|
|
287
|
-
|
|
287
|
+
const newViolations = (result.violations ?? []).filter((violation) => {
|
|
288
|
+
const key = violationKey(violation);
|
|
289
|
+
const remaining = existingCounts.get(key) ?? 0;
|
|
290
|
+
if (remaining === 0) return true;
|
|
291
|
+
existingCounts.set(key, remaining - 1);
|
|
292
|
+
return false;
|
|
293
|
+
});
|
|
288
294
|
if (newViolations.length === 0) return;
|
|
289
295
|
|
|
290
296
|
const lines = newViolations.map(
|
|
@@ -368,8 +374,14 @@ function runArkCheckJsonFromRoot(root, config, extraArgs, manifest) {
|
|
|
368
374
|
const result = spawnSync(
|
|
369
375
|
process.execPath,
|
|
370
376
|
[arkCheckBin, '--root', root, '--config', config, ...manifestArgs, '--json', ...extraArgs],
|
|
371
|
-
{ encoding: 'utf8' }
|
|
377
|
+
{ encoding: 'utf8', timeout: 120_000, maxBuffer: 20 * 1024 * 1024 }
|
|
372
378
|
);
|
|
379
|
+
if (result.error) {
|
|
380
|
+
return {
|
|
381
|
+
data: null,
|
|
382
|
+
raw: `ark-check failed to execute: ${result.error.message}`,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
373
385
|
const stdout = result.stdout ?? '';
|
|
374
386
|
try {
|
|
375
387
|
return { data: JSON.parse(stdout), raw: stdout };
|
|
@@ -544,6 +556,18 @@ async function main() {
|
|
|
544
556
|
name: layer.name,
|
|
545
557
|
patterns: layer.patterns,
|
|
546
558
|
})),
|
|
559
|
+
allowNonLiteralDynamicImport: (filePath) => {
|
|
560
|
+
if (!filePath || !Array.isArray(config.dynamicImportAllowlist)) return false;
|
|
561
|
+
const rel = path.relative(args.root, path.resolve(args.root, filePath)).split(path.sep).join('/');
|
|
562
|
+
return config.dynamicImportAllowlist.some((pattern) => {
|
|
563
|
+
if (typeof pattern !== 'string') return false;
|
|
564
|
+
try {
|
|
565
|
+
return globToRegExp(pattern).test(rel);
|
|
566
|
+
} catch {
|
|
567
|
+
return false;
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
},
|
|
547
571
|
});
|
|
548
572
|
|
|
549
573
|
if (args.hook) {
|
|
@@ -706,7 +730,10 @@ async function main() {
|
|
|
706
730
|
// DomainModel there would tell the agent to create a second layer for the same
|
|
707
731
|
// prefix, making longest-prefix resolution ambiguous.
|
|
708
732
|
function suggestedLayers() {
|
|
709
|
-
const activeNames = new Set(
|
|
733
|
+
const activeNames = new Set([
|
|
734
|
+
...configLayers.map((layer) => layer.name),
|
|
735
|
+
...profile.layers.map((layer) => layer.name),
|
|
736
|
+
]);
|
|
710
737
|
const claimedPrefixes = new Set(
|
|
711
738
|
profile.layers.flatMap((layer) =>
|
|
712
739
|
(layer.prefixes ?? []).map((p) => (p.endsWith('.') ? p : `${p}.`))
|
|
@@ -732,13 +759,30 @@ async function main() {
|
|
|
732
759
|
);
|
|
733
760
|
}
|
|
734
761
|
const suggestions = suggestedLayers();
|
|
762
|
+
const contractLayers = usedProjectConfig
|
|
763
|
+
? configLayers.map((layer) => ({
|
|
764
|
+
...layer,
|
|
765
|
+
prefixes: Array.isArray(layer.intentPrefixes) ? layer.intentPrefixes : [],
|
|
766
|
+
}))
|
|
767
|
+
: profile.layers;
|
|
735
768
|
return JSON.stringify(
|
|
736
769
|
{
|
|
737
770
|
source: profile === ark.elevenLayerProfile ? 'strictDefaultElevenLayerProfile' : 'project',
|
|
738
771
|
name: profile.name,
|
|
739
|
-
|
|
772
|
+
// File placement contract: every configured layer, including layers that do not
|
|
773
|
+
// own intent prefixes (e.g. Tooling / FrameworkAdapters).
|
|
774
|
+
layers: contractLayers,
|
|
775
|
+
// Runtime/intent resolution profile kept explicit so consumers never have to infer
|
|
776
|
+
// why a prefix-less file layer is absent from intent resolution.
|
|
777
|
+
intentLayers: profile.layers,
|
|
740
778
|
rules: profile.rules,
|
|
741
779
|
...(Object.keys(forbiddenGlobals).length > 0 ? { forbiddenGlobals } : {}),
|
|
780
|
+
...(Array.isArray(config.dynamicImportAllowlist)
|
|
781
|
+
? { dynamicImportAllowlist: config.dynamicImportAllowlist }
|
|
782
|
+
: {}),
|
|
783
|
+
...(config.safety && typeof config.safety === 'object'
|
|
784
|
+
? { safety: config.safety }
|
|
785
|
+
: {}),
|
|
742
786
|
...(suggestions.length > 0
|
|
743
787
|
? {
|
|
744
788
|
suggestedLayers: suggestions,
|