arkgate 4.6.6 → 4.6.7
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 +30 -2
- package/README.md +5 -4
- package/bin/ark-check-runtime.mjs +8 -340
- package/bin/ark-mcp-runtime.mjs +7 -323
- package/bin/ark-shared.mjs +24 -158
- 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/deep-module-coach.mjs +3 -0
- package/bin/lib/doctor-human.mjs +509 -0
- package/bin/lib/doctor-plan.mjs +42 -429
- package/bin/lib/github-enforcement.mjs +22 -9
- package/bin/lib/html-report.mjs +24 -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/team-parliament-io.mjs +4 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/docs/README.md +2 -2
- package/docs/package-surface.md +2 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/templates/agent-skills/README.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,12 +5,40 @@ in the immutable pre-2.0 archive linked below.
|
|
|
5
5
|
|
|
6
6
|
## Unreleased
|
|
7
7
|
|
|
8
|
+
## 4.6.7 — 2026-08-24
|
|
9
|
+
|
|
10
|
+
**Patch** over **4.6.6**. Production-hardening: CODEOWNERS, eval/pack honesty, CLI extracts,
|
|
11
|
+
spawn timeouts, and HTML list cap. **No required config migration.** Does not close Z09.
|
|
12
|
+
|
|
13
|
+
**Status: published** (on npm `latest`; see `docs/releases/4.6.7.md`).
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- **CODEOWNERS:** `/src/`, `/bin/`, and `/schemas/` owned by `@pedroknigge`. GitHub still
|
|
18
|
+
needs `require_code_owner_reviews` (or an approving-review count) for that file to
|
|
19
|
+
enforce; the in-tree list is the product control.
|
|
20
|
+
|
|
21
|
+
- **Eval comparative fixture:** `saas-dashboard/without-ark` is a real Presentation→Domain
|
|
22
|
+
**value** import. Type-only was non-blocking and made the nightly oracle go false-green.
|
|
23
|
+
|
|
24
|
+
- **npm pack JSON:** `scripts/npm-pack-report.mjs` strips ANSI and parses JSON lines that
|
|
25
|
+
actually have `filename`. Empty `[]` / `{}` stay empty; missing filename still throws.
|
|
26
|
+
|
|
27
|
+
- **CLI extracts:** hook payload, package-manager helpers, and check args/config/watch live
|
|
28
|
+
in `bin/lib/`. Module-budget maxima were not raised.
|
|
29
|
+
|
|
30
|
+
- **git/gh timeouts:** `SPAWN_TIMEOUT_MS = 8000` on git/gh `spawnSync`. Timeout is
|
|
31
|
+
fail-closed (`status !== 0`).
|
|
32
|
+
|
|
33
|
+
- **HTML violation cap:** beginner and full showcase lists share a cap of 12 plus
|
|
34
|
+
`+N more (T total)`. KPI tiles still use the full array.
|
|
35
|
+
|
|
8
36
|
## 4.6.6 — 2026-08-22
|
|
9
37
|
|
|
10
38
|
**Patch** over **4.6.5**. Phase AL corrective honesty plus a slimmer public docs surface.
|
|
11
39
|
**No required config migration.** Does not close Z09. AL05 stays parked.
|
|
12
40
|
|
|
13
|
-
**Status:
|
|
41
|
+
**Status: published** (see `docs/releases/4.6.6.md`).
|
|
14
42
|
|
|
15
43
|
### Changed
|
|
16
44
|
|
|
@@ -49,7 +77,7 @@ in the immutable pre-2.0 archive linked below.
|
|
|
49
77
|
**Patch** over **4.6.4**. Adoption, placement, doctor, upgrade, and write-path honesty for
|
|
50
78
|
existing Next.js trees and multi-host teams. **No required config migration.**
|
|
51
79
|
|
|
52
|
-
**Status: published** (
|
|
80
|
+
**Status: published** (see `docs/releases/4.6.5.md`).
|
|
53
81
|
|
|
54
82
|
### Changed
|
|
55
83
|
|
package/README.md
CHANGED
|
@@ -16,10 +16,10 @@ and makes sure a “green” check means something real.
|
|
|
16
16
|
|
|
17
17
|
</div>
|
|
18
18
|
|
|
19
|
-
> **ArkGate 4.6.
|
|
19
|
+
> **ArkGate 4.6.7** is current on npm `latest`.
|
|
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.6.
|
|
22
|
+
> for Details). [4.6.7 notes](docs/releases/4.6.7.md) · [4.6.6](docs/releases/4.6.6.md) ·
|
|
23
23
|
> [Docs hub](docs/README.md) · [Product voice](docs/product-voice.md)
|
|
24
24
|
|
|
25
25
|
---
|
|
@@ -217,8 +217,9 @@ for real systems. Details: [docs/production-hardening.md](docs/production-harden
|
|
|
217
217
|
| Config · package surface · TS | [configuration](docs/configuration.md) · [package-surface](docs/package-surface.md) · [typescript-support](docs/typescript-support.md) |
|
|
218
218
|
| Brownfield | [docs/brownfield-adoption.md](docs/brownfield-adoption.md) |
|
|
219
219
|
| Security | [SECURITY.md](SECURITY.md) |
|
|
220
|
-
| Current
|
|
221
|
-
| Prior published (4.6.
|
|
220
|
+
| Current published (4.6.7 on npm `latest`) | [docs/releases/4.6.7.md](docs/releases/4.6.7.md) · [CHANGELOG](CHANGELOG.md) |
|
|
221
|
+
| Prior published (4.6.6) | [docs/releases/4.6.6.md](docs/releases/4.6.6.md) |
|
|
222
|
+
| Prior published (4.6.5) | [docs/releases/4.6.5.md](docs/releases/4.6.5.md) |
|
|
222
223
|
| Prior published (4.6.3) | [docs/releases/4.6.3.md](docs/releases/4.6.3.md) |
|
|
223
224
|
| Prior (4.6.2) | [docs/releases/4.6.2.md](docs/releases/4.6.2.md) |
|
|
224
225
|
| Prior (4.6.1) | [docs/releases/4.6.1.md](docs/releases/4.6.1.md) |
|
|
@@ -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:');
|
|
@@ -1964,87 +1702,17 @@ async function main() {
|
|
|
1964
1702
|
}
|
|
1965
1703
|
|
|
1966
1704
|
if (args.watch) {
|
|
1967
|
-
await runWatchMode(args
|
|
1705
|
+
await runWatchMode(args, {
|
|
1706
|
+
cliPath: __arkCheckCli,
|
|
1707
|
+
loadConfig: readConfig,
|
|
1708
|
+
dim: color.dim,
|
|
1709
|
+
});
|
|
1968
1710
|
return;
|
|
1969
1711
|
}
|
|
1970
1712
|
|
|
1971
1713
|
process.exitCode = designCheck.exitCode(observedOk && (!args.strictMerge || analysisComplete) ? 0 : 1);
|
|
1972
1714
|
}
|
|
1973
1715
|
|
|
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
1716
|
main().catch((error) => {
|
|
2049
1717
|
console.error(
|
|
2050
1718
|
process.env.ARK_DEBUG_STACK === '1' && error instanceof Error
|