@rhseung/ps-cli 1.6.0 → 1.7.1

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.
@@ -10023,12 +10023,250 @@ function getSolvingDir() {
10023
10023
  }
10024
10024
  return config.get("solvingDir") ?? "solving";
10025
10025
  }
10026
+ function getArchiveStrategy() {
10027
+ const projectConfig = getProjectConfigSync();
10028
+ if (projectConfig?.archiveStrategy !== void 0) {
10029
+ return projectConfig.archiveStrategy;
10030
+ }
10031
+ return config.get("archiveStrategy") ?? "flat";
10032
+ }
10033
+
10034
+ // src/utils/tier.ts
10035
+ import gradient from "gradient-string";
10036
+ var TIER_NAMES = [
10037
+ void 0,
10038
+ "Bronze V",
10039
+ "Bronze IV",
10040
+ "Bronze III",
10041
+ "Bronze II",
10042
+ "Bronze I",
10043
+ "Silver V",
10044
+ "Silver IV",
10045
+ "Silver III",
10046
+ "Silver II",
10047
+ "Silver I",
10048
+ "Gold V",
10049
+ "Gold IV",
10050
+ "Gold III",
10051
+ "Gold II",
10052
+ "Gold I",
10053
+ "Platinum V",
10054
+ "Platinum IV",
10055
+ "Platinum III",
10056
+ "Platinum II",
10057
+ "Platinum I",
10058
+ "Diamond V",
10059
+ "Diamond IV",
10060
+ "Diamond III",
10061
+ "Diamond II",
10062
+ "Diamond I",
10063
+ "Ruby V",
10064
+ "Ruby IV",
10065
+ "Ruby III",
10066
+ "Ruby II",
10067
+ "Ruby I",
10068
+ "Master"
10069
+ ];
10070
+ var TIER_COLORS = [
10071
+ void 0,
10072
+ "#9d4900",
10073
+ "#a54f00",
10074
+ "#ad5600",
10075
+ "#b55d0a",
10076
+ "#c67739",
10077
+ "#38546e",
10078
+ "#3d5a74",
10079
+ "#435f7a",
10080
+ "#496580",
10081
+ "#4e6a86",
10082
+ "#d28500",
10083
+ "#df8f00",
10084
+ "#ec9a00",
10085
+ "#f9a518",
10086
+ "#ffb028",
10087
+ "#00c78b",
10088
+ "#00d497",
10089
+ "#27e2a4",
10090
+ "#3ef0b1",
10091
+ "#51fdbd",
10092
+ "#009ee5",
10093
+ "#00a9f0",
10094
+ "#00b4fc",
10095
+ "#2bbfff",
10096
+ "#41caff",
10097
+ "#e0004c",
10098
+ "#ea0053",
10099
+ "#f5005a",
10100
+ "#ff0062",
10101
+ "#ff3071",
10102
+ "#b300e0"
10103
+ ];
10104
+ var TIER_IMAGE_BASE_URL = "https://d2gd6pc034wcta.cloudfront.net/tier";
10105
+ var MASTER_TIER_GRADIENT = [
10106
+ { r: 255, g: 124, b: 168 },
10107
+ { r: 180, g: 145, b: 255 },
10108
+ { r: 124, g: 249, b: 255 }
10109
+ ];
10110
+ var TIER_MIN_RATINGS = [
10111
+ 0,
10112
+ // Unrated (tier 0): 0-29
10113
+ 30,
10114
+ // Bronze V (tier 1)
10115
+ 60,
10116
+ // Bronze IV (tier 2)
10117
+ 90,
10118
+ // Bronze III (tier 3)
10119
+ 120,
10120
+ // Bronze II (tier 4)
10121
+ 150,
10122
+ // Bronze I (tier 5)
10123
+ 200,
10124
+ // Silver V (tier 6)
10125
+ 300,
10126
+ // Silver IV (tier 7)
10127
+ 400,
10128
+ // Silver III (tier 8)
10129
+ 500,
10130
+ // Silver II (tier 9)
10131
+ 650,
10132
+ // Silver I (tier 10)
10133
+ 800,
10134
+ // Gold V (tier 11)
10135
+ 950,
10136
+ // Gold IV (tier 12)
10137
+ 1100,
10138
+ // Gold III (tier 13)
10139
+ 1250,
10140
+ // Gold II (tier 14)
10141
+ 1400,
10142
+ // Gold I (tier 15)
10143
+ 1600,
10144
+ // Platinum V (tier 16)
10145
+ 1750,
10146
+ // Platinum IV (tier 17)
10147
+ 1900,
10148
+ // Platinum III (tier 18)
10149
+ 2e3,
10150
+ // Platinum II (tier 19)
10151
+ 2100,
10152
+ // Platinum I (tier 20)
10153
+ 2200,
10154
+ // Diamond V (tier 21)
10155
+ 2300,
10156
+ // Diamond IV (tier 22)
10157
+ 2400,
10158
+ // Diamond III (tier 23)
10159
+ 2500,
10160
+ // Diamond II (tier 24)
10161
+ 2600,
10162
+ // Diamond I (tier 25)
10163
+ 2700,
10164
+ // Ruby V (tier 26)
10165
+ 2800,
10166
+ // Ruby IV (tier 27)
10167
+ 2850,
10168
+ // Ruby III (tier 28)
10169
+ 2900,
10170
+ // Ruby II (tier 29)
10171
+ 2950,
10172
+ // Ruby I (tier 30)
10173
+ 3e3
10174
+ // Master (tier 31)
10175
+ ];
10176
+ function getTierMinRating(tier) {
10177
+ if (tier >= 0 && tier < TIER_MIN_RATINGS.length) {
10178
+ return TIER_MIN_RATINGS[tier] ?? 0;
10179
+ }
10180
+ return 0;
10181
+ }
10182
+ function getNextTierMinRating(tier) {
10183
+ if (tier === 31) {
10184
+ return null;
10185
+ }
10186
+ if (tier >= 0 && tier < TIER_MIN_RATINGS.length - 1) {
10187
+ return TIER_MIN_RATINGS[tier + 1] ?? null;
10188
+ }
10189
+ return null;
10190
+ }
10191
+ function calculateTierProgress(currentRating, tier) {
10192
+ if (tier === 31) {
10193
+ return 100;
10194
+ }
10195
+ const currentTierMin = getTierMinRating(tier);
10196
+ const nextTierMin = getNextTierMinRating(tier);
10197
+ if (nextTierMin === null) {
10198
+ return 100;
10199
+ }
10200
+ if (currentRating < currentTierMin) {
10201
+ return 0;
10202
+ }
10203
+ if (currentRating >= nextTierMin) {
10204
+ return 100;
10205
+ }
10206
+ const progress = (currentRating - currentTierMin) / (nextTierMin - currentTierMin) * 100;
10207
+ return Math.max(0, Math.min(100, progress));
10208
+ }
10209
+ function getTierName(level) {
10210
+ if (level === 0) return "Unrated";
10211
+ if (level >= 1 && level < TIER_NAMES.length) {
10212
+ return TIER_NAMES[level] || "Unrated";
10213
+ }
10214
+ return "Unrated";
10215
+ }
10216
+ function getTierColor(level) {
10217
+ if (level === 0) return "#2d2d2d";
10218
+ if (level === 31) {
10219
+ return gradient([...MASTER_TIER_GRADIENT]);
10220
+ }
10221
+ if (level >= 1 && level < TIER_COLORS.length) {
10222
+ return TIER_COLORS[level] || "#2d2d2d";
10223
+ }
10224
+ return "#2d2d2d";
10225
+ }
10226
+ function getTierImageUrl(level) {
10227
+ return `${TIER_IMAGE_BASE_URL}/${level}.svg`;
10228
+ }
10026
10229
 
