@anatolykoptev/krolik-cli 0.11.0 → 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.11.0";
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
  },
@@ -13328,14 +13343,19 @@ var init_schema2 = __esm({
13328
13343
  json: COMMON_FLAGS.json,
13329
13344
  model: { flag: "--model" },
13330
13345
  domain: { flag: "--domain" },
13331
- compact: { flag: "--compact" }
13346
+ compact: { flag: "--compact" },
13347
+ full: { flag: "--full" }
13332
13348
  };
13333
13349
  schemaTool = {
13334
13350
  name: "krolik_schema",
13335
13351
  description: `Analyze Prisma database schema. Returns models, fields, relations, and enums.
13336
13352
 
13337
- **For large schemas**, use compact mode or filters to reduce output:
13338
- - compact: true - shows only model names with relations (no field details)
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:**
13339
13359
  - model: "User" - filter by model name (partial match)
13340
13360
  - domain: "Auth" - filter by domain (derived from schema filename)`,
13341
13361
  inputSchema: {
@@ -13356,7 +13376,11 @@ var init_schema2 = __esm({
13356
13376
  },
13357
13377
  compact: {
13358
13378
  type: "boolean",
13359
- description: "Compact output - models with relations only, no field details. Recommended for large schemas."
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."
13360
13384
  }
13361
13385
  }
13362
13386
  },
@@ -39185,8 +39209,8 @@ function formatAI2(data) {
39185
39209
  ` <stats routers="${data.routers.length}" procedures="${data.totalProcedures}" queries="${data.queries}" mutations="${data.mutations}" protected="${data.protectedCount}" />`
39186
39210
  );
39187
39211
  lines.push("");
39188
- const grouped = groupByDomain2(data.routers);
39189
- for (const [domain, routers] of Object.entries(grouped)) {
39212
+ const grouped = groupByDomainDynamic(data.routers);
39213
+ for (const [domain, routers] of grouped) {
39190
39214
  if (routers.length === 0) continue;
39191
39215
  lines.push(` <domain name="${domain}">`);
39192
39216
  for (const router of routers) {
@@ -39206,6 +39230,80 @@ function formatAI2(data) {
39206
39230
  lines.push("</trpc-routes>");
39207
39231
  return lines.join("\n");
39208
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
+ }
39209
39307
  function formatMarkdown2(data) {
39210
39308
  const lines = [
39211
39309
  "# API Routes (tRPC)",
@@ -39244,28 +39342,34 @@ function formatMarkdown2(data) {
39244
39342
  lines.push("*Generated by krolik-cli*");
39245
39343
  return lines.join("\n");
39246
39344
  }
39247
- function groupByDomain2(routers) {
39248
- const domains = {
39249
- Business: [],
39250
- User: [],
39251
- Content: [],
39252
- Social: [],
39253
- System: []
39254
- };
39345
+ function groupByDomainDynamic(routers) {
39346
+ const result = /* @__PURE__ */ new Map();
39255
39347
  for (const router of routers) {
39256
- if (router.file.startsWith("business")) {
39257
- domains.Business?.push(router);
39258
- } else if (["user", "favorites", "userLists", "userTodos"].some((k) => router.file.includes(k))) {
39259
- domains.User?.push(router);
39260
- } else if (["places", "events", "reviews", "search"].some((k) => router.file.includes(k))) {
39261
- domains.Content?.push(router);
39262
- } else if (["social", "activity", "referral", "interactions"].some((k) => router.file.includes(k))) {
39263
- domains.Social?.push(router);
39264
- } else {
39265
- domains.System?.push(router);
39348
+ const domain = inferDomainFromPath(router.file);
39349
+ if (!result.has(domain)) {
39350
+ result.set(domain, []);
39266
39351
  }
39352
+ result.get(domain).push(router);
39267
39353
  }
39268
- 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;
39269
39373
  }
39270
39374
  var MAX_LENGTH2, SLICE_ARG1_VALUE2;
39271
39375
  var init_output6 = __esm({
@@ -39603,7 +39707,15 @@ async function runRoutes(ctx) {
39603
39707
  printRoutes(result, logger2);
39604
39708
  return;
39605
39709
  }
39606
- 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));
39607
39719
  }
39608
39720
  var init_routes3 = __esm({
39609
39721
  "src/commands/routes/index.ts"() {
@@ -39614,16 +39726,229 @@ var init_routes3 = __esm({
39614
39726
  }
39615
39727
  });
39616
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
+
39617
39903
  // src/commands/schema/grouping.ts
39618
39904
  function groupByFile2(models) {
39619
39905
  return groupByProperty(models, "file");
39620
39906
  }
39621
39907
  function groupByDomain3(models) {
39622
- return groupBy(models, (model) => inferDomain(model.file));
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);
39935
+ }
39936
+ }
39937
+ return new Map([...result.entries()].sort((a, b) => a[0].localeCompare(b[0])));
39623
39938
  }
39624
- function inferDomain(filename) {
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) {
39625
39950
  const base = filename.replace(/\.prisma$/, "").replace(/^.*\//, "");
39626
- if (!base) return "Other";
39951
+ if (!base || base === "schema") return "Other";
39627
39952
  const parts = base.split(/[-_]/);
39628
39953
  const formatted = parts.map((part) => {
39629
39954
  const lower = part.toLowerCase();
@@ -39639,6 +39964,7 @@ var init_grouping4 = __esm({
39639
39964
  "src/commands/schema/grouping.ts"() {
39640
39965
  init_esm_shims();
39641
39966
  init_core2();
39967
+ init_relation_graph();
39642
39968
  UPPERCASE_ABBREVS = ["crm", "api", "ugc", "sso", "oauth", "jwt", "sql"];
39643
39969
  }
39644
39970
  });
@@ -39766,7 +40092,137 @@ function formatMarkdown3(data) {
39766
40092
  lines.push("*Generated by krolik-cli*");
39767
40093
  return lines.join("\n");
39768
40094
  }
39769
- function formatCompact(data, fullData) {
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) {
39770
40226
  const lines = [];
39771
40227
  const total = fullData ?? data;
39772
40228
  const isFiltered = fullData && data.modelCount !== fullData.modelCount;
@@ -39820,11 +40276,24 @@ function formatCompact(data, fullData) {
39820
40276
  lines.push("</prisma-schema>");
39821
40277
  return lines.join("\n");
39822
40278
  }
40279
+ var STANDARD_FIELDS, OBVIOUS_DEFAULTS;
39823
40280
  var init_output7 = __esm({
39824
40281
  "src/commands/schema/output.ts"() {
39825
40282
  init_esm_shims();
39826
40283
  init_format();
39827
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
+ ]);
39828
40297
  }
39829
40298
  });
39830
40299
 
@@ -39921,10 +40390,14 @@ async function runSchema(ctx) {
39921
40390
  return;
39922
40391
  }
39923
40392
  if (options.compact) {
39924
- console.log(formatCompact(result, fullResult));
40393
+ console.log(formatCompact2(result, fullResult));
39925
40394
  return;
39926
40395
  }
39927
- console.log(hasFilters ? formatAI3(result) : xmlOutput);
40396
+ if (options.full) {
40397
+ console.log(hasFilters ? formatAI3(result) : xmlOutput);
40398
+ return;
40399
+ }
40400
+ console.log(formatSmart2(result, hasFilters ? fullResult : void 0));
39928
40401
  }
39929
40402
  var init_schema3 = __esm({
39930
40403
  "src/commands/schema/index.ts"() {
@@ -59165,28 +59638,74 @@ function registerReviewCommand(program) {
59165
59638
 
59166
59639
  // src/cli/commands/routes.ts
59167
59640
  init_esm_shims();
59641
+ init_projects();
59168
59642
  function registerRoutesCommand(program) {
59169
- 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) => {
59170
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
+ }
59171
59667
  const ctx = await createContext3(program, options);
59172
- await runRoutes2(ctx);
59668
+ await runRoutes2({
59669
+ ...ctx,
59670
+ options: {
59671
+ ...ctx.options,
59672
+ compact: options.compact,
59673
+ full: options.full
59674
+ }
59675
+ });
59173
59676
  });
59174
59677
  }
59175
59678
 
59176
59679
  // src/cli/commands/schema.ts
59177
59680
  init_esm_shims();
59681
+ init_projects();
59178
59682
  function registerSchemaCommand(program) {
59179
- 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)").addHelpText(
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(
59180
59684
  "after",
59181
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
+
59182
59691
  Examples:
59183
- krolik schema # Full schema (may be large)
59184
- krolik schema --compact # Compact view - just models and relations
59185
- krolik schema --domain Bookings # Only Bookings domain
59186
- krolik schema --model User # Only models containing "User"
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
59187
59698
  `
59188
59699
  ).action(async (options) => {
59189
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
+ }
59190
59709
  const ctx = await createContext3(program, options);
59191
59710
  await runSchema2({
59192
59711
  ...ctx,
@@ -59194,7 +59713,8 @@ Examples:
59194
59713
  ...ctx.options,
59195
59714
  model: options.model,
59196
59715
  domain: options.domain,
59197
- compact: options.compact
59716
+ compact: options.compact,
59717
+ full: options.full
59198
59718
  }
59199
59719
  });
59200
59720
  });