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.
Files changed (57) hide show
  1. package/CHANGELOG.md +142 -0
  2. package/README.md +7 -5
  3. package/bin/ark-check-runtime.mjs +244 -25
  4. package/bin/ark-check.mjs +10 -1
  5. package/bin/ark-layer-match.mjs +80 -5
  6. package/bin/ark-shared.mjs +170 -9
  7. package/bin/ark.mjs +52 -5
  8. package/bin/lib/adapter-contract.mjs +7 -1
  9. package/bin/lib/agent-gates.mjs +2 -0
  10. package/bin/lib/analysis-engine.mjs +6 -6
  11. package/bin/lib/arkrules-sensors.mjs +63 -22
  12. package/bin/lib/ci-and-commands.mjs +148 -8
  13. package/bin/lib/core-ratchet.mjs +9 -4
  14. package/bin/lib/doctor-advisories.mjs +8 -1
  15. package/bin/lib/doctor-plan.mjs +277 -59
  16. package/bin/lib/enforcement-honesty.mjs +351 -26
  17. package/bin/lib/enforcement-state.mjs +1 -1
  18. package/bin/lib/field-install.mjs +35 -2
  19. package/bin/lib/graph-blind.mjs +1 -1
  20. package/bin/lib/html-report-advisories.mjs +8 -25
  21. package/bin/lib/html-report-depth.mjs +167 -3
  22. package/bin/lib/html-report.mjs +12 -5
  23. package/bin/lib/install-migrate.mjs +109 -6
  24. package/bin/lib/managed-upgrade.mjs +100 -1
  25. package/bin/lib/presets.mjs +314 -46
  26. package/bin/lib/project-root.mjs +268 -0
  27. package/bin/lib/remediation.mjs +12 -11
  28. package/bin/lib/rules-inventory.mjs +71 -29
  29. package/bin/lib/rules-under-contract.mjs +389 -5
  30. package/bin/lib/start-preview.mjs +48 -14
  31. package/bin/lib/suggestions.mjs +118 -3
  32. package/bin/lib/unavailable-analysis.mjs +2 -0
  33. package/bin/lib/upgrade-command.mjs +325 -14
  34. package/bin/lib/write-path-capabilities.mjs +38 -9
  35. package/dist/eslint/index.cjs +2 -2
  36. package/dist/eslint/index.d.ts +27 -2
  37. package/dist/eslint/index.js +2 -2
  38. package/dist/index.cjs +16 -14
  39. package/dist/index.d.ts +3 -1
  40. package/dist/index.js +16 -14
  41. package/docs/README.md +3 -2
  42. package/docs/ai-gates.md +15 -11
  43. package/docs/brownfield-adoption.md +38 -0
  44. package/docs/configuration.md +59 -7
  45. package/docs/package-surface.md +3 -2
  46. package/docs/product-voice.md +10 -1
  47. package/docs/typescript-support.md +9 -5
  48. package/docs/use.md +7 -5
  49. package/package.json +3 -1
  50. package/server.json +3 -3
  51. package/templates/architecture-playbook.json +3 -0
  52. package/templates/layers/shared-types.starter.json +29 -0
  53. package/templates/skills/ark-adopt.md +2 -0
  54. package/templates/skills/ark-explain.md +23 -5
  55. package/templates/skills/ark-explore.md +21 -1
  56. package/templates/skills/ark-fix.md +16 -5
  57. package/templates/skills/ark-upgrade.md +57 -11