10027
10230
  // src/utils/problem-id.ts
10028
10231
  import { join as join2 } from "path";
10232
+ function sanitizeFileName(name) {
10233
+ return name.replace(/[<>:"/\\|?*]/g, "").replace(/\s+/g, "-").toLowerCase().trim();
10234
+ }
10235
+ function getTierDirName(level) {
10236
+ const tierName = getTierName(level);
10237
+ if (tierName === "Unrated") {
10238
+ return "unrated";
10239
+ }
10240
+ return sanitizeFileName(tierName);
10241
+ }
10242
+ function getArchiveSubPath(problemId, strategy = "flat", problem) {
10243
+ switch (strategy) {
10244
+ case "flat":
10245
+ return "";
10246
+ case "by-range": {
10247
+ const range = Math.floor(problemId / 1e3) * 1e3;
10248
+ return String(range).padStart(5, "0");
10249
+ }
10250
+ case "by-tier": {
10251
+ if (!problem) {
10252
+ return "";
10253
+ }
10254
+ return getTierDirName(problem.level);
10255
+ }
10256
+ case "by-tag": {
10257
+ if (!problem || !problem.tags || problem.tags.length === 0) {
10258
+ return "";
10259
+ }
10260
+ return sanitizeFileName(problem.tags[0]);
10261
+ }
10262
+ default:
10263
+ return "";
10264
+ }
10265
+ }
10029
10266
  function detectProblemIdFromPath(cwd = process.cwd()) {
10030
10267
  const problemDir = getProblemDir();
10031
10268
  const solvingDir = getSolvingDir();
10269
+ const archiveStrategy = getArchiveStrategy();
10032
10270
  const normalizedPath = cwd.replace(/\\/g, "/");
10033
10271
  const dirsToCheck = [problemDir, solvingDir].filter(
10034
10272
  (dir) => dir && dir !== "." && dir !== ""
@@ -10054,18 +10292,61 @@ function detectProblemIdFromPath(cwd = process.cwd()) {
10054
10292
  if (!afterDir) {
10055
10293
  continue;
10056
10294
  }
10057
- const firstSegment = afterDir.split("/")[0];
10058
- if (!firstSegment) {
10295
+ const segments = afterDir.split("/").filter(Boolean);
10296
+ if (segments.length === 0) {
10059
10297
  continue;
10060
10298
  }
10061
- const problemId = parseInt(firstSegment, 10);
10062
- if (isNaN(problemId) || problemId <= 0) {
10063
- continue;
10299
+ let problemId = null;
10300
+ if (archiveStrategy === "flat") {
10301
+ const firstSegment = segments[0];
10302
+ if (firstSegment) {
10303
+ const id = parseInt(firstSegment, 10);
10304
+ if (!isNaN(id) && id > 0 && firstSegment === id.toString() && segments.length === 1) {
10305
+ problemId = id;
10306
+ }
10307
+ }
10308
+ } else if (archiveStrategy === "by-range") {
10309
+ if (segments.length === 2) {
10310
+ const secondSegment = segments[1];
10311
+ if (secondSegment) {
10312
+ const id = parseInt(secondSegment, 10);
10313
+ if (!isNaN(id) && id > 0 && secondSegment === id.toString()) {
10314
+ problemId = id;
10315
+ }
10316
+ }
10317
+ }
10318
+ } else if (archiveStrategy === "by-tier" || archiveStrategy === "by-tag") {
10319
+ if (segments.length === 2) {
10320
+ const secondSegment = segments[1];
10321
+ if (secondSegment) {
10322
+ const id = parseInt(secondSegment, 10);
10323
+ if (!isNaN(id) && id > 0 && secondSegment === id.toString()) {
10324
+ problemId = id;
10325
+ }
10326
+ }
10327
+ }
10064
10328
  }
10065
- if (firstSegment !== problemId.toString()) {
10066
- continue;
10329
+ if (!problemId) {
10330
+ const lastSegment = segments[segments.length - 1];
10331
+ if (lastSegment) {
10332
+ const id = parseInt(lastSegment, 10);
10333
+ if (!isNaN(id) && id > 0 && lastSegment === id.toString() && segments.length === 1) {
10334
+ problemId = id;
10335
+ }
10336
+ }
10337
+ if (!problemId && segments.length === 2) {
10338
+ const secondSegment = segments[1];
10339
+ if (secondSegment) {
10340
+ const id = parseInt(secondSegment, 10);
10341
+ if (!isNaN(id) && id > 0 && secondSegment === id.toString()) {
10342
+ problemId = id;
10343
+ }
10344
+ }
10345
+ }
10346
+ }
10347
+ if (problemId) {
10348
+ return problemId;
10067
10349
  }
10068
- return problemId;
10069
10350
  }
10070
10351
  return null;
10071
10352
  }
@@ -10078,16 +10359,24 @@ function getProblemId(args, cwd = process.cwd()) {
10078
10359
  }
10079
10360
  return detectProblemIdFromPath(cwd);
10080
10361
  }
10081
- function getProblemDirPath(problemId, cwd = process.cwd()) {
10362
+ function getProblemDirPath(problemId, cwd = process.cwd(), problem) {
10082
10363
  const problemDir = getProblemDir();
10364
+ const archiveStrategy = getArchiveStrategy();
10083
10365
  const projectRoot = findProjectRoot(cwd);
10084
10366
  const baseDir = projectRoot || cwd;
10367
+ const subPath = getArchiveSubPath(problemId, archiveStrategy, problem);
10085
10368
  if (problemDir === "." || problemDir === "") {
10369
+ if (subPath) {
10370
+ return join2(baseDir, subPath, problemId.toString());
10371
+ }
10086
10372
  return join2(baseDir, problemId.toString());
10087
10373
  }
10374
+ if (subPath) {
10375
+ return join2(baseDir, problemDir, subPath, problemId.toString());
10376
+ }
10088
10377
  return join2(baseDir, problemDir, problemId.toString());
10089
10378
  }
10090
- function getSolvingDirPath(problemId, cwd = process.cwd()) {
10379
+ function getSolvingDirPath(problemId, cwd = process.cwd(), _) {
10091
10380
  const solvingDir = getSolvingDir();
10092
10381
  const projectRoot = findProjectRoot(cwd);
10093
10382
  const baseDir = projectRoot || cwd;
@@ -10098,8 +10387,17 @@ function getSolvingDirPath(problemId, cwd = process.cwd()) {
10098
10387
  }
10099
10388
 
10100
10389
  // src/utils/execution-context.ts
10390
+ import { access } from "fs/promises";
10101
10391
  import { readdir } from "fs/promises";
10102
10392
  import { join as join3 } from "path";
10393
+ async function directoryExists(dirPath) {
10394
+ try {
10395
+ await access(dirPath);
10396
+ return true;
10397
+ } catch {
10398
+ return false;
10399
+ }
10400
+ }
10103
10401
  async function resolveProblemContext(args, options = {}) {
10104
10402
  const { requireId = false } = options;
10105
10403
  const currentPathProblemId = detectProblemIdFromPath(process.cwd());
@@ -10110,7 +10408,24 @@ async function resolveProblemContext(args, options = {}) {
10110
10408
  );
10111
10409
  }
10112
10410
  const isCurrentDir = problemId === null || problemId !== null && currentPathProblemId === problemId;
10113
- const problemDir = problemId && !isCurrentDir ? getProblemDirPath(problemId) : process.cwd();
10411
+ let problemDir;
10412
+ if (problemId && !isCurrentDir) {
10413
+ const solvingDirPath = getSolvingDirPath(problemId);
10414
+ const problemDirPath = getProblemDirPath(problemId);
10415
+ const solvingDirExists = await directoryExists(solvingDirPath);
10416
+ if (solvingDirExists) {
10417
+ problemDir = solvingDirPath;
10418
+ } else {
10419
+ const problemDirExists = await directoryExists(problemDirPath);
10420
+ if (problemDirExists) {
10421
+ problemDir = problemDirPath;
10422
+ } else {
10423
+ problemDir = solvingDirPath;
10424
+ }
10425
+ }
10426
+ } else {
10427
+ problemDir = process.cwd();
10428
+ }
10114
10429
  return {
10115
10430
  problemId,
10116
10431
  problemDir,
@@ -10281,6 +10596,12 @@ export {
10281
10596
  getSolvedAcHandle,
10282
10597
  getProblemDir,
10283
10598
  getSolvingDir,
10599
+ getArchiveStrategy,
10600
+ getNextTierMinRating,
10601
+ calculateTierProgress,
10602
+ getTierName,
10603
+ getTierColor,
10604
+ getTierImageUrl,
10284
10605
  detectProblemIdFromPath,
10285
10606
  getProblemId,
10286
10607
  getProblemDirPath,
@@ -3,12 +3,13 @@ import {
3
3
  Command,
4
4
  CommandBuilder,
5
5
  CommandDef,
6
+ getArchiveStrategy,
6
7
  getAutoOpenEditor,
7
8
  getDefaultLanguage,
8
9
  getEditor,
9
10
  getProblemDir,
10
11
  getSolvedAcHandle
11
- } from "../chunk-RVD22OUQ.js";
12
+ } from "../chunk-TNGUME4H.js";
12
13
  import {
13
14
  __decorateClass,
14
15
  getSupportedLanguages,
@@ -104,6 +105,18 @@ function useConfig({
104
105
  case "problem-dir":
105
106
  updatedConfig.problemDir = value;
106
107
  break;
108
+ case "archive-strategy": {
109
+ const validStrategies = ["flat", "by-range", "by-tier", "by-tag"];
110
+ if (!validStrategies.includes(value)) {
111
+ console.error(
112
+ `\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uC544\uCE74\uC774\uBE59 \uC804\uB7B5\uC785\uB2C8\uB2E4: ${value}
113
+ \uC9C0\uC6D0 \uC804\uB7B5: ${validStrategies.join(", ")}`
114
+ );
115
+ process.exit(1);
116
+ }
117
+ updatedConfig.archiveStrategy = value;
118
+ break;
119
+ }
107
120
  default:
108
121
  console.error(`\uC54C \uC218 \uC5C6\uB294 \uC124\uC815 \uD0A4: ${configKey}`);
109
122
  process.exit(1);
@@ -147,6 +160,7 @@ function getConfigHelp() {
147
160
  auto-open-editor fetch \uD6C4 \uC790\uB3D9\uC73C\uB85C \uC5D0\uB514\uD130 \uC5F4\uAE30 (true/false)
148
161
  solved-ac-handle Solved.ac \uD578\uB4E4 (stats \uBA85\uB839\uC5B4\uC6A9)
149
162
  problem-dir \uBB38\uC81C \uB514\uB809\uD1A0\uB9AC \uACBD\uB85C (\uAE30\uBCF8\uAC12: problems, "." \uB610\uB294 ""\uB294 \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8)
163
+ archive-strategy \uC544\uCE74\uC774\uBE59 \uC804\uB7B5 (flat, by-range, by-tier, by-tag)
150
164
 
151
165
  \uC635\uC158:
152
166
  --help, -h \uB3C4\uC6C0\uB9D0 \uD45C\uC2DC
@@ -166,7 +180,8 @@ var CONFIG_KEYS = [
166
180
  { label: "editor", value: "editor" },
167
181
  { label: "auto-open-editor", value: "auto-open-editor" },
168
182
  { label: "solved-ac-handle", value: "solved-ac-handle" },
169
- { label: "problem-dir", value: "problem-dir" }
183
+ { label: "problem-dir", value: "problem-dir" },
184
+ { label: "archive-strategy", value: "archive-strategy" }
170
185
  ];
171
186
  function ConfigView({
172
187
  configKey,
@@ -199,6 +214,7 @@ function ConfigView({
199
214
  const autoOpen = config?.autoOpenEditor ?? getAutoOpenEditor();
200
215
  const handle = config?.solvedAcHandle ?? getSolvedAcHandle();
201
216
  const problemDir = config?.problemDir ?? getProblemDir();
217
+ const archiveStrategy = config?.archiveStrategy ?? getArchiveStrategy();
202
218
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
203
219
  /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\u2699\uFE0F \uD604\uC7AC \uC124\uC815 (.ps-cli.json)" }) }),
204
220
  /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
@@ -226,6 +242,11 @@ function ConfigView({
226
242
  /* @__PURE__ */ jsx(Text, { color: "gray", children: "problem-dir:" }),
227
243
  /* @__PURE__ */ jsx(Text, { children: " " }),
228
244
  /* @__PURE__ */ jsx(Text, { bold: true, children: problemDir })
245
+ ] }),
246
+ /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
247
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "archive-strategy:" }),
248
+ /* @__PURE__ */ jsx(Text, { children: " " }),
249
+ /* @__PURE__ */ jsx(Text, { bold: true, children: archiveStrategy })
229
250
  ] })
230
251
  ] })
231
252
  ] });
@@ -248,6 +269,9 @@ function ConfigView({
248
269
  case "problem-dir":
249
270
  configValue = config?.problemDir ?? getProblemDir();
250
271
  break;
272
+ case "archive-strategy":
273
+ configValue = config?.archiveStrategy ?? getArchiveStrategy();
274
+ break;
251
275
  default:
252
276
  console.error(`\uC54C \uC218 \uC5C6\uB294 \uC124\uC815 \uD0A4: ${configKey}`);
253
277
  process.exit(1);
@@ -418,6 +442,8 @@ var ConfigCommand = class extends Command {
418
442
  return "Solved.ac \uD578\uB4E4 \uC785\uB825";
419
443
  case "problem-dir":
420
444
  return "\uBB38\uC81C \uB514\uB809\uD1A0\uB9AC \uACBD\uB85C \uC785\uB825";
445
+ case "archive-strategy":
446
+ return "\uC544\uCE74\uC774\uBE59 \uC804\uB7B5 \uC785\uB825 (flat, by-range, by-tier, by-tag)";
421
447
  default:
422
448
  return "\uAC12 \uC785\uB825";
423
449
  }
@@ -434,6 +460,8 @@ var ConfigCommand = class extends Command {
434
460
  return "Solved.ac \uC0AC\uC6A9\uC790 \uD578\uB4E4";
435
461
  case "problem-dir":
436
462
  return '\uBB38\uC81C \uB514\uB809\uD1A0\uB9AC \uACBD\uB85C (\uAE30\uBCF8\uAC12: "problems", \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8: ".")';
463
+ case "archive-strategy":
464
+ return "\uC544\uCE74\uC774\uBE59 \uC804\uB7B5: flat (\uD3C9\uBA74), by-range (1000\uBC88\uB300 \uBB36\uAE30), by-tier (\uD2F0\uC5B4\uBCC4), by-tag (\uD0DC\uADF8\uBCC4)";
437
465
  default:
438
466
  return "";
439
467
  }
@@ -448,6 +476,8 @@ var ConfigCommand = class extends Command {
448
476
  return ["true", "false"];
449
477
  case "problem-dir":
450
478
  return ["problems", ".", ""];
479
+ case "archive-strategy":
480
+ return ["flat", "by-range", "by-tier", "by-tag"];
451
481
  default:
452
482
  return [];
453
483
  }
@@ -1,13 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  scrapeProblem
4
- } from "../chunk-AG6KWWHS.js";
4
+ } from "../chunk-4ISG24GW.js";
5
5
  import {
6
- getProblem,
7
- getTierColor,
8
- getTierImageUrl,
9
- getTierName
10
- } from "../chunk-NH36IFWR.js";
6
+ getProblem
7
+ } from "../chunk-A6STXEAE.js";
11
8
  import {
12
9
  Command,
13
10
  CommandBuilder,
@@ -15,8 +12,11 @@ import {
15
12
  getAutoOpenEditor,
16
13
  getEditor,
17
14
  getProblemId,
18
- getSolvingDirPath
19
- } from "../chunk-RVD22OUQ.js";
15
+ getSolvingDirPath,
16
+ getTierColor,
17
+ getTierImageUrl,
18
+ getTierName
19
+ } from "../chunk-TNGUME4H.js";
20
20
  import {
21
21
  __decorateClass,
22
22
  getLanguageConfig,
@@ -84,7 +84,7 @@ function getProjectRoot() {
84
84
  return join(__dirname, "../..");
85
85
  }
86
86
  async function generateProblemFiles(problem, language = "python") {
87
- const problemDir = getSolvingDirPath(problem.id);
87
+ const problemDir = getSolvingDirPath(problem.id, process.cwd(), problem);
88
88
  await mkdir(problemDir, { recursive: true });
89
89
  const langConfig = getLanguageConfig(language);
90
90
  const projectRoot = getProjectRoot();
@@ -186,6 +186,7 @@ ${tags}
186
186
  id: problem.id,
187
187
  title: problem.title,
188
188
  level: problem.level,
189
+ tags: problem.tags,
189
190
  timeLimit: problem.timeLimit,
190
191
  timeLimitMs: parseTimeLimitToMs(problem.timeLimit),
191
192
  memoryLimit: problem.memoryLimit