ai-ops-cli 0.1.14 → 0.1.16

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/index.js CHANGED
@@ -4,8 +4,8 @@
4
4
  import { Command } from "commander";
5
5
 
6
6
  // src/commands/init.ts
7
- import * as p3 from "@clack/prompts";
8
- import { join as join9 } from "path";
7
+ import * as p4 from "@clack/prompts";
8
+ import { join as join10 } from "path";
9
9
 
10
10
  // src/core/schemas/rule.schema.ts
11
11
  import { z } from "zod";
@@ -44,7 +44,8 @@ var PresetSchema = z2.object({
44
44
  import { z as z3 } from "zod";
45
45
  var SettingsConfigSchema = z3.object({
46
46
  claude: z3.array(z3.string().min(1)).optional(),
47
- gemini: z3.array(z3.string().min(1)).optional()
47
+ gemini: z3.array(z3.string().min(1)).optional(),
48
+ prettierignore: z3.boolean().optional()
48
49
  }).strict();
49
50
  var WorkspaceEntrySchema = z3.object({
50
51
  preset: z3.string().min(1),
@@ -64,6 +65,8 @@ var ManifestSchema = z3.object({
64
65
  appended_files: z3.array(z3.string().min(1)).optional(),
65
66
  /** init 시 선택된 settings 항목 — update 시 재생성에 사용 */
66
67
  settings: SettingsConfigSchema.optional(),
68
+ /** init/update 실행 시점의 CLI 패키지 버전 — 버전 변경 감지에 사용 */
69
+ cliVersion: z3.string().optional(),
67
70
  /** SSOT 데이터 파일들의 deterministic SHA-256 해시 (6자리 hex). diff/update 판단 기준 */
68
71
  sourceHash: z3.string().regex(/^[a-f0-9]{6}$/, "sourceHash must be 6 lowercase hex chars"),
69
72
  generatedAt: z3.string().datetime({ offset: true })
@@ -237,7 +240,7 @@ var partitionRules = (rules) => {
237
240
  return { global, domain };
238
241
  };
239
242
  var renderFrontmatter = (paths) => {
240
- const lines = paths.map((p7) => ` - "${p7}"`).join("\n");
243
+ const lines = paths.map((p8) => ` - "${p8}"`).join("\n");
241
244
  return `---
242
245
  paths:
243
246
  ${lines}
@@ -292,7 +295,18 @@ var renderForTool = (toolId, rules, workspaceMappings) => {
292
295
  // src/core/source-hash.ts
293
296
  import { createHash } from "crypto";
294
297
  import { readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
295
- import { resolve as resolve2 } from "path";
298
+ import { dirname, resolve as resolve2 } from "path";
299
+ import { fileURLToPath } from "url";
300
+ var __dirname = dirname(fileURLToPath(import.meta.url));
301
+ var getCliVersion = () => {
302
+ try {
303
+ const pkgPath = resolve2(__dirname, "..", "..", "package.json");
304
+ const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
305
+ return pkg.version;
306
+ } catch {
307
+ return "unknown";
308
+ }
309
+ };
296
310
  var computeHash = (contents) => createHash("sha256").update(contents.join("")).digest("hex").slice(0, 6);
297
311
  var computeSourceHash = (rulesDir) => {
298
312
  const files = readdirSync2(rulesDir).filter((f) => f.endsWith(".yaml")).sort();
@@ -311,6 +325,7 @@ var buildManifest = (params) => ManifestSchema.parse({
311
325
  claude: params.settings.claude ? [...params.settings.claude] : void 0,
312
326
  gemini: params.settings.gemini ? [...params.settings.gemini] : void 0
313
327
  } : void 0,
328
+ cliVersion: params.cliVersion,
314
329
  sourceHash: params.sourceHash,
315
330
  generatedAt: (/* @__PURE__ */ new Date()).toISOString()
316
331
  });
@@ -319,20 +334,7 @@ var buildManifest = (params) => ManifestSchema.parse({
319
334
  var MANAGED_MARKER = "<!-- managed by ai-ops -->";
320
335
  var SECTION_START = "<!-- ai-ops:start -->";
321
336
  var SECTION_END = "<!-- ai-ops:end -->";
322
- var wrapWithHeader = (content, meta) => {
323
- const metaLine = `<!-- sourceHash: ${meta.sourceHash} | generatedAt: ${meta.generatedAt} -->`;
324
- return `${MANAGED_MARKER}
325
- ${metaLine}
326
-
327
- ${content}`;
328
- };
329
- var isManagedFile = (content) => content.startsWith(MANAGED_MARKER);
330
- var stripManagedHeader = (content) => {
331
- if (!isManagedFile(content)) return content;
332
- const lines = content.split("\n");
333
- const stripped = lines.slice(3).join("\n");
334
- return stripped;
335
- };
337
+ var hasLegacyHeader = (content) => content.includes(MANAGED_MARKER);
336
338
  var wrapWithSection = (content, meta) => {
337
339
  const metaLine = `<!-- sourceHash: ${meta.sourceHash} | generatedAt: ${meta.generatedAt} -->`;
338
340
  return `${SECTION_START}
@@ -356,12 +358,12 @@ var replaceAiOpsSection = (existing, newSection) => {
356
358
  if (startIdx === -1 || endIdx === -1) return existing;
357
359
  const before = existing.slice(0, startIdx).trimEnd();
358
360
  const after = existing.slice(endIdx + SECTION_END.length).trimStart();
359
- return before + "\n\n" + newSection + (after ? "\n\n" + after : "") + "\n";
361
+ return [before, newSection, after].filter(Boolean).join("\n\n") + "\n";
360
362
  };
361
363
 
362
364
  // src/core/manifest-io.ts
363
365
  import { mkdirSync, readFileSync as readFileSync3, writeFileSync } from "fs";
364
- import { dirname, join as join2 } from "path";
366
+ import { dirname as dirname2, join as join2 } from "path";
365
367
  var MANIFEST_FILENAME = ".ai-ops-manifest.json";
366
368
  var parseManifest = (json) => ManifestSchema.parse(JSON.parse(json));
367
369
  var serializeManifest = (manifest) => JSON.stringify(manifest, null, 2) + "\n";
@@ -376,20 +378,21 @@ var readManifest = (manifestPath) => {
376
378
  return parseManifest(raw);
377
379
  };
378
380
  var writeManifest = (manifestPath, manifest) => {
379
- mkdirSync(dirname(manifestPath), { recursive: true });
381
+ mkdirSync(dirname2(manifestPath), { recursive: true });
380
382
  writeFileSync(manifestPath, serializeManifest(manifest), "utf-8");
381
383
  };
382
384
 
383
385
  // src/core/diff.ts
384
386
  var computeDiff = (params) => {
385
- const { previous, currentRules, currentSourceHash } = params;
387
+ const { previous, currentRules, currentSourceHash, currentCliVersion } = params;
386
388
  const previousSet = new Set(previous.installed_rules);
387
389
  const currentSet = new Set(currentRules);
388
390
  const added = currentRules.filter((id) => !previousSet.has(id));
389
391
  const removed = previous.installed_rules.filter((id) => !currentSet.has(id));
390
392
  const sourceChanged = previous.sourceHash !== currentSourceHash;
391
- const status = added.length > 0 || removed.length > 0 || sourceChanged ? "changed" : "up-to-date";
392
- return { status, added, removed, sourceChanged };
393
+ const versionChanged = previous.cliVersion !== void 0 && currentCliVersion !== void 0 && previous.cliVersion !== currentCliVersion;
394
+ const status = added.length > 0 || removed.length > 0 || sourceChanged || versionChanged ? "changed" : "up-to-date";
395
+ return { status, added, removed, sourceChanged, versionChanged };
393
396
  };
394
397
 
395
398
  // src/core/install-plan.ts
@@ -400,7 +403,7 @@ var buildInstallPlan = (params) => {
400
403
  if (toolId === "claude-code" && renderResult.tool === "claude-code") {
401
404
  return renderResult.files.map(({ relativePath, content }) => ({
402
405
  relativePath,
403
- content: wrapWithHeader(content, meta)
406
+ content: wrapWithSection(content, meta)
404
407
  }));
405
408
  }
406
409
  if (toolId === "codex" && renderResult.tool === "codex" || toolId === "gemini" && renderResult.tool === "gemini") {
@@ -410,13 +413,13 @@ var buildInstallPlan = (params) => {
410
413
  const rootContent = toolId === "codex" ? renderResult.rootContent + CODEX_PLAN_SECTION : renderResult.rootContent;
411
414
  actions.push({
412
415
  relativePath: join3(config.dir, config.rootFileName),
413
- content: wrapWithHeader(rootContent, meta)
416
+ content: wrapWithSection(rootContent, meta)
414
417
  });
415
418
  }
416
419
  if (renderResult.domainContent) {
417
420
  actions.push({
418
421
  relativePath: join3(config.dir, config.domainFileName),
419
- content: wrapWithHeader(renderResult.domainContent, meta)
422
+ content: wrapWithSection(renderResult.domainContent, meta)
420
423
  });
421
424
  }
422
425
  return actions;
@@ -462,10 +465,10 @@ var inferInstalledFiles = (manifest) => {
462
465
  };
463
466
 
464
467
  // src/core/paths.ts
465
- import { dirname as dirname2, resolve as resolve3 } from "path";
466
- import { fileURLToPath } from "url";
467
- var __dirname = dirname2(fileURLToPath(import.meta.url));
468
- var COMPILER_DATA_DIR = resolve3(__dirname, "..", "..", "data");
468
+ import { dirname as dirname3, resolve as resolve3 } from "path";
469
+ import { fileURLToPath as fileURLToPath2 } from "url";
470
+ var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
471
+ var COMPILER_DATA_DIR = resolve3(__dirname2, "..", "..", "data");
469
472
 
470
473
  // src/lib/paths.ts
471
474
  import { join as join5 } from "path";
@@ -520,33 +523,32 @@ var listWorkspaceCandidates = (basePath) => {
520
523
 
521
524
  // src/lib/install.ts
522
525
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
523
- import { dirname as dirname3, resolve as resolve5 } from "path";
524
- var installFiles = (basePath, actions, meta) => {
526
+ import { dirname as dirname4, resolve as resolve5 } from "path";
527
+ var installFiles = (basePath, actions, _meta) => {
525
528
  const written = [];
526
529
  const appended = [];
527
530
  const skipped = [];
528
531
  for (const action of actions) {
529
532
  const absPath = resolve5(basePath, action.relativePath);
530
- if (existsSync2(absPath)) {
533
+ if (!existsSync2(absPath)) {
534
+ mkdirSync2(dirname4(absPath), { recursive: true });
535
+ writeFileSync2(absPath, action.content + "\n", "utf-8");
536
+ written.push(action.relativePath);
537
+ } else {
531
538
  const existing = readFileSync4(absPath, "utf-8");
532
- if (isManagedFile(existing)) {
533
- writeFileSync2(absPath, action.content, "utf-8");
534
- written.push(action.relativePath);
535
- } else if (hasAiOpsSection(existing)) {
536
- const sectionContent = wrapWithSection(stripManagedHeader(action.content), meta);
537
- const updated = replaceAiOpsSection(existing, sectionContent);
539
+ if (hasAiOpsSection(existing)) {
540
+ const updated = replaceAiOpsSection(existing, action.content);
538
541
  writeFileSync2(absPath, updated, "utf-8");
539
- appended.push(action.relativePath);
542
+ const stripped = stripAiOpsSection(existing);
543
+ (stripped.trim().length > 0 ? appended : written).push(action.relativePath);
544
+ } else if (hasLegacyHeader(existing)) {
545
+ writeFileSync2(absPath, action.content + "\n", "utf-8");
546
+ written.push(action.relativePath);
540
547
  } else {
541
- const sectionContent = wrapWithSection(stripManagedHeader(action.content), meta);
542
- const updated = existing.trimEnd() + "\n\n" + sectionContent + "\n";
548
+ const updated = existing.trimEnd() + "\n\n" + action.content + "\n";
543
549
  writeFileSync2(absPath, updated, "utf-8");
544
550
  appended.push(action.relativePath);
545
551
  }
546
- } else {
547
- mkdirSync2(dirname3(absPath), { recursive: true });
548
- writeFileSync2(absPath, action.content, "utf-8");
549
- written.push(action.relativePath);
550
552
  }
551
553
  }
552
554
  return { written, appended, skipped };
@@ -554,8 +556,40 @@ var installFiles = (basePath, actions, meta) => {
554
556
 
555
557
  // src/lib/gemini-settings.ts
556
558
  import * as p from "@clack/prompts";
557
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
559
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync3 } from "fs";
558
560
  import { join as join7 } from "path";
561
+
562
+ // src/lib/deep-merge.util.ts
563
+ var deepMerge = (base, patch) => {
564
+ const result = { ...base };
565
+ for (const [key, value] of Object.entries(patch)) {
566
+ if (value !== null && typeof value === "object" && !Array.isArray(value) && typeof result[key] === "object" && result[key] !== null && !Array.isArray(result[key])) {
567
+ result[key] = deepMerge(result[key], value);
568
+ } else {
569
+ result[key] = value;
570
+ }
571
+ }
572
+ return result;
573
+ };
574
+ var deepRemoveKeys = (base, patch) => {
575
+ const result = { ...base };
576
+ for (const [key, value] of Object.entries(patch)) {
577
+ if (!(key in result)) continue;
578
+ if (value !== null && typeof value === "object" && !Array.isArray(value) && typeof result[key] === "object" && result[key] !== null && !Array.isArray(result[key])) {
579
+ const nested = deepRemoveKeys(result[key], value);
580
+ if (Object.keys(nested).length === 0) {
581
+ delete result[key];
582
+ } else {
583
+ result[key] = nested;
584
+ }
585
+ } else {
586
+ delete result[key];
587
+ }
588
+ }
589
+ return result;
590
+ };
591
+
592
+ // src/lib/gemini-settings.ts
559
593
  var SETTING_GROUPS = [
560
594
  {
561
595
  value: "ui",
@@ -582,17 +616,6 @@ var SETTING_GROUPS = [
582
616
  patch: { experimental: { jitContext: true, plan: true } }
583
617
  }
584
618
  ];
585
- var deepMerge = (base, patch) => {
586
- const result = { ...base };
587
- for (const [key, value] of Object.entries(patch)) {
588
- if (value !== null && typeof value === "object" && !Array.isArray(value) && typeof result[key] === "object" && result[key] !== null) {
589
- result[key] = deepMerge(result[key], value);
590
- } else {
591
- result[key] = value;
592
- }
593
- }
594
- return result;
595
- };
596
619
  var promptGeminiSettings = async () => {
597
620
  const wantSettings = await p.confirm({
598
621
  message: "Gemini CLI \uC124\uC815 \uD30C\uC77C(.gemini/settings.json)\uC744 \uC124\uCE58\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
@@ -632,10 +655,33 @@ var installGeminiSettings = (basePath, selectedValues) => {
632
655
  mkdirSync3(settingsDir, { recursive: true });
633
656
  writeFileSync3(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
634
657
  };
658
+ var uninstallGeminiSettings = (basePath, selectedValues) => {
659
+ const settingsPath = join7(basePath, ".gemini", "settings.json");
660
+ if (!existsSync3(settingsPath)) return "notFound";
661
+ let existing = {};
662
+ try {
663
+ existing = JSON.parse(readFileSync5(settingsPath, "utf-8"));
664
+ } catch {
665
+ rmSync(settingsPath, { force: true });
666
+ return "deleted";
667
+ }
668
+ let result = existing;
669
+ for (const val of selectedValues) {
670
+ const group = SETTING_GROUPS.find((g) => g.value === val);
671
+ if (!group) continue;
672
+ result = deepRemoveKeys(result, group.patch);
673
+ }
674
+ if (Object.keys(result).length === 0) {
675
+ rmSync(settingsPath, { force: true });
676
+ return "deleted";
677
+ }
678
+ writeFileSync3(settingsPath, JSON.stringify(result, null, 2) + "\n", "utf-8");
679
+ return "cleaned";
680
+ };
635
681
 
636
682
  // src/lib/claude-settings.ts
637
683
  import * as p2 from "@clack/prompts";
638
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
684
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync6, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
639
685
  import { join as join8 } from "path";
640
686
  var SETTING_GROUPS2 = [
641
687
  {
@@ -651,17 +697,6 @@ var SETTING_GROUPS2 = [
651
697
  patch: { plansDirectory: "./.claude/plans" }
652
698
  }
653
699
  ];
654
- var deepMerge2 = (base, patch) => {
655
- const result = { ...base };
656
- for (const [key, value] of Object.entries(patch)) {
657
- if (value !== null && typeof value === "object" && !Array.isArray(value) && typeof result[key] === "object" && result[key] !== null) {
658
- result[key] = deepMerge2(result[key], value);
659
- } else {
660
- result[key] = value;
661
- }
662
- }
663
- return result;
664
- };
665
700
  var promptClaudeSettings = async () => {
666
701
  const wantSettings = await p2.confirm({
667
702
  message: "Claude Code \uC124\uC815 \uD30C\uC77C(.claude/settings.local.json)\uC744 \uC124\uCE58\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
@@ -696,11 +731,131 @@ var installClaudeSettings = (basePath, selectedValues) => {
696
731
  for (const val of selectedValues) {
697
732
  const group = SETTING_GROUPS2.find((g) => g.value === val);
698
733
  if (!group) continue;
699
- merged = deepMerge2(merged, group.patch);
734
+ merged = deepMerge(merged, group.patch);
700
735
  }
701
736
  mkdirSync4(settingsDir, { recursive: true });
702
737
  writeFileSync4(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
703
738
  };
739
+ var uninstallClaudeSettings = (basePath, selectedValues) => {
740
+ const settingsPath = join8(basePath, ".claude", "settings.local.json");
741
+ if (!existsSync4(settingsPath)) return "notFound";
742
+ let existing = {};
743
+ try {
744
+ existing = JSON.parse(readFileSync6(settingsPath, "utf-8"));
745
+ } catch {
746
+ rmSync2(settingsPath, { force: true });
747
+ return "deleted";
748
+ }
749
+ let result = existing;
750
+ for (const val of selectedValues) {
751
+ const group = SETTING_GROUPS2.find((g) => g.value === val);
752
+ if (!group) continue;
753
+ result = deepRemoveKeys(result, group.patch);
754
+ }
755
+ if (Object.keys(result).length === 0) {
756
+ rmSync2(settingsPath, { force: true });
757
+ return "deleted";
758
+ }
759
+ writeFileSync4(settingsPath, JSON.stringify(result, null, 2) + "\n", "utf-8");
760
+ return "cleaned";
761
+ };
762
+
763
+ // src/lib/prettier-ignore.ts
764
+ import * as p3 from "@clack/prompts";
765
+ import { existsSync as existsSync5, readFileSync as readFileSync7, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "fs";
766
+ import { join as join9 } from "path";
767
+ var PRETTIER_IGNORE_CONTENT = `# CLAUDE
768
+ .claude/rules/
769
+ **/CLAUDE.md
770
+
771
+ # GEMINI
772
+ **/GEMINI.md
773
+
774
+ # CODEX
775
+ **/AGENTS.md
776
+ **/AGENTS.override.md
777
+
778
+ .ai-ops-manifest.json`;
779
+ var SECTION_START2 = "# ai-ops:start";
780
+ var SECTION_END2 = "# ai-ops:end";
781
+ var wrapSection = (content) => `${SECTION_START2}
782
+ ${content}
783
+ ${SECTION_END2}`;
784
+ var hasAiOpsSection2 = (content) => content.includes(SECTION_START2) && content.includes(SECTION_END2);
785
+ var replaceSection = (content, newContent) => {
786
+ const lines = content.split("\n");
787
+ const result = [];
788
+ let inside = false;
789
+ let replaced = false;
790
+ for (const line of lines) {
791
+ if (line.trim() === SECTION_START2) {
792
+ inside = true;
793
+ result.push(wrapSection(newContent));
794
+ replaced = true;
795
+ continue;
796
+ }
797
+ if (line.trim() === SECTION_END2) {
798
+ inside = false;
799
+ continue;
800
+ }
801
+ if (!inside) result.push(line);
802
+ }
803
+ if (!replaced) result.push(wrapSection(newContent));
804
+ return result.join("\n");
805
+ };
806
+ var stripAiOpsSection2 = (content) => {
807
+ const lines = content.split("\n");
808
+ const result = [];
809
+ let inside = false;
810
+ for (const line of lines) {
811
+ if (line.trim() === SECTION_START2) {
812
+ inside = true;
813
+ continue;
814
+ }
815
+ if (line.trim() === SECTION_END2) {
816
+ inside = false;
817
+ continue;
818
+ }
819
+ if (!inside) result.push(line);
820
+ }
821
+ return result.join("\n");
822
+ };
823
+ var promptPrettierIgnore = async () => {
824
+ const want = await p3.confirm({
825
+ message: ".prettierignore\uB97C \uC124\uCE58\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? (VSCode Prettier \uC790\uB3D9 \uD3EC\uB9F7\uC73C\uB85C\uBD80\uD130 AI \uADDC\uCE59 \uD30C\uC77C \uBCF4\uD638)",
826
+ initialValue: false
827
+ });
828
+ if (p3.isCancel(want)) return false;
829
+ return want;
830
+ };
831
+ var installPrettierIgnore = (basePath) => {
832
+ const filePath = join9(basePath, ".prettierignore");
833
+ const section = wrapSection(PRETTIER_IGNORE_CONTENT);
834
+ if (!existsSync5(filePath)) {
835
+ writeFileSync5(filePath, section + "\n", "utf-8");
836
+ return;
837
+ }
838
+ const existing = readFileSync7(filePath, "utf-8");
839
+ if (hasAiOpsSection2(existing)) {
840
+ writeFileSync5(filePath, replaceSection(existing, PRETTIER_IGNORE_CONTENT), "utf-8");
841
+ return;
842
+ }
843
+ const separator = existing.endsWith("\n") ? "\n" : "\n\n";
844
+ writeFileSync5(filePath, existing + separator + section + "\n", "utf-8");
845
+ };
846
+ var uninstallPrettierIgnore = (basePath) => {
847
+ const filePath = join9(basePath, ".prettierignore");
848
+ if (!existsSync5(filePath)) return "notFound";
849
+ const existing = readFileSync7(filePath, "utf-8");
850
+ if (!hasAiOpsSection2(existing)) return "notFound";
851
+ const stripped = stripAiOpsSection2(existing).trim();
852
+ if (stripped.length === 0) {
853
+ rmSync3(filePath, { force: true });
854
+ return "deleted";
855
+ }
856
+ writeFileSync5(filePath, stripped + "\n", "utf-8");
857
+ return "cleaned";
858
+ };
704
859
 
705
860
  // src/commands/init.ts
706
861
  var TOOL_OPTIONS = [
@@ -717,7 +872,7 @@ var deduplicateRules = (rules) => {
717
872
  });
718
873
  };
719
874
  var selectPresetAndFineTune = async (workspaceName, presets, allRules) => {
720
- const preset = await p3.select({
875
+ const preset = await p4.select({
721
876
  message: `[${workspaceName}] \uD504\uB9AC\uC14B\uC744 \uC120\uD0DD\uD558\uC138\uC694`,
722
877
  options: presets.map((pr) => ({
723
878
  value: pr,
@@ -725,25 +880,25 @@ var selectPresetAndFineTune = async (workspaceName, presets, allRules) => {
725
880
  hint: pr.description
726
881
  }))
727
882
  });
728
- if (p3.isCancel(preset)) return null;
883
+ if (p4.isCancel(preset)) return null;
729
884
  const presetRuleGroups = resolvePresetRuleGroups(preset, allRules);
730
885
  const globalGroups = presetRuleGroups.filter((group) => group.rules.every(isGlobalRule));
731
886
  const domainGroups = presetRuleGroups.filter((group) => !group.rules.every(isGlobalRule));
732
887
  const globalGroupIds = globalGroups.map((group) => group.id);
733
888
  const globalRules = globalGroupIds.length > 0 ? resolvePresetRules({ ...preset, rules: globalGroupIds }, allRules) : [];
734
889
  if (globalRules.length > 0) {
735
- p3.note(globalRules.map((r) => ` \u2713 ${r.id}`).join("\n"), `[${workspaceName}] \uAE30\uBCF8 \uADDC\uCE59 (\uC7A0\uAE08)`);
890
+ p4.note(globalRules.map((r) => ` \u2713 ${r.id}`).join("\n"), `[${workspaceName}] \uAE30\uBCF8 \uADDC\uCE59 (\uC7A0\uAE08)`);
736
891
  }
737
892
  if (domainGroups.length === 0) {
738
893
  return { workspace: workspaceName, preset, finalRules: resolvePresetRules(preset, allRules) };
739
894
  }
740
- const selectedDomain = await p3.multiselect({
895
+ const selectedDomain = await p4.multiselect({
741
896
  message: `[${workspaceName}] \uB3C4\uBA54\uC778 \uADDC\uCE59 \uC120\uD0DD (\uD574\uC81C\uD558\uC5EC \uC81C\uC678)`,
742
897
  options: domainGroups.map((group) => ({ value: group.id, label: group.id })),
743
898
  initialValues: domainGroups.map((group) => group.id),
744
899
  required: false
745
900
  });
746
- if (p3.isCancel(selectedDomain)) return null;
901
+ if (p4.isCancel(selectedDomain)) return null;
747
902
  const selectedLogicalRuleIds = [...globalGroupIds, ...selectedDomain];
748
903
  return {
749
904
  workspace: workspaceName,
@@ -759,8 +914,8 @@ var installHierarchicalMonorepo = (toolId, mappings, basePath, meta) => {
759
914
  const { global } = partitionRules(allRules);
760
915
  if (global.length > 0) {
761
916
  const rootAction = {
762
- relativePath: join9(config.dir, config.rootFileName),
763
- content: wrapWithHeader(renderRulesToMarkdown(global), meta)
917
+ relativePath: join10(config.dir, config.rootFileName),
918
+ content: wrapWithSection(renderRulesToMarkdown(global), meta)
764
919
  };
765
920
  const r = installFiles(basePath, [rootAction], meta);
766
921
  written.push(...r.written);
@@ -770,8 +925,8 @@ var installHierarchicalMonorepo = (toolId, mappings, basePath, meta) => {
770
925
  const { domain } = partitionRules(mapping.finalRules);
771
926
  if (domain.length === 0) continue;
772
927
  const domainAction = {
773
- relativePath: join9(mapping.workspace, config.domainFileName),
774
- content: wrapWithHeader(renderRulesToMarkdown(domain), meta)
928
+ relativePath: join10(mapping.workspace, config.domainFileName),
929
+ content: wrapWithSection(renderRulesToMarkdown(domain), meta)
775
930
  };
776
931
  const r = installFiles(basePath, [domainAction], meta);
777
932
  written.push(...r.written);
@@ -793,22 +948,22 @@ var installClaudeCodeMonorepo = (mappings, basePath, meta) => {
793
948
  var initCommand = async () => {
794
949
  const basePath = resolveBasePath();
795
950
  const rulesDir = resolveRulesDir();
796
- p3.intro("ai-ops init");
797
- const selectedTools = await p3.multiselect({
951
+ p4.intro("ai-ops init");
952
+ const selectedTools = await p4.multiselect({
798
953
  message: "AI \uB3C4\uAD6C\uB97C \uC120\uD0DD\uD558\uC138\uC694",
799
954
  options: TOOL_OPTIONS,
800
955
  required: true
801
956
  });
802
- if (p3.isCancel(selectedTools)) {
803
- p3.cancel("\uCDE8\uC18C\uB428");
957
+ if (p4.isCancel(selectedTools)) {
958
+ p4.cancel("\uCDE8\uC18C\uB428");
804
959
  process.exit(0);
805
960
  }
806
- const isMonorepo = await p3.confirm({
961
+ const isMonorepo = await p4.confirm({
807
962
  message: "\uBAA8\uB178\uB808\uD3EC \uD504\uB85C\uC81D\uD2B8\uC785\uB2C8\uAE4C?",
808
963
  initialValue: false
809
964
  });
810
- if (p3.isCancel(isMonorepo)) {
811
- p3.cancel("\uCDE8\uC18C\uB428");
965
+ if (p4.isCancel(isMonorepo)) {
966
+ p4.cancel("\uCDE8\uC18C\uB428");
812
967
  process.exit(0);
813
968
  }
814
969
  const allRules = loadAllRules(rulesDir);
@@ -818,25 +973,25 @@ var initCommand = async () => {
818
973
  if (!isMonorepo) {
819
974
  const mapping = await selectPresetAndFineTune(".", presets, allRules);
820
975
  if (!mapping) {
821
- p3.cancel("\uCDE8\uC18C\uB428");
976
+ p4.cancel("\uCDE8\uC18C\uB428");
822
977
  process.exit(0);
823
978
  }
824
979
  mappings.push(mapping);
825
980
  } else {
826
981
  const candidates = listWorkspaceCandidates(basePath);
827
- const selectedWorkspaces = await p3.multiselect({
982
+ const selectedWorkspaces = await p4.multiselect({
828
983
  message: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uC120\uD0DD\uD558\uC138\uC694",
829
984
  options: candidates.map((c) => ({ value: c, label: c })),
830
985
  required: true
831
986
  });
832
- if (p3.isCancel(selectedWorkspaces)) {
833
- p3.cancel("\uCDE8\uC18C\uB428");
987
+ if (p4.isCancel(selectedWorkspaces)) {
988
+ p4.cancel("\uCDE8\uC18C\uB428");
834
989
  process.exit(0);
835
990
  }
836
991
  for (const ws of selectedWorkspaces) {
837
992
  const mapping = await selectPresetAndFineTune(ws, presets, allRules);
838
993
  if (!mapping) {
839
- p3.cancel("\uCDE8\uC18C\uB428");
994
+ p4.cancel("\uCDE8\uC18C\uB428");
840
995
  process.exit(0);
841
996
  }
842
997
  mappings.push(mapping);
@@ -844,7 +999,8 @@ var initCommand = async () => {
844
999
  }
845
1000
  const geminiSettingValues = selectedTools.includes("gemini") ? await promptGeminiSettings() : null;
846
1001
  const claudeSettingValues = selectedTools.includes("claude-code") ? await promptClaudeSettings() : null;
847
- const s = p3.spinner();
1002
+ const wantPrettierIgnore = await promptPrettierIgnore();
1003
+ const s = p4.spinner();
848
1004
  s.start("\uADDC\uCE59 \uC124\uCE58 \uC911...");
849
1005
  const meta = { sourceHash, generatedAt: (/* @__PURE__ */ new Date()).toISOString() };
850
1006
  const allInstalledFiles = [];
@@ -870,11 +1026,12 @@ var initCommand = async () => {
870
1026
  }
871
1027
  if (geminiSettingValues && geminiSettingValues.length > 0) {
872
1028
  installGeminiSettings(basePath, geminiSettingValues);
873
- allInstalledFiles.push(".gemini/settings.json");
874
1029
  }
875
1030
  if (claudeSettingValues && claudeSettingValues.length > 0) {
876
1031
  installClaudeSettings(basePath, claudeSettingValues);
877
- allInstalledFiles.push(".claude/settings.local.json");
1032
+ }
1033
+ if (wantPrettierIgnore) {
1034
+ installPrettierIgnore(basePath);
878
1035
  }
879
1036
  s.stop("\uADDC\uCE59 \uC124\uCE58 \uC644\uB8CC");
880
1037
  const allInstalledRuleIds = deduplicateRules(mappings.flatMap((m) => m.finalRules)).map((r) => r.id);
@@ -889,46 +1046,50 @@ var initCommand = async () => {
889
1046
  installedRules: allInstalledRuleIds,
890
1047
  installedFiles: allInstalledFiles,
891
1048
  appendedFiles: allAppended,
892
- settings: claudeSettingValues || geminiSettingValues ? {
1049
+ settings: claudeSettingValues || geminiSettingValues || wantPrettierIgnore ? {
893
1050
  claude: claudeSettingValues ? [...claudeSettingValues] : void 0,
894
- gemini: geminiSettingValues ? [...geminiSettingValues] : void 0
1051
+ gemini: geminiSettingValues ? [...geminiSettingValues] : void 0,
1052
+ prettierignore: wantPrettierIgnore || void 0
895
1053
  } : void 0,
1054
+ cliVersion: getCliVersion(),
896
1055
  sourceHash
897
1056
  });
898
1057
  writeManifest(resolveManifestPath(basePath), manifest);
899
1058
  if (allAppended.length > 0) {
900
- p3.log.info(`\uAE30\uC874 \uD30C\uC77C\uC5D0 \uC139\uC158 \uCD94\uAC00\uB428 (\uB0B4\uC6A9 \uBCF4\uC874):
1059
+ p4.log.info(`\uAE30\uC874 \uD30C\uC77C\uC5D0 \uC139\uC158 \uCD94\uAC00\uB428 (\uB0B4\uC6A9 \uBCF4\uC874):
901
1060
  ${allAppended.map((f) => ` ${f}`).join("\n")}`);
902
1061
  }
903
- p3.log.success(`\uC124\uCE58\uB41C \uADDC\uCE59: ${allInstalledRuleIds.length}\uAC1C`);
904
- p3.outro("ai-ops init \uC644\uB8CC");
1062
+ p4.log.success(`\uC124\uCE58\uB41C \uADDC\uCE59: ${allInstalledRuleIds.length}\uAC1C`);
1063
+ p4.outro("ai-ops init \uC644\uB8CC");
905
1064
  };
906
1065
 
907
1066
  // src/commands/update.ts
908
- import * as p4 from "@clack/prompts";
909
- import { join as join10 } from "path";
1067
+ import * as p5 from "@clack/prompts";
1068
+ import { join as join11 } from "path";
910
1069
  var updateCommand = async (opts) => {
911
1070
  const basePath = resolveBasePath();
912
1071
  const manifestPath = resolveManifestPath(basePath);
913
- p4.intro("ai-ops update");
1072
+ p5.intro("ai-ops update");
914
1073
  const manifest = readManifest(manifestPath);
915
1074
  if (!manifest) {
916
- p4.log.error("manifest\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 ai-ops init\uC744 \uC2E4\uD589\uD558\uC138\uC694.");
1075
+ p5.log.error("manifest\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 ai-ops init\uC744 \uC2E4\uD589\uD558\uC138\uC694.");
917
1076
  process.exit(1);
918
1077
  }
919
1078
  const rulesDir = resolveRulesDir();
920
1079
  const sourceHash = computeSourceHash(rulesDir);
1080
+ const cliVersion = getCliVersion();
921
1081
  const diffResult = computeDiff({
922
1082
  previous: manifest,
923
1083
  currentRules: manifest.installed_rules,
924
- currentSourceHash: sourceHash
1084
+ currentSourceHash: sourceHash,
1085
+ currentCliVersion: cliVersion
925
1086
  });
926
1087
  if (diffResult.status === "up-to-date" && !opts.force) {
927
- p4.log.info("\uBCC0\uACBD \uC0AC\uD56D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
928
- p4.outro("ai-ops update \uC644\uB8CC");
1088
+ p5.log.info("\uBCC0\uACBD \uC0AC\uD56D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
1089
+ p5.outro("ai-ops update \uC644\uB8CC");
929
1090
  return;
930
1091
  }
931
- const s = p4.spinner();
1092
+ const s = p5.spinner();
932
1093
  s.start("\uADDC\uCE59 \uAC31\uC2E0 \uC911...");
933
1094
  const allRules = loadAllRules(rulesDir);
934
1095
  const meta = { sourceHash, generatedAt: (/* @__PURE__ */ new Date()).toISOString() };
@@ -957,8 +1118,8 @@ var updateCommand = async (opts) => {
957
1118
  const { global } = partitionRules(allRulesToInstall);
958
1119
  if (global.length > 0) {
959
1120
  const rootAction = {
960
- relativePath: join10(config.dir, config.rootFileName),
961
- content: wrapWithHeader(renderRulesToMarkdown(global), meta)
1121
+ relativePath: join11(config.dir, config.rootFileName),
1122
+ content: wrapWithSection(renderRulesToMarkdown(global), meta)
962
1123
  };
963
1124
  const r = installFiles(basePath, [rootAction], meta);
964
1125
  allInstalledFiles.push(...r.written);
@@ -970,8 +1131,8 @@ var updateCommand = async (opts) => {
970
1131
  const { domain } = partitionRules(wsRules);
971
1132
  if (domain.length === 0) continue;
972
1133
  const domainAction = {
973
- relativePath: join10(ws, config.domainFileName),
974
- content: wrapWithHeader(renderRulesToMarkdown(domain), meta)
1134
+ relativePath: join11(ws, config.domainFileName),
1135
+ content: wrapWithSection(renderRulesToMarkdown(domain), meta)
975
1136
  };
976
1137
  const r = installFiles(basePath, [domainAction], meta);
977
1138
  allInstalledFiles.push(...r.written);
@@ -997,6 +1158,9 @@ var updateCommand = async (opts) => {
997
1158
  if (manifest.settings?.gemini) {
998
1159
  installGeminiSettings(basePath, manifest.settings.gemini);
999
1160
  }
1161
+ if (manifest.settings?.prettierignore) {
1162
+ installPrettierIgnore(basePath);
1163
+ }
1000
1164
  const newManifest = buildManifest({
1001
1165
  tools: manifest.tools,
1002
1166
  scope: manifest.scope,
@@ -1007,23 +1171,25 @@ var updateCommand = async (opts) => {
1007
1171
  appendedFiles: allAppended.length > 0 ? allAppended : manifest.appended_files,
1008
1172
  settings: manifest.settings ? {
1009
1173
  claude: manifest.settings.claude,
1010
- gemini: manifest.settings.gemini
1174
+ gemini: manifest.settings.gemini,
1175
+ prettierignore: manifest.settings.prettierignore
1011
1176
  } : void 0,
1177
+ cliVersion,
1012
1178
  sourceHash
1013
1179
  });
1014
1180
  writeManifest(manifestPath, newManifest);
1015
1181
  s.stop("\uADDC\uCE59 \uAC31\uC2E0 \uC644\uB8CC");
1016
- p4.outro("ai-ops update \uC644\uB8CC");
1182
+ p5.outro("ai-ops update \uC644\uB8CC");
1017
1183
  };
1018
1184
 
1019
1185
  // src/commands/diff.ts
1020
- import * as p5 from "@clack/prompts";
1186
+ import * as p6 from "@clack/prompts";
1021
1187
  var diffCommand = async () => {
1022
1188
  const basePath = resolveBasePath();
1023
- p5.intro("ai-ops diff");
1189
+ p6.intro("ai-ops diff");
1024
1190
  const manifest = readManifest(resolveManifestPath(basePath));
1025
1191
  if (!manifest) {
1026
- p5.log.error("manifest\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 ai-ops init\uC744 \uC2E4\uD589\uD558\uC138\uC694.");
1192
+ p6.log.error("manifest\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 ai-ops init\uC744 \uC2E4\uD589\uD558\uC138\uC694.");
1027
1193
  process.exit(1);
1028
1194
  }
1029
1195
  const sourceHash = computeSourceHash(resolveRulesDir());
@@ -1033,28 +1199,28 @@ var diffCommand = async () => {
1033
1199
  currentSourceHash: sourceHash
1034
1200
  });
1035
1201
  if (result.status === "up-to-date") {
1036
- p5.log.success("\uBCC0\uACBD \uC0AC\uD56D \uC5C6\uC74C. \uCD5C\uC2E0 \uC0C1\uD0DC\uC785\uB2C8\uB2E4.");
1202
+ p6.log.success("\uBCC0\uACBD \uC0AC\uD56D \uC5C6\uC74C. \uCD5C\uC2E0 \uC0C1\uD0DC\uC785\uB2C8\uB2E4.");
1037
1203
  } else {
1038
1204
  if (result.sourceChanged) {
1039
- p5.log.warn(`\uC18C\uC2A4 \uBCC0\uACBD \uAC10\uC9C0: ${manifest.sourceHash} \u2192 ${sourceHash}`);
1205
+ p6.log.warn(`\uC18C\uC2A4 \uBCC0\uACBD \uAC10\uC9C0: ${manifest.sourceHash} \u2192 ${sourceHash}`);
1040
1206
  }
1041
1207
  if (result.added.length > 0) {
1042
- p5.log.info(`\uCD94\uAC00\uB41C \uADDC\uCE59: ${result.added.join(", ")}`);
1208
+ p6.log.info(`\uCD94\uAC00\uB41C \uADDC\uCE59: ${result.added.join(", ")}`);
1043
1209
  }
1044
1210
  if (result.removed.length > 0) {
1045
- p5.log.info(`\uC81C\uAC70\uB41C \uADDC\uCE59: ${result.removed.join(", ")}`);
1211
+ p6.log.info(`\uC81C\uAC70\uB41C \uADDC\uCE59: ${result.removed.join(", ")}`);
1046
1212
  }
1047
1213
  }
1048
- p5.outro("ai-ops diff \uC644\uB8CC");
1214
+ p6.outro("ai-ops diff \uC644\uB8CC");
1049
1215
  };
1050
1216
 
1051
1217
  // src/commands/uninstall.ts
1052
- import * as p6 from "@clack/prompts";
1053
- import { rmSync as rmSync2 } from "fs";
1218
+ import * as p7 from "@clack/prompts";
1219
+ import { rmSync as rmSync5 } from "fs";
1054
1220
 
1055
1221
  // src/lib/uninstall.ts
1056
- import { existsSync as existsSync5, readFileSync as readFileSync7, rmSync, readdirSync as readdirSync4, writeFileSync as writeFileSync5 } from "fs";
1057
- import { resolve as resolve6, dirname as dirname4 } from "path";
1222
+ import { existsSync as existsSync6, readFileSync as readFileSync8, rmSync as rmSync4, readdirSync as readdirSync4, writeFileSync as writeFileSync6 } from "fs";
1223
+ import { resolve as resolve6, dirname as dirname5 } from "path";
1058
1224
  var removeFiles = (basePath, relativePaths) => {
1059
1225
  const deleted = [];
1060
1226
  const cleaned = [];
@@ -1062,23 +1228,26 @@ var removeFiles = (basePath, relativePaths) => {
1062
1228
  const notFound = [];
1063
1229
  for (const rel of relativePaths) {
1064
1230
  const absPath = resolve6(basePath, rel);
1065
- if (!existsSync5(absPath)) {
1231
+ if (!existsSync6(absPath)) {
1066
1232
  notFound.push(rel);
1067
1233
  continue;
1068
1234
  }
1069
- const content = readFileSync7(absPath, "utf-8");
1070
- if (!isManagedFile(content)) {
1071
- if (hasAiOpsSection(content)) {
1072
- const stripped = stripAiOpsSection(content);
1073
- writeFileSync5(absPath, stripped, "utf-8");
1074
- cleaned.push(rel);
1235
+ const content = readFileSync8(absPath, "utf-8");
1236
+ if (hasAiOpsSection(content)) {
1237
+ const stripped = stripAiOpsSection(content);
1238
+ if (stripped.trim().length === 0) {
1239
+ rmSync4(absPath);
1240
+ deleted.push(rel);
1075
1241
  } else {
1076
- skipped.push(rel);
1242
+ writeFileSync6(absPath, stripped, "utf-8");
1243
+ cleaned.push(rel);
1077
1244
  }
1078
- continue;
1245
+ } else if (hasLegacyHeader(content)) {
1246
+ rmSync4(absPath);
1247
+ deleted.push(rel);
1248
+ } else {
1249
+ skipped.push(rel);
1079
1250
  }
1080
- rmSync(absPath);
1081
- deleted.push(rel);
1082
1251
  }
1083
1252
  return { deleted, cleaned, skipped, notFound };
1084
1253
  };
@@ -1086,11 +1255,11 @@ var cleanEmptyDirs = (basePath, dirs) => {
1086
1255
  const removed = [];
1087
1256
  for (const dir of dirs) {
1088
1257
  const absDir = resolve6(basePath, dir);
1089
- if (!existsSync5(absDir)) continue;
1258
+ if (!existsSync6(absDir)) continue;
1090
1259
  try {
1091
1260
  const entries = readdirSync4(absDir);
1092
1261
  if (entries.length === 0) {
1093
- rmSync(absDir, { recursive: true });
1262
+ rmSync4(absDir, { recursive: true });
1094
1263
  removed.push(dir);
1095
1264
  }
1096
1265
  } catch {
@@ -1101,7 +1270,7 @@ var cleanEmptyDirs = (basePath, dirs) => {
1101
1270
  var collectManagedDirs = (relativePaths) => {
1102
1271
  const dirs = /* @__PURE__ */ new Set();
1103
1272
  for (const rel of relativePaths) {
1104
- const dir = dirname4(rel);
1273
+ const dir = dirname5(rel);
1105
1274
  if (dir !== ".") {
1106
1275
  dirs.add(dir);
1107
1276
  }
@@ -1110,64 +1279,85 @@ var collectManagedDirs = (relativePaths) => {
1110
1279
  };
1111
1280
 
1112
1281
  // src/commands/uninstall.ts
1282
+ var SETTINGS_PATHS = /* @__PURE__ */ new Set([".claude/settings.local.json", ".gemini/settings.json"]);
1113
1283
  var uninstallCommand = async () => {
1114
1284
  const basePath = resolveBasePath();
1115
1285
  const manifestPath = resolveManifestPath(basePath);
1116
- p6.intro("ai-ops uninstall");
1286
+ p7.intro("ai-ops uninstall");
1117
1287
  const manifest = readManifest(manifestPath);
1118
1288
  if (!manifest) {
1119
- p6.log.error("manifest\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 ai-ops init\uC744 \uC2E4\uD589\uD558\uC138\uC694.");
1289
+ p7.log.error("manifest\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 ai-ops init\uC744 \uC2E4\uD589\uD558\uC138\uC694.");
1120
1290
  process.exit(1);
1121
1291
  }
1122
1292
  const targetFiles = [
1123
1293
  ...manifest.installed_files ?? inferInstalledFiles(manifest),
1124
1294
  ...manifest.appended_files ?? []
1125
- ];
1295
+ ].filter((f) => !SETTINGS_PATHS.has(f));
1126
1296
  if (targetFiles.length === 0) {
1127
- p6.log.warn("\uC0AD\uC81C\uD560 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
1128
- p6.outro("ai-ops uninstall \uC644\uB8CC");
1297
+ p7.log.warn("\uC0AD\uC81C\uD560 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
1298
+ p7.outro("ai-ops uninstall \uC644\uB8CC");
1129
1299
  return;
1130
1300
  }
1131
- p6.log.info(`\uC0AD\uC81C \uB300\uC0C1 \uD30C\uC77C (${targetFiles.length}\uAC1C):
1301
+ p7.log.info(`\uC0AD\uC81C \uB300\uC0C1 \uD30C\uC77C (${targetFiles.length}\uAC1C):
1132
1302
  ${targetFiles.map((f) => ` ${f}`).join("\n")}`);
1133
- const confirmed = await p6.confirm({
1303
+ const confirmed = await p7.confirm({
1134
1304
  message: "\uC704 \uD30C\uC77C\uACFC manifest\uB97C \uBAA8\uB450 \uC0AD\uC81C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
1135
1305
  initialValue: false
1136
1306
  });
1137
- if (p6.isCancel(confirmed) || !confirmed) {
1138
- p6.cancel("\uCDE8\uC18C\uB428");
1307
+ if (p7.isCancel(confirmed) || !confirmed) {
1308
+ p7.cancel("\uCDE8\uC18C\uB428");
1139
1309
  process.exit(0);
1140
1310
  }
1311
+ const settingsMessages = [];
1312
+ if (manifest.settings?.claude) {
1313
+ const status = uninstallClaudeSettings(basePath, manifest.settings.claude);
1314
+ if (status === "deleted") settingsMessages.push("\uC0AD\uC81C: .claude/settings.local.json");
1315
+ else if (status === "cleaned") settingsMessages.push("ai-ops \uD0A4 \uC81C\uAC70 (\uC0AC\uC6A9\uC790 \uC124\uC815 \uBCF4\uC874): .claude/settings.local.json");
1316
+ }
1317
+ if (manifest.settings?.gemini) {
1318
+ const status = uninstallGeminiSettings(basePath, manifest.settings.gemini);
1319
+ if (status === "deleted") settingsMessages.push("\uC0AD\uC81C: .gemini/settings.json");
1320
+ else if (status === "cleaned") settingsMessages.push("ai-ops \uD0A4 \uC81C\uAC70 (\uC0AC\uC6A9\uC790 \uC124\uC815 \uBCF4\uC874): .gemini/settings.json");
1321
+ }
1322
+ if (manifest.settings?.prettierignore) {
1323
+ const status = uninstallPrettierIgnore(basePath);
1324
+ if (status === "deleted") settingsMessages.push("\uC0AD\uC81C: .prettierignore");
1325
+ else if (status === "cleaned") settingsMessages.push("ai-ops \uC139\uC158 \uC81C\uAC70 (\uC0AC\uC6A9\uC790 \uB0B4\uC6A9 \uBCF4\uC874): .prettierignore");
1326
+ }
1141
1327
  const result = removeFiles(basePath, targetFiles);
1142
1328
  const dirs = collectManagedDirs(targetFiles);
1143
1329
  const removedDirs = cleanEmptyDirs(basePath, dirs);
1144
- rmSync2(manifestPath, { force: true });
1330
+ rmSync5(manifestPath, { force: true });
1145
1331
  if (result.deleted.length > 0) {
1146
- p6.log.success(`\uC0AD\uC81C \uC644\uB8CC (${result.deleted.length}\uAC1C):
1332
+ p7.log.success(`\uC0AD\uC81C \uC644\uB8CC (${result.deleted.length}\uAC1C):
1147
1333
  ${result.deleted.map((f) => ` ${f}`).join("\n")}`);
1148
1334
  }
1149
1335
  if (result.cleaned.length > 0) {
1150
- p6.log.success(
1336
+ p7.log.success(
1151
1337
  `\uC139\uC158 \uC81C\uAC70 \uC644\uB8CC (\uC0AC\uC6A9\uC790 \uB0B4\uC6A9 \uBCF4\uC874, ${result.cleaned.length}\uAC1C):
1152
1338
  ${result.cleaned.map((f) => ` ${f}`).join("\n")}`
1153
1339
  );
1154
1340
  }
1155
1341
  if (result.skipped.length > 0) {
1156
- p6.log.warn(
1342
+ p7.log.warn(
1157
1343
  `\uAC74\uB108\uB700 (non-managed \uD30C\uC77C \uBCF4\uD638, ${result.skipped.length}\uAC1C):
1158
1344
  ${result.skipped.map((f) => ` ${f}`).join("\n")}`
1159
1345
  );
1160
1346
  }
1161
1347
  if (result.notFound.length > 0) {
1162
- p6.log.info(`\uC774\uBBF8 \uC5C6\uC74C (${result.notFound.length}\uAC1C):
1348
+ p7.log.info(`\uC774\uBBF8 \uC5C6\uC74C (${result.notFound.length}\uAC1C):
1163
1349
  ${result.notFound.map((f) => ` ${f}`).join("\n")}`);
1164
1350
  }
1165
1351
  if (removedDirs.length > 0) {
1166
- p6.log.info(`\uBE48 \uB514\uB809\uD1A0\uB9AC \uC815\uB9AC (${removedDirs.length}\uAC1C):
1352
+ p7.log.info(`\uBE48 \uB514\uB809\uD1A0\uB9AC \uC815\uB9AC (${removedDirs.length}\uAC1C):
1167
1353
  ${removedDirs.map((d) => ` ${d}`).join("\n")}`);
1168
1354
  }
1169
- p6.log.success(`manifest \uC0AD\uC81C: ${MANIFEST_FILENAME}`);
1170
- p6.outro("ai-ops uninstall \uC644\uB8CC");
1355
+ if (settingsMessages.length > 0) {
1356
+ p7.log.success(`\uC124\uC815 \uD30C\uC77C \uCC98\uB9AC:
1357
+ ${settingsMessages.map((m) => ` ${m}`).join("\n")}`);
1358
+ }
1359
+ p7.log.success(`manifest \uC0AD\uC81C: ${MANIFEST_FILENAME}`);
1360
+ p7.outro("ai-ops uninstall \uC644\uB8CC");
1171
1361
  };
1172
1362
 
1173
1363
  // src/bin/index.ts