arkgate 4.6.5 → 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 +72 -2106
- package/README.md +11 -9
- package/bin/ark-check-runtime.mjs +36 -332
- package/bin/ark-mcp-runtime.mjs +7 -323
- package/bin/ark-shared.mjs +24 -158
- package/bin/ark.mjs +13 -3
- package/bin/lib/adoption-stance.mjs +104 -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/ci-merge-boundary.mjs +4 -2
- package/bin/lib/deep-module-coach.mjs +3 -0
- package/bin/lib/design-delta.mjs +2 -2
- package/bin/lib/design-smells.mjs +1 -1
- package/bin/lib/diagnostic-catalog.mjs +1 -1
- package/bin/lib/doctor-advisories.mjs +2 -2
- package/bin/lib/doctor-human.mjs +509 -0
- package/bin/lib/doctor-next-actions.mjs +20 -2
- package/bin/lib/doctor-plan.mjs +86 -456
- package/bin/lib/enforcement-honesty.mjs +70 -0
- package/bin/lib/first-run-help.mjs +8 -7
- package/bin/lib/github-enforcement.mjs +22 -9
- package/bin/lib/html-report-advisories.mjs +10 -2
- package/bin/lib/html-report.mjs +26 -9
- package/bin/lib/mcp-adoption.mjs +19 -0
- package/bin/lib/mcp-hook-payload.mjs +328 -0
- package/bin/lib/package-manager.mjs +174 -0
- package/bin/lib/policy-delta-io.mjs +5 -1
- package/bin/lib/post-green-path.mjs +5 -1
- package/bin/lib/product-copy.mjs +6 -3
- package/bin/lib/start-preview.mjs +12 -22
- package/bin/lib/status-command.mjs +16 -0
- package/bin/lib/status-manifest.mjs +8 -2
- package/bin/lib/team-parliament-io.mjs +66 -2
- package/bin/lib/team-parliament.mjs +25 -5
- package/bin/lib/unavailable-analysis.mjs +1 -0
- package/dist/index.cjs +2 -2
- package/dist/index.d.ts +10 -2
- package/dist/index.js +2 -2
- package/docs/README.md +6 -10
- package/docs/ai-gates.md +12 -5
- package/docs/configuration.md +9 -1
- package/docs/diagnostics.md +2 -2
- package/docs/package-surface.md +6 -4
- package/docs/product-voice.md +6 -4
- package/docs/threat-model.md +2 -2
- package/docs/use.md +5 -4
- package/package.json +1 -1
- package/schemas/ark.design-delta.schema.json +1 -1
- package/server.json +2 -2
- package/templates/agent-skills/README.md +1 -1
package/README.md
CHANGED
|
@@ -16,9 +16,11 @@ and makes sure a “green” check means something real.
|
|
|
16
16
|
|
|
17
17
|
</div>
|
|
18
18
|
|
|
19
|
-
> **ArkGate 4.6.
|
|
20
|
-
>
|
|
21
|
-
>
|
|
19
|
+
> **ArkGate 4.6.7** is current on npm `latest`.
|
|
20
|
+
> A tree is **adopted** only with a required GitHub status running `arkgate-check --strict-merge`,
|
|
21
|
+
> or `.ark/adoption-stance.json` `stance: "advisory-only"`. Doctor is compact (`--doctor --all`
|
|
22
|
+
> for Details). [4.6.7 notes](docs/releases/4.6.7.md) · [4.6.6](docs/releases/4.6.6.md) ·
|
|
23
|
+
> [Docs hub](docs/README.md) · [Product voice](docs/product-voice.md)
|
|
22
24
|
|
|
23
25
|
---
|
|
24
26
|
|
|
@@ -41,12 +43,11 @@ npm install -D arkgate typescript
|
|
|
41
43
|
npx arkgate start # preview files + commands
|
|
42
44
|
npx arkgate start --apply # compact contract + host router + CI plan
|
|
43
45
|
npx arkgate-check --doctor # control plane: status light + primary next action
|
|
46
|
+
npx arkgate-check --doctor --all # encyclopedia (Details)
|
|
44
47
|
```
|
|
45
48
|
|
|
46
49
|
That is the product. Doctor is the control plane — when stuck, do **primary next action #1**.
|
|
47
|
-
|
|
48
|
-
dependency inversion — **not a score**). Leftover lenses mean design work may remain even when
|
|
49
|
-
imports are green. Details: [use.md — Improvement compass](docs/use.md#improvement-compass-not-a-score).
|
|
50
|
+
JSON still carries improvement compass and coach (**not a score**). Compact human output does not.
|
|
50
51
|
|
|
51
52
|
```text
|
|
52
53
|
start → doctor (+ compass) → /ark-adopt (session 0) → day-to-day /ark-place
|
|
@@ -102,7 +103,7 @@ check: [4.3.0 — What ArkGate is / isn't](docs/releases/4.3.0.md#what-arkgate-i
|
|
|
102
103
|
|-------|--------|-----------|
|
|
103
104
|
| **Suggest** | Thin / new tree | Finish `start` → doctor |
|
|
104
105
|
| **Adapt** | Not fully protected | Doctor action #1 |
|
|
105
|
-
| **Enforce** | Honest edges
|
|
106
|
+
| **Enforce** | Honest import edges, and no new UI business-rule files vs merge-base | Keep write path + CI |
|
|
106
107
|
| **Enforce · design-weak** | Edges clean; design residual remains | Shape residual — not “done” |
|
|
107
108
|
|
|
108
109
|
Details: [docs/use.md](docs/use.md).
|
|
@@ -216,8 +217,9 @@ for real systems. Details: [docs/production-hardening.md](docs/production-harden
|
|
|
216
217
|
| Config · package surface · TS | [configuration](docs/configuration.md) · [package-surface](docs/package-surface.md) · [typescript-support](docs/typescript-support.md) |
|
|
217
218
|
| Brownfield | [docs/brownfield-adoption.md](docs/brownfield-adoption.md) |
|
|
218
219
|
| Security | [SECURITY.md](SECURITY.md) |
|
|
219
|
-
| Current
|
|
220
|
-
| 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) |
|
|
221
223
|
| Prior published (4.6.3) | [docs/releases/4.6.3.md](docs/releases/4.6.3.md) |
|
|
222
224
|
| Prior (4.6.2) | [docs/releases/4.6.2.md](docs/releases/4.6.2.md) |
|
|
223
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';
|
|
@@ -105,10 +103,15 @@ import { runArchitectureScan } from './lib/architecture-scan.mjs';
|
|
|
105
103
|
import { ANALYSIS_COMPLETENESS, analysisIncompleteStatement } from './lib/analysis-completeness.mjs';
|
|
106
104
|
import { reportUnavailableAnalysis } from './lib/unavailable-analysis.mjs';
|
|
107
105
|
import { validateHardWriteRequest } from './lib/enforcement-profiles.mjs';
|
|
108
|
-
import {
|
|
106
|
+
import {
|
|
107
|
+
analyzePolicyTransition,
|
|
108
|
+
discoverLocalBaseRef,
|
|
109
|
+
normalizePolicyBaseRef,
|
|
110
|
+
} from './lib/policy-delta-io.mjs';
|
|
109
111
|
import {
|
|
110
112
|
applyAgainstRatchet,
|
|
111
113
|
bindTeamBaseRefs,
|
|
114
|
+
contractSessionFrom,
|
|
112
115
|
filterChangedGovernedFiles,
|
|
113
116
|
runTeamPreflight,
|
|
114
117
|
ungovernedDumpMessage,
|
|
@@ -121,164 +124,9 @@ import {
|
|
|
121
124
|
resolveEffectiveProjectRoot,
|
|
122
125
|
} from './lib/project-root.mjs';
|
|
123
126
|
import { demoteArkRuleTeethUnderClassificationFloor } from './lib/rules-under-contract.mjs';
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
root: process.cwd(),
|
|
128
|
-
config: 'ark.config.json',
|
|
129
|
-
manifest: undefined,
|
|
130
|
-
printConfig: undefined,
|
|
131
|
-
tsconfig: undefined,
|
|
132
|
-
json: false,
|
|
133
|
-
strictConfig: false,
|
|
134
|
-
strictMerge: false,
|
|
135
|
-
requireGates: false,
|
|
136
|
-
requireWriteHook: undefined,
|
|
137
|
-
init: false,
|
|
138
|
-
installAgentGates: false,
|
|
139
|
-
compact: false,
|
|
140
|
-
tools: undefined,
|
|
141
|
-
force: false,
|
|
142
|
-
skillsOnly: false,
|
|
143
|
-
baseline: undefined,
|
|
144
|
-
policyBase: undefined,
|
|
145
|
-
policyBaseRef: undefined,
|
|
146
|
-
policyAck: undefined, failOnNewSmells: false, baseRef: undefined,
|
|
147
|
-
contractSession: false,
|
|
148
|
-
contractDiff: false,
|
|
149
|
-
changed: false,
|
|
150
|
-
against: undefined,
|
|
151
|
-
base: undefined,
|
|
152
|
-
persona: undefined,
|
|
153
|
-
author: undefined,
|
|
154
|
-
failUngoverned: false,
|
|
155
|
-
updateBaseline: false,
|
|
156
|
-
noCache: false,
|
|
157
|
-
resident: false,
|
|
158
|
-
coverage: false,
|
|
159
|
-
migrateCommands: false,
|
|
160
|
-
doctor: false,
|
|
161
|
-
plan: false,
|
|
162
|
-
recommend: false,
|
|
163
|
-
writePlan: false,
|
|
164
|
-
listPolicyPacks: false,
|
|
165
|
-
applyPolicyPack: undefined,
|
|
166
|
-
watch: false,
|
|
167
|
-
beginner: false,
|
|
168
|
-
openReport: false,
|
|
169
|
-
noOpenReport: false,
|
|
170
|
-
version: false,
|
|
171
|
-
help: false,
|
|
172
|
-
all: false,
|
|
173
|
-
followConfigRoot: false,
|
|
174
|
-
};
|
|
175
|
-
const requireValue = (flag, index) => {
|
|
176
|
-
const value = argv[index + 1];
|
|
177
|
-
if (value === undefined || value.startsWith('-')) {
|
|
178
|
-
throw new Error(`Missing value for ${flag}. Run arkgate-check --help for usage.`);
|
|
179
|
-
}
|
|
180
|
-
return value;
|
|
181
|
-
};
|
|
182
|
-
for (let i = 2; i < argv.length; i += 1) {
|
|
183
|
-
const arg = argv[i];
|
|
184
|
-
if (arg === '--json') args.json = true;
|
|
185
|
-
else if (arg === '--strict' || arg === '--strict-merge') {
|
|
186
|
-
args.strictConfig = true;
|
|
187
|
-
args.requireGates = true;
|
|
188
|
-
args.strictMerge = true;
|
|
189
|
-
}
|
|
190
|
-
else if (arg === '--strict-config') args.strictConfig = true;
|
|
191
|
-
else if (arg === '--require-gates') {
|
|
192
|
-
args.requireGates = true;
|
|
193
|
-
args.strictConfig = true;
|
|
194
|
-
}
|
|
195
|
-
else if (arg === '--require-write-hook') {
|
|
196
|
-
args.requireWriteHook = requireValue(arg, i++).trim().toLowerCase();
|
|
197
|
-
}
|
|
198
|
-
else if (arg === '--init') args.init = true;
|
|
199
|
-
else if (arg === '--preset') args.preset = requireValue(arg, i++);
|
|
200
|
-
else if (arg === '--install-agent-gates') args.installAgentGates = true;
|
|
201
|
-
else if (arg === '--compact') args.compact = true;
|
|
202
|
-
else if (arg === '--tools') {
|
|
203
|
-
// Consume the next arg only when it isn't another flag (same rule as --baseline),
|
|
204
|
-
// so `--tools --force` can't silently eat --force as a "tool name".
|
|
205
|
-
const next = argv[i + 1];
|
|
206
|
-
if (next !== undefined && !next.startsWith('-')) {
|
|
207
|
-
i += 1;
|
|
208
|
-
args.tools = next
|
|
209
|
-
.split(',')
|
|
210
|
-
.map((tool) => tool.trim().toLowerCase())
|
|
211
|
-
.filter(Boolean);
|
|
212
|
-
} else {
|
|
213
|
-
args.tools = []; // flag without a value — rejected in runInstallAgentGates
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
else if (arg === '--force') args.force = true;
|
|
217
|
-
else if (arg === '--follow-config-root') args.followConfigRoot = true;
|
|
218
|
-
else if (arg === '--skills-only') args.skillsOnly = true;
|
|
219
|
-
else if (arg === '--coverage') args.coverage = true;
|
|
220
|
-
else if (arg === '--doctor') args.doctor = true;
|
|
221
|
-
else if (arg === '--plan') args.plan = true;
|
|
222
|
-
else if (arg === '--rules-inventory') args.rulesInventory = true;
|
|
223
|
-
else if (arg === '--recommend') args.recommend = true;
|
|
224
|
-
else if (arg === '--write-plan') args.writePlan = true;
|
|
225
|
-
else if (arg === '--list-policy-packs') args.listPolicyPacks = true;
|
|
226
|
-
else if (arg === '--apply-policy-pack') args.applyPolicyPack = requireValue(arg, i++);
|
|
227
|
-
else if (arg === '--suggest-include') args.suggestInclude = true;
|
|
228
|
-
else if (arg === '--adopt-contract') args.adoptContract = true;
|
|
229
|
-
else if (arg === '--migrate-contract') args.migrateContract = true;
|
|
230
|
-
else if (arg === '--ratchet-cores') args.ratchetCores = true;
|
|
231
|
-
else if (arg === '--write') args.write = true;
|
|
232
|
-
else if (arg === '--watch') args.watch = true;
|
|
233
|
-
else if (arg === '--beginner') args.beginner = true;
|
|
234
|
-
else if (arg === '--codex-home') args.codexHome = true;
|
|
235
|
-
else if (arg === '--claude-home') args.claudeHome = true;
|
|
236
|
-
else if (arg === '--grok-home') args.grokHome = true;
|
|
237
|
-
else if (arg === '--agent-homes') {
|
|
238
|
-
args.agentHomes = true;
|
|
239
|
-
args.codexHome = true;
|
|
240
|
-
args.claudeHome = true;
|
|
241
|
-
args.grokHome = true;
|
|
242
|
-
}
|
|
243
|
-
else if (arg === '--migrate-commands') args.migrateCommands = true;
|
|
244
|
-
else if (arg === '--no-cache') args.noCache = true;
|
|
245
|
-
else if (arg === '--resident') args.resident = true;
|
|
246
|
-
else if (arg === '--report') {
|
|
247
|
-
const next = argv[i + 1];
|
|
248
|
-
args.report = next && !next.startsWith('-') ? argv[++i] : 'ark-report.html';
|
|
249
|
-
}
|
|
250
|
-
else if (arg === '--reset-origin') args.resetOrigin = true;
|
|
251
|
-
else if (arg === '--no-archive') args.noArchive = true;
|
|
252
|
-
else if (arg === '--open') args.openReport = true;
|
|
253
|
-
else if (arg === '--no-open') args.noOpenReport = true;
|
|
254
|
-
else if (arg === '--baseline' || arg === '--update-baseline') {
|
|
255
|
-
if (arg === '--update-baseline') args.updateBaseline = true;
|
|
256
|
-
// optional path value: consume the next arg only when it isn't another flag
|
|
257
|
-
const next = argv[i + 1];
|
|
258
|
-
args.baseline = next && !next.startsWith('-') ? argv[++i] : '.ark-baseline.json';
|
|
259
|
-
}
|
|
260
|
-
else if (arg === '--policy-base') args.policyBase = requireValue(arg, i++);
|
|
261
|
-
else if (arg === '--policy-base-ref') args.policyBaseRef = requireValue(arg, i++);
|
|
262
|
-
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++);
|
|
263
|
-
else if (arg === '--contract-session') args.contractSession = true;
|
|
264
|
-
else if (arg === '--contract-diff') args.contractDiff = true;
|
|
265
|
-
else if (arg === '--changed') args.changed = true;
|
|
266
|
-
else if (arg === '--against') args.against = requireValue(arg, i++);
|
|
267
|
-
else if (arg === '--base') args.base = requireValue(arg, i++);
|
|
268
|
-
else if (arg === '--persona') args.persona = requireValue(arg, i++);
|
|
269
|
-
else if (arg === '--author') args.author = requireValue(arg, i++);
|
|
270
|
-
else if (arg === '--root') args.root = path.resolve(requireValue(arg, i++));
|
|
271
|
-
else if (arg === '--config') args.config = requireValue(arg, i++);
|
|
272
|
-
else if (arg === '--manifest') args.manifest = requireValue(arg, i++);
|
|
273
|
-
else if (arg === '--print-config') args.printConfig = requireValue(arg, i++);
|
|
274
|
-
else if (arg === '--tsconfig') args.tsconfig = requireValue(arg, i++);
|
|
275
|
-
else if (arg === '--help' || arg === '-h') args.help = true;
|
|
276
|
-
else if (arg === '--all') args.all = true;
|
|
277
|
-
else if (arg === '--version' || arg === '-V') args.version = true;
|
|
278
|
-
else throw new Error(`Unknown argument: ${arg}. Run arkgate-check --help for usage.`);
|
|
279
|
-
}
|
|
280
|
-
return args;
|
|
281
|
-
}
|
|
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';
|
|
282
130
|
|
|
283
131
|
/** Path shown to humans: project-relative when inside root, absolute otherwise (no `../../..`). */
|
|
284
132
|
function displayPathFromRoot(root, absPath) {
|
|
@@ -302,101 +150,6 @@ function readConfig(root, configPath) {
|
|
|
302
150
|
return parseArkConfigJson(fs.readFileSync(fullPath, 'utf8'), fullPath).config;
|
|
303
151
|
}
|
|
304
152
|
|
|
305
|
-
/**
|
|
306
|
-
* Infer an ark.config.json from the directories that actually exist in the project,
|
|
307
|
-
* using the same layer→directory conventions as the eleven-layer template. A directory
|
|
308
|
-
* only counts when it contains at least one source file, so an empty scaffold dir can't
|
|
309
|
-
* produce a layer whose pattern matches nothing (which --strict-config would fail).
|
|
310
|
-
*/
|
|
311
|
-
function detectConfig(root) {
|
|
312
|
-
const srcDir = fs.existsSync(path.join(root, 'src')) ? 'src' : '.';
|
|
313
|
-
const layers = [];
|
|
314
|
-
|
|
315
|
-
for (const entry of DEFAULT_INTENT_PREFIXES) {
|
|
316
|
-
const directories = (DEFAULT_LAYER_DIRECTORIES[entry.layer] ?? []).filter(
|
|
317
|
-
(directory) => walk(path.join(root, srcDir, directory), [], { root }).length > 0
|
|
318
|
-
);
|
|
319
|
-
if (directories.length === 0) continue;
|
|
320
|
-
layers.push({
|
|
321
|
-
name: entry.layer,
|
|
322
|
-
patterns: directories.map((directory) => `${normalize(path.join(srcDir, directory))}/**`),
|
|
323
|
-
intentPrefixes: entry.prefixes,
|
|
324
|
-
...(entry.layer === 'DomainModel'
|
|
325
|
-
? { forbiddenGlobals: DEFAULT_DOMAIN_FORBIDDEN_GLOBALS }
|
|
326
|
-
: {}),
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
const names = new Set(layers.map((layer) => layer.name));
|
|
331
|
-
const rules = DEFAULT_RULES.filter((rule) => names.has(rule.from) && names.has(rule.to));
|
|
332
|
-
|
|
333
|
-
return { srcDir, config: { include: [srcDir], layers, rules } };
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/** Top-level directories under srcDir not covered by any detected layer pattern. */
|
|
337
|
-
function uncoveredDirectories(root, srcDir, layers) {
|
|
338
|
-
const base = path.join(root, srcDir);
|
|
339
|
-
if (!fs.existsSync(base)) return [];
|
|
340
|
-
return fs
|
|
341
|
-
.readdirSync(base, { withFileTypes: true })
|
|
342
|
-
.filter(
|
|
343
|
-
(entry) =>
|
|
344
|
-
entry.isDirectory() &&
|
|
345
|
-
entry.name !== 'node_modules' &&
|
|
346
|
-
entry.name !== 'dist' &&
|
|
347
|
-
!entry.name.startsWith('.')
|
|
348
|
-
)
|
|
349
|
-
.map((entry) => entry.name)
|
|
350
|
-
.filter((name) => {
|
|
351
|
-
const prefix = `${normalize(path.join(srcDir, name))}/`;
|
|
352
|
-
return !layers.some((layer) =>
|
|
353
|
-
layer.patterns.some((pattern) => pattern.startsWith(prefix))
|
|
354
|
-
);
|
|
355
|
-
});
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// detectWorkspaces: shared implementation in ark-shared.mjs (npm/pnpm/rush/lerna +
|
|
359
|
-
// conventional multi-package roots).
|
|
360
|
-
|
|
361
|
-
// Deny every "upward" edge for an ordered layer list (index 0 = outermost/top,
|
|
362
|
-
// which may import everything below it). Inner/lower layers must not import outer
|
|
363
|
-
// ones — the shared shape behind linear layered and feature-sliced layouts.
|
|
364
|
-
|
|
365
|
-
function proposeForUncovered(root, srcDir, layers) {
|
|
366
|
-
const proposals = [];
|
|
367
|
-
for (const top of uncoveredDirectories(root, srcDir, layers)) {
|
|
368
|
-
const direct = suggestLayerForDir(top);
|
|
369
|
-
if (direct) {
|
|
370
|
-
proposals.push({ dir: `${srcDir}/${top}`, ...direct });
|
|
371
|
-
continue;
|
|
372
|
-
}
|
|
373
|
-
let children = [];
|
|
374
|
-
try {
|
|
375
|
-
children = fs
|
|
376
|
-
.readdirSync(path.join(root, srcDir, top), { withFileTypes: true })
|
|
377
|
-
.filter((e) => e.isDirectory() && e.name !== 'node_modules' && !e.name.startsWith('.'))
|
|
378
|
-
.map((e) => e.name);
|
|
379
|
-
} catch {
|
|
380
|
-
/* not a readable directory — treat as unrecognized below */
|
|
381
|
-
}
|
|
382
|
-
if (children.length > 0) {
|
|
383
|
-
// Descend: propose per child so a mixed `lib/` yields lib/repositories → Persistence
|
|
384
|
-
// AND flags lib/db as unrecognized, instead of dropping the parts Ark can't place.
|
|
385
|
-
for (const child of children) {
|
|
386
|
-
const hit = suggestLayerForDir(child);
|
|
387
|
-
proposals.push(
|
|
388
|
-
hit
|
|
389
|
-
? { dir: `${srcDir}/${top}/${child}`, ...hit }
|
|
390
|
-
: { dir: `${srcDir}/${top}/${child}`, unrecognized: true }
|
|
391
|
-
);
|
|
392
|
-
}
|
|
393
|
-
} else {
|
|
394
|
-
proposals.push({ dir: `${srcDir}/${top}`, unrecognized: true });
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
return proposals;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
153
|
function printInitNextSteps(root) {
|
|
401
154
|
console.log('');
|
|
402
155
|
console.log('Next steps:');
|
|
@@ -1324,6 +1077,11 @@ async function main() {
|
|
|
1324
1077
|
console.log('✔ Ark check passed (no governed source or constitution files in the diff).');
|
|
1325
1078
|
} else {
|
|
1326
1079
|
console.error(preflight.halt.message);
|
|
1080
|
+
const blocking = policyDelta?.findings?.find(
|
|
1081
|
+
(finding) =>
|
|
1082
|
+
finding.classification === 'weakening' || finding.classification === 'judgment-required'
|
|
1083
|
+
);
|
|
1084
|
+
if (blocking?.nextAction) console.error(`Next: ${blocking.nextAction}`);
|
|
1327
1085
|
}
|
|
1328
1086
|
process.exitCode = preflight.halt.exitCode;
|
|
1329
1087
|
return;
|
|
@@ -1406,7 +1164,17 @@ async function main() {
|
|
|
1406
1164
|
populatedLayerCount,
|
|
1407
1165
|
});
|
|
1408
1166
|
|
|
1409
|
-
const
|
|
1167
|
+
const createdPathsOnly = Boolean(args.strictMerge && !args.failOnNewSmells);
|
|
1168
|
+
const designCheck = createDesignDeltaCheck({
|
|
1169
|
+
enabled: args.failOnNewSmells || args.strictMerge,
|
|
1170
|
+
createdPathsOnly,
|
|
1171
|
+
missingBase: createdPathsOnly ? 'skip' : 'fail-closed',
|
|
1172
|
+
root,
|
|
1173
|
+
config,
|
|
1174
|
+
configPath: args.config,
|
|
1175
|
+
baseRef: resolveDesignDeltaBaseRef(root, args.baseRef),
|
|
1176
|
+
ts,
|
|
1177
|
+
});
|
|
1410
1178
|
const designDelta = designCheck.result;
|
|
1411
1179
|
|
|
1412
1180
|
if (args.doctor) {
|
|
@@ -1417,6 +1185,7 @@ async function main() {
|
|
|
1417
1185
|
configWalkedUp: args.configWalkedUp === true,
|
|
1418
1186
|
safety, designDelta,
|
|
1419
1187
|
ts, parseHealth, completeness,
|
|
1188
|
+
all: args.all === true,
|
|
1420
1189
|
});
|
|
1421
1190
|
if (designDelta) process.exitCode = !designDelta.complete ? 2 : designDelta.valid ? 0 : 1; return;
|
|
1422
1191
|
}
|
|
@@ -1427,6 +1196,11 @@ async function main() {
|
|
|
1427
1196
|
}
|
|
1428
1197
|
|
|
1429
1198
|
if (args.updateBaseline) {
|
|
1199
|
+
if (!contractSessionFrom(args)) {
|
|
1200
|
+
console.error('Growing the baseline requires --contract-session. Freeze in a law-only PR.');
|
|
1201
|
+
process.exitCode = 1;
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1430
1204
|
const summary = summarizeViolations(violations);
|
|
1431
1205
|
// Bloquear y avisar: a lopsided freeze buries a likely contract bug as "debt". Refuse it
|
|
1432
1206
|
// (unless --force), diagnose, and point at the contract fix instead of the baseline.
|
|
@@ -1928,87 +1702,17 @@ async function main() {
|
|
|
1928
1702
|
}
|
|
1929
1703
|
|
|
1930
1704
|
if (args.watch) {
|
|
1931
|
-
await runWatchMode(args
|
|
1705
|
+
await runWatchMode(args, {
|
|
1706
|
+
cliPath: __arkCheckCli,
|
|
1707
|
+
loadConfig: readConfig,
|
|
1708
|
+
dim: color.dim,
|
|
1709
|
+
});
|
|
1932
1710
|
return;
|
|
1933
1711
|
}
|
|
1934
1712
|
|
|
1935
1713
|
process.exitCode = designCheck.exitCode(observedOk && (!args.strictMerge || analysisComplete) ? 0 : 1);
|
|
1936
1714
|
}
|
|
1937
1715
|
|
|
1938
|
-
async function runWatchMode(args) {
|
|
1939
|
-
const argv = process.argv.slice(2).filter((token) => token !== '--watch');
|
|
1940
|
-
let debounce;
|
|
1941
|
-
const rerun = () => {
|
|
1942
|
-
clearTimeout(debounce);
|
|
1943
|
-
debounce = setTimeout(() => {
|
|
1944
|
-
const result = spawnSync(process.execPath, [__arkCheckCli, ...argv], {
|
|
1945
|
-
cwd: args.root,
|
|
1946
|
-
stdio: 'inherit',
|
|
1947
|
-
env: process.env,
|
|
1948
|
-
});
|
|
1949
|
-
process.exitCode = result.status ?? 1;
|
|
1950
|
-
}, 300);
|
|
1951
|
-
};
|
|
1952
|
-
|
|
1953
|
-
let config;
|
|
1954
|
-
try {
|
|
1955
|
-
config = readConfig(args.root, args.config);
|
|
1956
|
-
} catch (error) {
|
|
1957
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
1958
|
-
process.exitCode = 2;
|
|
1959
|
-
return;
|
|
1960
|
-
}
|
|
1961
|
-
|
|
1962
|
-
for (const entry of config.include ?? []) {
|
|
1963
|
-
const target = path.join(args.root, entry);
|
|
1964
|
-
if (!fs.existsSync(target)) continue;
|
|
1965
|
-
try {
|
|
1966
|
-
const watcher = fs.watch(target, { recursive: true }, rerun);
|
|
1967
|
-
watcher.on('error', () => {
|
|
1968
|
-
watcher.close();
|
|
1969
|
-
watchByPolling(target, rerun);
|
|
1970
|
-
});
|
|
1971
|
-
} catch {
|
|
1972
|
-
watchByPolling(target, rerun);
|
|
1973
|
-
}
|
|
1974
|
-
}
|
|
1975
|
-
|
|
1976
|
-
console.log(color.dim('Watching governed paths for changes… (Ctrl+C to stop)'));
|
|
1977
|
-
await new Promise(() => {});
|
|
1978
|
-
}
|
|
1979
|
-
|
|
1980
|
-
function watchByPolling(target, onChange) {
|
|
1981
|
-
let previous = watchFingerprint(target);
|
|
1982
|
-
setInterval(() => {
|
|
1983
|
-
const current = watchFingerprint(target);
|
|
1984
|
-
if (current === previous) return;
|
|
1985
|
-
previous = current;
|
|
1986
|
-
onChange();
|
|
1987
|
-
}, 250);
|
|
1988
|
-
}
|
|
1989
|
-
|
|
1990
|
-
function watchFingerprint(target) {
|
|
1991
|
-
const pending = [target];
|
|
1992
|
-
const entries = [];
|
|
1993
|
-
while (pending.length > 0) {
|
|
1994
|
-
const current = pending.pop();
|
|
1995
|
-
let stat;
|
|
1996
|
-
try {
|
|
1997
|
-
stat = fs.statSync(current);
|
|
1998
|
-
} catch {
|
|
1999
|
-
continue;
|
|
2000
|
-
}
|
|
2001
|
-
entries.push(`${current}:${stat.mtimeMs}:${stat.size}`);
|
|
2002
|
-
if (!stat.isDirectory()) continue;
|
|
2003
|
-
try {
|
|
2004
|
-
for (const name of fs.readdirSync(current)) pending.push(path.join(current, name));
|
|
2005
|
-
} catch {
|
|
2006
|
-
// A concurrent delete is represented by the next fingerprint.
|
|
2007
|
-
}
|
|
2008
|
-
}
|
|
2009
|
-
return entries.sort().join('|');
|
|
2010
|
-
}
|
|
2011
|
-
|
|
2012
1716
|
main().catch((error) => {
|
|
2013
1717
|
console.error(
|
|
2014
1718
|
process.env.ARK_DEBUG_STACK === '1' && error instanceof Error
|