@ultrst-armada/krnl 1.0.2 → 1.0.4
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/README.md +67 -65
- package/package.json +15 -8
- package/packages/cli/dist/bin/krnl.cjs +808 -331
- package/packages/cli/dist/bin/krnl.js +50 -65
- package/packages/cli/dist/chunk-GHWYEAPD.js +1642 -0
- package/packages/cli/dist/index.cjs +761 -263
- package/packages/cli/dist/index.d.cts +45 -2
- package/packages/cli/dist/index.d.ts +45 -2
- package/packages/cli/dist/index.js +5 -1
- package/packages/cli/package.json +17 -10
- package/packages/core/dist/index.cjs +2912 -140
- package/packages/core/dist/index.d.cts +614 -27
- package/packages/core/dist/index.d.ts +614 -27
- package/packages/core/dist/index.js +2903 -140
- package/packages/core/package.json +15 -6
- package/packages/create-krnl/dist/index.cjs +102 -65
- package/packages/create-krnl/dist/index.js +102 -65
- package/packages/create-krnl/package.json +5 -3
- package/packages/eslint-plugin/dist/index.cjs +17 -55
- package/packages/eslint-plugin/dist/index.d.cts +8 -43
- package/packages/eslint-plugin/dist/index.d.ts +8 -43
- package/packages/eslint-plugin/dist/index.js +17 -55
- package/packages/eslint-plugin/package.json +5 -4
- package/packages/validate/dist/index.cjs +378 -85
- package/packages/validate/dist/index.d.cts +11 -1
- package/packages/validate/dist/index.d.ts +11 -1
- package/packages/validate/dist/index.js +375 -82
- package/packages/validate/package.json +14 -7
- package/packages/cli/dist/K_ENT_Entity-PQ5MURQY-ZG6FZXXX.js +0 -13
- package/packages/cli/dist/K_LIB_Krnl_Aiko_Client-OPZAVZCW-K4CRQYA5.js +0 -8
- package/packages/cli/dist/K_LIB_Krnl_Ep_Tools-ROOE2TQF-LNPXZYOE.js +0 -110
- package/packages/cli/dist/K_LIB_Krnl_Frontend_Builder-2ZDUH6PF-3ALZPNX4.js +0 -11
- package/packages/cli/dist/K_LIB_Krnl_Frontend_Generator-UYNRNX2X-PJTICYSW.js +0 -11
- package/packages/cli/dist/K_LIB_Krnl_Registry_Client-NXV6D2QW-M5TYU55O.js +0 -8
- package/packages/cli/dist/K_LIB_Krnl_SubApp_Engine-7HR5HWW2-H6STOXRK.js +0 -20
- package/packages/cli/dist/K_LIB_Krnl_SubApp_Registry-7XYGDKJK-XPJXSF7D.js +0 -19
- package/packages/cli/dist/K_LIB_Krnl_SubApp_Worker_Host-CQVTGGG7-CUYGTWOW.js +0 -7
- package/packages/cli/dist/K_LIB_Krnl_System_App_Adapter-M6FJKLGU-VR6T4N6J.js +0 -17
- package/packages/cli/dist/K_LIB_Krnl_System_Base_Functionality_APIs-ZZUZ6ZG5-RLJ4QWDP.js +0 -58
- package/packages/cli/dist/K_LIB_Krnl_System_Magnetic_Loader-XBQO2KXF-BOQGUASC.js +0 -22
- package/packages/cli/dist/K_LIB_Krnl_System_Unified_Http_Adapter-RCEGP3OG-XJI7KIT5.js +0 -8
- package/packages/cli/dist/K_LIB_Krnl_Telemetry-77FRZ3KV-K5VCWAFW.js +0 -9
- package/packages/cli/dist/chunk-3TRM6CYL.js +0 -152
- package/packages/cli/dist/chunk-5JDK35TG.js +0 -3872
- package/packages/cli/dist/chunk-63PTK33I.js +0 -997
- package/packages/cli/dist/chunk-6NIKAMUZ.js +0 -82
- package/packages/cli/dist/chunk-BOXWWXQG.js +0 -400
- package/packages/cli/dist/chunk-EEKLK7QJ.js +0 -565
- package/packages/cli/dist/chunk-EG7T2IAD.js +0 -107
- package/packages/cli/dist/chunk-ESTKOQ7T.js +0 -1146
- package/packages/cli/dist/chunk-F5EHXE2O.js +0 -1036
- package/packages/cli/dist/chunk-F65ANSQG.js +0 -269
- package/packages/cli/dist/chunk-FDNCADFE.js +0 -71
- package/packages/cli/dist/chunk-FZ6X7PHR.js +0 -1146
- package/packages/cli/dist/chunk-GDLRMDAN.js +0 -127
- package/packages/cli/dist/chunk-HWMTCMOS.js +0 -656
- package/packages/cli/dist/chunk-I74QIQOD.js +0 -5536
- package/packages/cli/dist/chunk-JTLSTO25.js +0 -71
- package/packages/cli/dist/chunk-JULBBJ27.js +0 -1033
- package/packages/cli/dist/chunk-OHVMVRBP.js +0 -117
- package/packages/cli/dist/chunk-P2PCLEKX.js +0 -666
- package/packages/cli/dist/chunk-PJQIUOX2.js +0 -7767
- package/packages/cli/dist/chunk-PPOF3DJW.js +0 -471
- package/packages/cli/dist/chunk-Q45257P6.js +0 -173
- package/packages/cli/dist/chunk-QNROWGBI.js +0 -1036
- package/packages/cli/dist/chunk-RJNYUXZD.js +0 -977
- package/packages/cli/dist/chunk-SRTOPJ5W.js +0 -104
- package/packages/cli/dist/chunk-TCEX3MPS.js +0 -702
- package/packages/cli/dist/chunk-TNOAJO2J.js +0 -675
- package/packages/cli/dist/chunk-UR2SUCSF.js +0 -701
- package/packages/cli/dist/chunk-X5LOIFM7.js +0 -68
- package/packages/cli/dist/chunk-XIRFOO3H.js +0 -589
- package/packages/cli/dist/chunk-ZAAV3JXQ.js +0 -809
- package/packages/core/dist/K_ENT_Entity-4MB7CU4A.js +0 -12
- package/packages/core/dist/K_ENT_Entity-DEJGIDGH.js +0 -12
- package/packages/core/dist/K_ENT_Entity-FXZO5UZ2.js +0 -12
- package/packages/core/dist/K_ENT_Entity-HDSCXC5F.js +0 -12
- package/packages/core/dist/K_ENT_Entity-JILABHLW.js +0 -12
- package/packages/core/dist/K_ENT_Entity-PQ5MURQY.js +0 -13
- package/packages/core/dist/K_ENT_Entity-ROAKDIHW.js +0 -12
- package/packages/core/dist/K_ENT_Entity-TBVFSKDV.js +0 -12
- package/packages/core/dist/K_ENT_Entity-ZFLKG3JO.js +0 -12
- package/packages/core/dist/K_LIB_Krnl_Aiko_Client-OPZAVZCW.js +0 -8
- package/packages/core/dist/K_LIB_Krnl_Ep_Tools-ROOE2TQF.js +0 -110
- package/packages/core/dist/K_LIB_Krnl_Frontend_Builder-2ZDUH6PF.js +0 -9
- package/packages/core/dist/K_LIB_Krnl_Frontend_Generator-UYNRNX2X.js +0 -9
- package/packages/core/dist/K_LIB_Krnl_Registry_Client-NXV6D2QW.js +0 -8
- package/packages/core/dist/K_LIB_Krnl_SubApp_Engine-7HR5HWW2.js +0 -20
- package/packages/core/dist/K_LIB_Krnl_SubApp_Registry-4C6GBQAG.js +0 -18
- package/packages/core/dist/K_LIB_Krnl_SubApp_Registry-6V23A6F6.js +0 -18
- package/packages/core/dist/K_LIB_Krnl_SubApp_Registry-7XYGDKJK.js +0 -19
- package/packages/core/dist/K_LIB_Krnl_SubApp_Registry-DYLLC5G2.js +0 -18
- package/packages/core/dist/K_LIB_Krnl_SubApp_Registry-F4FOFAUP.js +0 -18
- package/packages/core/dist/K_LIB_Krnl_SubApp_Registry-FBOXXMI3.js +0 -18
- package/packages/core/dist/K_LIB_Krnl_SubApp_Registry-Q73BWLNP.js +0 -18
- package/packages/core/dist/K_LIB_Krnl_SubApp_Registry-QM2C4MVA.js +0 -18
- package/packages/core/dist/K_LIB_Krnl_SubApp_Registry-QUGI6SMP.js +0 -18
- package/packages/core/dist/K_LIB_Krnl_SubApp_Registry-RGMTBHNP.js +0 -18
- package/packages/core/dist/K_LIB_Krnl_SubApp_Registry-RWR7UNDW.js +0 -18
- package/packages/core/dist/K_LIB_Krnl_SubApp_Worker_Host-CQVTGGG7.js +0 -7
- package/packages/core/dist/K_LIB_Krnl_System_App_Adapter-32MFJJ5Z.js +0 -17
- package/packages/core/dist/K_LIB_Krnl_System_App_Adapter-4J4CEWRY.js +0 -17
- package/packages/core/dist/K_LIB_Krnl_System_App_Adapter-A366YHF2.js +0 -17
- package/packages/core/dist/K_LIB_Krnl_System_App_Adapter-LK4LYJHI.js +0 -17
- package/packages/core/dist/K_LIB_Krnl_System_App_Adapter-M6FJKLGU.js +0 -17
- package/packages/core/dist/K_LIB_Krnl_System_App_Adapter-NW3K63II.js +0 -17
- package/packages/core/dist/K_LIB_Krnl_System_App_Adapter-QDHTI5ZV.js +0 -17
- package/packages/core/dist/K_LIB_Krnl_System_App_Adapter-VHCT6VIW.js +0 -17
- package/packages/core/dist/K_LIB_Krnl_System_App_Adapter-WBRKF24R.js +0 -17
- package/packages/core/dist/K_LIB_Krnl_System_App_Adapter-XKYQYT6Q.js +0 -17
- package/packages/core/dist/K_LIB_Krnl_System_App_Adapter-Z2PWZYDZ.js +0 -17
- package/packages/core/dist/K_LIB_Krnl_System_Base_Functionality_APIs-ZZUZ6ZG5.js +0 -58
- package/packages/core/dist/K_LIB_Krnl_System_Magnetic_Loader-2EZU7QF4.js +0 -21
- package/packages/core/dist/K_LIB_Krnl_System_Magnetic_Loader-6G3AQNAV.js +0 -21
- package/packages/core/dist/K_LIB_Krnl_System_Magnetic_Loader-B2NZMQUG.js +0 -21
- package/packages/core/dist/K_LIB_Krnl_System_Magnetic_Loader-C2RABMZP.js +0 -21
- package/packages/core/dist/K_LIB_Krnl_System_Magnetic_Loader-HHE3NZ4F.js +0 -21
- package/packages/core/dist/K_LIB_Krnl_System_Magnetic_Loader-LHFGR6WK.js +0 -21
- package/packages/core/dist/K_LIB_Krnl_System_Magnetic_Loader-M74BLM3N.js +0 -21
- package/packages/core/dist/K_LIB_Krnl_System_Magnetic_Loader-PT6ZA6EA.js +0 -21
- package/packages/core/dist/K_LIB_Krnl_System_Magnetic_Loader-SP52V3CX.js +0 -21
- package/packages/core/dist/K_LIB_Krnl_System_Magnetic_Loader-XBQO2KXF.js +0 -22
- package/packages/core/dist/K_LIB_Krnl_System_Magnetic_Loader-YNSEUHJJ.js +0 -21
- package/packages/core/dist/K_LIB_Krnl_System_Unified_Http_Adapter-M6SBHIVA.js +0 -8
- package/packages/core/dist/K_LIB_Krnl_System_Unified_Http_Adapter-RCEGP3OG.js +0 -8
- package/packages/core/dist/K_LIB_Krnl_Telemetry-77FRZ3KV.js +0 -9
- package/packages/core/dist/chunk-33L2J7V7.js +0 -82
- package/packages/core/dist/chunk-3ES4K2ZP.js +0 -817
- package/packages/core/dist/chunk-3SMHVVLL.js +0 -82
- package/packages/core/dist/chunk-3UEZ7JQA.js +0 -4877
- package/packages/core/dist/chunk-3XIE6KBD.js +0 -4877
- package/packages/core/dist/chunk-46ZLCML6.js +0 -107
- package/packages/core/dist/chunk-4UEL3DWJ.js +0 -107
- package/packages/core/dist/chunk-54GWZ5PC.js +0 -82
- package/packages/core/dist/chunk-6QCYRS5L.js +0 -127
- package/packages/core/dist/chunk-6ULMHNXR.js +0 -104
- package/packages/core/dist/chunk-7WUETJEV.js +0 -117
- package/packages/core/dist/chunk-A5KRAYDB.js +0 -107
- package/packages/core/dist/chunk-ANEAZ3PO.js +0 -107
- package/packages/core/dist/chunk-BE76RL6F.js +0 -82
- package/packages/core/dist/chunk-CIARVYDO.js +0 -107
- package/packages/core/dist/chunk-CR2ANE62.js +0 -107
- package/packages/core/dist/chunk-DI27MOR7.js +0 -173
- package/packages/core/dist/chunk-E6RLFHM5.js +0 -596
- package/packages/core/dist/chunk-EELAPMNV.js +0 -4877
- package/packages/core/dist/chunk-ENZ2RQVJ.js +0 -107
- package/packages/core/dist/chunk-F2U6CX4F.js +0 -4877
- package/packages/core/dist/chunk-FKG64OHL.js +0 -82
- package/packages/core/dist/chunk-FYAZJYM5.js +0 -772
- package/packages/core/dist/chunk-G4NP4RDQ.js +0 -82
- package/packages/core/dist/chunk-GADIRLRS.js +0 -473
- package/packages/core/dist/chunk-GR6CSLQJ.js +0 -772
- package/packages/core/dist/chunk-HDS7AT6O.js +0 -772
- package/packages/core/dist/chunk-HIMEZUU2.js +0 -71
- package/packages/core/dist/chunk-IB4UHG5I.js +0 -596
- package/packages/core/dist/chunk-J7ZYNVRS.js +0 -772
- package/packages/core/dist/chunk-JVLZU4K7.js +0 -604
- package/packages/core/dist/chunk-JYXXI7Q3.js +0 -82
- package/packages/core/dist/chunk-K54K2KZW.js +0 -192
- package/packages/core/dist/chunk-K6VHLBNH.js +0 -3316
- package/packages/core/dist/chunk-KCBP7ZYA.js +0 -596
- package/packages/core/dist/chunk-KFQGP6VL.js +0 -33
- package/packages/core/dist/chunk-KIUEUI7K.js +0 -82
- package/packages/core/dist/chunk-LB67NIZP.js +0 -949
- package/packages/core/dist/chunk-LDTYFUNW.js +0 -596
- package/packages/core/dist/chunk-MLVWARD4.js +0 -596
- package/packages/core/dist/chunk-MPYVXHKO.js +0 -2777
- package/packages/core/dist/chunk-MRQ3MITU.js +0 -772
- package/packages/core/dist/chunk-MVUN7I7O.js +0 -107
- package/packages/core/dist/chunk-NDDOIRW7.js +0 -596
- package/packages/core/dist/chunk-O3KDB5MH.js +0 -3330
- package/packages/core/dist/chunk-OEINUDSE.js +0 -400
- package/packages/core/dist/chunk-OPDENPT4.js +0 -4877
- package/packages/core/dist/chunk-P6XEM2LA.js +0 -429
- package/packages/core/dist/chunk-QKMRSQV4.js +0 -82
- package/packages/core/dist/chunk-R6CCWALL.js +0 -4877
- package/packages/core/dist/chunk-RAMMOO73.js +0 -774
- package/packages/core/dist/chunk-S3FTZFG6.js +0 -772
- package/packages/core/dist/chunk-T2ZVN434.js +0 -4877
- package/packages/core/dist/chunk-TFHLDMIA.js +0 -107
- package/packages/core/dist/chunk-TI3QUCIJ.js +0 -107
- package/packages/core/dist/chunk-TR2BAIGI.js +0 -107
- package/packages/core/dist/chunk-UGVVJKC6.js +0 -4877
- package/packages/core/dist/chunk-V2DX7YIU.js +0 -984
- package/packages/core/dist/chunk-W4IHYUYD.js +0 -772
- package/packages/core/dist/chunk-W6WBG5OY.js +0 -4877
- package/packages/core/dist/chunk-WN3M7O7H.js +0 -3312
- package/packages/core/dist/chunk-WR3NCU7U.js +0 -4877
- package/packages/core/dist/chunk-XC6B6L27.js +0 -82
- package/packages/core/dist/chunk-XNSE64ZY.js +0 -3316
- package/packages/core/dist/chunk-XROES2WN.js +0 -634
- package/packages/core/dist/chunk-XSCKNRCZ.js +0 -5020
- package/packages/core/dist/chunk-XVABPAUZ.js +0 -82
- package/packages/core/dist/chunk-YOBCB5C5.js +0 -596
- package/packages/core/dist/chunk-YUK6Q563.js +0 -666
- package/packages/core/dist/chunk-YXGL3ZEU.js +0 -1030
- package/packages/core/dist/chunk-ZINJ5OUX.js +0 -596
- package/packages/core/dist/chunk-ZPFD4BIQ.js +0 -604
- package/packages/core/dist/chunk-ZQZBFG6F.js +0 -4826
- package/packages/core/dist/chunk-ZZFRJGXM.js +0 -4877
- package/packages/create-krnl/dist/K_ENT_Entity-PQ5MURQY-R2OH4VXE.js +0 -14
- package/packages/create-krnl/dist/K_LIB_Krnl_Aiko_Client-OPZAVZCW-AZM5HF4M.js +0 -9
- package/packages/create-krnl/dist/K_LIB_Krnl_Ep_Tools-ROOE2TQF-X5N6SV2S.js +0 -111
- package/packages/create-krnl/dist/K_LIB_Krnl_Frontend_Builder-2ZDUH6PF-5PFHF4JU.js +0 -12
- package/packages/create-krnl/dist/K_LIB_Krnl_Frontend_Generator-UYNRNX2X-ED33OQPH.js +0 -12
- package/packages/create-krnl/dist/K_LIB_Krnl_Registry_Client-NXV6D2QW-YGQLVR4P.js +0 -9
- package/packages/create-krnl/dist/K_LIB_Krnl_SubApp_Engine-7HR5HWW2-VM7CTU2L.js +0 -21
- package/packages/create-krnl/dist/K_LIB_Krnl_SubApp_Registry-7XYGDKJK-Y2GQFJ7I.js +0 -21
- package/packages/create-krnl/dist/K_LIB_Krnl_SubApp_Worker_Host-CQVTGGG7-L3JQWO54.js +0 -8
- package/packages/create-krnl/dist/K_LIB_Krnl_System_App_Adapter-M6FJKLGU-KQARASPR.js +0 -19
- package/packages/create-krnl/dist/K_LIB_Krnl_System_Base_Functionality_APIs-ZZUZ6ZG5-OF3IGLVL.js +0 -59
- package/packages/create-krnl/dist/K_LIB_Krnl_System_Magnetic_Loader-XBQO2KXF-JOLHKOEX.js +0 -24
- package/packages/create-krnl/dist/K_LIB_Krnl_System_Unified_Http_Adapter-RCEGP3OG-L46LSHKQ.js +0 -10
- package/packages/create-krnl/dist/K_LIB_Krnl_Telemetry-77FRZ3KV-4JUPRR3X.js +0 -10
- package/packages/create-krnl/dist/chunk-3TRM6CYL.js +0 -152
- package/packages/create-krnl/dist/chunk-3URCB3QS.js +0 -127
- package/packages/create-krnl/dist/chunk-5JDK35TG.js +0 -3872
- package/packages/create-krnl/dist/chunk-A2XAXDOW.js +0 -812
- package/packages/create-krnl/dist/chunk-AN6TMQHG.js +0 -1033
- package/packages/create-krnl/dist/chunk-BOXWWXQG.js +0 -400
- package/packages/create-krnl/dist/chunk-BPTBREBG.js +0 -38
- package/packages/create-krnl/dist/chunk-FDNCADFE.js +0 -71
- package/packages/create-krnl/dist/chunk-JTLSTO25.js +0 -71
- package/packages/create-krnl/dist/chunk-M3NCUIPL.js +0 -107
- package/packages/create-krnl/dist/chunk-N2EONSOC.js +0 -7769
- package/packages/create-krnl/dist/chunk-OHVMVRBP.js +0 -117
- package/packages/create-krnl/dist/chunk-Q45257P6.js +0 -173
- package/packages/create-krnl/dist/chunk-QGM4M3NI.js +0 -37
- package/packages/create-krnl/dist/chunk-RJNYUXZD.js +0 -977
- package/packages/create-krnl/dist/chunk-SRTOPJ5W.js +0 -104
- package/packages/create-krnl/dist/chunk-UPBIGEC3.js +0 -666
- package/packages/create-krnl/dist/chunk-V2DB7UD7.js +0 -82
- package/packages/create-krnl/dist/chunk-WMHLIUI6.js +0 -3644
- package/packages/create-krnl/dist/chunk-XIRFOO3H.js +0 -589
- package/packages/create-krnl/dist/chunk-Y2CEEVZP.js +0 -5536
- package/packages/create-krnl/dist/main-DXUMPBND.js +0 -2217
- package/packages/create-krnl/dist/wrapper-SDF2VKX3.js +0 -31
|
@@ -0,0 +1,1642 @@
|
|
|
1
|
+
// ../validate/dist/index.js
|
|
2
|
+
import * as fs2 from "fs";
|
|
3
|
+
import * as path3 from "path";
|
|
4
|
+
import { Project } from "ts-morph";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
import { SyntaxKind } from "ts-morph";
|
|
7
|
+
import { SyntaxKind as SyntaxKind2, Node } from "ts-morph";
|
|
8
|
+
import * as fs from "fs";
|
|
9
|
+
import * as path2 from "path";
|
|
10
|
+
import { SyntaxKind as SyntaxKind3 } from "ts-morph";
|
|
11
|
+
import { SyntaxKind as SyntaxKind4 } from "ts-morph";
|
|
12
|
+
function checkFileLimits(sf, options) {
|
|
13
|
+
const errors = [];
|
|
14
|
+
const warnings = [];
|
|
15
|
+
const filePath = sf.getFilePath();
|
|
16
|
+
const sourceText = sf.getFullText();
|
|
17
|
+
const lines = sourceText.split("\n");
|
|
18
|
+
if (lines.length > options.maxFileLineLimit) {
|
|
19
|
+
errors.push({
|
|
20
|
+
ruleId: "krnl/file-line-limit",
|
|
21
|
+
filePath,
|
|
22
|
+
message: `File exceeds maximum length of ${options.maxFileLineLimit} lines (currently ${lines.length} lines). Decompose into sub-components, custom hooks, quad-units, or concept libraries.`,
|
|
23
|
+
severity: "error"
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
for (const decl of sf.getImportDeclarations()) {
|
|
27
|
+
const moduleSpec = decl.getModuleSpecifierValue();
|
|
28
|
+
if (moduleSpec.startsWith(".") && !moduleSpec.endsWith(".js") && !moduleSpec.endsWith(".json") && !moduleSpec.endsWith(".css") && !moduleSpec.endsWith(".svg") && !moduleSpec.endsWith(".png")) {
|
|
29
|
+
errors.push({
|
|
30
|
+
ruleId: "krnl/import-suffix",
|
|
31
|
+
filePath,
|
|
32
|
+
line: decl.getStartLineNumber(),
|
|
33
|
+
message: `Relative import '${moduleSpec}' must end with '.js' for NodeNext module resolution.`,
|
|
34
|
+
severity: "error"
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const isHelper = filePath.includes("helper_functions") || filePath.includes("/helpers/") || filePath.includes("\\helpers\\");
|
|
39
|
+
if (isHelper) {
|
|
40
|
+
for (const fn of sf.getFunctions()) {
|
|
41
|
+
const fnLines = fn.getText().split("\n").length;
|
|
42
|
+
if (fnLines > options.maxHelperLineLimit && !sourceText.includes("Concepts.")) {
|
|
43
|
+
errors.push({
|
|
44
|
+
ruleId: "krnl/helper-line-limit",
|
|
45
|
+
filePath,
|
|
46
|
+
line: fn.getStartLineNumber(),
|
|
47
|
+
message: `Helper function '${fn.getName() || "anonymous"}' exceeds ${options.maxHelperLineLimit} lines (${fnLines} lines). Must delegate to a root libraries/{Domain}Concepts.ts library.`,
|
|
48
|
+
severity: "error"
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const isMainLogic = filePath.endsWith("logic.ts") || filePath.endsWith("logic.js");
|
|
54
|
+
if (isMainLogic) {
|
|
55
|
+
const substantiveLines = lines.filter((l) => {
|
|
56
|
+
const trimmed = l.trim();
|
|
57
|
+
return trimmed.length > 0 && !trimmed.startsWith("//") && !trimmed.startsWith("/*") && !trimmed.startsWith("*");
|
|
58
|
+
});
|
|
59
|
+
if (substantiveLines.length > options.maxMainLogicLimit && !sourceText.includes("helpers") && !sourceText.includes("Concepts") && !sourceText.includes("state")) {
|
|
60
|
+
errors.push({
|
|
61
|
+
ruleId: "krnl/main-delegation",
|
|
62
|
+
filePath,
|
|
63
|
+
message: `Main logic function has ${substantiveLines.length} substantive lines without delegating to Layer 1 helper functions or Layer 0 libraries.`,
|
|
64
|
+
severity: "error"
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return { errors, warnings };
|
|
69
|
+
}
|
|
70
|
+
function checkComponentRules(sf) {
|
|
71
|
+
const errors = [];
|
|
72
|
+
const warnings = [];
|
|
73
|
+
const filePath = sf.getFilePath();
|
|
74
|
+
const baseName = path.basename(filePath);
|
|
75
|
+
if (filePath.endsWith(".tsx") || filePath.endsWith(".jsx")) {
|
|
76
|
+
const componentNames = [];
|
|
77
|
+
for (const fn of sf.getFunctions()) {
|
|
78
|
+
const name = fn.getName();
|
|
79
|
+
if (name && /^[A-Z]/.test(name)) {
|
|
80
|
+
const hasJsx = fn.getDescendantsOfKind(SyntaxKind.JsxElement).length > 0 || fn.getDescendantsOfKind(SyntaxKind.JsxSelfClosingElement).length > 0 || fn.getDescendantsOfKind(SyntaxKind.JsxFragment).length > 0;
|
|
81
|
+
if (hasJsx) componentNames.push(name);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
for (const stmt of sf.getVariableStatements()) {
|
|
85
|
+
for (const decl of stmt.getDeclarations()) {
|
|
86
|
+
const name = decl.getName();
|
|
87
|
+
if (/^[A-Z]/.test(name)) {
|
|
88
|
+
const typeText = decl.getTypeNode()?.getText() || "";
|
|
89
|
+
const isFcType = typeText.includes("FC") || typeText.includes("FunctionComponent") || typeText.includes("ComponentType");
|
|
90
|
+
const init = decl.getInitializer();
|
|
91
|
+
if (init) {
|
|
92
|
+
const hasJsx = init.getDescendantsOfKind(SyntaxKind.JsxElement).length > 0 || init.getDescendantsOfKind(SyntaxKind.JsxSelfClosingElement).length > 0 || init.getDescendantsOfKind(SyntaxKind.JsxFragment).length > 0;
|
|
93
|
+
if (isFcType || hasJsx) {
|
|
94
|
+
if (!componentNames.includes(name)) componentNames.push(name);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
for (const cls of sf.getClasses()) {
|
|
101
|
+
const name = cls.getName();
|
|
102
|
+
const ext = cls.getExtends()?.getText() || "";
|
|
103
|
+
if (name && (ext.includes("Component") || ext.includes("PureComponent"))) {
|
|
104
|
+
if (!componentNames.includes(name)) componentNames.push(name);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (componentNames.length > 1) {
|
|
108
|
+
errors.push({
|
|
109
|
+
ruleId: "krnl/one-component-per-file",
|
|
110
|
+
filePath,
|
|
111
|
+
message: `File '${baseName}' defines multiple React components (${componentNames.join(
|
|
112
|
+
", "
|
|
113
|
+
)}). Krnl strictly enforces ONE component per file. Decompose into sub-component files.`,
|
|
114
|
+
severity: "error"
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const isHelper = filePath.includes("helper_functions") || filePath.includes("/helpers/") || filePath.includes("\\helpers\\");
|
|
119
|
+
if (isHelper) {
|
|
120
|
+
for (const decl of sf.getImportDeclarations()) {
|
|
121
|
+
const spec = decl.getModuleSpecifierValue();
|
|
122
|
+
if (["fs", "node:fs", "http", "node:http", "ws"].includes(spec)) {
|
|
123
|
+
errors.push({
|
|
124
|
+
ruleId: "krnl/helper-purity",
|
|
125
|
+
filePath,
|
|
126
|
+
line: decl.getStartLineNumber(),
|
|
127
|
+
message: `Helper functions (Layer 1) cannot directly import I/O modules ('${spec}'). Delegate to Layer 0 libraries or ServiceFacade context.`,
|
|
128
|
+
severity: "error"
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return { errors, warnings };
|
|
134
|
+
}
|
|
135
|
+
function checkOrchestrationRules(sf) {
|
|
136
|
+
const errors = [];
|
|
137
|
+
const warnings = [];
|
|
138
|
+
const filePath = sf.getFilePath();
|
|
139
|
+
const sourceText = sf.getText();
|
|
140
|
+
const calls = sf.getDescendantsOfKind(SyntaxKind2.CallExpression);
|
|
141
|
+
for (const call of calls) {
|
|
142
|
+
const exprText = call.getExpression().getText();
|
|
143
|
+
if (exprText === "setInterval" || exprText === "window.setInterval") {
|
|
144
|
+
errors.push({
|
|
145
|
+
ruleId: "krnl/no-polling",
|
|
146
|
+
filePath,
|
|
147
|
+
line: call.getStartLineNumber(),
|
|
148
|
+
message: `Prohibited use of 'setInterval'. Krnl applications must use reactive event streams or triggers.`,
|
|
149
|
+
severity: "error"
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (sourceText.includes("main = async") || sourceText.includes("export const main = async")) {
|
|
154
|
+
const match = sourceText.match(/main\s*=\s*async\s*\([^,]+,\s*\{([^}]*)\}\s*\)/);
|
|
155
|
+
if (match) {
|
|
156
|
+
const destructured = match[1];
|
|
157
|
+
if (/\bsystem\b/.test(destructured)) {
|
|
158
|
+
errors.push({
|
|
159
|
+
ruleId: "krnl/context-destructuring",
|
|
160
|
+
filePath,
|
|
161
|
+
message: `Functionality context parameter destructures 'system'. Functionality passes { state, orchestration } or service context.`,
|
|
162
|
+
severity: "error"
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const isOrchestrationClass = sf.getClasses().some((c) => {
|
|
168
|
+
const ext = c.getExtends();
|
|
169
|
+
return ext && (ext.getText().includes("FunctionalityOrchestration") || ext.getText().includes("Orchestration") || ext.getText().includes("krnlOrchestrationBase"));
|
|
170
|
+
});
|
|
171
|
+
if (isOrchestrationClass) {
|
|
172
|
+
for (const decl of sf.getImportDeclarations()) {
|
|
173
|
+
const spec = decl.getModuleSpecifierValue();
|
|
174
|
+
if (spec.includes("libraries/") || spec.includes("Concepts")) {
|
|
175
|
+
errors.push({
|
|
176
|
+
ruleId: "krnl/orchestration-no-libraries",
|
|
177
|
+
filePath,
|
|
178
|
+
line: decl.getStartLineNumber(),
|
|
179
|
+
message: `FunctionalityOrchestration class violates Library Calling Invariant. FunctionalityOrchestrations must not call or import libraries directly ('${spec}'). Only functionalities are permitted to call shared libraries.`,
|
|
180
|
+
severity: "error"
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
for (const cls of sf.getClasses()) {
|
|
185
|
+
const ext = cls.getExtends();
|
|
186
|
+
if (ext && (ext.getText().includes("FunctionalityOrchestration") || ext.getText().includes("Orchestration") || ext.getText().includes("krnlOrchestrationBase"))) {
|
|
187
|
+
const loops = [
|
|
188
|
+
...cls.getDescendantsOfKind(SyntaxKind2.ForStatement),
|
|
189
|
+
...cls.getDescendantsOfKind(SyntaxKind2.ForOfStatement),
|
|
190
|
+
...cls.getDescendantsOfKind(SyntaxKind2.ForInStatement),
|
|
191
|
+
...cls.getDescendantsOfKind(SyntaxKind2.WhileStatement),
|
|
192
|
+
...cls.getDescendantsOfKind(SyntaxKind2.DoStatement)
|
|
193
|
+
];
|
|
194
|
+
for (const loop of loops) {
|
|
195
|
+
errors.push({
|
|
196
|
+
ruleId: "krnl/orchestration-purity",
|
|
197
|
+
filePath,
|
|
198
|
+
line: loop.getStartLineNumber(),
|
|
199
|
+
message: `FunctionalityOrchestration '${cls.getName() || "Anonymous"}' violates Orchestration Purity Law. Loops and procedural algorithms are prohibited inside orchestrations. Move logic to a functionality or a global library function.`,
|
|
200
|
+
severity: "error"
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
const allowedMethods = /* @__PURE__ */ new Set([
|
|
204
|
+
"flow",
|
|
205
|
+
"getFlow",
|
|
206
|
+
"listFlows",
|
|
207
|
+
"clearFlows",
|
|
208
|
+
"thenFunctionality",
|
|
209
|
+
"parallel",
|
|
210
|
+
"branch",
|
|
211
|
+
"fanOut",
|
|
212
|
+
"thenFunctionalityOrchestration",
|
|
213
|
+
"getSteps",
|
|
214
|
+
"assertServiceBoundary"
|
|
215
|
+
]);
|
|
216
|
+
for (const method of cls.getMethods()) {
|
|
217
|
+
const methodName = method.getName();
|
|
218
|
+
if (!allowedMethods.has(methodName)) {
|
|
219
|
+
errors.push({
|
|
220
|
+
ruleId: "krnl/orchestration-purity",
|
|
221
|
+
filePath,
|
|
222
|
+
line: method.getStartLineNumber(),
|
|
223
|
+
message: `FunctionalityOrchestration '${cls.getName() || "Anonymous"}' defines custom method '${methodName}'. FunctionalityOrchestrations are purely declarative compositors. Define capabilities as functionalities or global library functions.`,
|
|
224
|
+
severity: "error"
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
for (const call of calls) {
|
|
231
|
+
const callText = call.getText();
|
|
232
|
+
if (callText.startsWith("this.flow(") || callText.startsWith("orchestration.flow(")) {
|
|
233
|
+
let currentExpr = call;
|
|
234
|
+
const chainCalls = [];
|
|
235
|
+
while (currentExpr && Node.isCallExpression(currentExpr)) {
|
|
236
|
+
const propAccess = currentExpr.getExpression();
|
|
237
|
+
if (Node.isPropertyAccessExpression(propAccess)) {
|
|
238
|
+
const methodName = propAccess.getName();
|
|
239
|
+
const args = currentExpr.getArguments();
|
|
240
|
+
const argText = args.length > 0 ? args[0].getText() : void 0;
|
|
241
|
+
chainCalls.unshift({ name: methodName, node: currentExpr, argText });
|
|
242
|
+
currentExpr = propAccess.getExpression();
|
|
243
|
+
} else {
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
let chainId = "orchestration";
|
|
248
|
+
for (const step of chainCalls) {
|
|
249
|
+
if (step.name === "flow") {
|
|
250
|
+
chainId = step.argText ? step.argText.replace(/['"]/g, "") : "orchestration";
|
|
251
|
+
} else if (step.name === "thenFunction") {
|
|
252
|
+
errors.push({
|
|
253
|
+
ruleId: "krnl/orchestration-no-direct-library-calls",
|
|
254
|
+
filePath,
|
|
255
|
+
line: step.node.getStartLineNumber(),
|
|
256
|
+
message: `FunctionalityOrchestration flow '${chainId}' violates Orchestration Purity Law. Orchestrations cannot chain direct library functions via thenFunction(). Orchestrations must only sequence functionalities (thenFunctionality) and peer functionality orchestrations (thenFunctionalityOrchestration).`,
|
|
257
|
+
severity: "error"
|
|
258
|
+
});
|
|
259
|
+
} else if (step.name === "thenService") {
|
|
260
|
+
errors.push({
|
|
261
|
+
ruleId: "krnl/orchestration-no-external-services",
|
|
262
|
+
filePath,
|
|
263
|
+
line: step.node.getStartLineNumber(),
|
|
264
|
+
message: `FunctionalityOrchestration flow '${chainId}' violates Plane Isolation Law L1. A service-level FunctionalityOrchestration cannot compose external services. Use Skipo ServiceOrchestration at the application layer instead.`,
|
|
265
|
+
severity: "error"
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return { errors, warnings };
|
|
273
|
+
}
|
|
274
|
+
function checkTopLevelPurity(sf) {
|
|
275
|
+
const errors = [];
|
|
276
|
+
const warnings = [];
|
|
277
|
+
const filePath = sf.getFilePath();
|
|
278
|
+
const baseName = path2.basename(filePath);
|
|
279
|
+
const isUiTopLevel = /^(App|main|index|_app|root)\.(tsx|jsx)$/i.test(baseName);
|
|
280
|
+
const isBackendTopLevel = /^(server|app|index)\.(ts|js)$/i.test(baseName) && !filePath.includes("/components/") && !filePath.includes("/orchestrations/") && !filePath.includes("/services/") && !filePath.includes("/libraries/");
|
|
281
|
+
if (isUiTopLevel) {
|
|
282
|
+
const jsxElements = sf.getDescendantsOfKind(SyntaxKind3.JsxOpeningElement);
|
|
283
|
+
const jsxSelfClosing = sf.getDescendantsOfKind(SyntaxKind3.JsxSelfClosingElement);
|
|
284
|
+
const allJsxTags = [...jsxElements, ...jsxSelfClosing];
|
|
285
|
+
for (const jsx of allJsxTags) {
|
|
286
|
+
const tagName = jsx.getTagNameNode().getText();
|
|
287
|
+
if (/^[a-z]/.test(tagName)) {
|
|
288
|
+
errors.push({
|
|
289
|
+
ruleId: "krnl/top-level-purity",
|
|
290
|
+
filePath,
|
|
291
|
+
line: jsx.getStartLineNumber(),
|
|
292
|
+
message: `Top-level file '${baseName}' violates Top-Level Purity Law. Raw JSX/HTML element '<${tagName}>' is strictly forbidden. Top-level files must ONLY import and compose React components.`,
|
|
293
|
+
severity: "error"
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
const calls = sf.getDescendantsOfKind(SyntaxKind3.CallExpression);
|
|
298
|
+
for (const call of calls) {
|
|
299
|
+
const exprText = call.getExpression().getText();
|
|
300
|
+
if ([
|
|
301
|
+
"useState",
|
|
302
|
+
"useEffect",
|
|
303
|
+
"useRef",
|
|
304
|
+
"useMemo",
|
|
305
|
+
"useCallback",
|
|
306
|
+
"useReducer",
|
|
307
|
+
"useContext",
|
|
308
|
+
"useLayoutEffect"
|
|
309
|
+
].includes(exprText) || /^use[A-Z]/.test(exprText) && exprText !== "use") {
|
|
310
|
+
errors.push({
|
|
311
|
+
ruleId: "krnl/top-level-purity",
|
|
312
|
+
filePath,
|
|
313
|
+
line: call.getStartLineNumber(),
|
|
314
|
+
message: `Top-level file '${baseName}' violates Top-Level Purity Law. React hook '${exprText}' is forbidden in top-level files. Encapsulate state and effects inside dedicated component files.`,
|
|
315
|
+
severity: "error"
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (isBackendTopLevel) {
|
|
321
|
+
const calls = sf.getDescendantsOfKind(SyntaxKind3.CallExpression);
|
|
322
|
+
for (const call of calls) {
|
|
323
|
+
const exprText = call.getExpression().getText();
|
|
324
|
+
if (/^(app|router|server)\.(get|post|put|delete|patch|options|head)$/.test(exprText)) {
|
|
325
|
+
errors.push({
|
|
326
|
+
ruleId: "krnl/top-level-purity",
|
|
327
|
+
filePath,
|
|
328
|
+
line: call.getStartLineNumber(),
|
|
329
|
+
message: `Backend entry file '${baseName}' violates Entry Purity Law. Inline route definition '${exprText}' is forbidden. Delegate to dedicated route/controller modules.`,
|
|
330
|
+
severity: "error"
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return { errors, warnings };
|
|
336
|
+
}
|
|
337
|
+
function checkWorkspaceLayout(projectRoot, errors) {
|
|
338
|
+
const krnlDir = path2.join(projectRoot, "krnl");
|
|
339
|
+
const srcDir = path2.join(projectRoot, "src");
|
|
340
|
+
const targetDir = fs.existsSync(krnlDir) ? krnlDir : fs.existsSync(srcDir) ? srcDir : null;
|
|
341
|
+
if (!targetDir) return;
|
|
342
|
+
const backendDir = path2.join(targetDir, "backend");
|
|
343
|
+
const frontendDir = path2.join(targetDir, "frontend");
|
|
344
|
+
if (fs.existsSync(backendDir)) {
|
|
345
|
+
const bServices = path2.join(backendDir, "services");
|
|
346
|
+
if (fs.existsSync(bServices)) {
|
|
347
|
+
for (const sEntry of fs.readdirSync(bServices, { withFileTypes: true })) {
|
|
348
|
+
if (!sEntry.isDirectory()) continue;
|
|
349
|
+
const sDir = path2.join(bServices, sEntry.name);
|
|
350
|
+
const sLibs = path2.join(sDir, "libraries");
|
|
351
|
+
if (fs.existsSync(sLibs)) {
|
|
352
|
+
for (const entry of fs.readdirSync(sLibs, { withFileTypes: true })) {
|
|
353
|
+
if (!entry.isDirectory()) continue;
|
|
354
|
+
const libPath = path2.join(sLibs, entry.name);
|
|
355
|
+
if (!fs.existsSync(path2.join(libPath, "manifest.json"))) {
|
|
356
|
+
errors.push({
|
|
357
|
+
ruleId: "krnl/concept-manifest-required",
|
|
358
|
+
filePath: libPath,
|
|
359
|
+
message: `Service-owned concept library "${sEntry.name}/${entry.name}" is missing a manifest.json file.`,
|
|
360
|
+
severity: "error"
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
const sFuncs = path2.join(sDir, "functionalities");
|
|
366
|
+
if (fs.existsSync(sFuncs)) {
|
|
367
|
+
for (const entry of fs.readdirSync(sFuncs, { withFileTypes: true })) {
|
|
368
|
+
if (!entry.isDirectory()) continue;
|
|
369
|
+
const funcPath = path2.join(sFuncs, entry.name);
|
|
370
|
+
if (!fs.existsSync(path2.join(funcPath, "manifest.json"))) {
|
|
371
|
+
errors.push({
|
|
372
|
+
ruleId: "krnl/functionality-manifest-required",
|
|
373
|
+
filePath: funcPath,
|
|
374
|
+
message: `Service-owned functionality "${sEntry.name}/${entry.name}" is missing a manifest.json file.`,
|
|
375
|
+
severity: "error"
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
const bLibs = path2.join(backendDir, "libraries");
|
|
383
|
+
if (fs.existsSync(bLibs)) {
|
|
384
|
+
for (const entry of fs.readdirSync(bLibs, { withFileTypes: true })) {
|
|
385
|
+
if (!entry.isDirectory()) continue;
|
|
386
|
+
const libPath = path2.join(bLibs, entry.name);
|
|
387
|
+
if (!fs.existsSync(path2.join(libPath, "manifest.json"))) {
|
|
388
|
+
errors.push({
|
|
389
|
+
ruleId: "krnl/concept-manifest-required",
|
|
390
|
+
filePath: libPath,
|
|
391
|
+
message: `Backend concept library "${entry.name}" is missing a manifest.json file.`,
|
|
392
|
+
severity: "error"
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
const bFuncs = path2.join(backendDir, "functionalities");
|
|
398
|
+
if (fs.existsSync(bFuncs)) {
|
|
399
|
+
for (const entry of fs.readdirSync(bFuncs, { withFileTypes: true })) {
|
|
400
|
+
if (!entry.isDirectory()) continue;
|
|
401
|
+
const funcPath = path2.join(bFuncs, entry.name);
|
|
402
|
+
if (!fs.existsSync(path2.join(funcPath, "manifest.json"))) {
|
|
403
|
+
errors.push({
|
|
404
|
+
ruleId: "krnl/functionality-manifest-required",
|
|
405
|
+
filePath: funcPath,
|
|
406
|
+
message: `Backend functionality "${entry.name}" is missing a manifest.json file.`,
|
|
407
|
+
severity: "error"
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
if (fs.existsSync(frontendDir)) {
|
|
414
|
+
const fLibs = path2.join(frontendDir, "libraries");
|
|
415
|
+
if (fs.existsSync(fLibs)) {
|
|
416
|
+
for (const entry of fs.readdirSync(fLibs, { withFileTypes: true })) {
|
|
417
|
+
if (!entry.isDirectory()) continue;
|
|
418
|
+
const libPath = path2.join(fLibs, entry.name);
|
|
419
|
+
if (!fs.existsSync(path2.join(libPath, "manifest.json"))) {
|
|
420
|
+
errors.push({
|
|
421
|
+
ruleId: "krnl/frontend-concept-manifest-required",
|
|
422
|
+
filePath: libPath,
|
|
423
|
+
message: `Frontend concept library "${entry.name}" is missing a manifest.json file.`,
|
|
424
|
+
severity: "error"
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
const fFuncs = path2.join(frontendDir, "functionalities");
|
|
430
|
+
if (fs.existsSync(fFuncs)) {
|
|
431
|
+
for (const entry of fs.readdirSync(fFuncs, { withFileTypes: true })) {
|
|
432
|
+
if (!entry.isDirectory()) continue;
|
|
433
|
+
const funcPath = path2.join(fFuncs, entry.name);
|
|
434
|
+
if (!fs.existsSync(path2.join(funcPath, "manifest.json"))) {
|
|
435
|
+
errors.push({
|
|
436
|
+
ruleId: "krnl/frontend-functionality-manifest-required",
|
|
437
|
+
filePath: funcPath,
|
|
438
|
+
message: `Frontend functionality "${entry.name}" is missing a manifest.json file.`,
|
|
439
|
+
severity: "error"
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
function checkServiceBoundaries(sf) {
|
|
447
|
+
const errors = [];
|
|
448
|
+
const warnings = [];
|
|
449
|
+
const filePath = sf.getFilePath().replace(/\\/g, "/");
|
|
450
|
+
const serviceMatch = filePath.match(/\/services\/([^/]+)\//);
|
|
451
|
+
if (!serviceMatch) {
|
|
452
|
+
return { errors, warnings };
|
|
453
|
+
}
|
|
454
|
+
const currentService = serviceMatch[1];
|
|
455
|
+
const imports = sf.getImportDeclarations();
|
|
456
|
+
for (const imp of imports) {
|
|
457
|
+
const spec = imp.getModuleSpecifierValue();
|
|
458
|
+
const crossServiceLibMatch = spec.match(/(?:^|\/|\.\.\/)services\/([^/]+)\/libraries\//) || spec.match(/\.\.\/(?:(?:\.\.\/)+)?([^/]+)\/libraries\//);
|
|
459
|
+
if (crossServiceLibMatch) {
|
|
460
|
+
const targetService = crossServiceLibMatch[1];
|
|
461
|
+
if (targetService !== currentService && targetService !== "." && targetService !== "..") {
|
|
462
|
+
errors.push({
|
|
463
|
+
ruleId: "krnl/no-cross-service-library-imports",
|
|
464
|
+
filePath,
|
|
465
|
+
line: imp.getStartLineNumber(),
|
|
466
|
+
message: `Cross-service library import forbidden: '${spec}'. Service '${currentService}' cannot import '${targetService}' libraries directly via filesystem. Access libraries through the '${targetService}' ServiceFacade (e.g. ${targetService}Facade.libraries).`,
|
|
467
|
+
severity: "error"
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return { errors, warnings };
|
|
473
|
+
}
|
|
474
|
+
var PRIMITIVE_AND_BUILTIN_TYPES = /* @__PURE__ */ new Set([
|
|
475
|
+
"string",
|
|
476
|
+
"number",
|
|
477
|
+
"boolean",
|
|
478
|
+
"symbol",
|
|
479
|
+
"bigint",
|
|
480
|
+
"void",
|
|
481
|
+
"undefined",
|
|
482
|
+
"null",
|
|
483
|
+
"never",
|
|
484
|
+
"unknown",
|
|
485
|
+
"any",
|
|
486
|
+
"object",
|
|
487
|
+
"Record",
|
|
488
|
+
"Array",
|
|
489
|
+
"ReadonlyArray",
|
|
490
|
+
"Map",
|
|
491
|
+
"Set",
|
|
492
|
+
"WeakMap",
|
|
493
|
+
"WeakSet",
|
|
494
|
+
"Promise",
|
|
495
|
+
"Date",
|
|
496
|
+
"RegExp",
|
|
497
|
+
"Error",
|
|
498
|
+
"Uint8Array",
|
|
499
|
+
"Int8Array",
|
|
500
|
+
"Uint16Array",
|
|
501
|
+
"Int16Array",
|
|
502
|
+
"Uint32Array",
|
|
503
|
+
"Int32Array",
|
|
504
|
+
"Float32Array",
|
|
505
|
+
"Float64Array",
|
|
506
|
+
"ArrayBuffer",
|
|
507
|
+
"Buffer",
|
|
508
|
+
"Iterable",
|
|
509
|
+
"AsyncIterable",
|
|
510
|
+
"Iterator",
|
|
511
|
+
"AsyncIterator",
|
|
512
|
+
"Partial",
|
|
513
|
+
"Required",
|
|
514
|
+
"Readonly",
|
|
515
|
+
"Pick",
|
|
516
|
+
"Omit",
|
|
517
|
+
"Exclude",
|
|
518
|
+
"Extract",
|
|
519
|
+
"NonNullable",
|
|
520
|
+
"Parameters",
|
|
521
|
+
"ConstructorParameters",
|
|
522
|
+
"ReturnType",
|
|
523
|
+
"InstanceType"
|
|
524
|
+
]);
|
|
525
|
+
function detectServiceContext(filePath) {
|
|
526
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
527
|
+
const match = normalized.match(/(?:^|\/)services\/([^/]+)\/libraries\/([^/]+)/);
|
|
528
|
+
if (match) {
|
|
529
|
+
return { serviceName: match[1], libraryName: match[2] };
|
|
530
|
+
}
|
|
531
|
+
return null;
|
|
532
|
+
}
|
|
533
|
+
function containsIdentifier(name, target) {
|
|
534
|
+
return name.toLowerCase().includes(target.toLowerCase());
|
|
535
|
+
}
|
|
536
|
+
function isCustomContextualType(typeName, serviceName, localTypes) {
|
|
537
|
+
if (PRIMITIVE_AND_BUILTIN_TYPES.has(typeName)) return false;
|
|
538
|
+
if (localTypes.has(typeName)) return true;
|
|
539
|
+
if (containsIdentifier(typeName, serviceName)) return true;
|
|
540
|
+
if (/^[A-Z][a-zA-Z0-9]+$/.test(typeName)) return true;
|
|
541
|
+
return false;
|
|
542
|
+
}
|
|
543
|
+
function hasContextualTypeReference(node, serviceName, localTypes) {
|
|
544
|
+
const typeRefs = node.getDescendantsOfKind(SyntaxKind4.TypeReference);
|
|
545
|
+
for (const ref of typeRefs) {
|
|
546
|
+
const text = ref.getTypeName().getText().trim();
|
|
547
|
+
if (isCustomContextualType(text, serviceName, localTypes)) {
|
|
548
|
+
return true;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
return false;
|
|
552
|
+
}
|
|
553
|
+
function checkScopedLibraryRules(sf) {
|
|
554
|
+
const errors = [];
|
|
555
|
+
const warnings = [];
|
|
556
|
+
const promotableAssets = [];
|
|
557
|
+
const filePath = sf.getFilePath().replace(/\\/g, "/");
|
|
558
|
+
const context = detectServiceContext(filePath);
|
|
559
|
+
if (!context) {
|
|
560
|
+
return { errors, warnings, promotableAssets };
|
|
561
|
+
}
|
|
562
|
+
const { serviceName, libraryName } = context;
|
|
563
|
+
const localTypes = /* @__PURE__ */ new Set();
|
|
564
|
+
sf.getInterfaces().forEach((i) => localTypes.add(i.getName()));
|
|
565
|
+
sf.getTypeAliases().forEach((t) => localTypes.add(t.getName()));
|
|
566
|
+
sf.getClasses().forEach((c) => {
|
|
567
|
+
const n = c.getName();
|
|
568
|
+
if (n) localTypes.add(n);
|
|
569
|
+
});
|
|
570
|
+
sf.getEnums().forEach((e) => localTypes.add(e.getName()));
|
|
571
|
+
const functions = sf.getFunctions();
|
|
572
|
+
for (const fn of functions) {
|
|
573
|
+
const fnName = fn.getName();
|
|
574
|
+
if (!fnName) continue;
|
|
575
|
+
const isContextual = hasContextualTypeReference(fn, serviceName, localTypes);
|
|
576
|
+
const hasServiceName = containsIdentifier(fnName, serviceName);
|
|
577
|
+
if (isContextual) {
|
|
578
|
+
if (!hasServiceName) {
|
|
579
|
+
errors.push({
|
|
580
|
+
ruleId: "krnl/service-library-asset-naming",
|
|
581
|
+
filePath,
|
|
582
|
+
line: fn.getStartLineNumber(),
|
|
583
|
+
message: `Service library asset '${fnName}' in service '${serviceName}' violates scoped library naming convention. Custom library asset identifiers must contain the owning service name ('${serviceName}'). Expected name containing '${serviceName}' (e.g. '${serviceName.toLowerCase()}${fnName[0].toUpperCase()}${fnName.slice(1)}'), or eliminate contextual custom type dependencies to make it a generic promotable utility.`,
|
|
584
|
+
severity: "error"
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
} else {
|
|
588
|
+
promotableAssets.push({
|
|
589
|
+
name: fnName,
|
|
590
|
+
kind: "function",
|
|
591
|
+
filePath,
|
|
592
|
+
scope: "service",
|
|
593
|
+
scopeName: serviceName,
|
|
594
|
+
target: "GutTS"
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
for (const stmt of sf.getVariableStatements()) {
|
|
599
|
+
for (const decl of stmt.getDeclarations()) {
|
|
600
|
+
const varName = decl.getName();
|
|
601
|
+
const init = decl.getInitializer();
|
|
602
|
+
const isFunction = init && (init.getKind() === SyntaxKind4.ArrowFunction || init.getKind() === SyntaxKind4.FunctionExpression);
|
|
603
|
+
if (isFunction) {
|
|
604
|
+
const isContextual = hasContextualTypeReference(decl, serviceName, localTypes);
|
|
605
|
+
const hasServiceName = containsIdentifier(varName, serviceName);
|
|
606
|
+
if (isContextual) {
|
|
607
|
+
if (!hasServiceName) {
|
|
608
|
+
errors.push({
|
|
609
|
+
ruleId: "krnl/service-library-asset-naming",
|
|
610
|
+
filePath,
|
|
611
|
+
line: decl.getStartLineNumber(),
|
|
612
|
+
message: `Service library asset '${varName}' in service '${serviceName}' violates scoped library naming convention. Custom library asset identifiers must contain the owning service name ('${serviceName}'). Expected name containing '${serviceName}', or eliminate contextual custom type dependencies to make it a generic promotable utility.`,
|
|
613
|
+
severity: "error"
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
} else {
|
|
617
|
+
promotableAssets.push({
|
|
618
|
+
name: varName,
|
|
619
|
+
kind: "function",
|
|
620
|
+
filePath,
|
|
621
|
+
scope: "service",
|
|
622
|
+
scopeName: serviceName,
|
|
623
|
+
target: "GutTS"
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
} else {
|
|
627
|
+
const isContextual = hasContextualTypeReference(decl, serviceName, localTypes);
|
|
628
|
+
const hasServiceName = containsIdentifier(varName, serviceName);
|
|
629
|
+
if (isContextual && !hasServiceName) {
|
|
630
|
+
errors.push({
|
|
631
|
+
ruleId: "krnl/service-library-asset-naming",
|
|
632
|
+
filePath,
|
|
633
|
+
line: decl.getStartLineNumber(),
|
|
634
|
+
message: `Service library asset '${varName}' in service '${serviceName}' violates scoped library naming convention. Custom library asset identifiers must contain the owning service name ('${serviceName}').`,
|
|
635
|
+
severity: "error"
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
for (const iface of sf.getInterfaces()) {
|
|
642
|
+
const name = iface.getName();
|
|
643
|
+
const hasServiceName = containsIdentifier(name, serviceName);
|
|
644
|
+
const hasProps = iface.getProperties().length > 0;
|
|
645
|
+
if (hasProps && !hasServiceName) {
|
|
646
|
+
errors.push({
|
|
647
|
+
ruleId: "krnl/service-library-asset-naming",
|
|
648
|
+
filePath,
|
|
649
|
+
line: iface.getStartLineNumber(),
|
|
650
|
+
message: `Service library interface '${name}' in service '${serviceName}' violates scoped library naming convention. Custom library asset identifiers must contain the owning service name ('${serviceName}'). Expected name like '${serviceName}${name}'.`,
|
|
651
|
+
severity: "error"
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
for (const t of sf.getTypeAliases()) {
|
|
656
|
+
const name = t.getName();
|
|
657
|
+
const hasServiceName = containsIdentifier(name, serviceName);
|
|
658
|
+
const isContextual = hasContextualTypeReference(t, serviceName, localTypes);
|
|
659
|
+
if (isContextual && !hasServiceName) {
|
|
660
|
+
errors.push({
|
|
661
|
+
ruleId: "krnl/service-library-asset-naming",
|
|
662
|
+
filePath,
|
|
663
|
+
line: t.getStartLineNumber(),
|
|
664
|
+
message: `Service library type alias '${name}' in service '${serviceName}' violates scoped library naming convention. Custom library asset identifiers must contain the owning service name ('${serviceName}'). Expected name like '${serviceName}${name}'.`,
|
|
665
|
+
severity: "error"
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
for (const cls of sf.getClasses()) {
|
|
670
|
+
const name = cls.getName();
|
|
671
|
+
if (!name) continue;
|
|
672
|
+
const hasServiceName = containsIdentifier(name, serviceName);
|
|
673
|
+
if (!hasServiceName) {
|
|
674
|
+
errors.push({
|
|
675
|
+
ruleId: "krnl/service-library-asset-naming",
|
|
676
|
+
filePath,
|
|
677
|
+
line: cls.getStartLineNumber(),
|
|
678
|
+
message: `Service library class '${name}' in service '${serviceName}' violates scoped library naming convention. Custom library asset identifiers must contain the owning service name ('${serviceName}'). Expected name like '${serviceName}${name}'.`,
|
|
679
|
+
severity: "error"
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
return { errors, warnings, promotableAssets };
|
|
684
|
+
}
|
|
685
|
+
var KrnlValidatorEngine = class {
|
|
686
|
+
project;
|
|
687
|
+
projectRoot;
|
|
688
|
+
limitOptions;
|
|
689
|
+
constructor(optionsOrRoot) {
|
|
690
|
+
const options = typeof optionsOrRoot === "string" ? { projectRoot: optionsOrRoot } : optionsOrRoot;
|
|
691
|
+
this.projectRoot = path3.resolve(options.projectRoot || process.cwd());
|
|
692
|
+
this.limitOptions = {
|
|
693
|
+
maxFileLineLimit: options.maxFileLineLimit ?? 200,
|
|
694
|
+
maxMainLogicLimit: options.maxMainLogicLimit ?? 20,
|
|
695
|
+
maxHelperLineLimit: options.maxHelperLineLimit ?? 30
|
|
696
|
+
};
|
|
697
|
+
const tsConfigPath = path3.join(this.projectRoot, "tsconfig.json");
|
|
698
|
+
if (fs2.existsSync(tsConfigPath)) {
|
|
699
|
+
this.project = new Project({ tsConfigFilePath: tsConfigPath, skipAddingFilesFromTsConfig: true });
|
|
700
|
+
} else {
|
|
701
|
+
this.project = new Project({ compilerOptions: {
|
|
702
|
+
target: 99
|
|
703
|
+
/* ESNext */
|
|
704
|
+
} });
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
validateAll() {
|
|
708
|
+
const files = this.collectSourceFiles(this.projectRoot);
|
|
709
|
+
const errors = [];
|
|
710
|
+
const warnings = [];
|
|
711
|
+
const promotableAssets = [];
|
|
712
|
+
checkWorkspaceLayout(this.projectRoot, errors);
|
|
713
|
+
for (const f of files) {
|
|
714
|
+
const sf = this.project.addSourceFileAtPath(f);
|
|
715
|
+
const fileReport = this.validateSourceFile(sf);
|
|
716
|
+
errors.push(...fileReport.errors);
|
|
717
|
+
warnings.push(...fileReport.warnings);
|
|
718
|
+
if (fileReport.promotableAssets) {
|
|
719
|
+
promotableAssets.push(...fileReport.promotableAssets);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
return {
|
|
723
|
+
valid: errors.length === 0,
|
|
724
|
+
totalFiles: files.length,
|
|
725
|
+
errors,
|
|
726
|
+
warnings,
|
|
727
|
+
promotableAssets
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
validateSourceFile(sf) {
|
|
731
|
+
const filePath = sf.getFilePath();
|
|
732
|
+
const baseName = path3.basename(filePath);
|
|
733
|
+
if (baseName.includes("_pb.") || baseName.endsWith(".d.ts")) {
|
|
734
|
+
return { errors: [], warnings: [], promotableAssets: [] };
|
|
735
|
+
}
|
|
736
|
+
const limits = checkFileLimits(sf, this.limitOptions);
|
|
737
|
+
const purity = checkTopLevelPurity(sf);
|
|
738
|
+
const components = checkComponentRules(sf);
|
|
739
|
+
const orchestration = checkOrchestrationRules(sf);
|
|
740
|
+
const boundaries = checkServiceBoundaries(sf);
|
|
741
|
+
const scopedLibraries = checkScopedLibraryRules(sf);
|
|
742
|
+
return {
|
|
743
|
+
errors: [
|
|
744
|
+
...limits.errors,
|
|
745
|
+
...purity.errors,
|
|
746
|
+
...components.errors,
|
|
747
|
+
...orchestration.errors,
|
|
748
|
+
...boundaries.errors,
|
|
749
|
+
...scopedLibraries.errors
|
|
750
|
+
],
|
|
751
|
+
warnings: [
|
|
752
|
+
...limits.warnings,
|
|
753
|
+
...purity.warnings,
|
|
754
|
+
...components.warnings,
|
|
755
|
+
...orchestration.warnings,
|
|
756
|
+
...boundaries.warnings,
|
|
757
|
+
...scopedLibraries.warnings
|
|
758
|
+
],
|
|
759
|
+
promotableAssets: scopedLibraries.promotableAssets
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
collectSourceFiles(dir, collected = []) {
|
|
763
|
+
if (!fs2.existsSync(dir)) return collected;
|
|
764
|
+
const entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
765
|
+
for (const entry of entries) {
|
|
766
|
+
if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git") continue;
|
|
767
|
+
const full = path3.join(dir, entry.name);
|
|
768
|
+
if (entry.isDirectory()) {
|
|
769
|
+
this.collectSourceFiles(full, collected);
|
|
770
|
+
} else if (entry.isFile() && (entry.name.endsWith(".ts") || entry.name.endsWith(".js") || entry.name.endsWith(".tsx") || entry.name.endsWith(".jsx"))) {
|
|
771
|
+
collected.push(full);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
return collected;
|
|
775
|
+
}
|
|
776
|
+
};
|
|
777
|
+
|
|
778
|
+
// src/commands/validate.ts
|
|
779
|
+
async function runValidate(projectRoot = process.cwd()) {
|
|
780
|
+
console.log(`
|
|
781
|
+
\u{1F50D} [Krnl Validate] Scanning project at: ${projectRoot}
|
|
782
|
+
`);
|
|
783
|
+
const validator = new KrnlValidatorEngine({ projectRoot });
|
|
784
|
+
const report = validator.validateAll();
|
|
785
|
+
if (report.warnings.length > 0) {
|
|
786
|
+
console.warn(`\u26A0\uFE0F [WARNINGS] Found ${report.warnings.length} warning(s):`);
|
|
787
|
+
for (const w of report.warnings) {
|
|
788
|
+
console.warn(` - [${w.ruleId}] ${w.filePath}${w.line ? `:${w.line}` : ""}: ${w.message}`);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
if (!report.valid) {
|
|
792
|
+
console.error(`
|
|
793
|
+
\u274C [VALIDATION FAILED] Found ${report.errors.length} error(s):
|
|
794
|
+
`);
|
|
795
|
+
for (const err of report.errors) {
|
|
796
|
+
console.error(` \u274C [${err.ruleId}] ${err.filePath}${err.line ? `:${err.line}` : ""}`);
|
|
797
|
+
console.error(` ${err.message}
|
|
798
|
+
`);
|
|
799
|
+
}
|
|
800
|
+
return false;
|
|
801
|
+
}
|
|
802
|
+
if (report.promotableAssets && report.promotableAssets.length > 0) {
|
|
803
|
+
console.log(`\u{1F4E6} [PROMOTABLE ASSETS] Detected ${report.promotableAssets.length} generic library asset(s) eligible for GutTS promotion:`);
|
|
804
|
+
for (const asset of report.promotableAssets) {
|
|
805
|
+
console.log(` \u2726 [${asset.scope}:${asset.scopeName}] ${asset.name} (${asset.kind}) -> ${asset.target}`);
|
|
806
|
+
}
|
|
807
|
+
console.log("");
|
|
808
|
+
}
|
|
809
|
+
console.log(`\u2705 [VALIDATION PASSED] ${report.totalFiles} source files verified clean against Krnl paradigms.
|
|
810
|
+
`);
|
|
811
|
+
return true;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
// ../core/dist/index.js
|
|
815
|
+
import * as fs3 from "fs";
|
|
816
|
+
import * as path4 from "path";
|
|
817
|
+
|
|
818
|
+
// ../../node_modules/.pnpm/@dinilmusick+gutts@0.1.0/node_modules/@dinilmusick/gutts/dist/json/json-parse.js
|
|
819
|
+
function parseJson(text) {
|
|
820
|
+
return JSON.parse(text);
|
|
821
|
+
}
|
|
822
|
+
function tryParseJson(text) {
|
|
823
|
+
try {
|
|
824
|
+
return parseJson(text);
|
|
825
|
+
} catch {
|
|
826
|
+
return void 0;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// ../core/dist/index.js
|
|
831
|
+
var asyncHooks = globalThis.process?.getBuiltinModule?.("node:async_hooks");
|
|
832
|
+
var KrnlPackageReconciler = class {
|
|
833
|
+
/**
|
|
834
|
+
* Scans all manifest.json files across backend and frontend and reconciles root package.json.
|
|
835
|
+
*/
|
|
836
|
+
static reconcile(projectRoot = process.cwd(), writeChanges = true) {
|
|
837
|
+
const manifests = this.findManifests(projectRoot);
|
|
838
|
+
const aggregatedDeps = {};
|
|
839
|
+
const aggregatedDevDeps = {};
|
|
840
|
+
for (const mPath of manifests) {
|
|
841
|
+
try {
|
|
842
|
+
const raw = fs3.readFileSync(mPath, "utf8");
|
|
843
|
+
const json = tryParseJson(raw) ?? {};
|
|
844
|
+
if (json.dependencies && typeof json.dependencies === "object") {
|
|
845
|
+
for (const [pkg, ver] of Object.entries(json.dependencies)) {
|
|
846
|
+
if (!aggregatedDeps[pkg]) aggregatedDeps[pkg] = [];
|
|
847
|
+
if (!aggregatedDeps[pkg].includes(String(ver))) {
|
|
848
|
+
aggregatedDeps[pkg].push(String(ver));
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
if (json.devDependencies && typeof json.devDependencies === "object") {
|
|
853
|
+
for (const [pkg, ver] of Object.entries(json.devDependencies)) {
|
|
854
|
+
if (!aggregatedDevDeps[pkg]) aggregatedDevDeps[pkg] = [];
|
|
855
|
+
if (!aggregatedDevDeps[pkg].includes(String(ver))) {
|
|
856
|
+
aggregatedDevDeps[pkg].push(String(ver));
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
} catch (err) {
|
|
861
|
+
console.warn(`[KrnlPackageReconciler] Warning: Could not parse ${mPath}:`, err);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
const conflicts = [];
|
|
865
|
+
const resolvedDeps = {};
|
|
866
|
+
const resolvedDevDeps = {};
|
|
867
|
+
for (const [pkg, versions] of Object.entries(aggregatedDeps)) {
|
|
868
|
+
if (versions.length > 1) {
|
|
869
|
+
conflicts.push({ package: pkg, versions });
|
|
870
|
+
}
|
|
871
|
+
resolvedDeps[pkg] = versions[versions.length - 1];
|
|
872
|
+
}
|
|
873
|
+
for (const [pkg, versions] of Object.entries(aggregatedDevDeps)) {
|
|
874
|
+
if (versions.length > 1 && !conflicts.some((c) => c.package === pkg)) {
|
|
875
|
+
conflicts.push({ package: pkg, versions });
|
|
876
|
+
}
|
|
877
|
+
resolvedDevDeps[pkg] = versions[versions.length - 1];
|
|
878
|
+
}
|
|
879
|
+
const pkgJsonPath = path4.join(projectRoot, "package.json");
|
|
880
|
+
let updated = false;
|
|
881
|
+
if (fs3.existsSync(pkgJsonPath)) {
|
|
882
|
+
const pkgContent = JSON.parse(fs3.readFileSync(pkgJsonPath, "utf8"));
|
|
883
|
+
pkgContent.dependencies = pkgContent.dependencies || {};
|
|
884
|
+
pkgContent.devDependencies = pkgContent.devDependencies || {};
|
|
885
|
+
let hasChanges = false;
|
|
886
|
+
for (const [pkg, ver] of Object.entries(resolvedDeps)) {
|
|
887
|
+
if (pkgContent.dependencies[pkg] !== ver) {
|
|
888
|
+
pkgContent.dependencies[pkg] = ver;
|
|
889
|
+
hasChanges = true;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
for (const [pkg, ver] of Object.entries(resolvedDevDeps)) {
|
|
893
|
+
if (!pkgContent.dependencies[pkg] && pkgContent.devDependencies[pkg] !== ver) {
|
|
894
|
+
pkgContent.devDependencies[pkg] = ver;
|
|
895
|
+
hasChanges = true;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
if (hasChanges && writeChanges) {
|
|
899
|
+
fs3.writeFileSync(pkgJsonPath, JSON.stringify(pkgContent, null, 2) + "\n");
|
|
900
|
+
updated = true;
|
|
901
|
+
console.log(`[KrnlPackageReconciler] Reconciled package.json from ${manifests.length} manifest(s).`);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
return {
|
|
905
|
+
updated,
|
|
906
|
+
totalManifests: manifests.length,
|
|
907
|
+
addedDependencies: resolvedDeps,
|
|
908
|
+
addedDevDependencies: resolvedDevDeps,
|
|
909
|
+
conflicts
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Discovers all manifest.json files in krnl/ (or src/)
|
|
914
|
+
*/
|
|
915
|
+
static findManifests(projectRoot) {
|
|
916
|
+
const searchDirs = [
|
|
917
|
+
path4.join(projectRoot, "krnl", "backend", "services"),
|
|
918
|
+
path4.join(projectRoot, "krnl", "backend", "libraries"),
|
|
919
|
+
path4.join(projectRoot, "krnl", "backend", "functionalities"),
|
|
920
|
+
path4.join(projectRoot, "krnl", "frontend", "libraries"),
|
|
921
|
+
path4.join(projectRoot, "krnl", "frontend", "functionalities"),
|
|
922
|
+
path4.join(projectRoot, "src", "backend", "services"),
|
|
923
|
+
path4.join(projectRoot, "src", "services"),
|
|
924
|
+
path4.join(projectRoot, "src", "functionalities"),
|
|
925
|
+
path4.join(projectRoot, "src", "libraries")
|
|
926
|
+
];
|
|
927
|
+
const found = [];
|
|
928
|
+
for (const dir of searchDirs) {
|
|
929
|
+
if (fs3.existsSync(dir)) {
|
|
930
|
+
this.scanRecursive(dir, found);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
return found;
|
|
934
|
+
}
|
|
935
|
+
static scanRecursive(dir, found) {
|
|
936
|
+
try {
|
|
937
|
+
const entries = fs3.readdirSync(dir, { withFileTypes: true });
|
|
938
|
+
for (const entry of entries) {
|
|
939
|
+
if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git") continue;
|
|
940
|
+
const full = path4.join(dir, entry.name);
|
|
941
|
+
if (entry.isDirectory()) {
|
|
942
|
+
this.scanRecursive(full, found);
|
|
943
|
+
} else if (entry.isFile() && entry.name === "manifest.json") {
|
|
944
|
+
found.push(full);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
} catch {
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
};
|
|
951
|
+
|
|
952
|
+
// src/commands/generators/functionalityOrchestrationGenerator.ts
|
|
953
|
+
import * as path6 from "path";
|
|
954
|
+
import * as fs5 from "fs";
|
|
955
|
+
|
|
956
|
+
// src/commands/generators/common.ts
|
|
957
|
+
import * as fs4 from "fs";
|
|
958
|
+
import * as path5 from "path";
|
|
959
|
+
function getPlaneRoots(projectRoot) {
|
|
960
|
+
const krnlRoot = path5.join(projectRoot, "krnl");
|
|
961
|
+
if (fs4.existsSync(krnlRoot)) {
|
|
962
|
+
return {
|
|
963
|
+
backend: path5.join(krnlRoot, "backend"),
|
|
964
|
+
frontend: path5.join(krnlRoot, "frontend"),
|
|
965
|
+
isKrnlDir: true
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
return {
|
|
969
|
+
backend: path5.join(projectRoot, "src"),
|
|
970
|
+
frontend: path5.join(projectRoot, "src"),
|
|
971
|
+
isKrnlDir: false
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// src/commands/generators/functionalityOrchestrationGenerator.ts
|
|
976
|
+
function generateFunctionalityOrchestration(name, projectRoot) {
|
|
977
|
+
const { backend } = getPlaneRoots(projectRoot);
|
|
978
|
+
const orchDir = path6.join(backend, "orchestrations", name);
|
|
979
|
+
fs5.mkdirSync(path6.join(orchDir, "tests"), { recursive: true });
|
|
980
|
+
fs5.writeFileSync(
|
|
981
|
+
path6.join(orchDir, `${name}FunctionalityOrchestration.ts`),
|
|
982
|
+
`import { FunctionalityOrchestration } from '@ultrst-armada/krnl-core';
|
|
983
|
+
|
|
984
|
+
/**
|
|
985
|
+
* @role functionality-orchestration
|
|
986
|
+
* Declaratively composes functionalities within this service into named flows.
|
|
987
|
+
*/
|
|
988
|
+
export class ${name}FunctionalityOrchestration extends FunctionalityOrchestration {
|
|
989
|
+
constructor() {
|
|
990
|
+
super({
|
|
991
|
+
id: '${name}FunctionalityOrchestration',
|
|
992
|
+
name: '${name}',
|
|
993
|
+
version: '1.0.0',
|
|
994
|
+
description: '${name} declarative functionality orchestration flow',
|
|
995
|
+
});
|
|
996
|
+
|
|
997
|
+
this.flow('default')
|
|
998
|
+
.thenFunctionality('Execute${name}')
|
|
999
|
+
.register({ visibility: 'public' });
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
export default ${name}FunctionalityOrchestration;
|
|
1004
|
+
`
|
|
1005
|
+
);
|
|
1006
|
+
fs5.writeFileSync(
|
|
1007
|
+
path6.join(orchDir, "tests", `${name}FunctionalityOrchestration.test.ts`),
|
|
1008
|
+
`import { describe, it, expect } from 'vitest';
|
|
1009
|
+
import { ${name}FunctionalityOrchestration } from '../${name}FunctionalityOrchestration.js';
|
|
1010
|
+
|
|
1011
|
+
describe('${name}FunctionalityOrchestration', () => {
|
|
1012
|
+
it('instantiates and configures declarative functionality flow correctly', () => {
|
|
1013
|
+
const orchestration = new ${name}FunctionalityOrchestration();
|
|
1014
|
+
expect(orchestration.id).toBe('${name}FunctionalityOrchestration');
|
|
1015
|
+
expect(orchestration.getFlow('default')).toBeDefined();
|
|
1016
|
+
expect(orchestration.getFlow('default')?.steps).toHaveLength(1);
|
|
1017
|
+
});
|
|
1018
|
+
});
|
|
1019
|
+
`
|
|
1020
|
+
);
|
|
1021
|
+
console.log(`\u2705 Created FunctionalityOrchestration: krnl/backend/orchestrations/${name}/`);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// src/commands/generators/functionalityGenerator.ts
|
|
1025
|
+
import * as fs6 from "fs";
|
|
1026
|
+
import * as path7 from "path";
|
|
1027
|
+
function generateFunctionality(name, projectRoot, parentServiceOrOptions) {
|
|
1028
|
+
const { backend, frontend } = getPlaneRoots(projectRoot);
|
|
1029
|
+
const options = typeof parentServiceOrOptions === "string" ? { parentService: parentServiceOrOptions } : parentServiceOrOptions || {};
|
|
1030
|
+
let targetService = options.parentService;
|
|
1031
|
+
if (!targetService) {
|
|
1032
|
+
const servicesDir = path7.join(backend, "services");
|
|
1033
|
+
if (fs6.existsSync(servicesDir)) {
|
|
1034
|
+
const existing = fs6.readdirSync(servicesDir, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
1035
|
+
if (existing.length === 1) {
|
|
1036
|
+
targetService = existing[0].name;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
const bFuncDir = targetService ? path7.join(backend, "services", targetService, "functionalities", name) : path7.join(backend, "functionalities", name);
|
|
1041
|
+
fs6.mkdirSync(path7.join(bFuncDir, "main_function"), { recursive: true });
|
|
1042
|
+
fs6.mkdirSync(path7.join(bFuncDir, "helper_functions"), { recursive: true });
|
|
1043
|
+
fs6.mkdirSync(path7.join(bFuncDir, "events"), { recursive: true });
|
|
1044
|
+
fs6.mkdirSync(path7.join(bFuncDir, "tests"), { recursive: true });
|
|
1045
|
+
if (options.withState) {
|
|
1046
|
+
fs6.mkdirSync(path7.join(bFuncDir, "state_data"), { recursive: true });
|
|
1047
|
+
fs6.writeFileSync(
|
|
1048
|
+
path7.join(bFuncDir, "state_data", "state.ts"),
|
|
1049
|
+
`export interface ${name}State {
|
|
1050
|
+
executionCount: number;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
export const initialState: ${name}State = {
|
|
1054
|
+
executionCount: 0
|
|
1055
|
+
};
|
|
1056
|
+
`
|
|
1057
|
+
);
|
|
1058
|
+
}
|
|
1059
|
+
fs6.writeFileSync(
|
|
1060
|
+
path7.join(bFuncDir, `${name}Atom.ts`),
|
|
1061
|
+
`export interface ${name}Input {
|
|
1062
|
+
payload?: string;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
export interface ${name}Output {
|
|
1066
|
+
success: boolean;
|
|
1067
|
+
timestamp: number;
|
|
1068
|
+
}
|
|
1069
|
+
`
|
|
1070
|
+
);
|
|
1071
|
+
fs6.writeFileSync(
|
|
1072
|
+
path7.join(bFuncDir, "manifest.json"),
|
|
1073
|
+
JSON.stringify(
|
|
1074
|
+
{
|
|
1075
|
+
name,
|
|
1076
|
+
service: targetService || void 0,
|
|
1077
|
+
description: `${name} quad-unit functionality`,
|
|
1078
|
+
dependencies: {}
|
|
1079
|
+
},
|
|
1080
|
+
null,
|
|
1081
|
+
2
|
|
1082
|
+
)
|
|
1083
|
+
);
|
|
1084
|
+
fs6.writeFileSync(
|
|
1085
|
+
path7.join(bFuncDir, "main_function", "logic.ts"),
|
|
1086
|
+
`import type { ${name}Input, ${name}Output } from "../${name}Atom.js";
|
|
1087
|
+
|
|
1088
|
+
export const main = async (input: ${name}Input, context: any): Promise<${name}Output> => {
|
|
1089
|
+
return {
|
|
1090
|
+
success: true,
|
|
1091
|
+
timestamp: Date.now()
|
|
1092
|
+
};
|
|
1093
|
+
};
|
|
1094
|
+
`
|
|
1095
|
+
);
|
|
1096
|
+
fs6.writeFileSync(
|
|
1097
|
+
path7.join(bFuncDir, "main_function", "lifecycle.ts"),
|
|
1098
|
+
`export async function onMount() {}
|
|
1099
|
+
export async function onTeardown() {}
|
|
1100
|
+
`
|
|
1101
|
+
);
|
|
1102
|
+
fs6.writeFileSync(
|
|
1103
|
+
path7.join(bFuncDir, "helper_functions", "helper.ts"),
|
|
1104
|
+
`/**
|
|
1105
|
+
* @role component
|
|
1106
|
+
*/
|
|
1107
|
+
export function exampleHelper(): boolean {
|
|
1108
|
+
return true;
|
|
1109
|
+
}
|
|
1110
|
+
`
|
|
1111
|
+
);
|
|
1112
|
+
fs6.writeFileSync(
|
|
1113
|
+
path7.join(bFuncDir, "events", "events.ts"),
|
|
1114
|
+
`/**
|
|
1115
|
+
* @role component
|
|
1116
|
+
*/
|
|
1117
|
+
export interface ${name}CompletedEvent {
|
|
1118
|
+
timestamp: number;
|
|
1119
|
+
}
|
|
1120
|
+
`
|
|
1121
|
+
);
|
|
1122
|
+
fs6.writeFileSync(
|
|
1123
|
+
path7.join(bFuncDir, "tests", `${name}.test.ts`),
|
|
1124
|
+
`import { describe, it, expect } from "vitest";
|
|
1125
|
+
|
|
1126
|
+
describe("${name} functionality", () => {
|
|
1127
|
+
it("passes basic test", () => {
|
|
1128
|
+
expect(true).toBe(true);
|
|
1129
|
+
});
|
|
1130
|
+
});
|
|
1131
|
+
`
|
|
1132
|
+
);
|
|
1133
|
+
const fFuncDir = path7.join(frontend, "functionalities", name);
|
|
1134
|
+
fs6.mkdirSync(path7.join(fFuncDir, "tests"), { recursive: true });
|
|
1135
|
+
fs6.writeFileSync(
|
|
1136
|
+
path7.join(fFuncDir, `${name}0.tsx`),
|
|
1137
|
+
`import React from "react";
|
|
1138
|
+
|
|
1139
|
+
export const ${name}0: React.FC = () => {
|
|
1140
|
+
return (
|
|
1141
|
+
<div className="krnl-func-${name.toLowerCase()}">
|
|
1142
|
+
<h3>${name} (Variant 0)</h3>
|
|
1143
|
+
</div>
|
|
1144
|
+
);
|
|
1145
|
+
};
|
|
1146
|
+
|
|
1147
|
+
export default ${name}0;
|
|
1148
|
+
`
|
|
1149
|
+
);
|
|
1150
|
+
fs6.writeFileSync(
|
|
1151
|
+
path7.join(fFuncDir, "manifest.json"),
|
|
1152
|
+
JSON.stringify({ name: `${name}UI`, dependencies: {} }, null, 2)
|
|
1153
|
+
);
|
|
1154
|
+
fs6.writeFileSync(
|
|
1155
|
+
path7.join(fFuncDir, "tests", `${name}0.test.tsx`),
|
|
1156
|
+
`import { describe, it, expect } from "vitest";
|
|
1157
|
+
|
|
1158
|
+
describe("${name}0 Component", () => {
|
|
1159
|
+
it("renders correctly", () => {
|
|
1160
|
+
expect(true).toBe(true);
|
|
1161
|
+
});
|
|
1162
|
+
});
|
|
1163
|
+
`
|
|
1164
|
+
);
|
|
1165
|
+
KrnlPackageReconciler.reconcile(projectRoot, true);
|
|
1166
|
+
console.log(`\u2705 Created Backend Functionality: ${path7.relative(projectRoot, bFuncDir)}/`);
|
|
1167
|
+
console.log(`\u2705 Created Frontend Functionality: ${path7.relative(projectRoot, fFuncDir)}/`);
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
// src/commands/generators/libraryGenerator.ts
|
|
1171
|
+
import * as fs7 from "fs";
|
|
1172
|
+
import * as path8 from "path";
|
|
1173
|
+
function generateLibrary(name, projectRoot, parentService) {
|
|
1174
|
+
const { backend, frontend } = getPlaneRoots(projectRoot);
|
|
1175
|
+
const conceptName = name.endsWith("Concepts") ? name : `${name}Concepts`;
|
|
1176
|
+
let targetService = parentService;
|
|
1177
|
+
if (!targetService) {
|
|
1178
|
+
const servicesDir = path8.join(backend, "services");
|
|
1179
|
+
if (fs7.existsSync(servicesDir)) {
|
|
1180
|
+
const existing = fs7.readdirSync(servicesDir, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
1181
|
+
if (existing.length === 1) {
|
|
1182
|
+
targetService = existing[0].name;
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
if (!targetService) {
|
|
1187
|
+
targetService = "App";
|
|
1188
|
+
}
|
|
1189
|
+
const bLibDir = path8.join(backend, "services", targetService, "libraries", conceptName);
|
|
1190
|
+
fs7.mkdirSync(path8.join(bLibDir, "tests"), { recursive: true });
|
|
1191
|
+
fs7.writeFileSync(
|
|
1192
|
+
path8.join(bLibDir, "definitions.ts"),
|
|
1193
|
+
`/**
|
|
1194
|
+
* @file definitions.ts
|
|
1195
|
+
* @role component
|
|
1196
|
+
*/
|
|
1197
|
+
export interface ${conceptName}Options {
|
|
1198
|
+
enabled?: boolean;
|
|
1199
|
+
}
|
|
1200
|
+
`
|
|
1201
|
+
);
|
|
1202
|
+
fs7.writeFileSync(
|
|
1203
|
+
path8.join(bLibDir, "functions.ts"),
|
|
1204
|
+
`/**
|
|
1205
|
+
* @file functions.ts
|
|
1206
|
+
* @role component
|
|
1207
|
+
*/
|
|
1208
|
+
import type { ${conceptName}Options } from "./definitions.js";
|
|
1209
|
+
|
|
1210
|
+
export function ${name.toLowerCase()}Transform<T>(data: T, _opts?: ${conceptName}Options): T {
|
|
1211
|
+
return data;
|
|
1212
|
+
}
|
|
1213
|
+
`
|
|
1214
|
+
);
|
|
1215
|
+
fs7.writeFileSync(
|
|
1216
|
+
path8.join(bLibDir, "index.ts"),
|
|
1217
|
+
`export * from "./definitions.js";
|
|
1218
|
+
export * from "./functions.js";
|
|
1219
|
+
`
|
|
1220
|
+
);
|
|
1221
|
+
fs7.writeFileSync(
|
|
1222
|
+
path8.join(bLibDir, "manifest.json"),
|
|
1223
|
+
JSON.stringify({ name: conceptName, service: targetService, dependencies: {} }, null, 2)
|
|
1224
|
+
);
|
|
1225
|
+
fs7.writeFileSync(
|
|
1226
|
+
path8.join(bLibDir, "tests", `${conceptName}.test.ts`),
|
|
1227
|
+
`import { describe, it, expect } from "vitest";
|
|
1228
|
+
import { ${name.toLowerCase()}Transform } from "../functions.js";
|
|
1229
|
+
|
|
1230
|
+
describe("${conceptName}", () => {
|
|
1231
|
+
it("runs correctly", () => {
|
|
1232
|
+
expect(${name.toLowerCase()}Transform(42)).toBe(42);
|
|
1233
|
+
});
|
|
1234
|
+
});
|
|
1235
|
+
`
|
|
1236
|
+
);
|
|
1237
|
+
const fLibDir = path8.join(frontend, "libraries", conceptName);
|
|
1238
|
+
fs7.mkdirSync(path8.join(fLibDir, "tests"), { recursive: true });
|
|
1239
|
+
fs7.writeFileSync(
|
|
1240
|
+
path8.join(fLibDir, "definitions.ts"),
|
|
1241
|
+
`export interface ${name}Props {
|
|
1242
|
+
label?: string;
|
|
1243
|
+
}
|
|
1244
|
+
`
|
|
1245
|
+
);
|
|
1246
|
+
fs7.writeFileSync(
|
|
1247
|
+
path8.join(fLibDir, "components.tsx"),
|
|
1248
|
+
`import React from "react";
|
|
1249
|
+
import type { ${name}Props } from "./definitions.js";
|
|
1250
|
+
import "./styles.css";
|
|
1251
|
+
|
|
1252
|
+
export const ${name}: React.FC<${name}Props> = ({ label = "${name}" }) => {
|
|
1253
|
+
return <div className="krnl-${name.toLowerCase()}">{label}</div>;
|
|
1254
|
+
};
|
|
1255
|
+
`
|
|
1256
|
+
);
|
|
1257
|
+
fs7.writeFileSync(
|
|
1258
|
+
path8.join(fLibDir, "styles.css"),
|
|
1259
|
+
`.krnl-${name.toLowerCase()} {
|
|
1260
|
+
font-family: inherit;
|
|
1261
|
+
}
|
|
1262
|
+
`
|
|
1263
|
+
);
|
|
1264
|
+
fs7.writeFileSync(
|
|
1265
|
+
path8.join(fLibDir, "manifest.json"),
|
|
1266
|
+
JSON.stringify({ name: `${conceptName}UI`, dependencies: {} }, null, 2)
|
|
1267
|
+
);
|
|
1268
|
+
fs7.writeFileSync(
|
|
1269
|
+
path8.join(fLibDir, "tests", `${name}.test.tsx`),
|
|
1270
|
+
`import { describe, it, expect } from "vitest";
|
|
1271
|
+
|
|
1272
|
+
describe("${name} UI Concept", () => {
|
|
1273
|
+
it("renders correctly", () => {
|
|
1274
|
+
expect(true).toBe(true);
|
|
1275
|
+
});
|
|
1276
|
+
});
|
|
1277
|
+
`
|
|
1278
|
+
);
|
|
1279
|
+
KrnlPackageReconciler.reconcile(projectRoot, true);
|
|
1280
|
+
console.log(`\u2705 Created Service-Owned Library: ${path8.relative(projectRoot, bLibDir)}/ (Owner: ${targetService})`);
|
|
1281
|
+
console.log(`\u2705 Created Frontend Concept Library: ${path8.relative(projectRoot, fLibDir)}/`);
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
// src/commands/generators/serviceGenerator.ts
|
|
1285
|
+
import * as fs8 from "fs";
|
|
1286
|
+
import * as path9 from "path";
|
|
1287
|
+
function generateService(name, projectRoot, defaultFrontend) {
|
|
1288
|
+
const frontendReference = defaultFrontend?.component ? {
|
|
1289
|
+
component: defaultFrontend.component,
|
|
1290
|
+
...defaultFrontend.package ? { package: defaultFrontend.package } : {},
|
|
1291
|
+
...defaultFrontend.export ? { export: defaultFrontend.export } : {},
|
|
1292
|
+
relationship: defaultFrontend.relationship ?? "recommended"
|
|
1293
|
+
} : void 0;
|
|
1294
|
+
const frontendConfig = frontendReference ? JSON.stringify(frontendReference, null, 2).replace(/\n/g, "\n ") : void 0;
|
|
1295
|
+
const { backend } = getPlaneRoots(projectRoot);
|
|
1296
|
+
const serviceDir = path9.join(backend, "services", name);
|
|
1297
|
+
const funcsDir = path9.join(serviceDir, "functionalities");
|
|
1298
|
+
const orchsDir = path9.join(serviceDir, "orchestrations");
|
|
1299
|
+
const libsDir = path9.join(serviceDir, "libraries");
|
|
1300
|
+
const testsDir = path9.join(serviceDir, "tests");
|
|
1301
|
+
fs8.mkdirSync(funcsDir, { recursive: true });
|
|
1302
|
+
fs8.mkdirSync(orchsDir, { recursive: true });
|
|
1303
|
+
fs8.mkdirSync(libsDir, { recursive: true });
|
|
1304
|
+
fs8.mkdirSync(testsDir, { recursive: true });
|
|
1305
|
+
const conceptName = `${name}Concepts`;
|
|
1306
|
+
const defaultLibDir = path9.join(libsDir, conceptName);
|
|
1307
|
+
fs8.mkdirSync(path9.join(defaultLibDir, "tests"), { recursive: true });
|
|
1308
|
+
fs8.writeFileSync(
|
|
1309
|
+
path9.join(defaultLibDir, "definitions.ts"),
|
|
1310
|
+
`/**
|
|
1311
|
+
* @file definitions.ts
|
|
1312
|
+
* @role component
|
|
1313
|
+
*/
|
|
1314
|
+
export interface ${name}Options {
|
|
1315
|
+
enabled?: boolean;
|
|
1316
|
+
}
|
|
1317
|
+
`
|
|
1318
|
+
);
|
|
1319
|
+
fs8.writeFileSync(
|
|
1320
|
+
path9.join(defaultLibDir, "functions.ts"),
|
|
1321
|
+
`/**
|
|
1322
|
+
* @file functions.ts
|
|
1323
|
+
* @role component
|
|
1324
|
+
*/
|
|
1325
|
+
import type { ${name}Options } from "./definitions.js";
|
|
1326
|
+
|
|
1327
|
+
export function format${name}Name(input: string, _opts?: ${name}Options): string {
|
|
1328
|
+
return "${name}: " + input;
|
|
1329
|
+
}
|
|
1330
|
+
`
|
|
1331
|
+
);
|
|
1332
|
+
fs8.writeFileSync(
|
|
1333
|
+
path9.join(defaultLibDir, "index.ts"),
|
|
1334
|
+
`export * from "./definitions.js";
|
|
1335
|
+
export * from "./functions.js";
|
|
1336
|
+
`
|
|
1337
|
+
);
|
|
1338
|
+
fs8.writeFileSync(
|
|
1339
|
+
path9.join(defaultLibDir, "manifest.json"),
|
|
1340
|
+
JSON.stringify({ name: conceptName, description: `${name} service-owned library`, dependencies: {} }, null, 2)
|
|
1341
|
+
);
|
|
1342
|
+
fs8.writeFileSync(
|
|
1343
|
+
path9.join(defaultLibDir, "tests", `${conceptName}.test.ts`),
|
|
1344
|
+
`import { describe, it, expect } from "vitest";
|
|
1345
|
+
import { format${name}Name } from "../functions.js";
|
|
1346
|
+
|
|
1347
|
+
describe("${conceptName}", () => {
|
|
1348
|
+
it("formats name correctly", () => {
|
|
1349
|
+
expect(format${name}Name("test")).toContain("${name}");
|
|
1350
|
+
});
|
|
1351
|
+
});
|
|
1352
|
+
`
|
|
1353
|
+
);
|
|
1354
|
+
const frontendProperty = frontendConfig ? ` defaultFrontend: ${frontendConfig},
|
|
1355
|
+
` : "";
|
|
1356
|
+
fs8.writeFileSync(
|
|
1357
|
+
path9.join(serviceDir, `${name}ServiceFacade.ts`),
|
|
1358
|
+
`import { ServiceFacade } from "@ultrst-armada/krnl-core";
|
|
1359
|
+
import * as ${name.toLowerCase()}Concepts from "./libraries/${conceptName}/index.js";
|
|
1360
|
+
|
|
1361
|
+
export const ${name}ServiceFacade = new ServiceFacade({
|
|
1362
|
+
id: "${name.toLowerCase()}.service",
|
|
1363
|
+
name: "${name}Service",
|
|
1364
|
+
version: "1.0.0",
|
|
1365
|
+
description: "${name} service facade",
|
|
1366
|
+
${frontendProperty} libraries: {
|
|
1367
|
+
${conceptName}: ${name.toLowerCase()}Concepts,
|
|
1368
|
+
},
|
|
1369
|
+
});
|
|
1370
|
+
|
|
1371
|
+
export default ${name}ServiceFacade;
|
|
1372
|
+
`
|
|
1373
|
+
);
|
|
1374
|
+
fs8.writeFileSync(
|
|
1375
|
+
path9.join(testsDir, `${name}ServiceFacade.test.ts`),
|
|
1376
|
+
`import { describe, it, expect } from "vitest";
|
|
1377
|
+
import { ${name}ServiceFacade } from "../${name}ServiceFacade.js";
|
|
1378
|
+
|
|
1379
|
+
describe("${name}ServiceFacade", () => {
|
|
1380
|
+
it("initializes and exposes service-owned library", () => {
|
|
1381
|
+
expect(${name}ServiceFacade.id).toBe("${name.toLowerCase()}.service");
|
|
1382
|
+
expect(${name}ServiceFacade.libraries.${conceptName}).toBeDefined();
|
|
1383
|
+
expect(typeof ${name}ServiceFacade.libraries.${conceptName}.format${name}Name).toBe("function");
|
|
1384
|
+
});
|
|
1385
|
+
});
|
|
1386
|
+
`
|
|
1387
|
+
);
|
|
1388
|
+
KrnlPackageReconciler.reconcile(projectRoot, true);
|
|
1389
|
+
console.log(`\u2705 Created Service: ${path9.relative(projectRoot, serviceDir)}/`);
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
// src/commands/generate.ts
|
|
1393
|
+
async function runGenerate(type, name, parentService, projectRoot = process.cwd(), options = {}) {
|
|
1394
|
+
if (!name) {
|
|
1395
|
+
console.error(
|
|
1396
|
+
"\u274C Error: Resource name is required. Usage: krnl generate <service|orchestration|functionality|library> <Name> [parentService]"
|
|
1397
|
+
);
|
|
1398
|
+
process.exit(1);
|
|
1399
|
+
}
|
|
1400
|
+
const normalizedType = type.toLowerCase().replace(/[-_]/g, "");
|
|
1401
|
+
switch (normalizedType) {
|
|
1402
|
+
case "service":
|
|
1403
|
+
generateService(name, projectRoot, options.defaultFrontend);
|
|
1404
|
+
break;
|
|
1405
|
+
case "functionalityorchestration":
|
|
1406
|
+
case "orchestration":
|
|
1407
|
+
generateFunctionalityOrchestration(name, projectRoot);
|
|
1408
|
+
break;
|
|
1409
|
+
case "functionality":
|
|
1410
|
+
generateFunctionality(name, projectRoot, parentService);
|
|
1411
|
+
break;
|
|
1412
|
+
case "library":
|
|
1413
|
+
generateLibrary(name, projectRoot, parentService);
|
|
1414
|
+
break;
|
|
1415
|
+
default:
|
|
1416
|
+
console.error(
|
|
1417
|
+
`\u274C Unknown resource type "${type}". Valid types: service, orchestration, functionality, library`
|
|
1418
|
+
);
|
|
1419
|
+
process.exit(1);
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
// src/commands/watch.ts
|
|
1424
|
+
import * as fs9 from "fs";
|
|
1425
|
+
import * as path10 from "path";
|
|
1426
|
+
function generateMagneticDeclarations(projectRoot = process.cwd()) {
|
|
1427
|
+
const { results: functionalities, libDirs } = scanDirectory(projectRoot);
|
|
1428
|
+
let dtsContent = `// Automatically generated by Krnl Magnetic Watcher. Do not edit manually.
|
|
1429
|
+
|
|
1430
|
+
`;
|
|
1431
|
+
dtsContent += `import { Orchestration } from '@ultrst-armada/krnl-core';
|
|
1432
|
+
|
|
1433
|
+
`;
|
|
1434
|
+
const globalDeclarations = [];
|
|
1435
|
+
for (const func of functionalities) {
|
|
1436
|
+
const helpersDir = path10.join(func.funcPath, "helper_functions");
|
|
1437
|
+
if (fs9.existsSync(helpersDir)) {
|
|
1438
|
+
const files = fs9.readdirSync(helpersDir);
|
|
1439
|
+
for (const file of files) {
|
|
1440
|
+
if (file.endsWith(".js") || file.endsWith(".ts")) {
|
|
1441
|
+
const filePath = path10.join(helpersDir, file);
|
|
1442
|
+
const baseName = path10.basename(file, path10.extname(file));
|
|
1443
|
+
const importPath = `./${path10.relative(projectRoot, filePath).replace(/\\/g, "/")}`;
|
|
1444
|
+
const { named, hasDefault } = parseExports(filePath, baseName);
|
|
1445
|
+
if (hasDefault) {
|
|
1446
|
+
globalDeclarations.push(` const ${baseName}: typeof import('${importPath}')['default'];`);
|
|
1447
|
+
}
|
|
1448
|
+
for (const name of named) {
|
|
1449
|
+
globalDeclarations.push(` const ${name}: typeof import('${importPath}')['${name}'];`);
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
const eventsDir = path10.join(func.funcPath, "events");
|
|
1455
|
+
if (fs9.existsSync(eventsDir)) {
|
|
1456
|
+
const files = fs9.readdirSync(eventsDir);
|
|
1457
|
+
for (const file of files) {
|
|
1458
|
+
if (file.endsWith(".js") || file.endsWith(".ts")) {
|
|
1459
|
+
const filePath = path10.join(eventsDir, file);
|
|
1460
|
+
const baseName = path10.basename(file, path10.extname(file));
|
|
1461
|
+
const importPath = `./${path10.relative(projectRoot, filePath).replace(/\\/g, "/")}`;
|
|
1462
|
+
const { named, hasDefault } = parseExports(filePath, baseName);
|
|
1463
|
+
if (hasDefault) {
|
|
1464
|
+
globalDeclarations.push(` const ${baseName}: typeof import('${importPath}')['default'];`);
|
|
1465
|
+
}
|
|
1466
|
+
for (const name of named) {
|
|
1467
|
+
globalDeclarations.push(` const ${name}: typeof import('${importPath}')['${name}'];`);
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
for (const libPath of libDirs) {
|
|
1474
|
+
scanLibFiles(libPath, projectRoot, globalDeclarations);
|
|
1475
|
+
}
|
|
1476
|
+
if (globalDeclarations.length > 0) {
|
|
1477
|
+
dtsContent += `declare global {
|
|
1478
|
+
${Array.from(new Set(globalDeclarations)).join("\n")}
|
|
1479
|
+
}
|
|
1480
|
+
`;
|
|
1481
|
+
}
|
|
1482
|
+
const outPath = path10.join(projectRoot, "krnl-magnetic.d.ts");
|
|
1483
|
+
fs9.writeFileSync(outPath, dtsContent);
|
|
1484
|
+
console.log(`[Krnl Watch] Updated ambient declarations: ${outPath}`);
|
|
1485
|
+
}
|
|
1486
|
+
function scanLibFiles(dir, projectRoot, decls) {
|
|
1487
|
+
if (!fs9.existsSync(dir)) return;
|
|
1488
|
+
for (const entry of fs9.readdirSync(dir, { withFileTypes: true })) {
|
|
1489
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
1490
|
+
const full = path10.join(dir, entry.name);
|
|
1491
|
+
if (entry.isDirectory()) {
|
|
1492
|
+
scanLibFiles(full, projectRoot, decls);
|
|
1493
|
+
} else if ((entry.name.endsWith(".ts") || entry.name.endsWith(".js")) && !entry.name.endsWith(".d.ts")) {
|
|
1494
|
+
const baseName = path10.basename(entry.name, path10.extname(entry.name));
|
|
1495
|
+
const importPath = `./${path10.relative(projectRoot, full).replace(/\\/g, "/")}`;
|
|
1496
|
+
const { named, hasDefault } = parseExports(full, baseName);
|
|
1497
|
+
if (hasDefault) decls.push(` const ${baseName}: typeof import('${importPath}')['default'];`);
|
|
1498
|
+
for (const n of named) decls.push(` const ${n}: typeof import('${importPath}')['${n}'];`);
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
function scanDirectory(dir, results = [], libDirs = []) {
|
|
1503
|
+
if (!fs9.existsSync(dir)) return { results, libDirs };
|
|
1504
|
+
const list = fs9.readdirSync(dir, { withFileTypes: true });
|
|
1505
|
+
for (const entry of list) {
|
|
1506
|
+
if (entry.isDirectory()) {
|
|
1507
|
+
if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git") continue;
|
|
1508
|
+
const fullPath = path10.join(dir, entry.name);
|
|
1509
|
+
if (entry.name === "functionalities") {
|
|
1510
|
+
const funcs = fs9.readdirSync(fullPath, { withFileTypes: true });
|
|
1511
|
+
for (const f of funcs) {
|
|
1512
|
+
if (f.isDirectory()) results.push({ funcPath: path10.join(fullPath, f.name), funcName: f.name });
|
|
1513
|
+
}
|
|
1514
|
+
} else if (entry.name === "libraries" || entry.name === "library") {
|
|
1515
|
+
libDirs.push(fullPath);
|
|
1516
|
+
} else {
|
|
1517
|
+
scanDirectory(fullPath, results, libDirs);
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
return { results, libDirs };
|
|
1522
|
+
}
|
|
1523
|
+
function parseExports(filePath, defaultName) {
|
|
1524
|
+
const content = fs9.readFileSync(filePath, "utf8");
|
|
1525
|
+
const named = [];
|
|
1526
|
+
let hasDefault = false;
|
|
1527
|
+
const namedRegex = /export\s+(?:const|let|var|function|class|async\s+function)\s+([a-zA-Z0-9_$]+)/g;
|
|
1528
|
+
let match;
|
|
1529
|
+
while ((match = namedRegex.exec(content)) !== null) {
|
|
1530
|
+
if (match[1]) named.push(match[1]);
|
|
1531
|
+
}
|
|
1532
|
+
if (content.includes("export default")) {
|
|
1533
|
+
hasDefault = true;
|
|
1534
|
+
}
|
|
1535
|
+
return { named, hasDefault };
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
// src/commands/build.ts
|
|
1539
|
+
import * as path11 from "path";
|
|
1540
|
+
import * as fs10 from "fs";
|
|
1541
|
+
import { execSync } from "child_process";
|
|
1542
|
+
async function runBuild(projectRoot = process.cwd(), options = {}) {
|
|
1543
|
+
KrnlPackageReconciler.reconcile(projectRoot, true);
|
|
1544
|
+
const target = options.service || options.orchestration;
|
|
1545
|
+
if (target) {
|
|
1546
|
+
console.log(`\u{1F3D7}\uFE0F [Krnl Build] Target service/orchestration variant: "${target}"`);
|
|
1547
|
+
} else if (options.all) {
|
|
1548
|
+
console.log(`\u{1F3D7}\uFE0F [Krnl Build] Combined multi-service build`);
|
|
1549
|
+
}
|
|
1550
|
+
console.log("\u{1F50D} [Krnl Build] Running AST & layer purity validation...");
|
|
1551
|
+
const valid = await runValidate(projectRoot);
|
|
1552
|
+
if (!valid) {
|
|
1553
|
+
console.error("\u274C [Krnl Build] Build aborted due to validation errors.");
|
|
1554
|
+
return { success: false, error: "Validation failed" };
|
|
1555
|
+
}
|
|
1556
|
+
generateMagneticDeclarations(projectRoot);
|
|
1557
|
+
const tsConfigPath = path11.join(projectRoot, "tsconfig.json");
|
|
1558
|
+
if (fs10.existsSync(tsConfigPath)) {
|
|
1559
|
+
console.log("\u{1F3D7}\uFE0F [Krnl Build] Compiling TypeScript source files to dist/...");
|
|
1560
|
+
const localTsc = path11.join(projectRoot, "node_modules", ".bin", "tsc");
|
|
1561
|
+
const tscCmd = fs10.existsSync(localTsc) ? localTsc : "tsc";
|
|
1562
|
+
try {
|
|
1563
|
+
execSync(`${tscCmd} -p tsconfig.json`, { cwd: projectRoot, stdio: "inherit" });
|
|
1564
|
+
} catch (err) {
|
|
1565
|
+
console.error("\u274C [Krnl Build] TypeScript compilation failed.");
|
|
1566
|
+
return { success: false, error: "TypeScript compilation failed" };
|
|
1567
|
+
}
|
|
1568
|
+
} else {
|
|
1569
|
+
console.log("\u2139\uFE0F [Krnl Build] No tsconfig.json found; skipping TypeScript compiler invocation.");
|
|
1570
|
+
}
|
|
1571
|
+
console.log("\u2705 [Krnl Build] Project validation, declaration generation, and compilation completed successfully.");
|
|
1572
|
+
return { success: true };
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
// src/commands/dev.ts
|
|
1576
|
+
import * as http from "http";
|
|
1577
|
+
import * as path12 from "path";
|
|
1578
|
+
import * as fs11 from "fs";
|
|
1579
|
+
async function runDev(projectRoot = process.cwd(), options = {}) {
|
|
1580
|
+
const port = options.port ?? 8080;
|
|
1581
|
+
KrnlPackageReconciler.reconcile(projectRoot, true);
|
|
1582
|
+
generateMagneticDeclarations(projectRoot);
|
|
1583
|
+
if (options.dryRun) {
|
|
1584
|
+
console.log(`\u26A1 [Krnl Dev] Development environment verified on port ${port} (--dry-run).`);
|
|
1585
|
+
return {
|
|
1586
|
+
port,
|
|
1587
|
+
close: async () => {
|
|
1588
|
+
}
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
const server = http.createServer((req, res) => {
|
|
1592
|
+
res.setHeader("Content-Type", "application/json");
|
|
1593
|
+
const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
1594
|
+
if (url.pathname === "/" || url.pathname === "/health") {
|
|
1595
|
+
res.writeHead(200);
|
|
1596
|
+
res.end(JSON.stringify({ status: "ok", service: "krnl-dev", port, uptime: process.uptime() }));
|
|
1597
|
+
return;
|
|
1598
|
+
}
|
|
1599
|
+
if (url.pathname === "/manifest") {
|
|
1600
|
+
const pkgPath = path12.join(projectRoot, "package.json");
|
|
1601
|
+
const pkg = fs11.existsSync(pkgPath) ? JSON.parse(fs11.readFileSync(pkgPath, "utf8")) : {};
|
|
1602
|
+
res.writeHead(200);
|
|
1603
|
+
res.end(JSON.stringify({ name: pkg.name || "krnl-app", version: pkg.version || "1.0.0" }));
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
res.writeHead(404);
|
|
1607
|
+
res.end(JSON.stringify({ error: "Route not found" }));
|
|
1608
|
+
});
|
|
1609
|
+
await new Promise((resolve2, reject) => {
|
|
1610
|
+
server.listen(port, () => resolve2());
|
|
1611
|
+
server.on("error", reject);
|
|
1612
|
+
});
|
|
1613
|
+
console.log(`\u26A1 [Krnl Dev] Development server listening on http://localhost:${port}`);
|
|
1614
|
+
const watcher = fs11.watch(projectRoot, { recursive: true }, (_evt, filename) => {
|
|
1615
|
+
if (filename && (filename.endsWith(".ts") || filename.endsWith(".js")) && !filename.includes("krnl-magnetic.d.ts")) {
|
|
1616
|
+
try {
|
|
1617
|
+
generateMagneticDeclarations(projectRoot);
|
|
1618
|
+
} catch {
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
if (filename && filename.endsWith("manifest.json")) {
|
|
1622
|
+
try {
|
|
1623
|
+
KrnlPackageReconciler.reconcile(projectRoot, true);
|
|
1624
|
+
} catch {
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
});
|
|
1628
|
+
const close = async () => {
|
|
1629
|
+
watcher.close();
|
|
1630
|
+
await new Promise((resolve2) => server.close(() => resolve2()));
|
|
1631
|
+
};
|
|
1632
|
+
return { server, close, port };
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
export {
|
|
1636
|
+
runValidate,
|
|
1637
|
+
KrnlPackageReconciler,
|
|
1638
|
+
runGenerate,
|
|
1639
|
+
generateMagneticDeclarations,
|
|
1640
|
+
runBuild,
|
|
1641
|
+
runDev
|
|
1642
|
+
};
|