@@ -56,6 +56,143 @@ export function peerIsolationEdges(layerNames, sliceFolders, message) {
56
56
  // `src/**/domain/**` would otherwise swallow `src/kernel/domain`. Do NOT use `**/kernel/**`
57
57
  // (that carves out legitimate `src/shared/kernel/**` SharedKernel paths).
58
58
  export const FRAMEWORK_INTERNAL_EXCLUDE = ['src/kernel/**', '**/src/kernel/**'];
59
+
60
+ /**
61
+ * Shared high-spec domain globs. Prefer these over Application/Presentation scatter
62
+ * (path-anchored specificity ranks interior domain above broad src/lib bags).
63
+ * Include kernel/domain paths for event-kernel trees; framework src/kernel
64
+ * exclude still applies when set on a layer.
65
+ */
66
+ export const DOMAIN_PATH_PATTERNS = Object.freeze([
67
+ '**/domain/**',
68
+ '**/entities/**',
69
+ '**/kernel/domain/**',
70
+ 'src/**/domain/**',
71
+ 'src/**/entities/**',
72
+ 'src/domain/**',
73
+ 'src/entities/**',
74
+ ]);
75
+
76
+ /**
77
+ * Next / App Router API shells — Application orchestration, not Presentation UI.
78
+ * High-spec patterns win over broad app Presentation globs (P0-A / DL-P0A-RETROFIT).
79
+ */
80
+ export const NEXT_API_APPLICATION_PATTERNS = Object.freeze([
81
+ '**/app/api/**',
82
+ '**/pages/api/**',
83
+ '**/app/**/api/**',
84
+ '**/pages/**/api/**',
85
+ 'app/api/**',
86
+ 'pages/api/**',
87
+ 'src/app/api/**',
88
+ 'src/pages/api/**',
89
+ 'src/app/**/api/**',
90
+ 'app/**/api/**',
91
+ // Explicit route handlers under api/ — beat Presentation src/**/route.ts when present.
92
+ 'src/app/api/**/route.ts',
93
+ 'src/app/api/**/route.tsx',
94
+ 'src/app/api/**/route.js',
95
+ 'app/api/**/route.ts',
96
+ 'app/api/**/route.tsx',
97
+ '**/app/api/**/route.ts',
98
+ '**/app/api/**/route.tsx',
99
+ '**/pages/api/**/*.ts',
100
+ '**/pages/api/**/*.js',
101
+ ]);
102
+
103
+ /**
104
+ * Data-client / repository / auth-client bags. Higher path-anchored specificity than
105
+ * bare `lib/**` or `src/lib/**` Application vacuum (NEW-APP-VACUUM-LIB, NEW-ADOPT-LIB-AS-PRESENTATION).
106
+ */
107
+ export const PERSISTENCE_PATH_PATTERNS = Object.freeze([
108
+ '**/repositories/**',
109
+ '**/repository/**',
110
+ '**/persistence/**',
111
+ '**/infrastructure/**',
112
+ '**/infra/**',
113
+ '**/db/**',
114
+ '**/data/**',
115
+ '**/supabase/**',
116
+ '**/airtable/**',
117
+ '**/prisma/**',
118
+ '**/turso/**',
119
+ '**/drizzle/**',
120
+ '**/kysely/**',
121
+ '**/mongoose/**',
122
+ '**/mongodb/**',
123
+ '**/firebase/**',
124
+ '**/firestore/**',
125
+ '**/planetscale/**',
126
+ '**/neon/**',
127
+ '**/lib/db/**',
128
+ '**/lib/prisma/**',
129
+ '**/lib/supabase/**',
130
+ '**/lib/airtable/**',
131
+ '**/lib/turso/**',
132
+ '**/lib/drizzle/**',
133
+ '**/lib/firebase/**',
134
+ '**/lib/firestore/**',
135
+ '**/lib/mongodb/**',
136
+ '**/lib/mongoose/**',
137
+ '**/lib/kysely/**',
138
+ '**/lib/auth/**',
139
+ 'src/db/**',
140
+ 'src/data/**',
141
+ 'src/repositories/**',
142
+ 'src/persistence/**',
143
+ 'src/infrastructure/**',
144
+ 'src/lib/db/**',
145
+ 'src/lib/prisma/**',
146
+ 'src/lib/supabase/**',
147
+ 'src/lib/airtable/**',
148
+ 'src/lib/turso/**',
149
+ 'src/lib/drizzle/**',
150
+ 'src/lib/firebase/**',
151
+ 'src/lib/firestore/**',
152
+ 'src/lib/mongodb/**',
153
+ 'src/lib/mongoose/**',
154
+ 'src/lib/kysely/**',
155
+ 'src/lib/auth/**',
156
+ 'src/server/db/**',
157
+ 'lib/db/**',
158
+ 'lib/prisma/**',
159
+ 'lib/supabase/**',
160
+ 'lib/airtable/**',
161
+ 'lib/turso/**',
162
+ 'lib/auth/**',
163
+ // Single-file clients at lib root (SPA / Vercel serverless companions)
164
+ 'lib/turso.js',
165
+ 'lib/turso.ts',
166
+ 'lib/prisma.js',
167
+ 'lib/prisma.ts',
168
+ 'lib/supabase.js',
169
+ 'lib/supabase.ts',
170
+ 'lib/airtable.js',
171
+ 'lib/airtable.ts',
172
+ 'lib/auth.js',
173
+ 'lib/auth.ts',
174
+ 'lib/db.js',
175
+ 'lib/db.ts',
176
+ ]);
177
+
178
+ /**
179
+ * Application orchestration under lib without the whole-src-lib vacuum.
180
+ * Never use lone `src/**` or bare `src/lib/**` as Application on Next/event trees.
181
+ */
182
+ export const APPLICATION_LIB_ORCHESTRATION_PATTERNS = Object.freeze([
183
+ 'src/lib/actions/**',
184
+ 'src/lib/services/**',
185
+ 'src/lib/server/**',
186
+ 'src/lib/use-cases/**',
187
+ 'src/lib/usecases/**',
188
+ 'src/lib/api-handlers/**',
189
+ 'src/lib/handlers/**',
190
+ '**/lib/actions/**',
191
+ '**/lib/services/**',
192
+ '**/lib/server/**',
193
+ '**/lib/api-handlers/**',
194
+ ]);
195
+
59
196
  /**
60
197
  * AR08 — attach lean arkRules map. Keys are always exact project layer names.
61
198
  * Sensor roles (domain-structure / orchestration / adapter-thin / generic) are
@@ -306,7 +443,7 @@ export const ARCHITECTURE_PRESETS = {
306
443
  {
307
444
  name: 'DomainModel',
308
445
  description: 'Pure business rules and entities. No I/O, no framework, no ambient globals.',
309
- patterns: ['src/**/domain/**'],
446
+ patterns: [...DOMAIN_PATH_PATTERNS],
310
447
  exclude: FRAMEWORK_INTERNAL_EXCLUDE,
311
448
  forbiddenGlobals: DEFAULT_DOMAIN_FORBIDDEN_GLOBALS,
312
449
  optional: true,
@@ -314,7 +451,14 @@ export const ARCHITECTURE_PRESETS = {
314
451
  {
315
452
  name: 'ApplicationOrchestration',
316
453
  description: 'Use cases that coordinate the domain through ports. No I/O of its own.',
317
- patterns: ['src/**/application/**'],
454
+ // Never lone src/** — Application is intentional orchestration folders only.
455
+ patterns: [
456
+ 'src/**/application/**',
457
+ 'src/**/use-cases/**',
458
+ 'src/**/usecases/**',
459
+ ...APPLICATION_LIB_ORCHESTRATION_PATTERNS,
460
+ ...NEXT_API_APPLICATION_PATTERNS,
461
+ ],
318
462
  exclude: FRAMEWORK_INTERNAL_EXCLUDE,
319
463
  optional: true,
320
464
  },
@@ -336,8 +480,7 @@ export const ARCHITECTURE_PRESETS = {
336
480
  patterns: [
337
481
  'src/**/infrastructure/**',
338
482
  'src/**/adapters/**',
339
- 'src/**/persistence/**',
340
- 'src/**/repositories/**',
483
+ ...PERSISTENCE_PATH_PATTERNS,
341
484
  ],
342
485
  exclude: FRAMEWORK_INTERNAL_EXCLUDE,
343
486
  optional: true,
@@ -376,15 +519,26 @@ export const ARCHITECTURE_PRESETS = {
376
519
  },
377
520
  {
378
521
  name: 'ApplicationOrchestration',
379
- description: 'Business services and use-case coordination.',
380
- patterns: ['src/**/application/**', 'src/**/services/**'],
522
+ description:
523
+ 'Business services and use-case coordination. Residual `src/**` is Application on non-Next layered trees only (specific domain/ui/db patterns win by specificity).',
524
+ patterns: [
525
+ 'src/**/application/**',
526
+ 'src/**/services/**',
527
+ 'src/**/use-cases/**',
528
+ ...APPLICATION_LIB_ORCHESTRATION_PATTERNS,
529
+ ...NEXT_API_APPLICATION_PATTERNS,
530
+ // Greenfield residual: bare src/index.ts etc. Domain/Presentation/Persistence
531
+ // patterns outrank this by specificity. Next overlay replaces Application bags
532
+ // and must NOT reintroduce a lone src/** catch-all on App Router trees.
533
+ 'src/**',
534
+ ],
381
535
  exclude: FRAMEWORK_INTERNAL_EXCLUDE,
382
536
  optional: true,
383
537
  },
384
538
  {
385
539
  name: 'DomainModel',
386
540
  description: 'Pure business rules and entities. No I/O, no framework, no ambient globals.',
387
- patterns: ['src/**/domain/**'],
541
+ patterns: [...DOMAIN_PATH_PATTERNS],
388
542
  exclude: FRAMEWORK_INTERNAL_EXCLUDE,
389
543
  forbiddenGlobals: DEFAULT_DOMAIN_FORBIDDEN_GLOBALS,
390
544
  optional: true,
@@ -392,12 +546,7 @@ export const ARCHITECTURE_PRESETS = {
392
546
  {
393
547
  name: 'PersistenceAdapters',
394
548
  description: 'Data access and infrastructure.',
395
- patterns: [
396
- 'src/**/persistence/**',
397
- 'src/**/data/**',
398
- 'src/**/repositories/**',
399
- 'src/**/infrastructure/**',
400
- ],
549
+ patterns: [...PERSISTENCE_PATH_PATTERNS],
401
550
  exclude: FRAMEWORK_INTERNAL_EXCLUDE,
402
551
  optional: true,
403
552
  },
@@ -490,8 +639,7 @@ export const ARCHITECTURE_PRESETS = {
490
639
  // Domain by intentional folders only — NOT bare **/types.ts (that mis-classifies
491
640
  // application bags like frontend/src/core/**/types.ts as Domain and creates false edges).
492
641
  patterns: [
493
- '**/domain/**',
494
- '**/entities/**',
642
+ ...DOMAIN_PATH_PATTERNS,
495
643
  '**/cinematic/types.ts',
496
644
  ...librarySourcePatterns,
497
645
  ],
@@ -500,18 +648,24 @@ export const ARCHITECTURE_PRESETS = {
500
648
  },
501
649
  {
502
650
  name: 'ApplicationOrchestration',
503
- description: 'Use cases and services that coordinate the domain through ports.',
651
+ description:
652
+ 'Use cases and services that coordinate the domain through ports. Next App Router API (`app/api/**`) and Pages API (`pages/api/**`) are orchestration shells, not UI.',
504
653
  patterns: [
505
654
  '**/application/**',
506
655
  '**/use-cases/**',
507
656
  '**/services/**',
657
+ // Next API route handlers (higher specificity than **/app/** Presentation).
658
+ // Route groups: app/(marketing)/api/** also match **/app/**/api/**
659
+ ...NEXT_API_APPLICATION_PATTERNS,
660
+ ...APPLICATION_LIB_ORCHESTRATION_PATTERNS,
508
661
  ...applicationSourcePatterns,
509
662
  ],
510
663
  optional: true,
511
664
  },
512
665
  {
513
666
  name: 'PresentationAdapters',
514
- description: 'Entrypoints — HTTP routes, controllers, UI, framework app/pages dirs.',
667
+ description:
668
+ 'Entrypoints — UI, framework app/pages dirs, controllers. Next `app/api` is Application, not this layer. Never bare lib/** (data clients are Persistence).',
515
669
  patterns: [
516
670
  '**/app/**',
517
671
  '**/pages/**',
@@ -520,7 +674,7 @@ export const ARCHITECTURE_PRESETS = {
520
674
  '**/http/**',
521
675
  '**/routes/**',
522
676
  '**/hooks/**',
523
- '**/lib/**',
677
+ // No bare **/lib/** — NEW-ADOPT-LIB-AS-PRESENTATION / NEW-APP-VACUUM-LIB.
524
678
  ],
525
679
  optional: true,
526
680
  },
@@ -528,10 +682,8 @@ export const ARCHITECTURE_PRESETS = {
528
682
  name: 'PersistenceAdapters',
529
683
  description: 'Implements ports with real infrastructure: DB, external APIs, filesystem.',
530
684
  patterns: [
531
- '**/infrastructure/**',
532
685
  '**/adapters/**',
533
- '**/persistence/**',
534
- '**/repositories/**',
686
+ ...PERSISTENCE_PATH_PATTERNS,
535
687
  ],
536
688
  optional: true,
537
689
  },
@@ -574,7 +726,7 @@ export const ARCHITECTURE_PRESETS = {
574
726
  description: 'Shared types and pure view-models (optional on UI-first trees).',
575
727
  // Avoid bare **/types.ts — see monorepo DomainModel note (false Domain on core/**/types.ts).
576
728
  patterns: [
577
- '**/domain/**',
729
+ ...DOMAIN_PATH_PATTERNS,
578
730
  '**/cinematic/types.ts',
579
731
  // Common pure types bag (not **/types.ts — that traps core/**/types.ts).
580
732
  'src/lib/types.ts',
@@ -585,17 +737,18 @@ export const ARCHITECTURE_PRESETS = {
585
737
  },
586
738
  {
587
739
  name: 'ApplicationOrchestration',
588
- description: 'Server actions, features, and non-UI lib orchestration (when present).',
740
+ description:
741
+ 'Server actions, features, Next API routes (`app/api/**` / `pages/api/**`), and non-UI lib orchestration (when present).',
589
742
  patterns: [
590
743
  'src/features/**',
591
744
  'src/server/**',
592
745
  'src/services/**',
593
746
  'src/use-cases/**',
594
747
  'src/actions/**',
595
- 'src/lib/actions/**',
596
- 'src/lib/services/**',
597
- '**/lib/actions/**',
598
- '**/lib/services/**',
748
+ // Specific lib orchestration only — never bare src/lib/** (NEW-APP-VACUUM-LIB).
749
+ ...APPLICATION_LIB_ORCHESTRATION_PATTERNS,
750
+ // Next API = Application shell (wins over **/app/** Presentation by specificity).
751
+ ...NEXT_API_APPLICATION_PATTERNS,
599
752
  ],
600
753
  exclude: FRAMEWORK_INTERNAL_EXCLUDE,
601
754
  optional: true,
@@ -605,26 +758,8 @@ export const ARCHITECTURE_PRESETS = {
605
758
  description: 'Client data access and external API adapters (when present).',
606
759
  // Prefer specific data-client bags over a presentation catch-all on **/lib/**
607
760
  patterns: [
608
- '**/infrastructure/**',
609
761
  '**/adapters/**',
610
- '**/repositories/**',
611
- '**/persistence/**',
612
- 'src/db/**',
613
- 'src/data/**',
614
- 'src/lib/db/**',
615
- 'src/lib/prisma/**',
616
- 'src/lib/supabase/**',
617
- 'src/lib/airtable/**',
618
- 'src/lib/firebase/**',
619
- 'src/lib/firestore/**',
620
- 'src/lib/mongodb/**',
621
- 'src/lib/mongoose/**',
622
- 'src/lib/drizzle/**',
623
- 'src/lib/kysely/**',
624
- '**/lib/supabase/**',
625
- '**/lib/airtable/**',
626
- '**/lib/prisma/**',
627
- '**/lib/db/**',
762
+ ...PERSISTENCE_PATH_PATTERNS,
628
763
  ],
629
764
  exclude: FRAMEWORK_INTERNAL_EXCLUDE,
630
765
  optional: true,
@@ -850,6 +985,104 @@ export const ARCHITECTURE_PRESETS = {
850
985
  root
851
986
  );
852
987
  },
988
+
989
+ /**
990
+ * Vite SPA + root Vercel `api/` / `lib/` (NEW-SPA-DEFAULT-LAYOUT).
991
+ * include covers src + serverless api + shared lib; never hexagonal src-only vacuum.
992
+ */
993
+ 'vite-vercel-spa': (_workspaces, root) =>
994
+ presetWithOverlays(
995
+ {
996
+ include: (() => {
997
+ if (!root) return ['src', 'api', 'lib'];
998
+ const dirs = ['src', 'api', 'lib'];
999
+ try {
1000
+ return dirs.filter((d) => fs.existsSync(path.join(root, d)));
1001
+ } catch {
1002
+ return dirs;
1003
+ }
1004
+ })(),
1005
+ layers: [
1006
+ {
1007
+ name: 'DomainModel',
1008
+ description: 'Pure domain folders when present (optional on SPA trees).',
1009
+ patterns: [...DOMAIN_PATH_PATTERNS],
1010
+ exclude: FRAMEWORK_INTERNAL_EXCLUDE,
1011
+ forbiddenGlobals: DEFAULT_DOMAIN_FORBIDDEN_GLOBALS,
1012
+ optional: true,
1013
+ },
1014
+ {
1015
+ name: 'ApplicationOrchestration',
1016
+ description:
1017
+ 'Vercel/serverless `api/**` handlers and non-UI orchestration (not Presentation).',
1018
+ patterns: [
1019
+ 'api/**',
1020
+ '**/api/**',
1021
+ 'src/api/**',
1022
+ 'src/server/**',
1023
+ 'src/services/**',
1024
+ 'src/actions/**',
1025
+ ...APPLICATION_LIB_ORCHESTRATION_PATTERNS,
1026
+ ...NEXT_API_APPLICATION_PATTERNS,
1027
+ ],
1028
+ exclude: FRAMEWORK_INTERNAL_EXCLUDE,
1029
+ optional: true,
1030
+ },
1031
+ {
1032
+ name: 'PersistenceAdapters',
1033
+ description: 'DB / CRM / auth clients under lib/ and data folders.',
1034
+ patterns: [...PERSISTENCE_PATH_PATTERNS, '**/adapters/**'],
1035
+ exclude: FRAMEWORK_INTERNAL_EXCLUDE,
1036
+ optional: true,
1037
+ },
1038
+ {
1039
+ name: 'PresentationAdapters',
1040
+ description:
1041
+ 'React/Vite UI surface. Not bare lib/** (data clients stay Persistence).',
1042
+ patterns: [
1043
+ 'src/components/**',
1044
+ 'src/hooks/**',
1045
+ 'src/pages/**',
1046
+ 'src/routes/**',
1047
+ 'src/ui/**',
1048
+ 'src/layouts/**',
1049
+ 'src/App.tsx',
1050
+ 'src/App.jsx',
1051
+ 'src/App.ts',
1052
+ 'src/App.js',
1053
+ 'src/main.tsx',
1054
+ 'src/main.jsx',
1055
+ 'src/main.ts',
1056
+ 'src/main.js',
1057
+ 'src/index.tsx',
1058
+ 'src/index.jsx',
1059
+ '**/components/**',
1060
+ '**/hooks/**',
1061
+ '**/pages/**',
1062
+ '**/routes/**',
1063
+ ],
1064
+ exclude: FRAMEWORK_INTERNAL_EXCLUDE,
1065
+ optional: true,
1066
+ },
1067
+ ],
1068
+ rules: [
1069
+ { from: 'DomainModel', to: 'PresentationAdapters', allowed: false },
1070
+ { from: 'DomainModel', to: 'PersistenceAdapters', allowed: false },
1071
+ { from: 'DomainModel', to: 'ApplicationOrchestration', allowed: false },
1072
+ { from: 'ApplicationOrchestration', to: 'PresentationAdapters', allowed: false },
1073
+ {
1074
+ from: 'PresentationAdapters',
1075
+ to: 'PersistenceAdapters',
1076
+ allowed: true,
1077
+ message:
1078
+ 'SPA day-one UI may reach data clients; prefer ports as the product grows.',
1079
+ },
1080
+ { from: 'PersistenceAdapters', to: 'PresentationAdapters', allowed: false },
1081
+ { from: 'PersistenceAdapters', to: 'ApplicationOrchestration', allowed: false },
1082
+ ],
1083
+ },
1084
+ root
1085
+ ),
853
1086
  };
854
1087
 
855
1088
  // Aliases: Clean / Onion map to the hexagonal factory (same matrix + globs). Avoid dual maintenance.
@@ -859,6 +1092,41 @@ ARCHITECTURE_PRESETS['onion-architecture'] = ARCHITECTURE_PRESETS.hexagonal;
859
1092
  /** Stable public preset keys (CLI help, score fit, docs). Order is display order. */
860
1093
  export const ARCHITECTURE_PRESET_NAMES = Object.keys(ARCHITECTURE_PRESETS);
861
1094
 
1095
+ /**
1096
+ * Additive P0-A retrofit: inject high-spec Next API → Application patterns when missing.
1097
+ * Pure — does not write files (DL-P0A-RETROFIT).
1098
+ *
1099
+ * @param {object} config ark.config-shaped object
1100
+ * @returns {{ changed: boolean, injected: string[], targetLayer: string|null, config: object }}
1101
+ */
1102
+ export function retrofitP0aApiApplicationPatterns(config) {
1103
+ const layers = Array.isArray(config?.layers) ? config.layers : [];
1104
+ const appLayer =
1105
+ layers.find((l) => l?.name === 'ApplicationOrchestration') ||
1106
+ layers.find((l) => /application|orchestr/i.test(l?.name ?? ''));
1107
+ if (!appLayer) {
1108
+ return { changed: false, injected: [], targetLayer: null, config };
1109
+ }
1110
+ const existing = new Set(appLayer.patterns ?? []);
1111
+ const injected = NEXT_API_APPLICATION_PATTERNS.filter((p) => !existing.has(p));
1112
+ if (injected.length === 0) {
1113
+ return { changed: false, injected: [], targetLayer: appLayer.name, config };
1114
+ }
1115
+ const nextLayers = layers.map((layer) => {
1116
+ if (layer.name !== appLayer.name) return layer;
1117
+ return {
1118
+ ...layer,
1119
+ patterns: [...new Set([...(layer.patterns ?? []), ...injected])],
1120
+ };
1121
+ });
1122
+ return {
1123
+ changed: true,
1124
+ injected: [...injected],
1125
+ targetLayer: appLayer.name,
1126
+ config: { ...config, layers: nextLayers },
1127
+ };
1128
+ }
1129
+
862
1130
  // ── Layer suggestion engine ──────────────────────────────────────────────────
863
1131
  // Everything here is HARVESTED from Ark's own canonical sources — the 11-layer defaults
864
1132
  // (DEFAULT_LAYER_DIRECTORIES) and the named presets — so a suggestion can never drift from