arkgate 4.6.6 → 4.7.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 +139 -2
- package/README.md +22 -10
- package/SECURITY.md +1 -1
- package/bin/ark-check-runtime.mjs +21 -341
- package/bin/ark-mcp-runtime.mjs +71 -325
- package/bin/ark-shared.mjs +24 -158
- package/bin/lib/adapter-contract.mjs +17 -36
- package/bin/lib/analysis-engine.mjs +6 -6
- package/bin/lib/ark-run-doctor.mjs +144 -0
- package/bin/lib/ark-run-facts.mjs +472 -0
- package/bin/lib/ark-run-report.mjs +57 -0
- package/bin/lib/ark-run-sensors.mjs +309 -0
- package/bin/lib/check-args.mjs +173 -0
- package/bin/lib/check-config-detect.mjs +101 -0
- package/bin/lib/check-watch.mjs +80 -0
- package/bin/lib/config-contract.mjs +86 -11
- package/bin/lib/deep-module-coach.mjs +3 -0
- package/bin/lib/diagnostic-catalog.mjs +8 -0
- package/bin/lib/doctor-advisories.mjs +45 -8
- package/bin/lib/doctor-human.mjs +519 -0
- package/bin/lib/doctor-plan.mjs +62 -445
- package/bin/lib/extra-merge-teeth.mjs +187 -0
- package/bin/lib/github-enforcement.mjs +22 -9
- package/bin/lib/html-report-advisories.mjs +2 -0
- package/bin/lib/html-report-depth.mjs +22 -2
- package/bin/lib/html-report.mjs +40 -7
- package/bin/lib/mcp-hook-payload.mjs +328 -0
- package/bin/lib/package-manager.mjs +174 -0
- package/bin/lib/policy-delta-io.mjs +4 -0
- package/bin/lib/remediation.mjs +132 -0
- package/bin/lib/resolved-candidate-facts.mjs +67 -2
- package/bin/lib/rules-under-contract.mjs +37 -89
- package/bin/lib/snippet-analysis.mjs +43 -2
- package/bin/lib/status-command.mjs +28 -0
- package/bin/lib/status-manifest.mjs +23 -0
- package/bin/lib/team-parliament-io.mjs +4 -0
- package/dist/{configTypes-l6XiwiC1.d.ts → configTypes-CgJimx9o.d.ts} +17 -3
- package/dist/eslint/index.cjs +6 -2
- package/dist/eslint/index.d.ts +70 -2
- package/dist/eslint/index.js +6 -2
- package/dist/index.cjs +35 -35
- package/dist/index.d.ts +787 -272
- package/dist/index.js +35 -35
- package/docs/README.md +4 -3
- package/docs/agent-guide.md +21 -15
- package/docs/ai-gates.md +13 -0
- package/docs/configuration.md +24 -11
- package/docs/develop.md +12 -3
- package/docs/diagnostics.md +75 -0
- package/docs/enthusiast/README.md +4 -3
- package/docs/package-surface.md +17 -13
- package/docs/product-voice.md +6 -3
- package/docs/threat-model.md +1 -1
- package/docs/use.md +5 -4
- package/package.json +1 -1
- package/schemas/ark.config.schema.json +41 -2
- package/schemas/ark.resolved-candidate-facts.schema.json +1 -1
- package/schemas/ark.status-manifest.schema.json +47 -0
- package/server.json +2 -2
- package/templates/agent-skills/README.md +1 -1
- package/templates/agent-skills/ark-adopt/SKILL.md +23 -2
- package/templates/agent-skills/ark-place/SKILL.md +26 -2
- package/templates/agent-skills/ark-runtime/SKILL.md +66 -24
- package/templates/skills/ark-adopt.md +23 -2
- package/templates/skills/ark-place.md +26 -2
- package/templates/skills/ark-runtime.md +66 -24
package/CHANGELOG.md
CHANGED
|
@@ -5,12 +5,149 @@ in the immutable pre-2.0 archive linked below.
|
|
|
5
5
|
|
|
6
6
|
## Unreleased
|
|
7
7
|
|
|
8
|
+
## 4.7.0 — 2026-08-25
|
|
9
|
+
|
|
10
|
+
**Minor** over **4.6.7**. Ships **ArkRun**: an opt-in extra on schema `1.2` for kernel
|
|
11
|
+
usage and complete declarations, plus companion `@arkgate/runtime` DX. Absence is
|
|
12
|
+
silent (Layers / ArkRules verdicts unchanged). In-memory stores remain
|
|
13
|
+
reference-only. **No required config migration.** Does not close Z09 / K01.
|
|
14
|
+
|
|
15
|
+
**Status: prepared** (see `docs/releases/4.7.0.md`). npm `latest` remains **4.6.7**
|
|
16
|
+
until publish.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **`arkRun` extra on `ark.config.json` schema `1.2` (RN02):** optional inline
|
|
21
|
+
`{ mode, compositionRoots, managedLayers, requireDeclarations }`. `1.1` and
|
|
22
|
+
earlier configs migrate in memory; absence is silent (Layers / ArkRules
|
|
23
|
+
verdicts unchanged). Unknown keys, unknown `managedLayers` names, and empty
|
|
24
|
+
`compositionRoots` in `enforced` mode fail closed (`ARKRUN_MISSING_ROOT`).
|
|
25
|
+
Advisory → enforced is a strengthening policy delta; demotion or deletion is
|
|
26
|
+
weakening and needs the existing hash-bound ack. ESLint envelope is RN06
|
|
27
|
+
(landed below); CI extra teeth landed in RN07. Does not close Z09 / K01.
|
|
28
|
+
|
|
29
|
+
- **ArkRun resolver facts on resolved-candidate-facts schema `1.2` (RN03):**
|
|
30
|
+
additive optional `arkRunKernelCalls`, `arkRunManagedNews`, and
|
|
31
|
+
`arkRunCompositionRootHits`. `1.0`/`1.1` payloads stay loadable (empty
|
|
32
|
+
arrays). Syntax evidence only — sensors consume these in RN04. Absence of
|
|
33
|
+
`arkRun` still leaves Layers / ArkRules verdicts unchanged. Does not close
|
|
34
|
+
Z09 / K01.
|
|
35
|
+
|
|
36
|
+
- **ArkRun tier-1 sensors (RN04):** when `arkRun` is present, closed sensors
|
|
37
|
+
emit `ARKRUN_MISSING_ROOT`, `ARKRUN_KERNEL_IN_DOMAIN`, `ARKRUN_DIRECT_NEW`,
|
|
38
|
+
`ARKRUN_UNDECLARED_EMIT`, `ARKRUN_UNDECLARED_HANDLE`, `ARKRUN_UNDECLARED_DEPEND`,
|
|
39
|
+
and `ARKRUN_TRANSPORT_BYPASS`. Advisory findings never flip `valid`; enforced
|
|
40
|
+
blocks. Absence of the extra is still silent on Layers / ArkRules verdicts.
|
|
41
|
+
Optional `arkRunDeclarations` facts stay additive on schema `1.2`. Dual-depth
|
|
42
|
+
catalog nextAction is RN05 (landed below). Does not close Z09 / K01.
|
|
43
|
+
|
|
44
|
+
- **ArkRun diagnostic catalog dual-depth (RN05):** closed `ARKRUN_*` catalog
|
|
45
|
+
entries have dual-depth remediation: casual `enthusiastHint` plus engineer
|
|
46
|
+
`nextAction` (target interpolates the call-site literal or specifier). Adding
|
|
47
|
+
an existing declaration-list string is `mechanical-safe` (`arkrun-declaration-list`)
|
|
48
|
+
only when that literal is already present; other ArkRun findings stay
|
|
49
|
+
`judgment`. Sensors, adapter fallback, and CLI remediation share
|
|
50
|
+
`deterministicNextAction`. Does not close Z09 / K01.
|
|
51
|
+
|
|
52
|
+
- **ArkRun ESLint envelope (RN06):** `arkgate/eslint` recommended config adds
|
|
53
|
+
`ark/no-arkrun-kernel-in-domain`, `ark/no-arkrun-direct-new`, and
|
|
54
|
+
`ark/no-arkrun-transport-bypass`. Same `ARKRUN_*` sensors as ark-check for
|
|
55
|
+
the import / `new` envelope; silent when `arkRun` is absent. Missing-root and
|
|
56
|
+
undeclared-* stay CLI/MCP/preflight. Does not close Z09 / K01.
|
|
57
|
+
|
|
58
|
+
- **ArkRun extra-teeth parity (RN07):** CLI `--strict-merge`, MCP `ark_check` /
|
|
59
|
+
snippet write, PreToolUse hook, atomic preflight, and CI share one ArkRun
|
|
60
|
+
verdict. Enforced extra teeth arm only when the layer plane is classified
|
|
61
|
+
(same ≥50% governed / ≥1 populated-layer floor as ArkRules); advisory and
|
|
62
|
+
absence stay silent on `valid`. Doctor/status `arkRun` section landed in
|
|
63
|
+
RN08 below. Does not close Z09 / K01.
|
|
64
|
+
|
|
65
|
+
- **ArkRun doctor / status / report (RN08):** `ark-check --doctor`, HTML
|
|
66
|
+
`--report`, and `ark status` / MCP `ark_status` expose an `arkRun` section
|
|
67
|
+
that is always `notAScore`. Residual is a finding-id count, never a score or
|
|
68
|
+
LLM verdict. `mergePlanes.arkRun` states whether the extra can fail merge;
|
|
69
|
+
advisory and absence never arm extra teeth. Report parity requires
|
|
70
|
+
`data-advisory="arkRun"`. Does not close Z09 / K01.
|
|
71
|
+
|
|
72
|
+
- **ArkRun companion branding (RN09):** `@arkgate/runtime` README and public
|
|
73
|
+
docs brand the kernel **ArkRun**. `createStrictArkKernel` stays the factory
|
|
74
|
+
(per-instance; no process-wide singleton). Kernel implementation stays out of
|
|
75
|
+
the `arkgate` tarball. Branding is not a production-durability claim. Does not
|
|
76
|
+
close Z09 / K01.
|
|
77
|
+
|
|
78
|
+
- **ArkRun interaction declarations (RN10):** `@arkgate/runtime` `register()`
|
|
79
|
+
accepts `uses` / `reactsTo` / `raises` / `sends` plus optional tooling-only
|
|
80
|
+
`extendedInfo`. `getDependencyInformationPackage()` returns a JSON-serializable
|
|
81
|
+
snapshot of ids, lifetime, and declarations — never factories, live instances,
|
|
82
|
+
or input DTOs. Companion registrations may omit declarations for local
|
|
83
|
+
experiments; enforced `arkRun` on the gate still requires them. Does not close
|
|
84
|
+
Z09 / K01.
|
|
85
|
+
|
|
86
|
+
- **ArkRun transport ports (RN11):** `@arkgate/runtime` `send()` is one call site
|
|
87
|
+
for `local` / `localBlocking` / `broker`. `ephemeral` defaults true (await local
|
|
88
|
+
recording or adapter accept — not a durability claim). Missing broker adapter
|
|
89
|
+
falls back to in-process local delivery, not cloud portability. No cloud SDKs
|
|
90
|
+
ship in the package. Does not close Z09 / K01.
|
|
91
|
+
|
|
92
|
+
- **ArkRun dev inspector (RN12):** `@arkgate/runtime` `startInspector()` /
|
|
93
|
+
`startArkRunInspector()` is opt-in. Default bind is `127.0.0.1`; `NODE_ENV=production`
|
|
94
|
+
vetoes start; public hosts (`0.0.0.0`, `::`) are rejected. HTTP is lazy-loaded.
|
|
95
|
+
`GET /snapshot` and `GET /events` (SSE) serve the information package plus
|
|
96
|
+
transport facts (no factories, no shipped cloud SDKs). Does not close Z09 / K01.
|
|
97
|
+
|
|
98
|
+
- **ArkRun graph slices (RN13):** `@arkgate/runtime` `requestGraph()` slices the
|
|
99
|
+
information package into `process` (raises / reactsTo / sends) or `technical`
|
|
100
|
+
(`uses`) graphs. Optional `nodeIds`, `degreesOfSeparation`, and include/exclude
|
|
101
|
+
query keep a neighborhood. `formatArkRunGraphMermaid()` / `graph.mermaid` is a
|
|
102
|
+
helper string, never a score. Inspector `GET /graph` serves the same slice.
|
|
103
|
+
Does not close Z09 / K01.
|
|
104
|
+
|
|
105
|
+
- **ArkRun skip corpus (RN14):** `tests/fixtures/arkrun-skip-corpus/` is the
|
|
106
|
+
executable proof: Application `new`, same-layer peer import, and homemade
|
|
107
|
+
`EventEmitter` stay green when `arkRun` is absent (Layers / ArkRules match
|
|
108
|
+
schema `1.1`) and fail write path, CLI, MCP, and `--strict-merge` when the
|
|
109
|
+
extra is enforced. Does not close Z09 / K01.
|
|
110
|
+
|
|
111
|
+
- **ArkRun skill-body deepen (RN15):** `/ark-runtime`, `/ark-place`, and
|
|
112
|
+
`/ark-adopt` teach the extra vs companion (advisory adopt, kernel-only
|
|
113
|
+
scaffold, composition-root wiring). Frozen **13** names — no `/ark-run`.
|
|
114
|
+
Skills never enforce; doctor `arkRun` stays `notAScore`. Agent Skills layout
|
|
115
|
+
stays 1:1 with `templates/skills`. Does not close Z09 / K01.
|
|
116
|
+
|
|
117
|
+
## 4.6.7 — 2026-08-24
|
|
118
|
+
|
|
119
|
+
**Patch** over **4.6.6**. Production-hardening: CODEOWNERS, eval/pack honesty, CLI extracts,
|
|
120
|
+
spawn timeouts, and HTML list cap. **No required config migration.** Does not close Z09.
|
|
121
|
+
|
|
122
|
+
**Status: published** (on npm `latest`; see `docs/releases/4.6.7.md`).
|
|
123
|
+
|
|
124
|
+
### Changed
|
|
125
|
+
|
|
126
|
+
- **CODEOWNERS:** `/src/`, `/bin/`, and `/schemas/` owned by `@pedroknigge`. GitHub still
|
|
127
|
+
needs `require_code_owner_reviews` (or an approving-review count) for that file to
|
|
128
|
+
enforce; the in-tree list is the product control.
|
|
129
|
+
|
|
130
|
+
- **Eval comparative fixture:** `saas-dashboard/without-ark` is a real Presentation→Domain
|
|
131
|
+
**value** import. Type-only was non-blocking and made the nightly oracle go false-green.
|
|
132
|
+
|
|
133
|
+
- **npm pack JSON:** `scripts/npm-pack-report.mjs` strips ANSI and parses JSON lines that
|
|
134
|
+
actually have `filename`. Empty `[]` / `{}` stay empty; missing filename still throws.
|
|
135
|
+
|
|
136
|
+
- **CLI extracts:** hook payload, package-manager helpers, and check args/config/watch live
|
|
137
|
+
in `bin/lib/`. Module-budget maxima were not raised.
|
|
138
|
+
|
|
139
|
+
- **git/gh timeouts:** `SPAWN_TIMEOUT_MS = 8000` on git/gh `spawnSync`. Timeout is
|
|
140
|
+
fail-closed (`status !== 0`).
|
|
141
|
+
|
|
142
|
+
- **HTML violation cap:** beginner and full showcase lists share a cap of 12 plus
|
|
143
|
+
`+N more (T total)`. KPI tiles still use the full array.
|
|
144
|
+
|
|
8
145
|
## 4.6.6 — 2026-08-22
|
|
9
146
|
|
|
10
147
|
**Patch** over **4.6.5**. Phase AL corrective honesty plus a slimmer public docs surface.
|
|
11
148
|
**No required config migration.** Does not close Z09. AL05 stays parked.
|
|
12
149
|
|
|
13
|
-
**Status:
|
|
150
|
+
**Status: published** (see `docs/releases/4.6.6.md`).
|
|
14
151
|
|
|
15
152
|
### Changed
|
|
16
153
|
|
|
@@ -49,7 +186,7 @@ in the immutable pre-2.0 archive linked below.
|
|
|
49
186
|
**Patch** over **4.6.4**. Adoption, placement, doctor, upgrade, and write-path honesty for
|
|
50
187
|
existing Next.js trees and multi-host teams. **No required config migration.**
|
|
51
188
|
|
|
52
|
-
**Status: published** (
|
|
189
|
+
**Status: published** (see `docs/releases/4.6.5.md`).
|
|
53
190
|
|
|
54
191
|
### Changed
|
|
55
192
|
|
package/README.md
CHANGED
|
@@ -16,11 +16,11 @@ and makes sure a “green” check means something real.
|
|
|
16
16
|
|
|
17
17
|
</div>
|
|
18
18
|
|
|
19
|
-
> **ArkGate 4.
|
|
19
|
+
> **ArkGate 4.7.0** is prepared on this tree. **4.6.7** remains npm `latest` until publish.
|
|
20
20
|
> A tree is **adopted** only with a required GitHub status running `arkgate-check --strict-merge`,
|
|
21
21
|
> or `.ark/adoption-stance.json` `stance: "advisory-only"`. Doctor is compact (`--doctor --all`
|
|
22
|
-
> for Details). [4.
|
|
23
|
-
> [Docs hub](docs/README.md) · [Product voice](docs/product-voice.md)
|
|
22
|
+
> for Details). [4.7.0 notes](docs/releases/4.7.0.md) · [4.6.7](docs/releases/4.6.7.md) ·
|
|
23
|
+
> [4.6.6](docs/releases/4.6.6.md) · [Docs hub](docs/README.md) · [Product voice](docs/product-voice.md)
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
@@ -78,11 +78,12 @@ A machine-readable architecture file (`ark.config.json`) plus enforcement:
|
|
|
78
78
|
|-------|----------------|--------|
|
|
79
79
|
| **Layers** (always) | Who may import whom — imports, placement, purity, isolation | `ark.config.json` layers + rules |
|
|
80
80
|
| **ArkRules** (opt-in; structure rules inside a layer) | Habits *inside* a layer — structure sensors + domain invariants as data | `arkRules` → `arkrules/<Layer>.json` |
|
|
81
|
+
| **ArkRun** (opt-in extra) | Kernel usage + complete declarations | `arkRun` on schema `1.2` |
|
|
81
82
|
|
|
82
|
-
Absence of ArkRules changes no inter-layer verdict. Label residual **`[Layer]`** vs **`[ArkRules]`**.
|
|
83
|
-
Details: [configuration](docs/configuration.md
|
|
83
|
+
Absence of ArkRules or ArkRun changes no inter-layer verdict. Label residual **`[Layer]`** vs **`[ArkRules]`**.
|
|
84
|
+
Details: [configuration](docs/configuration.md) · [use path](docs/use.md).
|
|
84
85
|
|
|
85
|
-
**Not** a web framework, ORM, or job runner. Optional
|
|
86
|
+
**Not** a web framework, ORM, or job runner. Optional **ArkRun** extra and companion kernel (`@arkgate/runtime`) are separate and not required for the gate. In-memory stores are not production durability.
|
|
86
87
|
|
|
87
88
|
**Name note:** npm package `arkgate` — not affiliated with the separate Archgate CLI project.
|
|
88
89
|
|
|
@@ -192,9 +193,18 @@ More: [docs/develop.md](docs/develop.md) · skills install: [docs/agent-guide.md
|
|
|
192
193
|
|
|
193
194
|
---
|
|
194
195
|
|
|
195
|
-
## Optional
|
|
196
|
+
## Optional ArkRun kernel
|
|
196
197
|
|
|
197
|
-
Gates need **no** app runtime. The experimental `@arkgate/runtime`
|
|
198
|
+
Gates need **no** app runtime. The experimental **ArkRun** companion (`@arkgate/runtime`) is separate
|
|
199
|
+
and is not a production-readiness claim. `createStrictArkKernel` is the factory: each call creates
|
|
200
|
+
an isolated instance (no process-wide singleton). Managed components declare `uses` / `reactsTo` /
|
|
201
|
+
`raises` / `sends`; `getDependencyInformationPackage()` is a JSON snapshot and never leaks factories.
|
|
202
|
+
`requestGraph()` slices that snapshot into process or technical graphs (`nodeIds`,
|
|
203
|
+
`degreesOfSeparation`, include/exclude query) with a Mermaid helper. `send()` is local /
|
|
204
|
+
localBlocking / broker (broker falls back to in-process local; `ephemeral`
|
|
205
|
+
defaults true; no cloud SDKs in the package). Opt-in `startInspector()` binds `127.0.0.1`,
|
|
206
|
+
refuses `NODE_ENV=production`, and lazy-loads HTTP for JSON snapshots, SSE, and `/graph`. The kernel is
|
|
207
|
+
not bundled in the `arkgate` tarball.
|
|
198
208
|
|
|
199
209
|
### Durability stance
|
|
200
210
|
|
|
@@ -217,8 +227,10 @@ for real systems. Details: [docs/production-hardening.md](docs/production-harden
|
|
|
217
227
|
| Config · package surface · TS | [configuration](docs/configuration.md) · [package-surface](docs/package-surface.md) · [typescript-support](docs/typescript-support.md) |
|
|
218
228
|
| Brownfield | [docs/brownfield-adoption.md](docs/brownfield-adoption.md) |
|
|
219
229
|
| Security | [SECURITY.md](SECURITY.md) |
|
|
220
|
-
| Current
|
|
221
|
-
|
|
|
230
|
+
| Current tree (4.7.0 prepared) | [docs/releases/4.7.0.md](docs/releases/4.7.0.md) · [CHANGELOG](CHANGELOG.md) |
|
|
231
|
+
| Current published (4.6.7 on npm `latest`) | [docs/releases/4.6.7.md](docs/releases/4.6.7.md) |
|
|
232
|
+
| Prior published (4.6.6) | [docs/releases/4.6.6.md](docs/releases/4.6.6.md) |
|
|
233
|
+
| Prior published (4.6.5) | [docs/releases/4.6.5.md](docs/releases/4.6.5.md) |
|
|
222
234
|
| Prior published (4.6.3) | [docs/releases/4.6.3.md](docs/releases/4.6.3.md) |
|
|
223
235
|
| Prior (4.6.2) | [docs/releases/4.6.2.md](docs/releases/4.6.2.md) |
|
|
224
236
|
| Prior (4.6.1) | [docs/releases/4.6.1.md](docs/releases/4.6.1.md) |
|
package/SECURITY.md
CHANGED
|
@@ -18,7 +18,7 @@ Preferred path: use GitHub's private vulnerability reporting for this repository
|
|
|
18
18
|
- reproduction steps
|
|
19
19
|
- expected impact
|
|
20
20
|
- whether the issue affects `arkgate-check` / `ark-check`, `arkgate-mcp` / `ark-mcp`,
|
|
21
|
-
generated agent gates, the GitHub Action, or the optional
|
|
21
|
+
generated agent gates, the GitHub Action, or the optional ArkRun kernel
|
|
22
22
|
|
|
23
23
|
If private vulnerability reporting is unavailable, open a minimal public issue asking for
|
|
24
24
|
a private security contact without including exploit details.
|
|
@@ -7,7 +7,6 @@ import { fileURLToPath } from 'node:url';
|
|
|
7
7
|
const __arkCheckCli = fileURLToPath(new URL('./ark-check.mjs', import.meta.url));
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
|
-
DEFAULT_DOMAIN_FORBIDDEN_GLOBALS,
|
|
11
10
|
DEFAULT_INTENT_PREFIXES,
|
|
12
11
|
DEFAULT_LAYER_DIRECTORIES,
|
|
13
12
|
DEFAULT_RULES,
|
|
@@ -80,7 +79,6 @@ import {
|
|
|
80
79
|
CONCENTRATION_MIN_VIOLATIONS,
|
|
81
80
|
} from './lib/violations.mjs';
|
|
82
81
|
import {
|
|
83
|
-
suggestLayerForDir,
|
|
84
82
|
detectBestFitModel,
|
|
85
83
|
dirSegmentsFromGlob,
|
|
86
84
|
} from './lib/suggestions.mjs';
|
|
@@ -126,174 +124,9 @@ import {
|
|
|
126
124
|
resolveEffectiveProjectRoot,
|
|
127
125
|
} from './lib/project-root.mjs';
|
|
128
126
|
import { demoteArkRuleTeethUnderClassificationFloor } from './lib/rules-under-contract.mjs';
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if (flag) return flag;
|
|
133
|
-
const envRef = normalizePolicyBaseRef(env.ARK_POLICY_BASE_REF);
|
|
134
|
-
if (envRef) return envRef;
|
|
135
|
-
const githubBase = typeof env.GITHUB_BASE_REF === 'string' ? env.GITHUB_BASE_REF.trim() : '';
|
|
136
|
-
if (githubBase) return `origin/${githubBase}`;
|
|
137
|
-
return discoverLocalBaseRef(root) || undefined;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function parseArgs(argv) {
|
|
141
|
-
const args = {
|
|
142
|
-
root: process.cwd(),
|
|
143
|
-
config: 'ark.config.json',
|
|
144
|
-
manifest: undefined,
|
|
145
|
-
printConfig: undefined,
|
|
146
|
-
tsconfig: undefined,
|
|
147
|
-
json: false,
|
|
148
|
-
strictConfig: false,
|
|
149
|
-
strictMerge: false,
|
|
150
|
-
requireGates: false,
|
|
151
|
-
requireWriteHook: undefined,
|
|
152
|
-
init: false,
|
|
153
|
-
installAgentGates: false,
|
|
154
|
-
compact: false,
|
|
155
|
-
tools: undefined,
|
|
156
|
-
force: false,
|
|
157
|
-
skillsOnly: false,
|
|
158
|
-
baseline: undefined,
|
|
159
|
-
policyBase: undefined,
|
|
160
|
-
policyBaseRef: undefined,
|
|
161
|
-
policyAck: undefined, failOnNewSmells: false, baseRef: undefined,
|
|
162
|
-
contractSession: false,
|
|
163
|
-
contractDiff: false,
|
|
164
|
-
changed: false,
|
|
165
|
-
against: undefined,
|
|
166
|
-
base: undefined,
|
|
167
|
-
persona: undefined,
|
|
168
|
-
author: undefined,
|
|
169
|
-
failUngoverned: false,
|
|
170
|
-
updateBaseline: false,
|
|
171
|
-
noCache: false,
|
|
172
|
-
resident: false,
|
|
173
|
-
coverage: false,
|
|
174
|
-
migrateCommands: false,
|
|
175
|
-
doctor: false,
|
|
176
|
-
plan: false,
|
|
177
|
-
recommend: false,
|
|
178
|
-
writePlan: false,
|
|
179
|
-
listPolicyPacks: false,
|
|
180
|
-
applyPolicyPack: undefined,
|
|
181
|
-
watch: false,
|
|
182
|
-
beginner: false,
|
|
183
|
-
openReport: false,
|
|
184
|
-
noOpenReport: false,
|
|
185
|
-
version: false,
|
|
186
|
-
help: false,
|
|
187
|
-
all: false,
|
|
188
|
-
followConfigRoot: false,
|
|
189
|
-
};
|
|
190
|
-
const requireValue = (flag, index) => {
|
|
191
|
-
const value = argv[index + 1];
|
|
192
|
-
if (value === undefined || value.startsWith('-')) {
|
|
193
|
-
throw new Error(`Missing value for ${flag}. Run arkgate-check --help for usage.`);
|
|
194
|
-
}
|
|
195
|
-
return value;
|
|
196
|
-
};
|
|
197
|
-
for (let i = 2; i < argv.length; i += 1) {
|
|
198
|
-
const arg = argv[i];
|
|
199
|
-
if (arg === '--json') args.json = true;
|
|
200
|
-
else if (arg === '--strict' || arg === '--strict-merge') {
|
|
201
|
-
args.strictConfig = true;
|
|
202
|
-
args.requireGates = true;
|
|
203
|
-
args.strictMerge = true;
|
|
204
|
-
}
|
|
205
|
-
else if (arg === '--strict-config') args.strictConfig = true;
|
|
206
|
-
else if (arg === '--require-gates') {
|
|
207
|
-
args.requireGates = true;
|
|
208
|
-
args.strictConfig = true;
|
|
209
|
-
}
|
|
210
|
-
else if (arg === '--require-write-hook') {
|
|
211
|
-
args.requireWriteHook = requireValue(arg, i++).trim().toLowerCase();
|
|
212
|
-
}
|
|
213
|
-
else if (arg === '--init') args.init = true;
|
|
214
|
-
else if (arg === '--preset') args.preset = requireValue(arg, i++);
|
|
215
|
-
else if (arg === '--install-agent-gates') args.installAgentGates = true;
|
|
216
|
-
else if (arg === '--compact') args.compact = true;
|
|
217
|
-
else if (arg === '--tools') {
|
|
218
|
-
// Consume the next arg only when it isn't another flag (same rule as --baseline),
|
|
219
|
-
// so `--tools --force` can't silently eat --force as a "tool name".
|
|
220
|
-
const next = argv[i + 1];
|
|
221
|
-
if (next !== undefined && !next.startsWith('-')) {
|
|
222
|
-
i += 1;
|
|
223
|
-
args.tools = next
|
|
224
|
-
.split(',')
|
|
225
|
-
.map((tool) => tool.trim().toLowerCase())
|
|
226
|
-
.filter(Boolean);
|
|
227
|
-
} else {
|
|
228
|
-
args.tools = []; // flag without a value — rejected in runInstallAgentGates
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
else if (arg === '--force') args.force = true;
|
|
232
|
-
else if (arg === '--follow-config-root') args.followConfigRoot = true;
|
|
233
|
-
else if (arg === '--skills-only') args.skillsOnly = true;
|
|
234
|
-
else if (arg === '--coverage') args.coverage = true;
|
|
235
|
-
else if (arg === '--doctor') args.doctor = true;
|
|
236
|
-
else if (arg === '--plan') args.plan = true;
|
|
237
|
-
else if (arg === '--rules-inventory') args.rulesInventory = true;
|
|
238
|
-
else if (arg === '--recommend') args.recommend = true;
|
|
239
|
-
else if (arg === '--write-plan') args.writePlan = true;
|
|
240
|
-
else if (arg === '--list-policy-packs') args.listPolicyPacks = true;
|
|
241
|
-
else if (arg === '--apply-policy-pack') args.applyPolicyPack = requireValue(arg, i++);
|
|
242
|
-
else if (arg === '--suggest-include') args.suggestInclude = true;
|
|
243
|
-
else if (arg === '--adopt-contract') args.adoptContract = true;
|
|
244
|
-
else if (arg === '--migrate-contract') args.migrateContract = true;
|
|
245
|
-
else if (arg === '--ratchet-cores') args.ratchetCores = true;
|
|
246
|
-
else if (arg === '--write') args.write = true;
|
|
247
|
-
else if (arg === '--watch') args.watch = true;
|
|
248
|
-
else if (arg === '--beginner') args.beginner = true;
|
|
249
|
-
else if (arg === '--codex-home') args.codexHome = true;
|
|
250
|
-
else if (arg === '--claude-home') args.claudeHome = true;
|
|
251
|
-
else if (arg === '--grok-home') args.grokHome = true;
|
|
252
|
-
else if (arg === '--agent-homes') {
|
|
253
|
-
args.agentHomes = true;
|
|
254
|
-
args.codexHome = true;
|
|
255
|
-
args.claudeHome = true;
|
|
256
|
-
args.grokHome = true;
|
|
257
|
-
}
|
|
258
|
-
else if (arg === '--migrate-commands') args.migrateCommands = true;
|
|
259
|
-
else if (arg === '--no-cache') args.noCache = true;
|
|
260
|
-
else if (arg === '--resident') args.resident = true;
|
|
261
|
-
else if (arg === '--report') {
|
|
262
|
-
const next = argv[i + 1];
|
|
263
|
-
args.report = next && !next.startsWith('-') ? argv[++i] : 'ark-report.html';
|
|
264
|
-
}
|
|
265
|
-
else if (arg === '--reset-origin') args.resetOrigin = true;
|
|
266
|
-
else if (arg === '--no-archive') args.noArchive = true;
|
|
267
|
-
else if (arg === '--open') args.openReport = true;
|
|
268
|
-
else if (arg === '--no-open') args.noOpenReport = true;
|
|
269
|
-
else if (arg === '--baseline' || arg === '--update-baseline') {
|
|
270
|
-
if (arg === '--update-baseline') args.updateBaseline = true;
|
|
271
|
-
// optional path value: consume the next arg only when it isn't another flag
|
|
272
|
-
const next = argv[i + 1];
|
|
273
|
-
args.baseline = next && !next.startsWith('-') ? argv[++i] : '.ark-baseline.json';
|
|
274
|
-
}
|
|
275
|
-
else if (arg === '--policy-base') args.policyBase = requireValue(arg, i++);
|
|
276
|
-
else if (arg === '--policy-base-ref') args.policyBaseRef = requireValue(arg, i++);
|
|
277
|
-
else if (arg === '--policy-ack') args.policyAck = requireValue(arg, i++); else if (arg === '--fail-on-new-smells') args.failOnNewSmells = true; else if (arg === '--base-ref') args.baseRef = requireValue(arg, i++);
|
|
278
|
-
else if (arg === '--contract-session') args.contractSession = true;
|
|
279
|
-
else if (arg === '--contract-diff') args.contractDiff = true;
|
|
280
|
-
else if (arg === '--changed') args.changed = true;
|
|
281
|
-
else if (arg === '--against') args.against = requireValue(arg, i++);
|
|
282
|
-
else if (arg === '--base') args.base = requireValue(arg, i++);
|
|
283
|
-
else if (arg === '--persona') args.persona = requireValue(arg, i++);
|
|
284
|
-
else if (arg === '--author') args.author = requireValue(arg, i++);
|
|
285
|
-
else if (arg === '--root') args.root = path.resolve(requireValue(arg, i++));
|
|
286
|
-
else if (arg === '--config') args.config = requireValue(arg, i++);
|
|
287
|
-
else if (arg === '--manifest') args.manifest = requireValue(arg, i++);
|
|
288
|
-
else if (arg === '--print-config') args.printConfig = requireValue(arg, i++);
|
|
289
|
-
else if (arg === '--tsconfig') args.tsconfig = requireValue(arg, i++);
|
|
290
|
-
else if (arg === '--help' || arg === '-h') args.help = true;
|
|
291
|
-
else if (arg === '--all') args.all = true;
|
|
292
|
-
else if (arg === '--version' || arg === '-V') args.version = true;
|
|
293
|
-
else throw new Error(`Unknown argument: ${arg}. Run arkgate-check --help for usage.`);
|
|
294
|
-
}
|
|
295
|
-
return args;
|
|
296
|
-
}
|
|
127
|
+
import { parseArgs, resolveDesignDeltaBaseRef } from './lib/check-args.mjs';
|
|
128
|
+
import { detectConfig, proposeForUncovered } from './lib/check-config-detect.mjs';
|
|
129
|
+
import { runWatchMode } from './lib/check-watch.mjs';
|
|
297
130
|
|
|
298
131
|
/** Path shown to humans: project-relative when inside root, absolute otherwise (no `../../..`). */
|
|
299
132
|
function displayPathFromRoot(root, absPath) {
|
|
@@ -317,101 +150,6 @@ function readConfig(root, configPath) {
|
|
|
317
150
|
return parseArkConfigJson(fs.readFileSync(fullPath, 'utf8'), fullPath).config;
|
|
318
151
|
}
|
|
319
152
|
|
|
320
|
-
/**
|
|
321
|
-
* Infer an ark.config.json from the directories that actually exist in the project,
|
|
322
|
-
* using the same layer→directory conventions as the eleven-layer template. A directory
|
|
323
|
-
* only counts when it contains at least one source file, so an empty scaffold dir can't
|
|
324
|
-
* produce a layer whose pattern matches nothing (which --strict-config would fail).
|
|
325
|
-
*/
|
|
326
|
-
function detectConfig(root) {
|
|
327
|
-
const srcDir = fs.existsSync(path.join(root, 'src')) ? 'src' : '.';
|
|
328
|
-
const layers = [];
|
|
329
|
-
|
|
330
|
-
for (const entry of DEFAULT_INTENT_PREFIXES) {
|
|
331
|
-
const directories = (DEFAULT_LAYER_DIRECTORIES[entry.layer] ?? []).filter(
|
|
332
|
-
(directory) => walk(path.join(root, srcDir, directory), [], { root }).length > 0
|
|
333
|
-
);
|
|
334
|
-
if (directories.length === 0) continue;
|
|
335
|
-
layers.push({
|
|
336
|
-
name: entry.layer,
|
|
337
|
-
patterns: directories.map((directory) => `${normalize(path.join(srcDir, directory))}/**`),
|
|
338
|
-
intentPrefixes: entry.prefixes,
|
|
339
|
-
...(entry.layer === 'DomainModel'
|
|
340
|
-
? { forbiddenGlobals: DEFAULT_DOMAIN_FORBIDDEN_GLOBALS }
|
|
341
|
-
: {}),
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
const names = new Set(layers.map((layer) => layer.name));
|
|
346
|
-
const rules = DEFAULT_RULES.filter((rule) => names.has(rule.from) && names.has(rule.to));
|
|
347
|
-
|
|
348
|
-
return { srcDir, config: { include: [srcDir], layers, rules } };
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
/** Top-level directories under srcDir not covered by any detected layer pattern. */
|
|
352
|
-
function uncoveredDirectories(root, srcDir, layers) {
|
|
353
|
-
const base = path.join(root, srcDir);
|
|
354
|
-
if (!fs.existsSync(base)) return [];
|
|
355
|
-
return fs
|
|
356
|
-
.readdirSync(base, { withFileTypes: true })
|
|
357
|
-
.filter(
|
|
358
|
-
(entry) =>
|
|
359
|
-
entry.isDirectory() &&
|
|
360
|
-
entry.name !== 'node_modules' &&
|
|
361
|
-
entry.name !== 'dist' &&
|
|
362
|
-
!entry.name.startsWith('.')
|
|
363
|
-
)
|
|
364
|
-
.map((entry) => entry.name)
|
|
365
|
-
.filter((name) => {
|
|
366
|
-
const prefix = `${normalize(path.join(srcDir, name))}/`;
|
|
367
|
-
return !layers.some((layer) =>
|
|
368
|
-
layer.patterns.some((pattern) => pattern.startsWith(prefix))
|
|
369
|
-
);
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// detectWorkspaces: shared implementation in ark-shared.mjs (npm/pnpm/rush/lerna +
|
|
374
|
-
// conventional multi-package roots).
|
|
375
|
-
|
|
376
|
-
// Deny every "upward" edge for an ordered layer list (index 0 = outermost/top,
|
|
377
|
-
// which may import everything below it). Inner/lower layers must not import outer
|
|
378
|
-
// ones — the shared shape behind linear layered and feature-sliced layouts.
|
|
379
|
-
|
|
380
|
-
function proposeForUncovered(root, srcDir, layers) {
|
|
381
|
-
const proposals = [];
|
|
382
|
-
for (const top of uncoveredDirectories(root, srcDir, layers)) {
|
|
383
|
-
const direct = suggestLayerForDir(top);
|
|
384
|
-
if (direct) {
|
|
385
|
-
proposals.push({ dir: `${srcDir}/${top}`, ...direct });
|
|
386
|
-
continue;
|
|
387
|
-
}
|
|
388
|
-
let children = [];
|
|
389
|
-
try {
|
|
390
|
-
children = fs
|
|
391
|
-
.readdirSync(path.join(root, srcDir, top), { withFileTypes: true })
|
|
392
|
-
.filter((e) => e.isDirectory() && e.name !== 'node_modules' && !e.name.startsWith('.'))
|
|
393
|
-
.map((e) => e.name);
|
|
394
|
-
} catch {
|
|
395
|
-
/* not a readable directory — treat as unrecognized below */
|
|
396
|
-
}
|
|
397
|
-
if (children.length > 0) {
|
|
398
|
-
// Descend: propose per child so a mixed `lib/` yields lib/repositories → Persistence
|
|
399
|
-
// AND flags lib/db as unrecognized, instead of dropping the parts Ark can't place.
|
|
400
|
-
for (const child of children) {
|
|
401
|
-
const hit = suggestLayerForDir(child);
|
|
402
|
-
proposals.push(
|
|
403
|
-
hit
|
|
404
|
-
? { dir: `${srcDir}/${top}/${child}`, ...hit }
|
|
405
|
-
: { dir: `${srcDir}/${top}/${child}`, unrecognized: true }
|
|
406
|
-
);
|
|
407
|
-
}
|
|
408
|
-
} else {
|
|
409
|
-
proposals.push({ dir: `${srcDir}/${top}`, unrecognized: true });
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
return proposals;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
153
|
function printInitNextSteps(root) {
|
|
416
154
|
console.log('');
|
|
417
155
|
console.log('Next steps:');
|
|
@@ -1733,6 +1471,7 @@ async function main() {
|
|
|
1733
1471
|
score: fitness.score,
|
|
1734
1472
|
mode: fitness.mode,
|
|
1735
1473
|
improvementCompass: reportCompass,
|
|
1474
|
+
arkRun: designDepth?.arkRun ?? null,
|
|
1736
1475
|
}),
|
|
1737
1476
|
leftoverDesignWork: designDepth?.designFitness?.designWeak === true,
|
|
1738
1477
|
};
|
|
@@ -1755,7 +1494,17 @@ async function main() {
|
|
|
1755
1494
|
adoption: adoptionForReport,
|
|
1756
1495
|
designDepth,
|
|
1757
1496
|
advisories: {
|
|
1758
|
-
...computeDoctorAdvisories(
|
|
1497
|
+
...computeDoctorAdvisories(
|
|
1498
|
+
root,
|
|
1499
|
+
config,
|
|
1500
|
+
coverage,
|
|
1501
|
+
rules,
|
|
1502
|
+
files,
|
|
1503
|
+
ts,
|
|
1504
|
+
parseHealth,
|
|
1505
|
+
undefined,
|
|
1506
|
+
activeViolations
|
|
1507
|
+
),
|
|
1759
1508
|
// Doctor parity: always emit improvement compass when doctor would (reportParity).
|
|
1760
1509
|
...(designDepth?.improvementCompass
|
|
1761
1510
|
? { improvementCompass: designDepth.improvementCompass }
|
|
@@ -1764,6 +1513,7 @@ async function main() {
|
|
|
1764
1513
|
...(designDepth?.deepModuleCoach
|
|
1765
1514
|
? { deepModuleCoach: designDepth.deepModuleCoach }
|
|
1766
1515
|
: {}),
|
|
1516
|
+
...(designDepth?.arkRun ? { arkRun: designDepth.arkRun } : {}),
|
|
1767
1517
|
},
|
|
1768
1518
|
};
|
|
1769
1519
|
const html = args.beginner
|
|
@@ -1964,87 +1714,17 @@ async function main() {
|
|
|
1964
1714
|
}
|
|
1965
1715
|
|
|
1966
1716
|
if (args.watch) {
|
|
1967
|
-
await runWatchMode(args
|
|
1717
|
+
await runWatchMode(args, {
|
|
1718
|
+
cliPath: __arkCheckCli,
|
|
1719
|
+
loadConfig: readConfig,
|
|
1720
|
+
dim: color.dim,
|
|
1721
|
+
});
|
|
1968
1722
|
return;
|
|
1969
1723
|
}
|
|
1970
1724
|
|
|
1971
1725
|
process.exitCode = designCheck.exitCode(observedOk && (!args.strictMerge || analysisComplete) ? 0 : 1);
|
|
1972
1726
|
}
|
|
1973
1727
|
|
|
1974
|
-
async function runWatchMode(args) {
|
|
1975
|
-
const argv = process.argv.slice(2).filter((token) => token !== '--watch');
|
|
1976
|
-
let debounce;
|
|
1977
|
-
const rerun = () => {
|
|
1978
|
-
clearTimeout(debounce);
|
|
1979
|
-
debounce = setTimeout(() => {
|
|
1980
|
-
const result = spawnSync(process.execPath, [__arkCheckCli, ...argv], {
|
|
1981
|
-
cwd: args.root,
|
|
1982
|
-
stdio: 'inherit',
|
|
1983
|
-
env: process.env,
|
|
1984
|
-
});
|
|
1985
|
-
process.exitCode = result.status ?? 1;
|
|
1986
|
-
}, 300);
|
|
1987
|
-
};
|
|
1988
|
-
|
|
1989
|
-
let config;
|
|
1990
|
-
try {
|
|
1991
|
-
config = readConfig(args.root, args.config);
|
|
1992
|
-
} catch (error) {
|
|
1993
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
1994
|
-
process.exitCode = 2;
|
|
1995
|
-
return;
|
|
1996
|
-
}
|
|
1997
|
-
|
|
1998
|
-
for (const entry of config.include ?? []) {
|
|
1999
|
-
const target = path.join(args.root, entry);
|
|
2000
|
-
if (!fs.existsSync(target)) continue;
|
|
2001
|
-
try {
|
|
2002
|
-
const watcher = fs.watch(target, { recursive: true }, rerun);
|
|
2003
|
-
watcher.on('error', () => {
|
|
2004
|
-
watcher.close();
|
|
2005
|
-
watchByPolling(target, rerun);
|
|
2006
|
-
});
|
|
2007
|
-
} catch {
|
|
2008
|
-
watchByPolling(target, rerun);
|
|
2009
|
-
}
|
|
2010
|
-
}
|
|
2011
|
-
|
|
2012
|
-
console.log(color.dim('Watching governed paths for changes… (Ctrl+C to stop)'));
|
|
2013
|
-
await new Promise(() => {});
|
|
2014
|
-
}
|
|
2015
|
-
|
|
2016
|
-
function watchByPolling(target, onChange) {
|
|
2017
|
-
let previous = watchFingerprint(target);
|
|
2018
|
-
setInterval(() => {
|
|
2019
|
-
const current = watchFingerprint(target);
|
|
2020
|
-
if (current === previous) return;
|
|
2021
|
-
previous = current;
|
|
2022
|
-
onChange();
|
|
2023
|
-
}, 250);
|
|
2024
|
-
}
|
|
2025
|
-
|
|
2026
|
-
function watchFingerprint(target) {
|
|
2027
|
-
const pending = [target];
|
|
2028
|
-
const entries = [];
|
|
2029
|
-
while (pending.length > 0) {
|
|
2030
|
-
const current = pending.pop();
|
|
2031
|
-
let stat;
|
|
2032
|
-
try {
|
|
2033
|
-
stat = fs.statSync(current);
|
|
2034
|
-
} catch {
|
|
2035
|
-
continue;
|
|
2036
|
-
}
|
|
2037
|
-
entries.push(`${current}:${stat.mtimeMs}:${stat.size}`);
|
|
2038
|
-
if (!stat.isDirectory()) continue;
|
|
2039
|
-
try {
|
|
2040
|
-
for (const name of fs.readdirSync(current)) pending.push(path.join(current, name));
|
|
2041
|
-
} catch {
|
|
2042
|
-
// A concurrent delete is represented by the next fingerprint.
|
|
2043
|
-
}
|
|
2044
|
-
}
|
|
2045
|
-
return entries.sort().join('|');
|
|
2046
|
-
}
|
|
2047
|
-
|
|
2048
1728
|
main().catch((error) => {
|
|
2049
1729
|
console.error(
|
|
2050
1730
|
process.env.ARK_DEBUG_STACK === '1' && error instanceof Error
|