@anatolykoptev/krolik-cli 0.10.1 → 0.12.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.
package/dist/bin/cli.js CHANGED
@@ -56,7 +56,7 @@ var KROLIK_VERSION, TEMPLATE_VERSION;
56
56
  var init_version = __esm({
57
57
  "src/version.ts"() {
58
58
  init_esm_shims();
59
- KROLIK_VERSION = "0.10.1";
59
+ KROLIK_VERSION = "0.12.0";
60
60
  TEMPLATE_VERSION = "6.1.0";
61
61
  }
62
62
  });
@@ -13289,18 +13289,33 @@ var init_routes2 = __esm({
13289
13289
  init_esm_shims();
13290
13290
  init_core();
13291
13291
  routesFlagSchema = {
13292
- json: COMMON_FLAGS.json
13292
+ json: COMMON_FLAGS.json,
13293
+ compact: { flag: "--compact" },
13294
+ full: { flag: "--full" }
13293
13295
  };
13294
13296
  routesTool = {
13295
13297
  name: "krolik_routes",
13296
- description: "Analyze tRPC API routes. Returns all procedures with types, inputs, and protection status.",
13298
+ description: `Analyze tRPC API routes. Returns all procedures with types and protection status.
13299
+
13300
+ **Output modes:**
13301
+ - (default) Smart format - groups procedures by type (queries/mutations), shows only unprotected as exceptions
13302
+ - compact: true - overview only, routers with Q/M counts (smallest output)
13303
+ - full: true - verbose legacy format with all procedure attributes`,
13297
13304
  inputSchema: {
13298
13305
  type: "object",
13299
13306
  properties: {
13300
13307
  ...PROJECT_PROPERTY,
13301
13308
  json: {
13302
13309
  type: "boolean",
13303
- description: "Return JSON format instead of markdown"
13310
+ description: "Return JSON format instead of XML"
13311
+ },
13312
+ compact: {
13313
+ type: "boolean",
13314
+ description: "Compact output - routers with procedure counts only. Smallest output."
13315
+ },
13316
+ full: {
13317
+ type: "boolean",
13318
+ description: "Full verbose output - all procedures with all attributes. Use only when you need complete details."
13304
13319
  }
13305
13320
  }
13306
13321
  },
@@ -13325,18 +13340,47 @@ var init_schema2 = __esm({
13325
13340
  init_esm_shims();
13326
13341
  init_core();
13327
13342
  schemaFlagSchema = {
13328
- json: COMMON_FLAGS.json
13343
+ json: COMMON_FLAGS.json,
13344
+ model: { flag: "--model" },
13345
+ domain: { flag: "--domain" },
13346
+ compact: { flag: "--compact" },
13347
+ full: { flag: "--full" }
13329
13348
  };
13330
13349
  schemaTool = {
13331
13350
  name: "krolik_schema",
13332
- description: "Analyze Prisma database schema. Returns all models, fields, relations, and enums.",
13351
+ description: `Analyze Prisma database schema. Returns models, fields, relations, and enums.
13352
+
13353
+ **Output modes:**
13354
+ - (default) Smart format - AI-optimized, hides standard fields (id, createdAt, updatedAt), obvious defaults
13355
+ - compact: true - overview only, models with relations (smallest output)
13356
+ - full: true - verbose legacy format with all fields and attributes
13357
+
13358
+ **Filters:**
13359
+ - model: "User" - filter by model name (partial match)
13360
+ - domain: "Auth" - filter by domain (derived from schema filename)`,
13333
13361
  inputSchema: {
13334
13362
  type: "object",
13335
13363
  properties: {
13336
13364
  ...PROJECT_PROPERTY,
13337
13365
  json: {
13338
13366
  type: "boolean",
13339
- description: "Return JSON format instead of markdown"
13367
+ description: "Return JSON format instead of XML"
13368
+ },
13369
+ model: {
13370
+ type: "string",
13371
+ description: 'Filter by model name (partial match, case-insensitive). Example: "User", "Booking"'
13372
+ },
13373
+ domain: {
13374
+ type: "string",
13375
+ description: 'Filter by domain (derived from schema filename). Example: "Auth", "Bookings", "CRM"'
13376
+ },
13377
+ compact: {
13378
+ type: "boolean",
13379
+ description: "Compact output - models with relations only, no field details. Smallest output."
13380
+ },
13381
+ full: {
13382
+ type: "boolean",
13383
+ description: "Full verbose output - all fields with all attributes. Use only when you need complete details."
13340
13384
  }
13341
13385
  }
13342
13386
  },
@@ -39165,8 +39209,8 @@ function formatAI2(data) {
39165
39209
  ` <stats routers="${data.routers.length}" procedures="${data.totalProcedures}" queries="${data.queries}" mutations="${data.mutations}" protected="${data.protectedCount}" />`
39166
39210
  );
39167
39211
  lines.push("");
39168
- const grouped = groupByDomain2(data.routers);
39169
- for (const [domain, routers] of Object.entries(grouped)) {
39212
+ const grouped = groupByDomainDynamic(data.routers);
39213
+ for (const [domain, routers] of grouped) {
39170
39214
  if (routers.length === 0) continue;
39171
39215
  lines.push(` <domain name="${domain}">`);
39172
39216
  for (const router of routers) {
@@ -39186,6 +39230,80 @@ function formatAI2(data) {
39186
39230
  lines.push("</trpc-routes>");
39187
39231
  return lines.join("\n");
39188
39232
  }
39233
+ function formatSmart(data) {
39234
+ const lines = [];
39235
+ const protectedRatio = data.protectedCount / data.totalProcedures;
39236
+ const mostProtected = protectedRatio > 0.6;
39237
+ lines.push("<trpc-routes>");
39238
+ lines.push(
39239
+ ` <stats routers="${data.routers.length}" procedures="${data.totalProcedures}" queries="${data.queries}" mutations="${data.mutations}" />`
39240
+ );
39241
+ if (mostProtected) {
39242
+ lines.push(
39243
+ ` <note>Most procedures are protected (${Math.round(protectedRatio * 100)}%). Unprotected shown explicitly.</note>`
39244
+ );
39245
+ }
39246
+ const grouped = groupByDomainDynamic(data.routers);
39247
+ const domains = Array.from(grouped.keys());
39248
+ lines.push(` <domains>${domains.join(", ")}</domains>`);
39249
+ lines.push("");
39250
+ for (const [domain, routers] of grouped) {
39251
+ if (routers.length === 0) continue;
39252
+ lines.push(` <domain name="${domain}">`);
39253
+ for (const router of routers) {
39254
+ const queries = router.procedures.filter((p) => p.type === "query").map((p) => p.name);
39255
+ const mutations = router.procedures.filter((p) => p.type === "mutation").map((p) => p.name);
39256
+ const unprotected = router.procedures.filter((p) => !p.isProtected).map((p) => p.name);
39257
+ lines.push(` <router file="${router.file}">`);
39258
+ if (queries.length > 0) {
39259
+ lines.push(` <queries>${queries.join(", ")}</queries>`);
39260
+ }
39261
+ if (mutations.length > 0) {
39262
+ lines.push(` <mutations>${mutations.join(", ")}</mutations>`);
39263
+ }
39264
+ if (mostProtected && unprotected.length > 0) {
39265
+ lines.push(` <public>${unprotected.join(", ")}</public>`);
39266
+ }
39267
+ if (!mostProtected) {
39268
+ const protectedProcs = router.procedures.filter((p) => p.isProtected).map((p) => p.name);
39269
+ if (protectedProcs.length > 0) {
39270
+ lines.push(` <protected>${protectedProcs.join(", ")}</protected>`);
39271
+ }
39272
+ }
39273
+ lines.push(" </router>");
39274
+ }
39275
+ lines.push(" </domain>");
39276
+ lines.push("");
39277
+ }
39278
+ lines.push("</trpc-routes>");
39279
+ return lines.join("\n");
39280
+ }
39281
+ function formatCompact(data) {
39282
+ const lines = [];
39283
+ lines.push('<trpc-routes mode="compact">');
39284
+ lines.push(
39285
+ ` <stats routers="${data.routers.length}" procedures="${data.totalProcedures}" queries="${data.queries}" mutations="${data.mutations}" />`
39286
+ );
39287
+ const grouped = groupByDomainDynamic(data.routers);
39288
+ const domains = Array.from(grouped.keys());
39289
+ lines.push(` <domains>${domains.join(", ")}</domains>`);
39290
+ lines.push("");
39291
+ for (const [domain, routers] of grouped) {
39292
+ if (routers.length === 0) continue;
39293
+ const routerSummaries = routers.map((r) => {
39294
+ const q = r.procedures.filter((p) => p.type === "query").length;
39295
+ const m = r.procedures.filter((p) => p.type === "mutation").length;
39296
+ return `${r.file}(${q}Q/${m}M)`;
39297
+ });
39298
+ lines.push(` <domain name="${domain}">`);
39299
+ lines.push(` ${routerSummaries.join(", ")}`);
39300
+ lines.push(" </domain>");
39301
+ }
39302
+ lines.push("");
39303
+ lines.push(" <hint>Use --full for procedure details</hint>");
39304
+ lines.push("</trpc-routes>");
39305
+ return lines.join("\n");
39306
+ }
39189
39307
  function formatMarkdown2(data) {
39190
39308
  const lines = [
39191
39309
  "# API Routes (tRPC)",
@@ -39224,28 +39342,34 @@ function formatMarkdown2(data) {
39224
39342
  lines.push("*Generated by krolik-cli*");
39225
39343
  return lines.join("\n");
39226
39344
  }
39227
- function groupByDomain2(routers) {
39228
- const domains = {
39229
- Business: [],
39230
- User: [],
39231
- Content: [],
39232
- Social: [],
39233
- System: []
39234
- };
39345
+ function groupByDomainDynamic(routers) {
39346
+ const result = /* @__PURE__ */ new Map();
39235
39347
  for (const router of routers) {
39236
- if (router.file.startsWith("business")) {
39237
- domains.Business?.push(router);
39238
- } else if (["user", "favorites", "userLists", "userTodos"].some((k) => router.file.includes(k))) {
39239
- domains.User?.push(router);
39240
- } else if (["places", "events", "reviews", "search"].some((k) => router.file.includes(k))) {
39241
- domains.Content?.push(router);
39242
- } else if (["social", "activity", "referral", "interactions"].some((k) => router.file.includes(k))) {
39243
- domains.Social?.push(router);
39244
- } else {
39245
- domains.System?.push(router);
39348
+ const domain = inferDomainFromPath(router.file);
39349
+ if (!result.has(domain)) {
39350
+ result.set(domain, []);
39246
39351
  }
39352
+ result.get(domain).push(router);
39247
39353
  }
39248
- return domains;
39354
+ return new Map([...result.entries()].sort((a, b) => a[0].localeCompare(b[0])));
39355
+ }
39356
+ function inferDomainFromPath(filePath) {
39357
+ const firstSegment = filePath.split("/")[0] ?? filePath;
39358
+ if (firstSegment.startsWith("business")) {
39359
+ return "Business";
39360
+ }
39361
+ if (firstSegment.startsWith("admin")) {
39362
+ return "Admin";
39363
+ }
39364
+ return firstSegment.charAt(0).toUpperCase() + firstSegment.slice(1);
39365
+ }
39366
+ function groupByDomain2(routers) {
39367
+ const grouped = groupByDomainDynamic(routers);
39368
+ const result = {};
39369
+ for (const [domain, routerList] of grouped) {
39370
+ result[domain] = routerList;
39371
+ }
39372
+ return result;
39249
39373
  }
39250
39374
  var MAX_LENGTH2, SLICE_ARG1_VALUE2;
39251
39375
  var init_output6 = __esm({
@@ -39583,7 +39707,15 @@ async function runRoutes(ctx) {
39583
39707
  printRoutes(result, logger2);
39584
39708
  return;
39585
39709
  }
39586
- console.log(xmlOutput);
39710
+ if (options.compact) {
39711
+ console.log(formatCompact(result));
39712
+ return;
39713
+ }
39714
+ if (options.full) {
39715
+ console.log(xmlOutput);
39716
+ return;
39717
+ }
39718
+ console.log(formatSmart(result));
39587
39719
  }
39588
39720
  var init_routes3 = __esm({
39589
39721
  "src/commands/routes/index.ts"() {
@@ -39594,42 +39726,246 @@ var init_routes3 = __esm({
39594
39726
  }
39595
39727
  });
39596
39728
 
39729
+ // src/commands/schema/relation-graph.ts
39730
+ function buildRelationGraph(models) {
39731
+ const nodes = /* @__PURE__ */ new Map();
39732
+ for (const model of models) {
39733
+ nodes.set(model.name, {
39734
+ model: model.name,
39735
+ file: model.file,
39736
+ relations: model.relations,
39737
+ referencedBy: [],
39738
+ inDegree: 0,
39739
+ outDegree: model.relations.length
39740
+ });
39741
+ }
39742
+ for (const model of models) {
39743
+ for (const relation of model.relations) {
39744
+ const targetNode = nodes.get(relation);
39745
+ if (targetNode) {
39746
+ targetNode.referencedBy.push(model.name);
39747
+ targetNode.inDegree++;
39748
+ }
39749
+ }
39750
+ }
39751
+ return nodes;
39752
+ }
39753
+ function findSCC(nodes) {
39754
+ const state = {
39755
+ index: /* @__PURE__ */ new Map(),
39756
+ lowlink: /* @__PURE__ */ new Map(),
39757
+ onStack: /* @__PURE__ */ new Set(),
39758
+ stack: [],
39759
+ sccs: [],
39760
+ currentIndex: 0
39761
+ };
39762
+ function strongConnect(nodeId) {
39763
+ state.index.set(nodeId, state.currentIndex);
39764
+ state.lowlink.set(nodeId, state.currentIndex);
39765
+ state.currentIndex++;
39766
+ state.stack.push(nodeId);
39767
+ state.onStack.add(nodeId);
39768
+ const node = nodes.get(nodeId);
39769
+ const deps = node?.relations ?? [];
39770
+ for (const depId of deps) {
39771
+ if (!nodes.has(depId)) continue;
39772
+ if (!state.index.has(depId)) {
39773
+ strongConnect(depId);
39774
+ state.lowlink.set(nodeId, Math.min(state.lowlink.get(nodeId), state.lowlink.get(depId)));
39775
+ } else if (state.onStack.has(depId)) {
39776
+ state.lowlink.set(nodeId, Math.min(state.lowlink.get(nodeId), state.index.get(depId)));
39777
+ }
39778
+ }
39779
+ if (state.lowlink.get(nodeId) === state.index.get(nodeId)) {
39780
+ const scc = [];
39781
+ let w;
39782
+ do {
39783
+ w = state.stack.pop();
39784
+ state.onStack.delete(w);
39785
+ scc.push(w);
39786
+ } while (w !== nodeId);
39787
+ state.sccs.push(scc);
39788
+ }
39789
+ }
39790
+ for (const nodeId of nodes.keys()) {
39791
+ if (!state.index.has(nodeId)) {
39792
+ strongConnect(nodeId);
39793
+ }
39794
+ }
39795
+ return state.sccs;
39796
+ }
39797
+ function findCommonPrefix(models) {
39798
+ if (models.length < 2) return null;
39799
+ const prefixes = /* @__PURE__ */ new Map();
39800
+ for (const model of models) {
39801
+ const parts = model.split(/(?=[A-Z])/);
39802
+ if (parts.length >= 2) {
39803
+ const prefix = parts[0];
39804
+ if (prefix.length >= 3) {
39805
+ prefixes.set(prefix, (prefixes.get(prefix) ?? 0) + 1);
39806
+ }
39807
+ }
39808
+ }
39809
+ for (const [prefix, count] of prefixes) {
39810
+ const ratio = count / models.length;
39811
+ if (ratio >= 0.6) {
39812
+ return prefix;
39813
+ }
39814
+ }
39815
+ return null;
39816
+ }
39817
+ function findCoreEntity(models, nodes) {
39818
+ let maxInDegree = 0;
39819
+ let core = null;
39820
+ for (const model of models) {
39821
+ const node = nodes.get(model);
39822
+ if (node && node.inDegree > maxInDegree) {
39823
+ maxInDegree = node.inDegree;
39824
+ core = model;
39825
+ }
39826
+ }
39827
+ return maxInDegree >= 2 ? core : null;
39828
+ }
39829
+ function inferClusterName(models, nodes) {
39830
+ const prefix = findCommonPrefix(models);
39831
+ if (prefix) {
39832
+ return {
39833
+ name: prefix.endsWith("s") ? prefix : `${prefix}s`,
39834
+ confidence: 85,
39835
+ source: "prefix"
39836
+ };
39837
+ }
39838
+ const core = findCoreEntity(models, nodes);
39839
+ if (core && models.length >= 2) {
39840
+ return {
39841
+ name: core.endsWith("s") ? core : `${core}s`,
39842
+ confidence: 75,
39843
+ source: "core-entity"
39844
+ };
39845
+ }
39846
+ const files = new Set(models.map((m) => nodes.get(m)?.file ?? ""));
39847
+ if (files.size === 1) {
39848
+ const file = [...files][0];
39849
+ const domain = file.replace(/\.prisma$/, "").replace(/^.*\//, "");
39850
+ if (domain && domain !== "schema") {
39851
+ return {
39852
+ name: domain.charAt(0).toUpperCase() + domain.slice(1),
39853
+ confidence: 50,
39854
+ source: "filename"
39855
+ };
39856
+ }
39857
+ }
39858
+ return { name: "", confidence: 0, source: "filename" };
39859
+ }
39860
+ function analyzeRelationGraph(models) {
39861
+ const nodes = buildRelationGraph(models);
39862
+ const sccs = findSCC(nodes);
39863
+ const clusters = [];
39864
+ const unclustered = [];
39865
+ for (const scc of sccs) {
39866
+ if (scc.length < MIN_CLUSTER_SIZE) {
39867
+ const node = nodes.get(scc[0]);
39868
+ if (!node || node.inDegree < 3 && node.outDegree < 3) {
39869
+ unclustered.push(...scc);
39870
+ continue;
39871
+ }
39872
+ }
39873
+ const { name, confidence, source } = inferClusterName(scc, nodes);
39874
+ if (confidence < MIN_CONFIDENCE2) {
39875
+ unclustered.push(...scc);
39876
+ continue;
39877
+ }
39878
+ const core = findCoreEntity(scc, nodes);
39879
+ clusters.push({
39880
+ name,
39881
+ core,
39882
+ models: scc.sort(),
39883
+ confidence,
39884
+ source
39885
+ });
39886
+ }
39887
+ clusters.sort((a, b) => b.models.length - a.models.length);
39888
+ return {
39889
+ nodes: Array.from(nodes.values()),
39890
+ clusters,
39891
+ unclustered: unclustered.sort()
39892
+ };
39893
+ }
39894
+ var MIN_CONFIDENCE2, MIN_CLUSTER_SIZE;
39895
+ var init_relation_graph = __esm({
39896
+ "src/commands/schema/relation-graph.ts"() {
39897
+ init_esm_shims();
39898
+ MIN_CONFIDENCE2 = 60;
39899
+ MIN_CLUSTER_SIZE = 2;
39900
+ }
39901
+ });
39902
+
39597
39903
  // src/commands/schema/grouping.ts
39598
39904
  function groupByFile2(models) {
39599
39905
  return groupByProperty(models, "file");
39600
39906
  }
39601
39907
  function groupByDomain3(models) {
39602
- return groupBy(models, (model) => inferDomain(model.file));
39603
- }
39604
- function inferDomain(filename) {
39605
- const base = filename.replace(".prisma", "").toLowerCase();
39606
- for (const [key, label] of Object.entries(DEFAULT_DOMAINS)) {
39607
- if (base.includes(key)) {
39608
- return label;
39908
+ if (models.length === 0) {
39909
+ return /* @__PURE__ */ new Map();
39910
+ }
39911
+ const files = new Set(models.map((m) => m.file));
39912
+ const isSingleFile = files.size === 1;
39913
+ if (!isSingleFile) {
39914
+ return groupByFilename(models);
39915
+ }
39916
+ const { clusters, unclustered } = analyzeRelationGraph(models);
39917
+ if (clusters.length === 0) {
39918
+ return groupByFilename(models);
39919
+ }
39920
+ const result = /* @__PURE__ */ new Map();
39921
+ const modelMap = new Map(models.map((m) => [m.name, m]));
39922
+ for (const cluster of clusters) {
39923
+ const clusterModels = cluster.models.map((name) => modelMap.get(name)).filter((m) => m !== void 0);
39924
+ if (clusterModels.length > 0) {
39925
+ result.set(cluster.name, clusterModels);
39926
+ }
39927
+ }
39928
+ if (unclustered.length > 0) {
39929
+ const unclusteredModels = unclustered.map((name) => modelMap.get(name)).filter((m) => m !== void 0);
39930
+ for (const model of unclusteredModels) {
39931
+ const domain = inferDomainFromFilename(model.file);
39932
+ const existing = result.get(domain) ?? [];
39933
+ existing.push(model);
39934
+ result.set(domain, existing);
39609
39935
  }
39610
39936
  }
39611
- return "Other";
39937
+ return new Map([...result.entries()].sort((a, b) => a[0].localeCompare(b[0])));
39938
+ }
39939
+ function groupByFilename(models) {
39940
+ const result = /* @__PURE__ */ new Map();
39941
+ for (const model of models) {
39942
+ const domain = inferDomainFromFilename(model.file);
39943
+ const existing = result.get(domain) ?? [];
39944
+ existing.push(model);
39945
+ result.set(domain, existing);
39946
+ }
39947
+ return new Map([...result.entries()].sort((a, b) => a[0].localeCompare(b[0])));
39948
+ }
39949
+ function inferDomainFromFilename(filename) {
39950
+ const base = filename.replace(/\.prisma$/, "").replace(/^.*\//, "");
39951
+ if (!base || base === "schema") return "Other";
39952
+ const parts = base.split(/[-_]/);
39953
+ const formatted = parts.map((part) => {
39954
+ const lower = part.toLowerCase();
39955
+ if (UPPERCASE_ABBREVS.includes(lower)) {
39956
+ return lower.toUpperCase();
39957
+ }
39958
+ return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
39959
+ });
39960
+ return formatted.join(" ");
39612
39961
  }
39613
- var DEFAULT_DOMAINS;
39962
+ var UPPERCASE_ABBREVS;
39614
39963
  var init_grouping4 = __esm({
39615
39964
  "src/commands/schema/grouping.ts"() {
39616
39965
  init_esm_shims();
39617
39966
  init_core2();
39618
- DEFAULT_DOMAINS = {
39619
- auth: "Authentication",
39620
- user: "Users",
39621
- content: "Content",
39622
- booking: "Bookings",
39623
- event: "Events",
39624
- ticket: "Ticketing",
39625
- business: "Business",
39626
- social: "Social",
39627
- gamification: "Gamification",
39628
- payment: "Payments",
39629
- notification: "Notifications",
39630
- integration: "Integrations",
39631
- system: "System"
39632
- };
39967
+ init_relation_graph();
39968
+ UPPERCASE_ABBREVS = ["crm", "api", "ugc", "sso", "oauth", "jwt", "sql"];
39633
39969
  }
39634
39970
  });
39635
39971
 
@@ -39756,11 +40092,208 @@ function formatMarkdown3(data) {
39756
40092
  lines.push("*Generated by krolik-cli*");
39757
40093
  return lines.join("\n");
39758
40094
  }
40095
+ function isObviousDefault(value) {
40096
+ if (!value) return false;
40097
+ return OBVIOUS_DEFAULTS.has(value) || value.startsWith('"') || value.startsWith("'");
40098
+ }
40099
+ function isRelationArray(field) {
40100
+ return field.isArray && field.type.charAt(0) === field.type.charAt(0).toUpperCase() && ![
40101
+ "String",
40102
+ "Int",
40103
+ "Float",
40104
+ "Boolean",
40105
+ "DateTime",
40106
+ "Json",
40107
+ "Bytes",
40108
+ "BigInt",
40109
+ "Decimal"
40110
+ ].includes(field.type);
40111
+ }
40112
+ function classifyField(field) {
40113
+ if (STANDARD_FIELDS.has(field.name)) return "skip";
40114
+ if (isRelationArray(field)) return "skip";
40115
+ if (field.name.endsWith("Id") || field.isUnique || field.isId) return "key";
40116
+ if (field.name.includes("At") || field.name.includes("Date") || field.name.startsWith("created") || field.name.startsWith("updated")) {
40117
+ return "meta";
40118
+ }
40119
+ return "business";
40120
+ }
40121
+ function formatSmart2(data, fullData) {
40122
+ const lines = [];
40123
+ const total = fullData ?? data;
40124
+ const isFiltered = fullData && data.modelCount !== fullData.modelCount;
40125
+ lines.push("<prisma-schema>");
40126
+ if (isFiltered) {
40127
+ lines.push(
40128
+ ` <stats models="${data.modelCount}" enums="${data.enumCount}" filtered="true" total="${total.modelCount}" />`
40129
+ );
40130
+ } else {
40131
+ lines.push(` <stats models="${data.modelCount}" enums="${data.enumCount}" />`);
40132
+ }
40133
+ const byDomain = groupByDomain3(data.models);
40134
+ const domains = Array.from(byDomain.keys()).sort();
40135
+ lines.push(` <domains>${domains.join(", ")}</domains>`);
40136
+ lines.push("");
40137
+ for (const [domain, models] of byDomain) {
40138
+ lines.push(` <domain name="${domain}">`);
40139
+ for (const model of models) {
40140
+ const keyFields = [];
40141
+ const businessFields = [];
40142
+ const metaFields = [];
40143
+ const relationArrays = [];
40144
+ for (const field of model.fields) {
40145
+ if (isRelationArray(field)) {
40146
+ relationArrays.push(`${field.name}\u2192${field.type}[]`);
40147
+ continue;
40148
+ }
40149
+ const category = classifyField(field);
40150
+ if (category === "skip") continue;
40151
+ const formatted = formatFieldSmart(field);
40152
+ switch (category) {
40153
+ case "key":
40154
+ keyFields.push(formatted);
40155
+ break;
40156
+ case "business":
40157
+ businessFields.push(formatted);
40158
+ break;
40159
+ case "meta":
40160
+ metaFields.push(formatted);
40161
+ break;
40162
+ }
40163
+ }
40164
+ lines.push(` <model name="${model.name}">`);
40165
+ const allRelations = [
40166
+ ...model.relations,
40167
+ ...relationArrays.length > 0 ? relationArrays : []
40168
+ ];
40169
+ if (allRelations.length > 0) {
40170
+ lines.push(` <relations>${allRelations.join(", ")}</relations>`);
40171
+ }
40172
+ if (keyFields.length > 0) {
40173
+ lines.push(` <keys>${keyFields.join(", ")}</keys>`);
40174
+ }
40175
+ if (businessFields.length > 0) {
40176
+ lines.push(` <data>${businessFields.join(", ")}</data>`);
40177
+ }
40178
+ if (metaFields.length > 0) {
40179
+ lines.push(` <meta>${metaFields.join(", ")}</meta>`);
40180
+ }
40181
+ lines.push(" </model>");
40182
+ }
40183
+ lines.push(" </domain>");
40184
+ lines.push("");
40185
+ }
40186
+ if (data.enums.length > 0) {
40187
+ lines.push(" <enums>");
40188
+ const enumsByPrefix = groupEnumsByPrefix(data.enums);
40189
+ for (const [prefix, names] of enumsByPrefix) {
40190
+ if (names.length === 1) {
40191
+ lines.push(` ${names[0]}`);
40192
+ } else {
40193
+ lines.push(` ${prefix}*: ${names.join(", ")}`);
40194
+ }
40195
+ }
40196
+ lines.push(" </enums>");
40197
+ }
40198
+ lines.push("</prisma-schema>");
40199
+ return lines.join("\n");
40200
+ }
40201
+ function formatFieldSmart(field) {
40202
+ let result = field.name;
40203
+ const simpleTypes = ["String", "Int", "Float", "Boolean", "DateTime", "Json"];
40204
+ if (!simpleTypes.includes(field.type)) {
40205
+ result += `:${field.type}`;
40206
+ }
40207
+ if (!field.isRequired) result += "?";
40208
+ if (field.isUnique && !field.name.endsWith("Id")) result += "!";
40209
+ if (field.isArray) result += "[]";
40210
+ if (field.default && !isObviousDefault(field.default)) {
40211
+ result += `=${field.default}`;
40212
+ }
40213
+ return result;
40214
+ }
40215
+ function groupEnumsByPrefix(enums) {
40216
+ const result = /* @__PURE__ */ new Map();
40217
+ for (const e of enums) {
40218
+ const match = e.name.match(/^([A-Z][a-z]+)/);
40219
+ const prefix = match?.[1] ?? "Other";
40220
+ if (!result.has(prefix)) result.set(prefix, []);
40221
+ result.get(prefix).push(e.name);
40222
+ }
40223
+ return new Map([...result.entries()].sort((a, b) => a[0].localeCompare(b[0])));
40224
+ }
40225
+ function formatCompact2(data, fullData) {
40226
+ const lines = [];
40227
+ const total = fullData ?? data;
40228
+ const isFiltered = fullData && data.modelCount !== fullData.modelCount;
40229
+ lines.push('<prisma-schema mode="compact">');
40230
+ if (isFiltered) {
40231
+ lines.push(
40232
+ ` <stats models="${data.modelCount}" enums="${data.enumCount}" filtered="true" total-models="${total.modelCount}" total-enums="${total.enumCount}" />`
40233
+ );
40234
+ } else {
40235
+ lines.push(` <stats models="${data.modelCount}" enums="${data.enumCount}" />`);
40236
+ }
40237
+ const byDomain = groupByDomain3(data.models);
40238
+ const domains = Array.from(byDomain.keys()).sort();
40239
+ lines.push(` <domains>${domains.join(", ")}</domains>`);
40240
+ lines.push("");
40241
+ for (const [domain, models] of byDomain) {
40242
+ const modelCount = models.length;
40243
+ lines.push(` <domain name="${domain}" models="${modelCount}">`);
40244
+ for (const model of models) {
40245
+ const relStr = model.relations.length > 0 ? ` relations="${model.relations.join(", ")}"` : "";
40246
+ const keyFields = model.fields.filter((f) => f.isId || f.isUnique || f.name.endsWith("Id"));
40247
+ const keyFieldNames = keyFields.map((f) => f.name).slice(0, 5);
40248
+ const keyStr = keyFieldNames.length > 0 ? ` keys="${keyFieldNames.join(", ")}"` : "";
40249
+ lines.push(
40250
+ ` <model name="${model.name}" fields="${model.fields.length}"${relStr}${keyStr} />`
40251
+ );
40252
+ }
40253
+ lines.push(" </domain>");
40254
+ }
40255
+ if (data.enums.length > 0) {
40256
+ lines.push("");
40257
+ lines.push(` <enums count="${data.enums.length}">`);
40258
+ const enumNames = data.enums.map((e) => e.name).sort();
40259
+ if (enumNames.length > 20) {
40260
+ const byPrefix = /* @__PURE__ */ new Map();
40261
+ for (const name of enumNames) {
40262
+ const prefix = name.replace(/[A-Z][a-z]+$/, "").replace(/Status$|Type$|Role$|State$/, "") || "General";
40263
+ if (!byPrefix.has(prefix)) byPrefix.set(prefix, []);
40264
+ byPrefix.get(prefix).push(name);
40265
+ }
40266
+ for (const [prefix, names] of byPrefix) {
40267
+ lines.push(` <group prefix="${prefix}">${names.join(", ")}</group>`);
40268
+ }
40269
+ } else {
40270
+ lines.push(` ${enumNames.join(", ")}`);
40271
+ }
40272
+ lines.push(" </enums>");
40273
+ }
40274
+ lines.push("");
40275
+ lines.push(' <hint>Use --model "Name" or --domain "Domain" for details</hint>');
40276
+ lines.push("</prisma-schema>");
40277
+ return lines.join("\n");
40278
+ }
40279
+ var STANDARD_FIELDS, OBVIOUS_DEFAULTS;
39759
40280
  var init_output7 = __esm({
39760
40281
  "src/commands/schema/output.ts"() {
39761
40282
  init_esm_shims();
39762
40283
  init_format();
39763
40284
  init_grouping4();
40285
+ STANDARD_FIELDS = /* @__PURE__ */ new Set(["id", "createdAt", "updatedAt"]);
40286
+ OBVIOUS_DEFAULTS = /* @__PURE__ */ new Set([
40287
+ "cuid(",
40288
+ "uuid(",
40289
+ "now(",
40290
+ "autoincrement(",
40291
+ "false",
40292
+ "true",
40293
+ "0",
40294
+ "1",
40295
+ '""'
40296
+ ]);
39764
40297
  }
39765
40298
  });
39766
40299
 
@@ -39800,6 +40333,29 @@ function findSchemaDir2(projectRoot, configSchemaDir) {
39800
40333
  }
39801
40334
  return null;
39802
40335
  }
40336
+ function filterSchema(result, options) {
40337
+ let { models, enums } = result;
40338
+ if (options.domain) {
40339
+ const domainLower = options.domain.toLowerCase();
40340
+ const byDomain = groupByDomain3(models);
40341
+ const matchingDomain = Array.from(byDomain.keys()).find((d) => d.toLowerCase() === domainLower);
40342
+ if (matchingDomain) {
40343
+ models = byDomain.get(matchingDomain) ?? [];
40344
+ } else {
40345
+ models = [];
40346
+ }
40347
+ }
40348
+ if (options.model) {
40349
+ const modelLower = options.model.toLowerCase();
40350
+ models = models.filter((m) => m.name.toLowerCase().includes(modelLower));
40351
+ }
40352
+ return {
40353
+ models,
40354
+ enums,
40355
+ modelCount: models.length,
40356
+ enumCount: enums.length
40357
+ };
40358
+ }
39803
40359
  async function runSchema(ctx) {
39804
40360
  const { config, logger: logger2, options } = ctx;
39805
40361
  const schemaDir = findSchemaDir2(config.projectRoot, config.prisma?.schemaDir);
@@ -39808,9 +40364,11 @@ async function runSchema(ctx) {
39808
40364
  logger2.info("Checked: prisma, packages/db/prisma, src/prisma, db/prisma");
39809
40365
  return;
39810
40366
  }
39811
- const result = analyzeSchema(schemaDir);
40367
+ const fullResult = analyzeSchema(schemaDir);
40368
+ const hasFilters = options.model || options.domain;
40369
+ const result = hasFilters ? filterSchema(fullResult, options) : fullResult;
39812
40370
  const format2 = options.format ?? "ai";
39813
- const xmlOutput = formatAI3(result);
40371
+ const xmlOutput = formatAI3(fullResult);
39814
40372
  saveKrolikFile(config.projectRoot, "SCHEMA.xml", xmlOutput);
39815
40373
  if (options.save) {
39816
40374
  const md = formatMarkdown3(result);
@@ -39831,12 +40389,21 @@ async function runSchema(ctx) {
39831
40389
  printSchema(result, logger2, options.groupBy ?? "file");
39832
40390
  return;
39833
40391
  }
39834
- console.log(xmlOutput);
40392
+ if (options.compact) {
40393
+ console.log(formatCompact2(result, fullResult));
40394
+ return;
40395
+ }
40396
+ if (options.full) {
40397
+ console.log(hasFilters ? formatAI3(result) : xmlOutput);
40398
+ return;
40399
+ }
40400
+ console.log(formatSmart2(result, hasFilters ? fullResult : void 0));
39835
40401
  }
39836
40402
  var init_schema3 = __esm({
39837
40403
  "src/commands/schema/index.ts"() {
39838
40404
  init_esm_shims();
39839
40405
  init_fs2();
40406
+ init_grouping4();
39840
40407
  init_output7();
39841
40408
  init_parser2();
39842
40409
  }
@@ -59071,21 +59638,85 @@ function registerReviewCommand(program) {
59071
59638
 
59072
59639
  // src/cli/commands/routes.ts
59073
59640
  init_esm_shims();
59641
+ init_projects();
59074
59642
  function registerRoutesCommand(program) {
59075
- program.command("routes").description("Analyze tRPC routes").option("--save", "Save to ROUTES.md").action(async (options) => {
59643
+ program.command("routes").description("Analyze tRPC routes").option("--save", "Save to ROUTES.md").option("-c, --compact", "Compact output (routers with procedure counts only)").option("-f, --full", "Full verbose output (all procedures with all attributes)").option("-p, --project <name>", "Project folder name (for multi-project workspaces)").addHelpText(
59644
+ "after",
59645
+ `
59646
+ Output modes:
59647
+ (default) Smart format - groups procedures by type, shows only exceptions
59648
+ --compact Overview only - routers with Q/M counts
59649
+ --full Verbose - all procedures, all attributes (legacy format)
59650
+
59651
+ Examples:
59652
+ krolik routes # Smart format (recommended)
59653
+ krolik routes --compact # Compact overview
59654
+ krolik routes --full # Full verbose output
59655
+ krolik routes --project piternow-wt-fix # Specific project
59656
+ `
59657
+ ).action(async (options) => {
59076
59658
  const { runRoutes: runRoutes2 } = await Promise.resolve().then(() => (init_routes3(), routes_exports));
59659
+ if (options.project) {
59660
+ const resolved = resolveProjectPath(process.cwd(), options.project);
59661
+ if ("error" in resolved) {
59662
+ console.error(resolved.error);
59663
+ process.exit(1);
59664
+ }
59665
+ process.env.KROLIK_PROJECT_ROOT = resolved.path;
59666
+ }
59077
59667
  const ctx = await createContext3(program, options);
59078
- await runRoutes2(ctx);
59668
+ await runRoutes2({
59669
+ ...ctx,
59670
+ options: {
59671
+ ...ctx.options,
59672
+ compact: options.compact,
59673
+ full: options.full
59674
+ }
59675
+ });
59079
59676
  });
59080
59677
  }
59081
59678
 
59082
59679
  // src/cli/commands/schema.ts
59083
59680
  init_esm_shims();
59681
+ init_projects();
59084
59682
  function registerSchemaCommand(program) {
59085
- program.command("schema").description("Analyze Prisma schema").option("--save", "Save to SCHEMA.md").action(async (options) => {
59683
+ program.command("schema").description("Analyze Prisma schema").option("--save", "Save to SCHEMA.md").option("-m, --model <name>", "Filter by model name (partial match)").option("-d, --domain <name>", "Filter by domain name").option("-c, --compact", "Compact output (models with relations only, no field details)").option("-f, --full", "Full verbose output (all fields with all attributes)").option("-p, --project <name>", "Project folder name (for multi-project workspaces)").addHelpText(
59684
+ "after",
59685
+ `
59686
+ Output modes:
59687
+ (default) Smart format - optimized for AI, hides standard fields
59688
+ --compact Overview only - models with relations, no field details
59689
+ --full Verbose - all fields, all attributes (legacy format)
59690
+
59691
+ Examples:
59692
+ krolik schema # Smart format (recommended)
59693
+ krolik schema --compact # Compact overview
59694
+ krolik schema --full # Full verbose output
59695
+ krolik schema --domain Bookings # Only Bookings domain
59696
+ krolik schema --model User # Only models containing "User"
59697
+ krolik schema --project piternow-wt-fix # Specific project
59698
+ `
59699
+ ).action(async (options) => {
59086
59700
  const { runSchema: runSchema2 } = await Promise.resolve().then(() => (init_schema3(), schema_exports));
59701
+ if (options.project) {
59702
+ const resolved = resolveProjectPath(process.cwd(), options.project);
59703
+ if ("error" in resolved) {
59704
+ console.error(resolved.error);
59705
+ process.exit(1);
59706
+ }
59707
+ process.env.KROLIK_PROJECT_ROOT = resolved.path;
59708
+ }
59087
59709
  const ctx = await createContext3(program, options);
59088
- await runSchema2(ctx);
59710
+ await runSchema2({
59711
+ ...ctx,
59712
+ options: {
59713
+ ...ctx.options,
59714
+ model: options.model,
59715
+ domain: options.domain,
59716
+ compact: options.compact,
59717
+ full: options.full
59718
+ }
59719
+ });
59089
59720
  });
59090
59721
  }
59091
59722