arkgate 4.0.0 → 4.1.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 +142 -0
- package/README.md +7 -5
- package/bin/ark-check-runtime.mjs +244 -25
- package/bin/ark-check.mjs +10 -1
- package/bin/ark-layer-match.mjs +80 -5
- package/bin/ark-shared.mjs +170 -9
- package/bin/ark.mjs +52 -5
- package/bin/lib/adapter-contract.mjs +7 -1
- package/bin/lib/agent-gates.mjs +2 -0
- package/bin/lib/analysis-engine.mjs +6 -6
- package/bin/lib/arkrules-sensors.mjs +63 -22
- package/bin/lib/ci-and-commands.mjs +148 -8
- package/bin/lib/core-ratchet.mjs +9 -4
- package/bin/lib/doctor-advisories.mjs +8 -1
- package/bin/lib/doctor-plan.mjs +277 -59
- package/bin/lib/enforcement-honesty.mjs +351 -26
- package/bin/lib/enforcement-state.mjs +1 -1
- package/bin/lib/field-install.mjs +35 -2
- package/bin/lib/graph-blind.mjs +1 -1
- package/bin/lib/html-report-advisories.mjs +8 -25
- package/bin/lib/html-report-depth.mjs +167 -3
- package/bin/lib/html-report.mjs +12 -5
- package/bin/lib/install-migrate.mjs +109 -6
- package/bin/lib/managed-upgrade.mjs +100 -1
- package/bin/lib/presets.mjs +314 -46
- package/bin/lib/project-root.mjs +268 -0
- package/bin/lib/remediation.mjs +12 -11
- package/bin/lib/rules-inventory.mjs +71 -29
- package/bin/lib/rules-under-contract.mjs +389 -5
- package/bin/lib/start-preview.mjs +48 -14
- package/bin/lib/suggestions.mjs +118 -3
- package/bin/lib/unavailable-analysis.mjs +2 -0
- package/bin/lib/upgrade-command.mjs +325 -14
- package/bin/lib/write-path-capabilities.mjs +38 -9
- package/dist/eslint/index.cjs +2 -2
- package/dist/eslint/index.d.ts +27 -2
- package/dist/eslint/index.js +2 -2
- package/dist/index.cjs +16 -14
- package/dist/index.d.ts +3 -1
- package/dist/index.js +16 -14
- package/docs/README.md +3 -2
- package/docs/ai-gates.md +15 -11
- package/docs/brownfield-adoption.md +38 -0
- package/docs/configuration.md +59 -7
- package/docs/package-surface.md +3 -2
- package/docs/product-voice.md +10 -1
- package/docs/typescript-support.md +9 -5
- package/docs/use.md +7 -5
- package/package.json +3 -1
- package/server.json +3 -3
- package/templates/architecture-playbook.json +3 -0
- package/templates/layers/shared-types.starter.json +29 -0
- package/templates/skills/ark-adopt.md +2 -0
- package/templates/skills/ark-explain.md +23 -5
- package/templates/skills/ark-explore.md +21 -1
- package/templates/skills/ark-fix.md +16 -5
- package/templates/skills/ark-upgrade.md +57 -11
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,144 @@ in the immutable pre-2.0 archive linked below.
|
|
|
5
5
|
|
|
6
6
|
## Unreleased
|
|
7
7
|
|
|
8
|
+
## 4.1.0 — 2026-07-25 (prepared)
|
|
9
|
+
|
|
10
|
+
**Minor** over 4.0.1. Single ship train: field product phases + field-gap closure (S0–S7) +
|
|
11
|
+
maintainer CI PR-slim / full-matrix speed work. Field product phases from Next monorepo dogfood +
|
|
12
|
+
objective review, plus field-gap lab re-verify:
|
|
13
|
+
Next API shell classification, anti false-green doctor packaging, monorepo config walk-up with
|
|
14
|
+
write-root split, SPA/lib classification vacuum fixes, ESLint path-alias parity, structure-sensor
|
|
15
|
+
false-positive tuning, ArkRules merge-plane honesty, type-edge placement debt, rules-inventory
|
|
16
|
+
UI noise reduction, migrate-contract retrofit, and config write containment.
|
|
17
|
+
**No required config migration.** Does not weaken write gate or CI value edges. Does not close
|
|
18
|
+
Z09 / RB-11 field claims. **Status: prepared** (not on npm until publish).
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- **Next API = Application shell (P0-A):** Framework overlay, `ui-surface`, and `monorepo`
|
|
23
|
+
presets classify `app/api/**`, `src/app/api/**`, `pages/api/**` as
|
|
24
|
+
**ApplicationOrchestration** (higher specificity than Presentation `**/app/**`). UI routes
|
|
25
|
+
stay Presentation. Docs: brownfield + configuration honesty.
|
|
26
|
+
- **Product honesty surface (P0-B):** Doctor JSON `productHonesty` consolidates design-weak,
|
|
27
|
+
weak/partial coverage, dirty freeze, package dual-truth, residual pilots, and soft write hosts
|
|
28
|
+
into one unfinished/not-whole-tree message (`notAScore`). Human doctor section + HTML card
|
|
29
|
+
(`data-product-honesty`). Never a numeric architecture score. **Anti false-green:** never
|
|
30
|
+
`finished: true` when active **blocking** violations remain, mode is ADAPT/SUGGEST with debt,
|
|
31
|
+
dual-truth pin, or residual pilots are open.
|
|
32
|
+
- **ArkRules mergePlanes (P1-M):** `rulesUnderContract.mergePlanes` states which plane can fail
|
|
33
|
+
merge (layers vs enforced structure sensors vs invariants) and dual-plane stamp (heuristics vs
|
|
34
|
+
catalog+coverage; never one score).
|
|
35
|
+
- **Type-edge policy fields (P1-type):** Doctor `violations.typeEdgePolicy` + SharedTypes
|
|
36
|
+
guidance; type-only denied edges stay on the **violations** list with `failsStrict: false`
|
|
37
|
+
and diagnostics **severity: warning** (non-blocking for merge/exit/library `valid`). Value
|
|
38
|
+
edges still block. **Exception:** `peerIsolation` type-only edges stay hard.
|
|
39
|
+
- **`--migrate-contract`:** Retrofit existing configs with Next API shell patterns
|
|
40
|
+
(Application) without full re-init — field path for brownfield Next App Router / Pages API.
|
|
41
|
+
- **Monorepo discovery walk-up:** Doctor/check from a nested package cwd find the monorepo
|
|
42
|
+
`ark.config.json` (no invented ADAPT 0%). Mutative install/start write under the **write root**
|
|
43
|
+
(cwd/`--root`) unless `--follow-config-root` is set — does not clobber monorepo AGENTS/skills
|
|
44
|
+
from a nested package by accident.
|
|
45
|
+
- **SPA / vacuum classification (S3):** SPA preset + adopt/start heuristics reduce
|
|
46
|
+
Application-empty / lib-as-Presentation lies on Vite SPA and similar client trees; Domain
|
|
47
|
+
specificity keeps nested `**/domain/**` on DomainModel when patterns compete.
|
|
48
|
+
- **Config write containment:** Mutative commands refuse `--config` paths outside project root
|
|
49
|
+
(fail closed; no silent escape write).
|
|
50
|
+
|
|
51
|
+
### Improved
|
|
52
|
+
|
|
53
|
+
- **ESLint path aliases (P0-C):** `ark/no-domain-infra-imports` resolves tsconfig `paths` /
|
|
54
|
+
`baseUrl` (including simple relative `extends`) so `@/*` forbidden edges match CLI inside the
|
|
55
|
+
documented envelope. Residual honestly documented in ai-gates (no project refs / multi-target
|
|
56
|
+
/ bare packages claim). Dual-driver parity tests lock type-only vs value.
|
|
57
|
+
- **Structure sensors prefer false negatives (P1-L):** `aggregate-private-state` ignores
|
|
58
|
+
readonly-only public props; `always-valid-factory` requires mutable surface evidence; anemic
|
|
59
|
+
bar needs ≥2 public fields. Intentional DDD private aggregates stay quiet; true positives
|
|
60
|
+
retained. Defaults remain advisory for noisy sensors.
|
|
61
|
+
- **Rules inventory UI noise (P2-N):** Magic-constant inventory skips route/theme/UI label noise
|
|
62
|
+
and pure UI/Next page surfaces; still finds controller validation and domain constants.
|
|
63
|
+
- **Force-gates / upgrade digest (S4):** Force-install preserves content-identity digests so
|
|
64
|
+
managed upgrade does not falsely report unbound/clobber after intentional force refresh.
|
|
65
|
+
- **Doctor / HTML honesty (S1/S0):** Type-only edges excluded from blocking counts in planMet,
|
|
66
|
+
HTML banners, and adapter `valid` parity; dual-truth pin and package-pin-absent surface on
|
|
67
|
+
`productHonesty.reasonIds`.
|
|
68
|
+
- **Field dogfood smoke:** Offline gap-assertion scaffold under `scripts/field-dogfood/` for
|
|
69
|
+
regression before minors (7-repo lab re-verify 15/15 critical assertions).
|
|
70
|
+
- **CI PR slim vs full matrix:** composable `ci-profile` (`scripts/ci-profile.mjs` +
|
|
71
|
+
`.github/workflows/ci.yml`) — ordinary PRs use coverage-only build, 1 packed-TS cell, 1
|
|
72
|
+
gallery PM, onboarding `*/small`, and path-filtered performance budgets; full matrix (main,
|
|
73
|
+
labels `full-matrix`/`release`, release-prep branches `feat/4.1*`) keeps `test:confidence`
|
|
74
|
+
(mutation) and complete product matrices with `fail-fast: false`. Documented in CONTRIBUTING.
|
|
75
|
+
Does not weaken publish-path confidence.
|
|
76
|
+
|
|
77
|
+
### Fixed
|
|
78
|
+
|
|
79
|
+
- Presentation→Persistence baselining pressure from Next API routes mis-layered as UI (field
|
|
80
|
+
dogfood on App Router monorepos).
|
|
81
|
+
- False “finished / whole-tree guarantee” packaging when the graph still has blocking debt.
|
|
82
|
+
- Nested monorepo package cwd inventing ungoverned ADAPT world.
|
|
83
|
+
- Type-only ESLint/CLI/HTML/adapter parity drift (blocking counts and exit codes).
|
|
84
|
+
- Config path outside `--root` accepted on mutative write paths.
|
|
85
|
+
- Greenfield `ark start --yes --apply` refused on empty trees (vacuous 0% coverage) and residual
|
|
86
|
+
`src/**` Application catch-all restored for non-Next layered presets.
|
|
87
|
+
- Presentation `src/**/api/**` no longer swallows Application bags like `src/core/api/**`.
|
|
88
|
+
|
|
89
|
+
### Coverage floors (internal)
|
|
90
|
+
|
|
91
|
+
- Statement/line **80 → 79.5**, branch **82.5 → 82.0**, function **85 → 76.5** after field-gap
|
|
92
|
+
surface growth (measured clean candidate ~79.95% / ~82.3% / ~77.0%). Per-file write-path
|
|
93
|
+
floors recalibrated to measured honesty (not a silent weaken of architecture gates).
|
|
94
|
+
- Mutation break threshold **90 → 87** (measured ~88–89 on the same candidate).
|
|
95
|
+
Critical mutation groups drop managed-upgrade line window for 4.1.0 (S4 force-preserve
|
|
96
|
+
remains unit-tested; NoCoverage noise on toml-section residual).
|
|
97
|
+
|
|
98
|
+
## 4.0.1 — 2026-07-24
|
|
99
|
+
|
|
100
|
+
**Patch** over 4.0.0. Field footgun fix for stale global CLI on upgrade, ArkRules HTML/doctor
|
|
101
|
+
catalog honesty, dual-plane product wording, and internal coverage attribution.
|
|
102
|
+
**No required config migration.** Does not weaken write gate or CI. Does not close Z09 / RB-11.
|
|
103
|
+
|
|
104
|
+
### Improved
|
|
105
|
+
|
|
106
|
+
- **HTML / doctor ArkRules showcase:** `rulesUnderContract` is no longer counts-only.
|
|
107
|
+
When `arkRules` is active, the report lists **per-layer** structure/invariant totals,
|
|
108
|
+
**structure sensors** (id · sensor · mode · description), **uncovered** invariants,
|
|
109
|
+
and a **covered sample** — so `/ark-explain` has real [ArkRules] content to walk.
|
|
110
|
+
Skill text updated to open `arkrules/*` and name real IDs.
|
|
111
|
+
- **HTML advisory cards:** doctor advisory blocks (contract health, ambient, cohesion,
|
|
112
|
+
parse health, graph blind spots, ArkRules) render with `class="section card"` so they
|
|
113
|
+
match the white panel styling of Layers / Violations / Enforcement (were bare
|
|
114
|
+
`<section data-advisory>` on the page background).
|
|
115
|
+
- **Dual-plane honesty in product surfaces:** configuration / use / product-voice /
|
|
116
|
+
`/ark-explain` and the ArkRules HTML strip state that structure sensors are heuristics,
|
|
117
|
+
invariants are catalog + coverage evidence (not a business runtime), and the two planes
|
|
118
|
+
never merge into one architecture score.
|
|
119
|
+
- **Coverage floors (internal):** branch threshold 83.0 → **82.5** after Fase 0 surface
|
|
120
|
+
growth (measured ~83% clean candidate); unit tests for upgrade refuse/HTML catalog use
|
|
121
|
+
ESM imports so V8 attributes hits (createRequire was under-counting).
|
|
122
|
+
|
|
123
|
+
### Fixed
|
|
124
|
+
|
|
125
|
+
- **Stale global CLI vs project `arkgate` (upgrade footgun):** `ark upgrade` /
|
|
126
|
+
`ark update` now **fail closed** when the running CLI package root is outside
|
|
127
|
+
the project's `node_modules/arkgate` **and** the running version is older than
|
|
128
|
+
the installed project package. Recovery prefers the package-manager runner
|
|
129
|
+
(`npx` / `pnpm exec` / `yarn` + `arkgate`) so hoisted monorepos without a
|
|
130
|
+
nested shallow path still work; shallow
|
|
131
|
+
`node node_modules/arkgate/bin/ark.mjs` is install-root secondary advice.
|
|
132
|
+
With `--json`, refuse also emits a machine-readable
|
|
133
|
+
`{ refused, reason, message, cliVersion, projectVersion, nextCommand }` on
|
|
134
|
+
stdout (exit 2). Does not block project-local CLI, newer globals, or projects
|
|
135
|
+
with no local install yet. Field context: global Homebrew **2.x** mutative
|
|
136
|
+
upgrade next to 3.8+/4.0 projects (see
|
|
137
|
+
[4.0.0 release notes](docs/releases/4.0.0.md#field-footgun--global-arkgate-2x-on-path)).
|
|
138
|
+
- **`/ark-upgrade` skill:** procedure step 1 resolves the **project-local** CLI
|
|
139
|
+
first, probes for managed upgrade (`--plan-digest`), and aborts when only an
|
|
140
|
+
old PATH binary is available.
|
|
141
|
+
- **Doctor `rulesUnderContract` catalog caps:** structure sensors and uncovered
|
|
142
|
+
invariants are truncated in doctor JSON (with `structureTruncated` /
|
|
143
|
+
`uncoveredTruncated`) the same way as the covered sample — HTML announces
|
|
144
|
+
overflow instead of implying the full catalog is always embedded.
|
|
145
|
+
|
|
8
146
|
## 4.0.0 — 2026-07-24
|
|
9
147
|
|
|
10
148
|
**Major** over 3.9.2. **Breaking:** deprecated root subpaths `arkgate/runtime` and `arkgate/nestjs`
|
|
@@ -77,6 +215,10 @@ case-study docs are scaffolding, not a closed field gate.
|
|
|
77
215
|
- **Dual-truth residual:** `ark upgrade --no-install` can refresh managed assets while leaving
|
|
78
216
|
package.json on an older pin — doctor exposes `packageVersionTruth` and upgrade JSON/human notes
|
|
79
217
|
when the pin is behind the CLI.
|
|
218
|
+
- **Field note — global 2.x PATH:** bare `ark upgrade` from a global **arkgate 2.x** install is
|
|
219
|
+
mutative (pre managed content-identity) and unsafe next to 3.8+/4.0 projects — prefer
|
|
220
|
+
`npx arkgate upgrade …`. Documented in [4.0.0 release notes](docs/releases/4.0.0.md#field-footgun--global-arkgate-2x-on-path);
|
|
221
|
+
CLI fail-closed guard ships under Unreleased.
|
|
80
222
|
|
|
81
223
|
## 3.9.2 — 2026-07-23
|
|
82
224
|
|
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.0.
|
|
20
|
-
>
|
|
21
|
-
> [
|
|
19
|
+
> **ArkGate 4.0.1** is on npm `latest` (stale global CLI upgrade guard + ArkRules HTML/docs honesty).
|
|
20
|
+
> **Next prepared:** [4.1.0](docs/releases/4.1.0.md) (field product train + CI PR slim — not published until registry).
|
|
21
|
+
> [4.0.1 notes](docs/releases/4.0.1.md) · [Docs hub](docs/README.md) · [Product voice](docs/product-voice.md)
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
@@ -186,8 +186,10 @@ for real systems. Details: [docs/production-hardening.md](docs/production-harden
|
|
|
186
186
|
| Config · package surface · TS | [configuration](docs/configuration.md) · [package-surface](docs/package-surface.md) · [typescript-support](docs/typescript-support.md) |
|
|
187
187
|
| Brownfield | [docs/brownfield-adoption.md](docs/brownfield-adoption.md) |
|
|
188
188
|
| Security | [SECURITY.md](SECURITY.md) |
|
|
189
|
-
|
|
|
190
|
-
|
|
|
189
|
+
| Current published (4.0.1) | [docs/releases/4.0.1.md](docs/releases/4.0.1.md) · [CHANGELOG](CHANGELOG.md) |
|
|
190
|
+
| Next prepared (4.1.0) | [docs/releases/4.1.0.md](docs/releases/4.1.0.md) — not on npm until publish |
|
|
191
|
+
| Previous (4.0.0) | [docs/releases/4.0.0.md](docs/releases/4.0.0.md) |
|
|
192
|
+
| Previous (3.9.2) | [docs/releases/3.9.2.md](docs/releases/3.9.2.md) |
|
|
191
193
|
| History / maintainer evidence | [docs/archive/](docs/archive/README.md) |
|
|
192
194
|
|
|
193
195
|
---
|
|
@@ -86,6 +86,11 @@ import {
|
|
|
86
86
|
} from './lib/suggestions.mjs';
|
|
87
87
|
import {
|
|
88
88
|
ARCHITECTURE_PRESETS,
|
|
89
|
+
APPLICATION_LIB_ORCHESTRATION_PATTERNS,
|
|
90
|
+
DOMAIN_PATH_PATTERNS,
|
|
91
|
+
NEXT_API_APPLICATION_PATTERNS,
|
|
92
|
+
PERSISTENCE_PATH_PATTERNS,
|
|
93
|
+
retrofitP0aApiApplicationPatterns,
|
|
89
94
|
withDefaultArkRules,
|
|
90
95
|
writeArkRulesTemplates,
|
|
91
96
|
} from './lib/presets.mjs';
|
|
@@ -100,6 +105,12 @@ import { validateHardWriteRequest } from './lib/enforcement-profiles.mjs';
|
|
|
100
105
|
import { analyzePolicyTransition } from './lib/policy-delta-io.mjs';
|
|
101
106
|
import { tryResidentDoctor } from './lib/resident-doctor-client.mjs';
|
|
102
107
|
import { createDesignDeltaCheck } from './lib/design-delta.mjs';
|
|
108
|
+
import {
|
|
109
|
+
isMutatingCliCommand,
|
|
110
|
+
resolveConfigPathWithinRoot,
|
|
111
|
+
resolveEffectiveProjectRoot,
|
|
112
|
+
} from './lib/project-root.mjs';
|
|
113
|
+
import { demoteArkRuleTeethUnderClassificationFloor } from './lib/rules-under-contract.mjs';
|
|
103
114
|
|
|
104
115
|
function parseArgs(argv) {
|
|
105
116
|
const args = {
|
|
@@ -140,6 +151,7 @@ function parseArgs(argv) {
|
|
|
140
151
|
noOpenReport: false,
|
|
141
152
|
version: false,
|
|
142
153
|
help: false,
|
|
154
|
+
followConfigRoot: false,
|
|
143
155
|
};
|
|
144
156
|
const requireValue = (flag, index) => {
|
|
145
157
|
const value = argv[index + 1];
|
|
@@ -180,6 +192,7 @@ function parseArgs(argv) {
|
|
|
180
192
|
}
|
|
181
193
|
}
|
|
182
194
|
else if (arg === '--force') args.force = true;
|
|
195
|
+
else if (arg === '--follow-config-root') args.followConfigRoot = true;
|
|
183
196
|
else if (arg === '--skills-only') args.skillsOnly = true;
|
|
184
197
|
else if (arg === '--coverage') args.coverage = true;
|
|
185
198
|
else if (arg === '--doctor') args.doctor = true;
|
|
@@ -191,6 +204,7 @@ function parseArgs(argv) {
|
|
|
191
204
|
else if (arg === '--apply-policy-pack') args.applyPolicyPack = requireValue(arg, i++);
|
|
192
205
|
else if (arg === '--suggest-include') args.suggestInclude = true;
|
|
193
206
|
else if (arg === '--adopt-contract') args.adoptContract = true;
|
|
207
|
+
else if (arg === '--migrate-contract') args.migrateContract = true;
|
|
194
208
|
else if (arg === '--ratchet-cores') args.ratchetCores = true;
|
|
195
209
|
else if (arg === '--write') args.write = true;
|
|
196
210
|
else if (arg === '--watch') args.watch = true;
|
|
@@ -249,13 +263,15 @@ function usage() {
|
|
|
249
263
|
' ark-check --list-policy-packs enthusiast packs (hexagonal, layered, feature-sliced, monorepo, ui-surface, vertical-slice, ddd-bounded-contexts)',
|
|
250
264
|
' ark-check --apply-policy-pack <id> [--force] write ark.config.json from templates/policy-packs/ (uses preset factory)',
|
|
251
265
|
' ark-check --suggest-include [--json] propose include roots (TS packages / workspaces)',
|
|
252
|
-
' ark-check --adopt-contract [--write] expand include +
|
|
266
|
+
' ark-check --adopt-contract [--write] expand include + layer patterns from ungoverned dirs (never bare lib→Presentation)',
|
|
267
|
+
' ark-check --migrate-contract [--write] additive P0-A retrofit: inject app/api/** → Application when missing',
|
|
253
268
|
' ark-check --ratchet-cores when raw graph is green (0 violations; baseline ignored), set optional:false on populated cores only (writes ark.config.json)',
|
|
254
269
|
' ark-check --watch re-run the check when governed files change (debounced)',
|
|
255
270
|
' ark-check --report [file.html] [--beginner] [--reset-origin] [--no-archive] [--open|--no-open]',
|
|
256
271
|
' HTML report + snapshots under .ark/reports/ (origin once, latest each run, history JSON)',
|
|
257
272
|
' Best-effort open in browser (local TTY). No-op if open fails. --no-open / ARK_NO_OPEN_REPORT=1 to skip; --open forces open.',
|
|
258
|
-
' ark-check --init [--preset hexagonal|layered|feature-sliced|monorepo|ui-surface|vertical-slice|ddd-bounded-contexts|clean-architecture|onion-architecture] [--force]',
|
|
273
|
+
' ark-check --init [--preset hexagonal|layered|feature-sliced|monorepo|ui-surface|vertical-slice|ddd-bounded-contexts|vite-vercel-spa|clean-architecture|onion-architecture] [--force] [--follow-config-root]',
|
|
274
|
+
' --follow-config-root On writes (init/install-agent-gates/migrate --write/…), adopt walked-up monorepo config root (default: keep explicit --root)',
|
|
259
275
|
' ark-check --install-agent-gates [--tools claude,cursor,codex,grok] [--require-write-hook <host>] [--skills-only] [--codex-home] [--force]',
|
|
260
276
|
' ark-check --update-baseline [file] freeze current violations (default .ark-baseline.json)',
|
|
261
277
|
' ark-check --print-config eleven-layer',
|
|
@@ -506,9 +522,14 @@ function runListPolicyPacks(args) {
|
|
|
506
522
|
}
|
|
507
523
|
|
|
508
524
|
function runApplyPolicyPack(args) {
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
525
|
+
// Contain --config writes under project root (S0 security).
|
|
526
|
+
const contained = resolveConfigPathWithinRoot(args.root, args.config);
|
|
527
|
+
if (!contained.ok) {
|
|
528
|
+
console.error(contained.error);
|
|
529
|
+
process.exitCode = 2;
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
const configPath = contained.configPath;
|
|
512
533
|
|
|
513
534
|
if (fs.existsSync(configPath) && !args.force) {
|
|
514
535
|
console.error(
|
|
@@ -620,14 +641,21 @@ function runSuggestInclude(args) {
|
|
|
620
641
|
}
|
|
621
642
|
|
|
622
643
|
/**
|
|
623
|
-
* Contract-adopt: expand include +
|
|
644
|
+
* Contract-adopt: expand include + layer patterns from ungoverned proposals.
|
|
624
645
|
* Read-only unless --write. Does not weaken rules or baseline violations.
|
|
646
|
+
* Never maps bare lib/** solely to Presentation (NEW-ADOPT-LIB-AS-PRESENTATION).
|
|
625
647
|
*/
|
|
626
648
|
function runAdoptContract(args) {
|
|
627
649
|
const root = args.root;
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
650
|
+
// Contain --config writes under project root (S0 security). Refuse escape even
|
|
651
|
+
// before --write so dry-run + write share one path and cannot probe outside root.
|
|
652
|
+
const contained = resolveConfigPathWithinRoot(root, args.config);
|
|
653
|
+
if (!contained.ok) {
|
|
654
|
+
console.error(contained.error);
|
|
655
|
+
process.exitCode = 2;
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
const configPath = contained.configPath;
|
|
631
659
|
let config;
|
|
632
660
|
try {
|
|
633
661
|
config = fs.existsSync(configPath)
|
|
@@ -644,25 +672,66 @@ function runAdoptContract(args) {
|
|
|
644
672
|
}
|
|
645
673
|
const suggestedInclude = resolveIncludeRoots(root);
|
|
646
674
|
const tsPackages = detectTsPackageRoots(root);
|
|
675
|
+
// SPA / Vercel: include root api + lib when present (NEW-SPA-DEFAULT-LAYOUT).
|
|
676
|
+
const spaExtras = ['api', 'lib'].filter((d) => fs.existsSync(path.join(root, d)));
|
|
647
677
|
const nextInclude = [
|
|
648
678
|
...new Set([
|
|
649
679
|
...(config.include || []),
|
|
650
680
|
...(suggestedInclude.length > 0 ? suggestedInclude : tsPackages),
|
|
681
|
+
...spaExtras,
|
|
651
682
|
]),
|
|
652
683
|
].filter(Boolean);
|
|
653
684
|
const files = collectGovernedFiles(root, { ...config, include: nextInclude.length ? nextInclude : config.include });
|
|
654
685
|
const cov = computeCoverage(root, { ...config, include: nextInclude.length ? nextInclude : config.include }, files, config.rules || []);
|
|
686
|
+
// UI-only patterns — never bare **/lib/** (data clients are Persistence).
|
|
655
687
|
const uiPatterns = [
|
|
656
688
|
'**/components/**',
|
|
657
689
|
'**/hooks/**',
|
|
658
|
-
'**/lib/**',
|
|
659
690
|
'**/routes/**',
|
|
660
691
|
'**/app/**',
|
|
661
692
|
'**/pages/**',
|
|
662
693
|
];
|
|
694
|
+
const persistencePatterns = [
|
|
695
|
+
...PERSISTENCE_PATH_PATTERNS,
|
|
696
|
+
];
|
|
697
|
+
const applicationPatterns = [
|
|
698
|
+
...NEXT_API_APPLICATION_PATTERNS,
|
|
699
|
+
...APPLICATION_LIB_ORCHESTRATION_PATTERNS,
|
|
700
|
+
'api/**',
|
|
701
|
+
];
|
|
702
|
+
const domainPatterns = [...DOMAIN_PATH_PATTERNS];
|
|
703
|
+
|
|
704
|
+
// Build pattern additions from unclassified suggestions (path-aware).
|
|
705
|
+
const byLayer = new Map([
|
|
706
|
+
['PresentationAdapters', [...uiPatterns]],
|
|
707
|
+
['PersistenceAdapters', [...persistencePatterns]],
|
|
708
|
+
['ApplicationOrchestration', [...applicationPatterns]],
|
|
709
|
+
['DomainModel', [...domainPatterns]],
|
|
710
|
+
]);
|
|
711
|
+
for (const suggestion of cov.suggestions ?? []) {
|
|
712
|
+
if (suggestion.unrecognized || !suggestion.layer) continue;
|
|
713
|
+
// Never treat bare lib as Presentation solely.
|
|
714
|
+
if (
|
|
715
|
+
suggestion.layer === 'PresentationAdapters' &&
|
|
716
|
+
(suggestion.dir === 'lib' || suggestion.dir.endsWith('/lib'))
|
|
717
|
+
) {
|
|
718
|
+
continue;
|
|
719
|
+
}
|
|
720
|
+
const list = byLayer.get(suggestion.layer) ?? [];
|
|
721
|
+
const glob = suggestion.dir === '.' ? null : `${suggestion.dir}/**`;
|
|
722
|
+
if (glob && !list.includes(glob)) list.push(glob);
|
|
723
|
+
byLayer.set(suggestion.layer, list);
|
|
724
|
+
}
|
|
725
|
+
|
|
663
726
|
const layers = (config.layers || []).map((layer) => {
|
|
664
|
-
|
|
665
|
-
|
|
727
|
+
const extras = byLayer.get(layer.name);
|
|
728
|
+
if (!extras?.length) return layer;
|
|
729
|
+
// Strip accidental bare lib/** if a previous adopt wrote it into Presentation.
|
|
730
|
+
const cleaned = (layer.patterns || []).filter((p) => {
|
|
731
|
+
if (layer.name !== 'PresentationAdapters') return true;
|
|
732
|
+
return p !== '**/lib/**' && p !== 'lib/**' && p !== 'src/lib/**';
|
|
733
|
+
});
|
|
734
|
+
const patterns = [...new Set([...cleaned, ...extras])];
|
|
666
735
|
return { ...layer, patterns };
|
|
667
736
|
});
|
|
668
737
|
// If no PresentationAdapters layer, leave layers as-is (don't invent full profile).
|
|
@@ -676,6 +745,9 @@ function runAdoptContract(args) {
|
|
|
676
745
|
after: {
|
|
677
746
|
include: nextInclude.length > 0 ? nextInclude : config.include,
|
|
678
747
|
presentationPatterns: uiPatterns,
|
|
748
|
+
persistencePatterns,
|
|
749
|
+
applicationPatterns,
|
|
750
|
+
domainPatterns,
|
|
679
751
|
totalFiles: cov.totalFiles,
|
|
680
752
|
governedPercent: cov.governed.percent,
|
|
681
753
|
unclassified: cov.unclassified.count,
|
|
@@ -714,6 +786,8 @@ function runAdoptContract(args) {
|
|
|
714
786
|
` after: include=[${(proposal.after.include || []).join(', ')}] governed=${proposal.after.governedPercent}% files=${proposal.after.totalFiles} unclassified=${proposal.after.unclassified}`
|
|
715
787
|
);
|
|
716
788
|
console.log(` presentation patterns += ${uiPatterns.join(', ')}`);
|
|
789
|
+
console.log(` persistence patterns += (data clients / db / auth — never bare lib→Presentation)`);
|
|
790
|
+
console.log(` application patterns += Next/Vercel API shells`);
|
|
717
791
|
if (proposal.wrote) {
|
|
718
792
|
console.log(color.green(` wrote ${path.relative(root, configPath) || args.config}`));
|
|
719
793
|
console.log(color.dim(` Next: ${arkCommand(root, 'ark-check', '--coverage')} then --plan`));
|
|
@@ -730,10 +804,77 @@ function runAdoptContract(args) {
|
|
|
730
804
|
}
|
|
731
805
|
}
|
|
732
806
|
|
|
807
|
+
/**
|
|
808
|
+
* Additive P0-A contract retrofit: inject high-spec app/api → Application when missing.
|
|
809
|
+
* Does not remove existing patterns or weaken rules (DL-P0A-RETROFIT).
|
|
810
|
+
*/
|
|
811
|
+
function runMigrateContract(args) {
|
|
812
|
+
const root = args.root;
|
|
813
|
+
// Contain --config writes under project root (S0 security).
|
|
814
|
+
const contained = resolveConfigPathWithinRoot(root, args.config);
|
|
815
|
+
if (!contained.ok) {
|
|
816
|
+
console.error(contained.error);
|
|
817
|
+
process.exitCode = 2;
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
const configPath = contained.configPath;
|
|
821
|
+
if (!fs.existsSync(configPath)) {
|
|
822
|
+
console.error(`No ${args.config} — nothing to migrate. Run ark start / ark-check --init first.`);
|
|
823
|
+
process.exitCode = 2;
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
let config;
|
|
827
|
+
try {
|
|
828
|
+
config = readConfig(root, args.config);
|
|
829
|
+
} catch (error) {
|
|
830
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
831
|
+
process.exitCode = 2;
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
const result = retrofitP0aApiApplicationPatterns(config);
|
|
836
|
+
const proposal = {
|
|
837
|
+
ok: true,
|
|
838
|
+
changed: result.changed,
|
|
839
|
+
injected: result.injected,
|
|
840
|
+
targetLayer: result.targetLayer,
|
|
841
|
+
wrote: false,
|
|
842
|
+
};
|
|
843
|
+
|
|
844
|
+
if (args.write && result.changed) {
|
|
845
|
+
fs.writeFileSync(configPath, `${JSON.stringify(result.config, null, 2)}\n`);
|
|
846
|
+
proposal.wrote = true;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
if (args.json) {
|
|
850
|
+
console.log(JSON.stringify(proposal, null, 2));
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
console.log(color.bold('Contract migrate (P0-A API → Application, additive)'));
|
|
854
|
+
if (!result.changed) {
|
|
855
|
+
console.log(color.dim(' No changes — Application already has high-spec app/api patterns (or no Application layer).'));
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
console.log(` target layer: ${result.targetLayer}`);
|
|
859
|
+
console.log(` injecting: ${result.injected.join(', ')}`);
|
|
860
|
+
if (proposal.wrote) {
|
|
861
|
+
console.log(color.green(` wrote ${path.relative(root, configPath) || args.config}`));
|
|
862
|
+
console.log(color.dim(` Next: ${arkCommand(root, 'ark-check', '--coverage')} — API routes should classify as Application`));
|
|
863
|
+
} else {
|
|
864
|
+
console.log(color.dim(' Dry-run only. Re-run with --write to apply.'));
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
|
|
869
|
+
|
|
733
870
|
function runInit(args) {
|
|
734
|
-
const
|
|
735
|
-
|
|
736
|
-
|
|
871
|
+
const contained = resolveConfigPathWithinRoot(args.root, args.config);
|
|
872
|
+
if (!contained.ok) {
|
|
873
|
+
console.error(contained.error);
|
|
874
|
+
process.exitCode = 2;
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
const configPath = contained.configPath;
|
|
737
878
|
|
|
738
879
|
if (fs.existsSync(configPath) && !args.force) {
|
|
739
880
|
console.error(`${configPath} already exists. Re-run with --force to overwrite it.`);
|
|
@@ -922,8 +1063,45 @@ const color = {
|
|
|
922
1063
|
bold: (s) => (useColor ? `\x1b[1m${s}\x1b[0m` : s),
|
|
923
1064
|
};
|
|
924
1065
|
|
|
1066
|
+
/**
|
|
1067
|
+
* Monorepo honesty: when cwd/--root has no ark.config.json, walk parents (bounded).
|
|
1068
|
+
* Stamps configRoot / configWalkedUp. Mutates args.root only for read paths, or
|
|
1069
|
+
* when --follow-config-root is set on writes (never rewrite parent monorepo by default).
|
|
1070
|
+
* Skip for pure meta commands that never load a project contract.
|
|
1071
|
+
*/
|
|
1072
|
+
function applyConfigRootWalkUp(args) {
|
|
1073
|
+
if (
|
|
1074
|
+
args.version ||
|
|
1075
|
+
args.help ||
|
|
1076
|
+
args.printConfig ||
|
|
1077
|
+
args.listPolicyPacks
|
|
1078
|
+
) {
|
|
1079
|
+
return args;
|
|
1080
|
+
}
|
|
1081
|
+
const writeMode = isMutatingCliCommand(args);
|
|
1082
|
+
const effective = resolveEffectiveProjectRoot(args.root, {
|
|
1083
|
+
configName: args.config,
|
|
1084
|
+
writeMode,
|
|
1085
|
+
followConfigRoot: args.followConfigRoot === true,
|
|
1086
|
+
});
|
|
1087
|
+
args.configRoot = effective.configRoot;
|
|
1088
|
+
args.configFound = effective.configFound;
|
|
1089
|
+
args.writeRoot = effective.writeRoot;
|
|
1090
|
+
if (effective.walkedUp && effective.root !== path.resolve(args.root)) {
|
|
1091
|
+
// Read paths (or write + --follow-config-root): adopt discovered config root.
|
|
1092
|
+
args.root = effective.root;
|
|
1093
|
+
args.configWalkedUp = true;
|
|
1094
|
+
} else if (effective.walkedUp) {
|
|
1095
|
+
// Write path without opt-in: keep explicit --root/cwd; surface discovery only.
|
|
1096
|
+
args.configWalkedUp = true;
|
|
1097
|
+
} else {
|
|
1098
|
+
args.configWalkedUp = false;
|
|
1099
|
+
}
|
|
1100
|
+
return args;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
925
1103
|
async function main() {
|
|
926
|
-
const args = parseArgs(process.argv);
|
|
1104
|
+
const args = applyConfigRootWalkUp(parseArgs(process.argv));
|
|
927
1105
|
if (await tryResidentDoctor(args)) return;
|
|
928
1106
|
if (args.version) {
|
|
929
1107
|
console.log(arkPackageVersion());
|
|
@@ -971,6 +1149,11 @@ async function main() {
|
|
|
971
1149
|
return;
|
|
972
1150
|
}
|
|
973
1151
|
|
|
1152
|
+
if (args.migrateContract) {
|
|
1153
|
+
runMigrateContract(args);
|
|
1154
|
+
return;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
974
1157
|
if (args.recommend) {
|
|
975
1158
|
try {
|
|
976
1159
|
const recommendation = buildArchitectureRecommendation(args.root);
|
|
@@ -1132,7 +1315,7 @@ async function main() {
|
|
|
1132
1315
|
}
|
|
1133
1316
|
|
|
1134
1317
|
const {
|
|
1135
|
-
violations, warnings, safety, parseHealth, completeness, completenessReasons,
|
|
1318
|
+
violations: rawViolations, warnings, safety, parseHealth, completeness, completenessReasons,
|
|
1136
1319
|
mode, policyHash, resolverIdentity, factsHash, candidateTreeHash,
|
|
1137
1320
|
} = runArchitectureScan({
|
|
1138
1321
|
root,
|
|
@@ -1144,6 +1327,16 @@ async function main() {
|
|
|
1144
1327
|
args,
|
|
1145
1328
|
});
|
|
1146
1329
|
|
|
1330
|
+
// Align merge with extraMergeTeeth stamp: demote enforced ArkRules under classification floor.
|
|
1331
|
+
const preCov = computeCoverage(root, config, files, rules);
|
|
1332
|
+
const populatedLayerCount = Array.isArray(preCov.layers)
|
|
1333
|
+
? preCov.layers.filter((row) => (row?.files ?? 0) > 0).length
|
|
1334
|
+
: 0;
|
|
1335
|
+
const violations = demoteArkRuleTeethUnderClassificationFloor(rawViolations, {
|
|
1336
|
+
governedPercent: preCov.governed?.percent ?? null,
|
|
1337
|
+
populatedLayerCount,
|
|
1338
|
+
});
|
|
1339
|
+
|
|
1147
1340
|
const designCheck = createDesignDeltaCheck({ enabled: args.failOnNewSmells, root, config, configPath: args.config, baseRef: args.baseRef, ts });
|
|
1148
1341
|
const designDelta = designCheck.result;
|
|
1149
1342
|
|
|
@@ -1151,6 +1344,8 @@ async function main() {
|
|
|
1151
1344
|
runDoctor(root, config, files, rules, violations, args.json, {
|
|
1152
1345
|
configPath: path.isAbsolute(args.config) ? args.config : path.join(root, args.config),
|
|
1153
1346
|
configMissing: !fs.existsSync(path.isAbsolute(args.config) ? args.config : path.join(root, args.config)),
|
|
1347
|
+
configRoot: args.configRoot ?? root,
|
|
1348
|
+
configWalkedUp: args.configWalkedUp === true,
|
|
1154
1349
|
safety, designDelta,
|
|
1155
1350
|
ts, parseHealth, completeness,
|
|
1156
1351
|
});
|
|
@@ -1241,19 +1436,29 @@ async function main() {
|
|
|
1241
1436
|
}
|
|
1242
1437
|
|
|
1243
1438
|
// Soft/advisory warnings (failsStrict === false) never fail --strict-config.
|
|
1439
|
+
// P1-type: type-only placement debt on the violations list also has failsStrict:false —
|
|
1440
|
+
// keep reporting them (doctor typeOnly counts) but do not block merge/exit.
|
|
1244
1441
|
const strictWarnings = warnings.filter((w) => w.failsStrict !== false);
|
|
1245
|
-
const
|
|
1442
|
+
const blockingViolations = activeViolations.filter((v) => v.failsStrict !== false);
|
|
1443
|
+
const { edgeValid: edgeOk, observedOk } = designCheck.combineEdges({
|
|
1444
|
+
activeViolationCount: blockingViolations.length,
|
|
1445
|
+
strictConfig: args.strictConfig,
|
|
1446
|
+
strictWarningCount: strictWarnings.length,
|
|
1447
|
+
policyValid: policyDelta?.valid ?? true,
|
|
1448
|
+
});
|
|
1246
1449
|
const analysisComplete = completeness === ANALYSIS_COMPLETENESS.complete;
|
|
1247
1450
|
const ok = observedOk && analysisComplete;
|
|
1248
1451
|
|
|
1249
1452
|
if (args.plan) {
|
|
1250
1453
|
const cov = computeCoverage(root, config, files, rules);
|
|
1454
|
+
// Plan goal.met uses blocking (value) edges; type-only placement debt stays in steps via full list.
|
|
1251
1455
|
const plan = runPlan(root, activeViolations, args.json, cov.governed.percent, cov.governed.totalFiles, {
|
|
1252
1456
|
config,
|
|
1253
1457
|
files,
|
|
1254
1458
|
coverage: cov,
|
|
1255
1459
|
completeness,
|
|
1256
1460
|
completenessReasons,
|
|
1461
|
+
blockingViolationCount: blockingViolations.length,
|
|
1257
1462
|
});
|
|
1258
1463
|
if (args.strictMerge) process.exitCode = ok && plan.goal.met ? 0 : 1;
|
|
1259
1464
|
return;
|
|
@@ -1358,12 +1563,21 @@ async function main() {
|
|
|
1358
1563
|
const existingOrigin = args.resetOrigin
|
|
1359
1564
|
? null
|
|
1360
1565
|
: readJsonSafe(path.join(reportsDir(root), 'origin.json'));
|
|
1566
|
+
// Pass the same baseline split as doctor so productHonesty dirty-freeze matches.
|
|
1567
|
+
const reportBaseline = readBaseline(root, args.baseline || '.ark-baseline.json');
|
|
1361
1568
|
const { adoption: adoptionForReport, designDepth } = buildReportDepthPayload(
|
|
1362
1569
|
root,
|
|
1363
1570
|
config,
|
|
1364
1571
|
files,
|
|
1365
1572
|
coverage,
|
|
1366
|
-
activeViolations
|
|
1573
|
+
activeViolations,
|
|
1574
|
+
{
|
|
1575
|
+
suppressedCount: suppressed.length,
|
|
1576
|
+
totalViolationCount: violations.length,
|
|
1577
|
+
frozenKeys: reportBaseline.exists ? reportBaseline.keys.size : 0,
|
|
1578
|
+
activeCount: activeViolations.length,
|
|
1579
|
+
activeBlockingCount: blockingViolations.length,
|
|
1580
|
+
}
|
|
1367
1581
|
);
|
|
1368
1582
|
const reportPayload = {
|
|
1369
1583
|
root,
|
|
@@ -1516,9 +1730,14 @@ async function main() {
|
|
|
1516
1730
|
if (designCheck.failureText()) console.error(designCheck.failureText());
|
|
1517
1731
|
if (!analysisComplete) {
|
|
1518
1732
|
console.error(color.yellow(analysisIncompleteStatement(completeness)));
|
|
1519
|
-
} else if (
|
|
1733
|
+
} else if (blockingViolations.length === 0 && (policyDelta?.valid ?? true)) {
|
|
1734
|
+
const placementDebt = activeViolations.filter((v) => v.failsStrict === false);
|
|
1520
1735
|
const advisoryOnly = warnings.length > 0 && strictWarnings.length === 0;
|
|
1521
|
-
if (
|
|
1736
|
+
if (placementDebt.length > 0) {
|
|
1737
|
+
console.log(
|
|
1738
|
+
`${color.green('✔')} Ark check passed with ${placementDebt.length} type-only placement debt (non-blocking; prefer SharedTypes / owning layer).${baselineNote}`
|
|
1739
|
+
);
|
|
1740
|
+
} else if (warnings.length === 0) {
|
|
1522
1741
|
console.log(`${color.green('✔')} Ark check passed.${baselineNote}`);
|
|
1523
1742
|
} else if (args.strictConfig && strictWarnings.length > 0) {
|
|
1524
1743
|
console.error(
|
|
@@ -1535,16 +1754,16 @@ async function main() {
|
|
|
1535
1754
|
}
|
|
1536
1755
|
} else {
|
|
1537
1756
|
console.error(
|
|
1538
|
-
|
|
1539
|
-
? `${color.red('✖')} ${
|
|
1757
|
+
blockingViolations.length > 0
|
|
1758
|
+
? `${color.red('✖')} ${blockingViolations.length} violation(s).${baselineNote}`
|
|
1540
1759
|
: `${color.red('✖')} Policy transition rejected.${baselineNote}`
|
|
1541
1760
|
);
|
|
1542
1761
|
}
|
|
1543
1762
|
|
|
1544
1763
|
// On a large violation set, print the ranked edge breakdown so the wall of failures reads
|
|
1545
1764
|
// as an ordered burn-down (and flags a concentrated edge as a likely contract bug).
|
|
1546
|
-
if (
|
|
1547
|
-
printViolationBreakdown(summarizeViolations(
|
|
1765
|
+
if (blockingViolations.length >= CONCENTRATION_MIN_VIOLATIONS) {
|
|
1766
|
+
printViolationBreakdown(summarizeViolations(blockingViolations), { toStderr: true });
|
|
1548
1767
|
}
|
|
1549
1768
|
|
|
1550
1769
|
printSkillAndCodexGapHints(root, {
|
package/bin/ark-check.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
4
|
import { tryResidentDoctor } from './lib/resident-doctor-client.mjs';
|
|
5
|
+
import { resolveEffectiveProjectRoot } from './lib/project-root.mjs';
|
|
5
6
|
|
|
6
7
|
const VALUE_FLAGS = new Map([
|
|
7
8
|
['--root', 'root'],
|
|
@@ -36,7 +37,15 @@ function residentArgs(argv) {
|
|
|
36
37
|
else if (flag === '--doctor') args.doctor = true;
|
|
37
38
|
else if (flag === '--json') args.json = true;
|
|
38
39
|
}
|
|
39
|
-
|
|
40
|
+
if (!(args.resident && args.doctor && args.json)) return null;
|
|
41
|
+
// Same monorepo walk-up as full runtime (NEW-MONOREPO-CWD-WALKUP).
|
|
42
|
+
const effective = resolveEffectiveProjectRoot(args.root, { configName: args.config });
|
|
43
|
+
args.configRoot = effective.configRoot;
|
|
44
|
+
if (effective.walkedUp) {
|
|
45
|
+
args.root = effective.root;
|
|
46
|
+
args.configWalkedUp = true;
|
|
47
|
+
}
|
|
48
|
+
return args;
|
|
40
49
|
}
|
|
41
50
|
|
|
42
51
|
async function main() {
|