arkgate 2.8.1 → 2.8.2
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 +28 -0
- package/bin/ark-shared.mjs +66 -4
- package/bin/ark.mjs +50 -9
- package/bin/lib/agent-gates.mjs +32 -3
- package/bin/lib/doctor-plan.mjs +16 -0
- package/bin/lib/html-report.mjs +15 -0
- package/bin/lib/presets.mjs +86 -12
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/nestjs/index.cjs +1 -1
- package/dist/nestjs/index.cjs.map +1 -1
- package/dist/nestjs/index.js +1 -1
- package/dist/nestjs/index.js.map +1 -1
- package/dist/runtime/index.cjs +1 -1
- package/dist/runtime/index.cjs.map +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/index.js.map +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to ArkGate (`arkgate`; formerly `ark-runtime-kernel`) are documented here.
|
|
4
4
|
|
|
5
|
+
## Unreleased
|
|
6
|
+
|
|
7
|
+
## 2.8.2 — 2026-07-09
|
|
8
|
+
|
|
9
|
+
Field-honesty patch (Next/UI host probe): no Nest false positives, no false ENFORCE on
|
|
10
|
+
ui-surface bags, honest `ark start` mode, Next proxy/middleware classification.
|
|
11
|
+
**No intentional CLI flag or JSON shape breaks.**
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **Nest detection false positive:** bare `*.service.ts` / similar names no longer set
|
|
16
|
+
`nestFramework` without `@nestjs/*` or controller/module/gateway/resolver files (Next/Node
|
|
17
|
+
apps no longer get a spurious `nestjs+next` overlay).
|
|
18
|
+
- **False ENFORCE on UI bags:** doctor/report mode stays **ADAPT** when Domain+Persistence are
|
|
19
|
+
empty while Presentation dominates, or when core layers with files remain `optional: true`.
|
|
20
|
+
- **ui-surface / Next defaults:** drop whole-`src` and bare `**/lib/**` presentation bags;
|
|
21
|
+
classify conventional data clients (`lib/supabase`, `lib/airtable`, `lib/prisma`, …) as
|
|
22
|
+
**Persistence**; add Application patterns for actions/services.
|
|
23
|
+
- **Generated CI:** when `package.json` has `lint` / `typecheck` scripts, the installed
|
|
24
|
+
GitHub Actions workflow runs them before ark-check (closes deploy-path gaps for Next hosts).
|
|
25
|
+
- **`ark start` wrap-up mode:** prefers `ark-check --doctor` `operatingMode` over plan-only
|
|
26
|
+
`resolveOperatingMode` (default **adapt**, double-lock against false **ENFORCE**).
|
|
27
|
+
- **Next middleware / proxy:** ui-surface + Next overlay classify `src/proxy.ts`, root
|
|
28
|
+
`proxy.ts`, and classic `middleware.ts` as **Presentation** (Next 16 rename no longer
|
|
29
|
+
leaves the edge entry ungoverned).
|
|
30
|
+
- **Idempotent Next overlay:** re-applying framework overlays no longer yields
|
|
31
|
+
`frameworkOverlay: "next+next"`.
|
|
32
|
+
|
|
5
33
|
## 2.8.1 — 2026-07-09
|
|
6
34
|
|
|
7
35
|
Runtime honesty release (roadmap **R8–R9**): EventBus publish pipeline decomposition and
|
package/bin/ark-shared.mjs
CHANGED
|
@@ -217,6 +217,15 @@ export function applyFrameworkLayoutOverlays(config, root) {
|
|
|
217
217
|
'src/**/error.tsx',
|
|
218
218
|
'src/**/route.ts',
|
|
219
219
|
'src/**/route.tsx',
|
|
220
|
+
// Next middleware edge entry (classic + Next 16 proxy rename)
|
|
221
|
+
'src/middleware.ts',
|
|
222
|
+
'src/middleware.js',
|
|
223
|
+
'middleware.ts',
|
|
224
|
+
'middleware.js',
|
|
225
|
+
'src/proxy.ts',
|
|
226
|
+
'src/proxy.js',
|
|
227
|
+
'proxy.ts',
|
|
228
|
+
'proxy.js',
|
|
220
229
|
]);
|
|
221
230
|
mergeLayerPatterns(next, 'ApplicationOrchestration', [
|
|
222
231
|
'src/features/**',
|
|
@@ -247,6 +256,21 @@ export function applyFrameworkLayoutOverlays(config, root) {
|
|
|
247
256
|
'src/lib/db/**',
|
|
248
257
|
'src/lib/prisma/**',
|
|
249
258
|
'src/server/db/**',
|
|
259
|
+
// Conventional client data bags under lib/ (higher specificity than bare src/lib/**).
|
|
260
|
+
'src/lib/supabase/**',
|
|
261
|
+
'src/lib/airtable/**',
|
|
262
|
+
'src/lib/firebase/**',
|
|
263
|
+
'src/lib/firestore/**',
|
|
264
|
+
'src/lib/mongodb/**',
|
|
265
|
+
'src/lib/mongoose/**',
|
|
266
|
+
'src/lib/drizzle/**',
|
|
267
|
+
'src/lib/kysely/**',
|
|
268
|
+
'src/lib/planetscale/**',
|
|
269
|
+
'src/lib/neon/**',
|
|
270
|
+
'**/lib/supabase/**',
|
|
271
|
+
'**/lib/airtable/**',
|
|
272
|
+
'**/lib/prisma/**',
|
|
273
|
+
'**/lib/db/**',
|
|
250
274
|
]);
|
|
251
275
|
// Demo assets, generated public output, and tool configs are not architecture surface.
|
|
252
276
|
const nextExcludes = [
|
|
@@ -262,9 +286,12 @@ export function applyFrameworkLayoutOverlays(config, root) {
|
|
|
262
286
|
'**/scripts/**',
|
|
263
287
|
];
|
|
264
288
|
next.exclude = [...new Set([...(next.exclude ?? []), ...nextExcludes])];
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
289
|
+
// Idempotent: re-applying overlays (e.g. re-init / re-start) must not yield "next+next".
|
|
290
|
+
if (!String(next.frameworkOverlay || '').split('+').includes('next')) {
|
|
291
|
+
next.frameworkOverlay = next.frameworkOverlay
|
|
292
|
+
? `${next.frameworkOverlay}+next`
|
|
293
|
+
: 'next';
|
|
294
|
+
}
|
|
268
295
|
}
|
|
269
296
|
|
|
270
297
|
if (signals.expressLike && !signals.nestFramework) {
|
|
@@ -323,15 +350,47 @@ export function applyFrameworkLayoutOverlays(config, root) {
|
|
|
323
350
|
/**
|
|
324
351
|
* Operating mode for the co-pilot surfaces (not "who the user is"):
|
|
325
352
|
* suggest | adapt | enforce
|
|
353
|
+
*
|
|
354
|
+
* ENFORCE means gates can honestly protect the tree. High governed% alone is not
|
|
355
|
+
* enough when core layers are empty (presentation bag false-green) or core layers
|
|
356
|
+
* with real files remain optional: true.
|
|
357
|
+
*
|
|
358
|
+
* @param {object} opts
|
|
359
|
+
* @param {number|null} [opts.governedPercent]
|
|
360
|
+
* @param {boolean|null} [opts.planMet]
|
|
361
|
+
* @param {boolean} [opts.mature]
|
|
362
|
+
* @param {number|null} [opts.totalFiles]
|
|
363
|
+
* @param {string[]} [opts.emptyLayers] layer names with zero matched files
|
|
364
|
+
* @param {number} [opts.coreOptionalWithFiles] count of core layers that have files but optional:true
|
|
365
|
+
* @param {number|null} [opts.presentationShare] PresentationAdapters file share 0..1 when known
|
|
326
366
|
*/
|
|
327
367
|
export function resolveOperatingMode({
|
|
328
368
|
governedPercent = null,
|
|
329
369
|
planMet = null,
|
|
330
370
|
mature = false,
|
|
331
371
|
totalFiles = null,
|
|
372
|
+
emptyLayers = null,
|
|
373
|
+
coreOptionalWithFiles = 0,
|
|
374
|
+
presentationShare = null,
|
|
332
375
|
} = {}) {
|
|
333
376
|
// Zero files in scope is never ENFORCE — the contract is not looking at any code.
|
|
334
377
|
if (totalFiles === 0) return 'adapt';
|
|
378
|
+
// Core layers still optional while holding real files → contract weaker than the tree.
|
|
379
|
+
if (coreOptionalWithFiles > 0) return 'adapt';
|
|
380
|
+
// Presentation-bag false green: almost everything is Presentation, Domain+Persistence empty.
|
|
381
|
+
const empty = Array.isArray(emptyLayers) ? emptyLayers : [];
|
|
382
|
+
const domainEmpty = empty.includes('DomainModel');
|
|
383
|
+
const persistenceEmpty = empty.includes('PersistenceAdapters');
|
|
384
|
+
if (
|
|
385
|
+
planMet === true &&
|
|
386
|
+
domainEmpty &&
|
|
387
|
+
persistenceEmpty &&
|
|
388
|
+
presentationShare != null &&
|
|
389
|
+
presentationShare >= 0.5 &&
|
|
390
|
+
(governedPercent ?? 0) >= 50
|
|
391
|
+
) {
|
|
392
|
+
return 'adapt';
|
|
393
|
+
}
|
|
335
394
|
if (planMet === true && (governedPercent == null || governedPercent >= 50)) return 'enforce';
|
|
336
395
|
if (governedPercent != null && governedPercent < 50) return 'adapt';
|
|
337
396
|
if (mature) return 'adapt';
|
|
@@ -1027,10 +1086,13 @@ export function collectRepoShapeSignals(root) {
|
|
|
1027
1086
|
|
|
1028
1087
|
const topNames = new Set(srcDirs.flatMap((d) => listTopLevelDirNames(root, d)));
|
|
1029
1088
|
// Framework / filename signals — strong enough that a tiny Nest starter is not a "prototype".
|
|
1089
|
+
// Nest detection: require real Nest surface (@nestjs/* or controller/module/gateway/resolver).
|
|
1090
|
+
// Do NOT treat bare `*.service.ts` / `*.guard.ts` as Nest — many Next/Node apps use those
|
|
1091
|
+
// names without Nest (false nestjs overlay + wrong doctor toolHints).
|
|
1030
1092
|
const nestFramework =
|
|
1031
1093
|
Object.keys(deps).some((name) => name.startsWith('@nestjs/')) ||
|
|
1032
1094
|
sourceFiles.some((file) =>
|
|
1033
|
-
/\.(controller|module|
|
|
1095
|
+
/\.(controller|module|gateway|resolver)\.ts$/i.test(file)
|
|
1034
1096
|
);
|
|
1035
1097
|
const nextFramework =
|
|
1036
1098
|
Boolean(deps.next) ||
|
package/bin/ark.mjs
CHANGED
|
@@ -393,27 +393,64 @@ async function start(args) {
|
|
|
393
393
|
console.log('Your architecture plan:');
|
|
394
394
|
runArkCheck(['--root', root, '--config', 'ark.config.json', '--plan'], { cwd: root });
|
|
395
395
|
|
|
396
|
-
// Capture plan JSON for an honest wrap-up
|
|
396
|
+
// Capture plan + doctor JSON for an honest wrap-up. Mode MUST match --doctor
|
|
397
|
+
// (emptyLayers, core-optional, presentation bag) — never claim ENFORCE from plan alone.
|
|
397
398
|
const planCapture = spawnSync(
|
|
398
399
|
process.execPath,
|
|
399
400
|
[arkCheck, '--root', root, '--config', 'ark.config.json', '--plan', '--json'],
|
|
400
401
|
{ cwd: root, encoding: 'utf8' }
|
|
401
402
|
);
|
|
403
|
+
const doctorCapture = spawnSync(
|
|
404
|
+
process.execPath,
|
|
405
|
+
[arkCheck, '--root', root, '--config', 'ark.config.json', '--doctor', '--json'],
|
|
406
|
+
{ cwd: root, encoding: 'utf8' }
|
|
407
|
+
);
|
|
402
408
|
let planOk = true;
|
|
403
409
|
let governedPercent = null;
|
|
404
|
-
let mode = '
|
|
410
|
+
let mode = 'adapt'; // conservative default — never default to enforce
|
|
405
411
|
try {
|
|
406
412
|
const parsed = JSON.parse(planCapture.stdout || '{}');
|
|
407
413
|
planOk = parsed.ok === true && parsed.plan?.goal?.met === true;
|
|
408
414
|
governedPercent = parsed.plan?.goal?.governedPercent ?? null;
|
|
409
415
|
const totalFiles = parsed.plan?.goal?.totalFiles ?? null;
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
416
|
+
|
|
417
|
+
let doctorMode = null;
|
|
418
|
+
try {
|
|
419
|
+
const doc = JSON.parse(doctorCapture.stdout || '{}');
|
|
420
|
+
doctorMode = doc.doctor?.operatingMode ?? null;
|
|
421
|
+
// Prefer doctor's mode (includes emptyLayers + coreOptional + presentation-bag honesty).
|
|
422
|
+
if (doctorMode === 'suggest' || doctorMode === 'adapt' || doctorMode === 'enforce') {
|
|
423
|
+
mode = doctorMode;
|
|
424
|
+
} else {
|
|
425
|
+
// Fallback: recompute with honesty inputs from doctor payload when present.
|
|
426
|
+
const emptyLayers = doc.doctor?.emptyLayers ?? [];
|
|
427
|
+
const coreOptionalWithFiles = Array.isArray(doc.doctor?.adoption?.coreOptional)
|
|
428
|
+
? doc.doctor.adoption.coreOptional.length
|
|
429
|
+
: 0;
|
|
430
|
+
const total = totalFiles || doc.doctor?.governed?.totalFiles || 0;
|
|
431
|
+
const presentationShare =
|
|
432
|
+
total > 0 && typeof doc.doctor?.governed?.percent === 'number'
|
|
433
|
+
? null // presentationShare only from coverage layers when available
|
|
434
|
+
: null;
|
|
435
|
+
mode = resolveOperatingMode({
|
|
436
|
+
governedPercent: totalFiles === 0 ? 0 : governedPercent,
|
|
437
|
+
planMet: parsed.plan?.goal?.met === true,
|
|
438
|
+
mature: Boolean(rec?.mature),
|
|
439
|
+
totalFiles: totalFiles ?? total,
|
|
440
|
+
emptyLayers,
|
|
441
|
+
coreOptionalWithFiles,
|
|
442
|
+
presentationShare,
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
} catch {
|
|
446
|
+
mode = resolveOperatingMode({
|
|
447
|
+
governedPercent: totalFiles === 0 ? 0 : governedPercent,
|
|
448
|
+
planMet: parsed.plan?.goal?.met === true,
|
|
449
|
+
mature: Boolean(rec?.mature),
|
|
450
|
+
totalFiles,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
417
454
|
// Fresh greenfield with good coverage but no real tree yet → suggest, not enforce theatre.
|
|
418
455
|
if (mode === 'enforce' && rec && !rec.mature && (governedPercent ?? 0) < 80) {
|
|
419
456
|
mode = 'suggest';
|
|
@@ -423,6 +460,10 @@ async function start(args) {
|
|
|
423
460
|
mode = 'adapt';
|
|
424
461
|
planOk = false;
|
|
425
462
|
}
|
|
463
|
+
// Never claim ENFORCE wrap-up if doctor would not (double-lock).
|
|
464
|
+
if (mode === 'enforce' && doctorMode && doctorMode !== 'enforce') {
|
|
465
|
+
mode = doctorMode;
|
|
466
|
+
}
|
|
426
467
|
} catch {
|
|
427
468
|
// If capture fails, stay conservative: don't claim full enforcement.
|
|
428
469
|
mode = 'adapt';
|
package/bin/lib/agent-gates.mjs
CHANGED
|
@@ -483,7 +483,12 @@ export function detectCiNode(root) {
|
|
|
483
483
|
return { kind: 'default', value: DEFAULT_CI_NODE_VERSION };
|
|
484
484
|
}
|
|
485
485
|
|
|
486
|
-
|
|
486
|
+
/**
|
|
487
|
+
* @param {{ name: string, install: string, run: string, cache: string, setup: string[] }} pm
|
|
488
|
+
* @param {{ kind: string, value: string }} ciNode
|
|
489
|
+
* @param {{ hasLintScript?: boolean, hasTypecheckScript?: boolean }} [quality]
|
|
490
|
+
*/
|
|
491
|
+
export function githubWorkflow(pm, ciNode, quality = {}) {
|
|
487
492
|
// pnpm/yarn setup (corepack enable) MUST run before actions/setup-node so the package
|
|
488
493
|
// manager is on PATH when setup-node's `cache: pnpm|yarn` tries to resolve the store —
|
|
489
494
|
// otherwise the cache step fails on a fresh runner ("Unable to locate executable file: pnpm").
|
|
@@ -500,6 +505,24 @@ export function githubWorkflow(pm, ciNode) {
|
|
|
500
505
|
# of sync, your local package manager is newer than this Node's — add a
|
|
501
506
|
# .nvmrc with your Node version so CI matches the dev environment.
|
|
502
507
|
node-version: '${ciNode.value}'`;
|
|
508
|
+
// When package.json already has lint/typecheck, emit CI steps so deploy-path
|
|
509
|
+
// honesty matches local scripts (Next/CRA often run these in production build).
|
|
510
|
+
const install = pm.install || '';
|
|
511
|
+
const runPrefix = install.startsWith('pnpm')
|
|
512
|
+
? 'pnpm run'
|
|
513
|
+
: install.startsWith('yarn')
|
|
514
|
+
? 'yarn'
|
|
515
|
+
: install.startsWith('bun')
|
|
516
|
+
? 'bun run'
|
|
517
|
+
: 'npm run';
|
|
518
|
+
const qualityBlock = [
|
|
519
|
+
quality.hasTypecheckScript
|
|
520
|
+
? ` - name: Typecheck\n run: ${runPrefix} typecheck`
|
|
521
|
+
: '',
|
|
522
|
+
quality.hasLintScript ? ` - name: Lint\n run: ${runPrefix} lint` : '',
|
|
523
|
+
]
|
|
524
|
+
.filter(Boolean)
|
|
525
|
+
.join('\n');
|
|
503
526
|
return `name: Ark architecture gate
|
|
504
527
|
|
|
505
528
|
on:
|
|
@@ -520,7 +543,7 @@ ${nodeSetup}
|
|
|
520
543
|
cache: ${pm.cache}
|
|
521
544
|
- name: Install dependencies
|
|
522
545
|
run: ${pm.install}
|
|
523
|
-
- name: Ark architecture check
|
|
546
|
+
${qualityBlock ? `${qualityBlock}\n` : ''} - name: Ark architecture check
|
|
524
547
|
run: ${pm.run}
|
|
525
548
|
`;
|
|
526
549
|
}
|
|
@@ -1608,7 +1631,13 @@ export function runInstallAgentGates(args) {
|
|
|
1608
1631
|
templates.push(['.mcp.json', mcpJson(root)]);
|
|
1609
1632
|
templates.push([
|
|
1610
1633
|
'.github/workflows/ark-check.yml',
|
|
1611
|
-
|
|
1634
|
+
(() => {
|
|
1635
|
+
const deploy = detectDeployPathQuality(root);
|
|
1636
|
+
return githubWorkflow(pm, detectCiNode(root), {
|
|
1637
|
+
hasLintScript: deploy.hasLintScript,
|
|
1638
|
+
hasTypecheckScript: deploy.hasTypecheckScript,
|
|
1639
|
+
});
|
|
1640
|
+
})(),
|
|
1612
1641
|
]);
|
|
1613
1642
|
if (tools.has('cursor')) {
|
|
1614
1643
|
templates.push(['.cursor/mcp.json', mcpJson(root)]);
|
package/bin/lib/doctor-plan.mjs
CHANGED
|
@@ -290,6 +290,15 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
|
|
|
290
290
|
governedPercent: cov.governed.percent,
|
|
291
291
|
planMet: activeCount === 0 && cov.governed.percent >= 50,
|
|
292
292
|
mature: cov.governed.totalFiles >= 150,
|
|
293
|
+
totalFiles: cov.governed.totalFiles,
|
|
294
|
+
emptyLayers: cov.emptyLayers,
|
|
295
|
+
coreOptionalWithFiles: adoption.coreOptional?.length ?? 0,
|
|
296
|
+
presentationShare: (() => {
|
|
297
|
+
const total = cov.governed.totalFiles || 0;
|
|
298
|
+
if (total <= 0) return null;
|
|
299
|
+
const p = cov.layers.find((r) => r.name === 'PresentationAdapters');
|
|
300
|
+
return p ? p.files / total : null;
|
|
301
|
+
})(),
|
|
293
302
|
}),
|
|
294
303
|
governed: cov.governed,
|
|
295
304
|
emptyLayers: cov.emptyLayers,
|
|
@@ -345,11 +354,18 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
|
|
|
345
354
|
console.log(color.bold(`Ark doctor — ${path.basename(path.resolve(root)) || '.'}`));
|
|
346
355
|
|
|
347
356
|
const emptyScope = cov.governed.totalFiles === 0;
|
|
357
|
+
const totalFiles = cov.governed.totalFiles || 0;
|
|
358
|
+
const presentationRow = cov.layers.find((r) => r.name === 'PresentationAdapters');
|
|
348
359
|
const mode = resolveOperatingMode({
|
|
349
360
|
governedPercent: emptyScope ? 0 : cov.governed.percent,
|
|
350
361
|
planMet:
|
|
351
362
|
activeCount === 0 && !emptyScope && cov.governed.percent >= 50,
|
|
352
363
|
mature: cov.governed.totalFiles >= 150,
|
|
364
|
+
totalFiles: cov.governed.totalFiles,
|
|
365
|
+
emptyLayers: cov.emptyLayers,
|
|
366
|
+
coreOptionalWithFiles: adoption.coreOptional?.length ?? 0,
|
|
367
|
+
presentationShare:
|
|
368
|
+
totalFiles > 0 && presentationRow ? presentationRow.files / totalFiles : null,
|
|
353
369
|
});
|
|
354
370
|
console.log('');
|
|
355
371
|
console.log(color.bold('Operating mode'));
|
package/bin/lib/html-report.mjs
CHANGED
|
@@ -153,6 +153,17 @@ export function computeReportFitness({ coverage, violations, ok, enforcement, co
|
|
|
153
153
|
const governedPercent = coverage?.governed?.percent ?? null;
|
|
154
154
|
const totalFiles = coverage?.governed?.totalFiles ?? 0;
|
|
155
155
|
const classifiedFiles = coverage?.governed?.classifiedFiles ?? 0;
|
|
156
|
+
const emptyLayers = (coverage?.layers ?? [])
|
|
157
|
+
.filter((r) => (r.files ?? 0) === 0)
|
|
158
|
+
.map((r) => r.name);
|
|
159
|
+
const presentationRow = (coverage?.layers ?? []).find(
|
|
160
|
+
(r) => r.name === 'PresentationAdapters'
|
|
161
|
+
);
|
|
162
|
+
const coreOptionalWithFiles = (config?.layers ?? []).filter((layer) => {
|
|
163
|
+
if (layer.optional !== true) return false;
|
|
164
|
+
const row = (coverage?.layers ?? []).find((r) => r.name === layer.name);
|
|
165
|
+
return (row?.files ?? 0) > 0;
|
|
166
|
+
}).length;
|
|
156
167
|
const mode = resolveOperatingMode({
|
|
157
168
|
governedPercent: totalFiles === 0 ? 0 : governedPercent,
|
|
158
169
|
planMet:
|
|
@@ -162,6 +173,10 @@ export function computeReportFitness({ coverage, violations, ok, enforcement, co
|
|
|
162
173
|
(governedPercent == null || governedPercent >= 50),
|
|
163
174
|
mature: totalFiles >= 150,
|
|
164
175
|
totalFiles,
|
|
176
|
+
emptyLayers,
|
|
177
|
+
coreOptionalWithFiles,
|
|
178
|
+
presentationShare:
|
|
179
|
+
totalFiles > 0 && presentationRow ? presentationRow.files / totalFiles : null,
|
|
165
180
|
});
|
|
166
181
|
const modeLabel = { suggest: 'SUGGEST', adapt: 'ADAPT', enforce: 'ENFORCE' }[mode] || String(mode).toUpperCase();
|
|
167
182
|
const modeBlurb = {
|
package/bin/lib/presets.mjs
CHANGED
|
@@ -267,22 +267,29 @@ export const ARCHITECTURE_PRESETS = {
|
|
|
267
267
|
name: 'DomainModel',
|
|
268
268
|
description: 'Shared types and pure view-models (optional on UI-first trees).',
|
|
269
269
|
// Avoid bare **/types.ts — see monorepo DomainModel note (false Domain on core/**/types.ts).
|
|
270
|
-
patterns: [
|
|
270
|
+
patterns: [
|
|
271
|
+
'**/domain/**',
|
|
272
|
+
'**/cinematic/types.ts',
|
|
273
|
+
// Common pure types bag (not **/types.ts — that traps core/**/types.ts).
|
|
274
|
+
'src/lib/types.ts',
|
|
275
|
+
],
|
|
271
276
|
exclude: FRAMEWORK_INTERNAL_EXCLUDE,
|
|
272
277
|
forbiddenGlobals: DEFAULT_DOMAIN_FORBIDDEN_GLOBALS,
|
|
273
278
|
optional: true,
|
|
274
279
|
},
|
|
275
280
|
{
|
|
276
|
-
name: '
|
|
277
|
-
description: '
|
|
281
|
+
name: 'ApplicationOrchestration',
|
|
282
|
+
description: 'Server actions, features, and non-UI lib orchestration (when present).',
|
|
278
283
|
patterns: [
|
|
279
|
-
'
|
|
280
|
-
'
|
|
281
|
-
'
|
|
282
|
-
'
|
|
283
|
-
'
|
|
284
|
-
'
|
|
285
|
-
'
|
|
284
|
+
'src/features/**',
|
|
285
|
+
'src/server/**',
|
|
286
|
+
'src/services/**',
|
|
287
|
+
'src/use-cases/**',
|
|
288
|
+
'src/actions/**',
|
|
289
|
+
'src/lib/actions/**',
|
|
290
|
+
'src/lib/services/**',
|
|
291
|
+
'**/lib/actions/**',
|
|
292
|
+
'**/lib/services/**',
|
|
286
293
|
],
|
|
287
294
|
exclude: FRAMEWORK_INTERNAL_EXCLUDE,
|
|
288
295
|
optional: true,
|
|
@@ -290,7 +297,61 @@ export const ARCHITECTURE_PRESETS = {
|
|
|
290
297
|
{
|
|
291
298
|
name: 'PersistenceAdapters',
|
|
292
299
|
description: 'Client data access and external API adapters (when present).',
|
|
293
|
-
|
|
300
|
+
// Prefer specific data-client bags over a presentation catch-all on **/lib/**
|
|
301
|
+
patterns: [
|
|
302
|
+
'**/infrastructure/**',
|
|
303
|
+
'**/adapters/**',
|
|
304
|
+
'**/repositories/**',
|
|
305
|
+
'**/persistence/**',
|
|
306
|
+
'src/db/**',
|
|
307
|
+
'src/data/**',
|
|
308
|
+
'src/lib/db/**',
|
|
309
|
+
'src/lib/prisma/**',
|
|
310
|
+
'src/lib/supabase/**',
|
|
311
|
+
'src/lib/airtable/**',
|
|
312
|
+
'src/lib/firebase/**',
|
|
313
|
+
'src/lib/firestore/**',
|
|
314
|
+
'src/lib/mongodb/**',
|
|
315
|
+
'src/lib/mongoose/**',
|
|
316
|
+
'src/lib/drizzle/**',
|
|
317
|
+
'src/lib/kysely/**',
|
|
318
|
+
'**/lib/supabase/**',
|
|
319
|
+
'**/lib/airtable/**',
|
|
320
|
+
'**/lib/prisma/**',
|
|
321
|
+
'**/lib/db/**',
|
|
322
|
+
],
|
|
323
|
+
exclude: FRAMEWORK_INTERNAL_EXCLUDE,
|
|
324
|
+
optional: true,
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
name: 'PresentationAdapters',
|
|
328
|
+
description: 'UI, routes, hooks, components (not a whole-src bag).',
|
|
329
|
+
// No **/src/** or bare **/lib/** — those swallowed data clients and forced false ENFORCE.
|
|
330
|
+
patterns: [
|
|
331
|
+
'**/components/**',
|
|
332
|
+
'**/hooks/**',
|
|
333
|
+
'**/routes/**',
|
|
334
|
+
'**/app/**',
|
|
335
|
+
'**/pages/**',
|
|
336
|
+
'src/app/**',
|
|
337
|
+
'src/pages/**',
|
|
338
|
+
'src/components/**',
|
|
339
|
+
'src/hooks/**',
|
|
340
|
+
'src/ui/**',
|
|
341
|
+
'src/layouts/**',
|
|
342
|
+
'app/**',
|
|
343
|
+
'pages/**',
|
|
344
|
+
'components/**',
|
|
345
|
+
// Next middleware edge entry (classic + Next 16 proxy rename)
|
|
346
|
+
'src/middleware.ts',
|
|
347
|
+
'src/middleware.js',
|
|
348
|
+
'middleware.ts',
|
|
349
|
+
'middleware.js',
|
|
350
|
+
'src/proxy.ts',
|
|
351
|
+
'src/proxy.js',
|
|
352
|
+
'proxy.ts',
|
|
353
|
+
'proxy.js',
|
|
354
|
+
],
|
|
294
355
|
exclude: FRAMEWORK_INTERNAL_EXCLUDE,
|
|
295
356
|
optional: true,
|
|
296
357
|
},
|
|
@@ -298,7 +359,20 @@ export const ARCHITECTURE_PRESETS = {
|
|
|
298
359
|
rules: [
|
|
299
360
|
{ from: 'DomainModel', to: 'PresentationAdapters', allowed: false },
|
|
300
361
|
{ from: 'DomainModel', to: 'PersistenceAdapters', allowed: false },
|
|
301
|
-
{ from: '
|
|
362
|
+
{ from: 'DomainModel', to: 'ApplicationOrchestration', allowed: false },
|
|
363
|
+
{ from: 'ApplicationOrchestration', to: 'PresentationAdapters', allowed: false },
|
|
364
|
+
// Next RSC often imports data clients from routes; deny is ideal but day-one
|
|
365
|
+
// ui-surface keeps this as a soft guidance edge (allowed) until ports exist —
|
|
366
|
+
// Persistence → Presentation stays denied when that edge appears.
|
|
367
|
+
{
|
|
368
|
+
from: 'PresentationAdapters',
|
|
369
|
+
to: 'PersistenceAdapters',
|
|
370
|
+
allowed: true,
|
|
371
|
+
message:
|
|
372
|
+
'UI/routes may reach data clients on day one (RSC); prefer application ports as the product grows.',
|
|
373
|
+
},
|
|
374
|
+
{ from: 'PersistenceAdapters', to: 'PresentationAdapters', allowed: false },
|
|
375
|
+
{ from: 'PersistenceAdapters', to: 'ApplicationOrchestration', allowed: false },
|
|
302
376
|
],
|
|
303
377
|
},
|
|
304
378
|
root
|