ai-ops-cli 0.1.15 → 0.1.17

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),
@@ -125,7 +126,8 @@ var loadRuleFile = (filePath) => {
125
126
  };
126
127
  var loadAllRules = (rulesDir) => {
127
128
  const files = readdirSync(rulesDir).filter((f) => f.endsWith(".yaml")).sort();
128
- return files.map((f) => loadRuleFile(resolve(rulesDir, f)));
129
+ const rules = files.map((f) => loadRuleFile(resolve(rulesDir, f)));
130
+ return sortRulesByPriority(rules);
129
131
  };
130
132
  var loadPresets = (presetsPath) => {
131
133
  const raw = readFileSync(presetsPath, "utf-8");
@@ -239,7 +241,7 @@ var partitionRules = (rules) => {
239
241
  return { global, domain };
240
242
  };
241
243
  var renderFrontmatter = (paths) => {
242
- const lines = paths.map((p7) => ` - "${p7}"`).join("\n");
244
+ const lines = paths.map((p8) => ` - "${p8}"`).join("\n");
243
245
  return `---
244
246
  paths:
245
247
  ${lines}
@@ -333,20 +335,7 @@ var buildManifest = (params) => ManifestSchema.parse({
333
335
  var MANAGED_MARKER = "<!-- managed by ai-ops -->";
334
336
  var SECTION_START = "<!-- ai-ops:start -->";
335
337
  var SECTION_END = "<!-- ai-ops:end -->";
336
- var wrapWithHeader = (content, meta) => {
337
- const metaLine = `<!-- sourceHash: ${meta.sourceHash} | generatedAt: ${meta.generatedAt} -->`;
338
- return `${MANAGED_MARKER}
339
- ${metaLine}
340
-
341
- ${content}`;
342
- };
343
- var isManagedFile = (content) => content.startsWith(MANAGED_MARKER);
344
- var stripManagedHeader = (content) => {
345
- if (!isManagedFile(content)) return content;
346
- const lines = content.split("\n");
347
- const stripped = lines.slice(3).join("\n");
348
- return stripped;
349
- };
338
+ var hasLegacyHeader = (content) => content.includes(MANAGED_MARKER);
350
339
  var wrapWithSection = (content, meta) => {
351
340
  const metaLine = `<!-- sourceHash: ${meta.sourceHash} | generatedAt: ${meta.generatedAt} -->`;
352
341
  return `${SECTION_START}
@@ -370,7 +359,7 @@ var replaceAiOpsSection = (existing, newSection) => {
370
359
  if (startIdx === -1 || endIdx === -1) return existing;
371
360
  const before = existing.slice(0, startIdx).trimEnd();
372
361
  const after = existing.slice(endIdx + SECTION_END.length).trimStart();
373
- return before + "\n\n" + newSection + (after ? "\n\n" + after : "") + "\n";
362
+ return [before, newSection, after].filter(Boolean).join("\n\n") + "\n";
374
363
  };
375
364
 
376
365
  // src/core/manifest-io.ts
@@ -409,13 +398,13 @@ var computeDiff = (params) => {
409
398
 
410
399
  // src/core/install-plan.ts
411
400
  import { join as join3 } from "path";
412
- var CODEX_PLAN_SECTION = "\n\n---\n\n## Plan Snapshot\n\nBefore implementation, save the latest `<proposed_plan>` to `.codex/plans/YYYYMMDD_<topic>.md` (`<topic>` = kebab-case title, fallback `task`).\nEnsure `.codex/plans` exists; if the filename exists, append `-v2`, `-v3`, ...\nDo not start any mutating implementation step until this file is written.";
401
+ var CODEX_PLAN_SECTION = "\n\n---\n\n## Plan Snapshot (Plan mode only)\n\n- This rule applies only when `collaboration_mode=Plan`.\n- Before implementation (file edits/creates, installs, commits), save the latest plan content to `.codex/plans/YYYYMMDD_<topic>.md`.\n- In `Default` mode, do not automatically create or update plan files.";
413
402
  var buildInstallPlan = (params) => {
414
403
  const { toolId, renderResult, meta } = params;
415
404
  if (toolId === "claude-code" && renderResult.tool === "claude-code") {
416
405
  return renderResult.files.map(({ relativePath, content }) => ({
417
406
  relativePath,
418
- content: wrapWithHeader(content, meta)
407
+ content: wrapWithSection(content, meta)
419
408
  }));
420
409
  }
421
410
  if (toolId === "codex" && renderResult.tool === "codex" || toolId === "gemini" && renderResult.tool === "gemini") {
@@ -425,13 +414,13 @@ var buildInstallPlan = (params) => {
425
414
  const rootContent = toolId === "codex" ? renderResult.rootContent + CODEX_PLAN_SECTION : renderResult.rootContent;
426
415
  actions.push({
427
416
  relativePath: join3(config.dir, config.rootFileName),
428
- content: wrapWithHeader(rootContent, meta)
417
+ content: wrapWithSection(rootContent, meta)
429
418
  });
430
419
  }
431
420
  if (renderResult.domainContent) {
432
421
  actions.push({
433
422
  relativePath: join3(config.dir, config.domainFileName),
434
- content: wrapWithHeader(renderResult.domainContent, meta)
423
+ content: wrapWithSection(renderResult.domainContent, meta)
435
424
  });
436
425
  }
437
426
  return actions;
@@ -536,32 +525,31 @@ var listWorkspaceCandidates = (basePath) => {
536
525
  // src/lib/install.ts
537
526
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
538
527
  import { dirname as dirname4, resolve as resolve5 } from "path";
539
- var installFiles = (basePath, actions, meta) => {
528
+ var installFiles = (basePath, actions, _meta) => {
540
529
  const written = [];
541
530
  const appended = [];
542
531
  const skipped = [];
543
532
  for (const action of actions) {
544
533
  const absPath = resolve5(basePath, action.relativePath);
545
- if (existsSync2(absPath)) {
534
+ if (!existsSync2(absPath)) {
535
+ mkdirSync2(dirname4(absPath), { recursive: true });
536
+ writeFileSync2(absPath, action.content + "\n", "utf-8");
537
+ written.push(action.relativePath);
538
+ } else {
546
539
  const existing = readFileSync4(absPath, "utf-8");
547
- if (isManagedFile(existing)) {
548
- writeFileSync2(absPath, action.content, "utf-8");
549
- written.push(action.relativePath);
550
- } else if (hasAiOpsSection(existing)) {
551
- const sectionContent = wrapWithSection(stripManagedHeader(action.content), meta);
552
- const updated = replaceAiOpsSection(existing, sectionContent);
540
+ if (hasAiOpsSection(existing)) {
541
+ const updated = replaceAiOpsSection(existing, action.content);
553
542
  writeFileSync2(absPath, updated, "utf-8");
554
- appended.push(action.relativePath);
543
+ const stripped = stripAiOpsSection(existing);
544
+ (stripped.trim().length > 0 ? appended : written).push(action.relativePath);
545
+ } else if (hasLegacyHeader(existing)) {
546
+ writeFileSync2(absPath, action.content + "\n", "utf-8");
547
+ written.push(action.relativePath);
555
548
  } else {
556
- const sectionContent = wrapWithSection(stripManagedHeader(action.content), meta);
557
- const updated = existing.trimEnd() + "\n\n" + sectionContent + "\n";
549
+ const updated = existing.trimEnd() + "\n\n" + action.content + "\n";
558
550
  writeFileSync2(absPath, updated, "utf-8");
559
551
  appended.push(action.relativePath);
560
552
  }
561
- } else {
562
- mkdirSync2(dirname4(absPath), { recursive: true });
563
- writeFileSync2(absPath, action.content, "utf-8");
564
- written.push(action.relativePath);
565
553
  }
566
554
  }
567
555
  return { written, appended, skipped };
@@ -569,8 +557,40 @@ var installFiles = (basePath, actions, meta) => {
569
557
 
570
558
  // src/lib/gemini-settings.ts
571
559
  import * as p from "@clack/prompts";
572
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
560
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync3 } from "fs";
573
561
  import { join as join7 } from "path";
562
+
563
+ // src/lib/deep-merge.util.ts
564
+ var deepMerge = (base, patch) => {
565
+ const result = { ...base };
566
+ for (const [key, value] of Object.entries(patch)) {
567
+ if (value !== null && typeof value === "object" && !Array.isArray(value) && typeof result[key] === "object" && result[key] !== null && !Array.isArray(result[key])) {
568
+ result[key] = deepMerge(result[key], value);
569
+ } else {
570
+ result[key] = value;
571
+ }
572
+ }
573
+ return result;
574
+ };
575
+ var deepRemoveKeys = (base, patch) => {
576
+ const result = { ...base };
577
+ for (const [key, value] of Object.entries(patch)) {
578
+ if (!(key in result)) continue;
579
+ if (value !== null && typeof value === "object" && !Array.isArray(value) && typeof result[key] === "object" && result[key] !== null && !Array.isArray(result[key])) {
580
+ const nested = deepRemoveKeys(result[key], value);
581
+ if (Object.keys(nested).length === 0) {
582
+ delete result[key];
583
+ } else {
584
+ result[key] = nested;
585
+ }
586
+ } else {
587
+ delete result[key];
588
+ }
589
+ }
590
+ return result;
591
+ };
592
+
593
+ // src/lib/gemini-settings.ts
574
594
  var SETTING_GROUPS = [
575
595
  {
576
596
  value: "ui",
@@ -597,17 +617,6 @@ var SETTING_GROUPS = [
597
617
  patch: { experimental: { jitContext: true, plan: true } }
598
618
  }
599
619
  ];
600
- var deepMerge = (base, patch) => {
601
- const result = { ...base };
602
- for (const [key, value] of Object.entries(patch)) {
603
- if (value !== null && typeof value === "object" && !Array.isArray(value) && typeof result[key] === "object" && result[key] !== null) {
604
- result[key] = deepMerge(result[key], value);
605
- } else {
606
- result[key] = value;
607
- }
608
- }
609
- return result;
610
- };
611
620
  var promptGeminiSettings = async () => {
612
621
  const wantSettings = await p.confirm({
613
622
  message: "Gemini CLI \uC124\uC815 \uD30C\uC77C(.gemini/settings.json)\uC744 \uC124\uCE58\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
@@ -647,10 +656,33 @@ var installGeminiSettings = (basePath, selectedValues) => {
647
656
  mkdirSync3(settingsDir, { recursive: true });
648
657
  writeFileSync3(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
649
658
  };
659
+ var uninstallGeminiSettings = (basePath, selectedValues) => {
660
+ const settingsPath = join7(basePath, ".gemini", "settings.json");
661
+ if (!existsSync3(settingsPath)) return "notFound";
662
+ let existing = {};
663
+ try {
664
+ existing = JSON.parse(readFileSync5(settingsPath, "utf-8"));
665
+ } catch {
666
+ rmSync(settingsPath, { force: true });
667
+ return "deleted";
668
+ }
669
+ let result = existing;
670
+ for (const val of selectedValues) {
671
+ const group = SETTING_GROUPS.find((g) => g.value === val);
672
+ if (!group) continue;
673
+ result = deepRemoveKeys(result, group.patch);
674
+ }
675
+ if (Object.keys(result).length === 0) {
676
+ rmSync(settingsPath, { force: true });
677
+ return "deleted";
678
+ }
679
+ writeFileSync3(settingsPath, JSON.stringify(result, null, 2) + "\n", "utf-8");
680
+ return "cleaned";
681
+ };
650
682
 
651
683
  // src/lib/claude-settings.ts
652
684
  import * as p2 from "@clack/prompts";
653
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
685
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync6, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
654
686
  import { join as join8 } from "path";
655
687
  var SETTING_GROUPS2 = [
656
688
  {
@@ -666,17 +698,6 @@ var SETTING_GROUPS2 = [
666
698
  patch: { plansDirectory: "./.claude/plans" }
667
699
  }
668
700
  ];
669
- var deepMerge2 = (base, patch) => {
670
- const result = { ...base };
671
- for (const [key, value] of Object.entries(patch)) {
672
- if (value !== null && typeof value === "object" && !Array.isArray(value) && typeof result[key] === "object" && result[key] !== null) {
673
- result[key] = deepMerge2(result[key], value);
674
- } else {
675
- result[key] = value;
676
- }
677
- }
678
- return result;
679
- };
680
701
  var promptClaudeSettings = async () => {
681
702
  const wantSettings = await p2.confirm({
682
703
  message: "Claude Code \uC124\uC815 \uD30C\uC77C(.claude/settings.local.json)\uC744 \uC124\uCE58\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
@@ -711,11 +732,131 @@ var installClaudeSettings = (basePath, selectedValues) => {
711
732
  for (const val of selectedValues) {
712
733
  const group = SETTING_GROUPS2.find((g) => g.value === val);
713
734
  if (!group) continue;
714
- merged = deepMerge2(merged, group.patch);
735
+ merged = deepMerge(merged, group.patch);
715
736
  }
716
737
  mkdirSync4(settingsDir, { recursive: true });
717
738
  writeFileSync4(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
718
739
  };
740
+ var uninstallClaudeSettings = (basePath, selectedValues) => {
741
+ const settingsPath = join8(basePath, ".claude", "settings.local.json");
742
+ if (!existsSync4(settingsPath)) return "notFound";
743
+ let existing = {};
744
+ try {
745
+ existing = JSON.parse(readFileSync6(settingsPath, "utf-8"));
746
+ } catch {
747
+ rmSync2(settingsPath, { force: true });
748
+ return "deleted";
749
+ }
750
+ let result = existing;
751
+ for (const val of selectedValues) {
752
+ const group = SETTING_GROUPS2.find((g) => g.value === val);
753
+ if (!group) continue;
754
+ result = deepRemoveKeys(result, group.patch);
755
+ }
756
+ if (Object.keys(result).length === 0) {
757
+ rmSync2(settingsPath, { force: true });
758
+ return "deleted";
759
+ }
760
+ writeFileSync4(settingsPath, JSON.stringify(result, null, 2) + "\n", "utf-8");
761
+ return "cleaned";
762
+ };
763
+
764
+ // src/lib/prettier-ignore.ts
765
+ import * as p3 from "@clack/prompts";
766
+ import { existsSync as existsSync5, readFileSync as readFileSync7, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "fs";
767
+ import { join as join9 } from "path";
768
+ var PRETTIER_IGNORE_CONTENT = `# CLAUDE
769
+ .claude/rules/
770
+ **/CLAUDE.md
771
+
772
+ # GEMINI
773
+ **/GEMINI.md
774
+
775
+ # CODEX
776
+ **/AGENTS.md
777
+ **/AGENTS.override.md
778
+
779
+ .ai-ops-manifest.json`;
780
+ var SECTION_START2 = "# ai-ops:start";
781
+ var SECTION_END2 = "# ai-ops:end";
782
+ var wrapSection = (content) => `${SECTION_START2}
783
+ ${content}
784
+ ${SECTION_END2}`;
785
+ var hasAiOpsSection2 = (content) => content.includes(SECTION_START2) && content.includes(SECTION_END2);
786
+ var replaceSection = (content, newContent) => {
787
+ const lines = content.split("\n");
788
+ const result = [];
789
+ let inside = false;
790
+ let replaced = false;
791
+ for (const line of lines) {
792
+ if (line.trim() === SECTION_START2) {
793
+ inside = true;
794
+ result.push(wrapSection(newContent));
795
+ replaced = true;
796
+ continue;
797
+ }
798
+ if (line.trim() === SECTION_END2) {
799
+ inside = false;
800
+ continue;
801
+ }
802
+ if (!inside) result.push(line);
803
+ }
804
+ if (!replaced) result.push(wrapSection(newContent));
805
+ return result.join("\n");
806
+ };
807
+ var stripAiOpsSection2 = (content) => {
808
+ const lines = content.split("\n");
809
+ const result = [];
810
+ let inside = false;
811
+ for (const line of lines) {
812
+ if (line.trim() === SECTION_START2) {
813
+ inside = true;
814
+ continue;
815
+ }
816
+ if (line.trim() === SECTION_END2) {
817
+ inside = false;
818
+ continue;
819
+ }
820
+ if (!inside) result.push(line);
821
+ }
822
+ return result.join("\n");
823
+ };
824
+ var promptPrettierIgnore = async () => {
825
+ const want = await p3.confirm({
826
+ 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)",
827
+ initialValue: false
828
+ });
829
+ if (p3.isCancel(want)) return false;
830
+ return want;
831
+ };
832
+ var installPrettierIgnore = (basePath) => {
833
+ const filePath = join9(basePath, ".prettierignore");
834
+ const section = wrapSection(PRETTIER_IGNORE_CONTENT);
835
+ if (!existsSync5(filePath)) {
836
+ writeFileSync5(filePath, section + "\n", "utf-8");
837
+ return;
838
+ }
839
+ const existing = readFileSync7(filePath, "utf-8");
840
+ if (hasAiOpsSection2(existing)) {
841
+ writeFileSync5(filePath, replaceSection(existing, PRETTIER_IGNORE_CONTENT), "utf-8");
842
+ return;
843
+ }
844
+ const separator = existing.endsWith("\n") ? "\n" : "\n\n";
845
+ writeFileSync5(filePath, existing + separator + section + "\n", "utf-8");
846
+ };
847
+ var uninstallPrettierIgnore = (basePath) => {
848
+ const filePath = join9(basePath, ".prettierignore");
849
+ if (!existsSync5(filePath)) return "notFound";
850
+ const existing = readFileSync7(filePath, "utf-8");
851
+ if (!hasAiOpsSection2(existing)) return "notFound";
852
+ const stripped = stripAiOpsSection2(existing).trim();
853
+ if (stripped.length === 0) {
854
+ rmSync3(filePath, { force: true });
855
+ return "deleted";
856
+ }
857
+ writeFileSync5(filePath, stripped + "\n", "utf-8");
858
+ return "cleaned";
859
+ };
719
860
 
720
861
  // src/commands/init.ts
721
862
  var TOOL_OPTIONS = [
@@ -732,7 +873,7 @@ var deduplicateRules = (rules) => {
732
873
  });
733
874
  };
734
875
  var selectPresetAndFineTune = async (workspaceName, presets, allRules) => {
735
- const preset = await p3.select({
876
+ const preset = await p4.select({
736
877
  message: `[${workspaceName}] \uD504\uB9AC\uC14B\uC744 \uC120\uD0DD\uD558\uC138\uC694`,
737
878
  options: presets.map((pr) => ({
738
879
  value: pr,
@@ -740,25 +881,25 @@ var selectPresetAndFineTune = async (workspaceName, presets, allRules) => {
740
881
  hint: pr.description
741
882
  }))
742
883
  });
743
- if (p3.isCancel(preset)) return null;
884
+ if (p4.isCancel(preset)) return null;
744
885
  const presetRuleGroups = resolvePresetRuleGroups(preset, allRules);
745
886
  const globalGroups = presetRuleGroups.filter((group) => group.rules.every(isGlobalRule));
746
887
  const domainGroups = presetRuleGroups.filter((group) => !group.rules.every(isGlobalRule));
747
888
  const globalGroupIds = globalGroups.map((group) => group.id);
748
889
  const globalRules = globalGroupIds.length > 0 ? resolvePresetRules({ ...preset, rules: globalGroupIds }, allRules) : [];
749
890
  if (globalRules.length > 0) {
750
- p3.note(globalRules.map((r) => ` \u2713 ${r.id}`).join("\n"), `[${workspaceName}] \uAE30\uBCF8 \uADDC\uCE59 (\uC7A0\uAE08)`);
891
+ p4.note(globalRules.map((r) => ` \u2713 ${r.id}`).join("\n"), `[${workspaceName}] \uAE30\uBCF8 \uADDC\uCE59 (\uC7A0\uAE08)`);
751
892
  }
752
893
  if (domainGroups.length === 0) {
753
894
  return { workspace: workspaceName, preset, finalRules: resolvePresetRules(preset, allRules) };
754
895
  }
755
- const selectedDomain = await p3.multiselect({
896
+ const selectedDomain = await p4.multiselect({
756
897
  message: `[${workspaceName}] \uB3C4\uBA54\uC778 \uADDC\uCE59 \uC120\uD0DD (\uD574\uC81C\uD558\uC5EC \uC81C\uC678)`,
757
898
  options: domainGroups.map((group) => ({ value: group.id, label: group.id })),
758
899
  initialValues: domainGroups.map((group) => group.id),
759
900
  required: false
760
901
  });
761
- if (p3.isCancel(selectedDomain)) return null;
902
+ if (p4.isCancel(selectedDomain)) return null;
762
903
  const selectedLogicalRuleIds = [...globalGroupIds, ...selectedDomain];
763
904
  return {
764
905
  workspace: workspaceName,
@@ -774,8 +915,8 @@ var installHierarchicalMonorepo = (toolId, mappings, basePath, meta) => {
774
915
  const { global } = partitionRules(allRules);
775
916
  if (global.length > 0) {
776
917
  const rootAction = {
777
- relativePath: join9(config.dir, config.rootFileName),
778
- content: wrapWithHeader(renderRulesToMarkdown(global), meta)
918
+ relativePath: join10(config.dir, config.rootFileName),
919
+ content: wrapWithSection(renderRulesToMarkdown(global), meta)
779
920
  };
780
921
  const r = installFiles(basePath, [rootAction], meta);
781
922
  written.push(...r.written);
@@ -785,8 +926,8 @@ var installHierarchicalMonorepo = (toolId, mappings, basePath, meta) => {
785
926
  const { domain } = partitionRules(mapping.finalRules);
786
927
  if (domain.length === 0) continue;
787
928
  const domainAction = {
788
- relativePath: join9(mapping.workspace, config.domainFileName),
789
- content: wrapWithHeader(renderRulesToMarkdown(domain), meta)
929
+ relativePath: join10(mapping.workspace, config.domainFileName),
930
+ content: wrapWithSection(renderRulesToMarkdown(domain), meta)
790
931
  };
791
932
  const r = installFiles(basePath, [domainAction], meta);
792
933
  written.push(...r.written);
@@ -808,22 +949,22 @@ var installClaudeCodeMonorepo = (mappings, basePath, meta) => {
808
949
  var initCommand = async () => {
809
950
  const basePath = resolveBasePath();
810
951
  const rulesDir = resolveRulesDir();
811
- p3.intro("ai-ops init");
812
- const selectedTools = await p3.multiselect({
952
+ p4.intro("ai-ops init");
953
+ const selectedTools = await p4.multiselect({
813
954
  message: "AI \uB3C4\uAD6C\uB97C \uC120\uD0DD\uD558\uC138\uC694",
814
955
  options: TOOL_OPTIONS,
815
956
  required: true
816
957
  });
817
- if (p3.isCancel(selectedTools)) {
818
- p3.cancel("\uCDE8\uC18C\uB428");
958
+ if (p4.isCancel(selectedTools)) {
959
+ p4.cancel("\uCDE8\uC18C\uB428");
819
960
  process.exit(0);
820
961
  }
821
- const isMonorepo = await p3.confirm({
962
+ const isMonorepo = await p4.confirm({
822
963
  message: "\uBAA8\uB178\uB808\uD3EC \uD504\uB85C\uC81D\uD2B8\uC785\uB2C8\uAE4C?",
823
964
  initialValue: false
824
965
  });
825
- if (p3.isCancel(isMonorepo)) {
826
- p3.cancel("\uCDE8\uC18C\uB428");
966
+ if (p4.isCancel(isMonorepo)) {
967
+ p4.cancel("\uCDE8\uC18C\uB428");
827
968
  process.exit(0);
828
969
  }
829
970
  const allRules = loadAllRules(rulesDir);
@@ -833,25 +974,25 @@ var initCommand = async () => {
833
974
  if (!isMonorepo) {
834
975
  const mapping = await selectPresetAndFineTune(".", presets, allRules);
835
976
  if (!mapping) {
836
- p3.cancel("\uCDE8\uC18C\uB428");
977
+ p4.cancel("\uCDE8\uC18C\uB428");
837
978
  process.exit(0);
838
979
  }
839
980
  mappings.push(mapping);
840
981
  } else {
841
982
  const candidates = listWorkspaceCandidates(basePath);
842
- const selectedWorkspaces = await p3.multiselect({
983
+ const selectedWorkspaces = await p4.multiselect({
843
984
  message: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uC120\uD0DD\uD558\uC138\uC694",
844
985
  options: candidates.map((c) => ({ value: c, label: c })),
845
986
  required: true
846
987
  });
847
- if (p3.isCancel(selectedWorkspaces)) {
848
- p3.cancel("\uCDE8\uC18C\uB428");
988
+ if (p4.isCancel(selectedWorkspaces)) {
989
+ p4.cancel("\uCDE8\uC18C\uB428");
849
990
  process.exit(0);
850
991
  }
851
992
  for (const ws of selectedWorkspaces) {
852
993
  const mapping = await selectPresetAndFineTune(ws, presets, allRules);
853
994
  if (!mapping) {
854
- p3.cancel("\uCDE8\uC18C\uB428");
995
+ p4.cancel("\uCDE8\uC18C\uB428");
855
996
  process.exit(0);
856
997
  }
857
998
  mappings.push(mapping);
@@ -859,7 +1000,8 @@ var initCommand = async () => {
859
1000
  }
860
1001
  const geminiSettingValues = selectedTools.includes("gemini") ? await promptGeminiSettings() : null;
861
1002
  const claudeSettingValues = selectedTools.includes("claude-code") ? await promptClaudeSettings() : null;
862
- const s = p3.spinner();
1003
+ const wantPrettierIgnore = await promptPrettierIgnore();
1004
+ const s = p4.spinner();
863
1005
  s.start("\uADDC\uCE59 \uC124\uCE58 \uC911...");
864
1006
  const meta = { sourceHash, generatedAt: (/* @__PURE__ */ new Date()).toISOString() };
865
1007
  const allInstalledFiles = [];
@@ -885,11 +1027,12 @@ var initCommand = async () => {
885
1027
  }
886
1028
  if (geminiSettingValues && geminiSettingValues.length > 0) {
887
1029
  installGeminiSettings(basePath, geminiSettingValues);
888
- allInstalledFiles.push(".gemini/settings.json");
889
1030
  }
890
1031
  if (claudeSettingValues && claudeSettingValues.length > 0) {
891
1032
  installClaudeSettings(basePath, claudeSettingValues);
892
- allInstalledFiles.push(".claude/settings.local.json");
1033
+ }
1034
+ if (wantPrettierIgnore) {
1035
+ installPrettierIgnore(basePath);
893
1036
  }
894
1037
  s.stop("\uADDC\uCE59 \uC124\uCE58 \uC644\uB8CC");
895
1038
  const allInstalledRuleIds = deduplicateRules(mappings.flatMap((m) => m.finalRules)).map((r) => r.id);
@@ -904,32 +1047,33 @@ var initCommand = async () => {
904
1047
  installedRules: allInstalledRuleIds,
905
1048
  installedFiles: allInstalledFiles,
906
1049
  appendedFiles: allAppended,
907
- settings: claudeSettingValues || geminiSettingValues ? {
1050
+ settings: claudeSettingValues || geminiSettingValues || wantPrettierIgnore ? {
908
1051
  claude: claudeSettingValues ? [...claudeSettingValues] : void 0,
909
- gemini: geminiSettingValues ? [...geminiSettingValues] : void 0
1052
+ gemini: geminiSettingValues ? [...geminiSettingValues] : void 0,
1053
+ prettierignore: wantPrettierIgnore || void 0
910
1054
  } : void 0,
911
1055
  cliVersion: getCliVersion(),
912
1056
  sourceHash
913
1057
  });
914
1058
  writeManifest(resolveManifestPath(basePath), manifest);
915
1059
  if (allAppended.length > 0) {
916
- p3.log.info(`\uAE30\uC874 \uD30C\uC77C\uC5D0 \uC139\uC158 \uCD94\uAC00\uB428 (\uB0B4\uC6A9 \uBCF4\uC874):
1060
+ p4.log.info(`\uAE30\uC874 \uD30C\uC77C\uC5D0 \uC139\uC158 \uCD94\uAC00\uB428 (\uB0B4\uC6A9 \uBCF4\uC874):
917
1061
  ${allAppended.map((f) => ` ${f}`).join("\n")}`);
918
1062
  }
919
- p3.log.success(`\uC124\uCE58\uB41C \uADDC\uCE59: ${allInstalledRuleIds.length}\uAC1C`);
920
- p3.outro("ai-ops init \uC644\uB8CC");
1063
+ p4.log.success(`\uC124\uCE58\uB41C \uADDC\uCE59: ${allInstalledRuleIds.length}\uAC1C`);
1064
+ p4.outro("ai-ops init \uC644\uB8CC");
921
1065
  };
922
1066
 
923
1067
  // src/commands/update.ts
924
- import * as p4 from "@clack/prompts";
925
- import { join as join10 } from "path";
1068
+ import * as p5 from "@clack/prompts";
1069
+ import { join as join11 } from "path";
926
1070
  var updateCommand = async (opts) => {
927
1071
  const basePath = resolveBasePath();
928
1072
  const manifestPath = resolveManifestPath(basePath);
929
- p4.intro("ai-ops update");
1073
+ p5.intro("ai-ops update");
930
1074
  const manifest = readManifest(manifestPath);
931
1075
  if (!manifest) {
932
- p4.log.error("manifest\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 ai-ops init\uC744 \uC2E4\uD589\uD558\uC138\uC694.");
1076
+ p5.log.error("manifest\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 ai-ops init\uC744 \uC2E4\uD589\uD558\uC138\uC694.");
933
1077
  process.exit(1);
934
1078
  }
935
1079
  const rulesDir = resolveRulesDir();
@@ -942,11 +1086,11 @@ var updateCommand = async (opts) => {
942
1086
  currentCliVersion: cliVersion
943
1087
  });
944
1088
  if (diffResult.status === "up-to-date" && !opts.force) {
945
- p4.log.info("\uBCC0\uACBD \uC0AC\uD56D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
946
- p4.outro("ai-ops update \uC644\uB8CC");
1089
+ p5.log.info("\uBCC0\uACBD \uC0AC\uD56D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
1090
+ p5.outro("ai-ops update \uC644\uB8CC");
947
1091
  return;
948
1092
  }
949
- const s = p4.spinner();
1093
+ const s = p5.spinner();
950
1094
  s.start("\uADDC\uCE59 \uAC31\uC2E0 \uC911...");
951
1095
  const allRules = loadAllRules(rulesDir);
952
1096
  const meta = { sourceHash, generatedAt: (/* @__PURE__ */ new Date()).toISOString() };
@@ -975,8 +1119,8 @@ var updateCommand = async (opts) => {
975
1119
  const { global } = partitionRules(allRulesToInstall);
976
1120
  if (global.length > 0) {
977
1121
  const rootAction = {
978
- relativePath: join10(config.dir, config.rootFileName),
979
- content: wrapWithHeader(renderRulesToMarkdown(global), meta)
1122
+ relativePath: join11(config.dir, config.rootFileName),
1123
+ content: wrapWithSection(renderRulesToMarkdown(global), meta)
980
1124
  };
981
1125
  const r = installFiles(basePath, [rootAction], meta);
982
1126
  allInstalledFiles.push(...r.written);
@@ -988,8 +1132,8 @@ var updateCommand = async (opts) => {
988
1132
  const { domain } = partitionRules(wsRules);
989
1133
  if (domain.length === 0) continue;
990
1134
  const domainAction = {
991
- relativePath: join10(ws, config.domainFileName),
992
- content: wrapWithHeader(renderRulesToMarkdown(domain), meta)
1135
+ relativePath: join11(ws, config.domainFileName),
1136
+ content: wrapWithSection(renderRulesToMarkdown(domain), meta)
993
1137
  };
994
1138
  const r = installFiles(basePath, [domainAction], meta);
995
1139
  allInstalledFiles.push(...r.written);
@@ -1015,6 +1159,9 @@ var updateCommand = async (opts) => {
1015
1159
  if (manifest.settings?.gemini) {
1016
1160
  installGeminiSettings(basePath, manifest.settings.gemini);
1017
1161
  }
1162
+ if (manifest.settings?.prettierignore) {
1163
+ installPrettierIgnore(basePath);
1164
+ }
1018
1165
  const newManifest = buildManifest({
1019
1166
  tools: manifest.tools,
1020
1167
  scope: manifest.scope,
@@ -1025,24 +1172,25 @@ var updateCommand = async (opts) => {
1025
1172
  appendedFiles: allAppended.length > 0 ? allAppended : manifest.appended_files,
1026
1173
  settings: manifest.settings ? {
1027
1174
  claude: manifest.settings.claude,
1028
- gemini: manifest.settings.gemini
1175
+ gemini: manifest.settings.gemini,
1176
+ prettierignore: manifest.settings.prettierignore
1029
1177
  } : void 0,
1030
1178
  cliVersion,
1031
1179
  sourceHash
1032
1180
  });
1033
1181
  writeManifest(manifestPath, newManifest);
1034
1182
  s.stop("\uADDC\uCE59 \uAC31\uC2E0 \uC644\uB8CC");
1035
- p4.outro("ai-ops update \uC644\uB8CC");
1183
+ p5.outro("ai-ops update \uC644\uB8CC");
1036
1184
  };
1037
1185
 
1038
1186
  // src/commands/diff.ts
1039
- import * as p5 from "@clack/prompts";
1187
+ import * as p6 from "@clack/prompts";
1040
1188
  var diffCommand = async () => {
1041
1189
  const basePath = resolveBasePath();
1042
- p5.intro("ai-ops diff");
1190
+ p6.intro("ai-ops diff");
1043
1191
  const manifest = readManifest(resolveManifestPath(basePath));
1044
1192
  if (!manifest) {
1045
- p5.log.error("manifest\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 ai-ops init\uC744 \uC2E4\uD589\uD558\uC138\uC694.");
1193
+ p6.log.error("manifest\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 ai-ops init\uC744 \uC2E4\uD589\uD558\uC138\uC694.");
1046
1194
  process.exit(1);
1047
1195
  }
1048
1196
  const sourceHash = computeSourceHash(resolveRulesDir());
@@ -1052,27 +1200,27 @@ var diffCommand = async () => {
1052
1200
  currentSourceHash: sourceHash
1053
1201
  });
1054
1202
  if (result.status === "up-to-date") {
1055
- p5.log.success("\uBCC0\uACBD \uC0AC\uD56D \uC5C6\uC74C. \uCD5C\uC2E0 \uC0C1\uD0DC\uC785\uB2C8\uB2E4.");
1203
+ p6.log.success("\uBCC0\uACBD \uC0AC\uD56D \uC5C6\uC74C. \uCD5C\uC2E0 \uC0C1\uD0DC\uC785\uB2C8\uB2E4.");
1056
1204
  } else {
1057
1205
  if (result.sourceChanged) {
1058
- p5.log.warn(`\uC18C\uC2A4 \uBCC0\uACBD \uAC10\uC9C0: ${manifest.sourceHash} \u2192 ${sourceHash}`);
1206
+ p6.log.warn(`\uC18C\uC2A4 \uBCC0\uACBD \uAC10\uC9C0: ${manifest.sourceHash} \u2192 ${sourceHash}`);
1059
1207
  }
1060
1208
  if (result.added.length > 0) {
1061
- p5.log.info(`\uCD94\uAC00\uB41C \uADDC\uCE59: ${result.added.join(", ")}`);
1209
+ p6.log.info(`\uCD94\uAC00\uB41C \uADDC\uCE59: ${result.added.join(", ")}`);
1062
1210
  }
1063
1211
  if (result.removed.length > 0) {
1064
- p5.log.info(`\uC81C\uAC70\uB41C \uADDC\uCE59: ${result.removed.join(", ")}`);
1212
+ p6.log.info(`\uC81C\uAC70\uB41C \uADDC\uCE59: ${result.removed.join(", ")}`);
1065
1213
  }
1066
1214
  }
1067
- p5.outro("ai-ops diff \uC644\uB8CC");
1215
+ p6.outro("ai-ops diff \uC644\uB8CC");
1068
1216
  };
1069
1217
 
1070
1218
  // src/commands/uninstall.ts
1071
- import * as p6 from "@clack/prompts";
1072
- import { rmSync as rmSync2 } from "fs";
1219
+ import * as p7 from "@clack/prompts";
1220
+ import { rmSync as rmSync5 } from "fs";
1073
1221
 
1074
1222
  // src/lib/uninstall.ts
1075
- import { existsSync as existsSync5, readFileSync as readFileSync7, rmSync, readdirSync as readdirSync4, writeFileSync as writeFileSync5 } from "fs";
1223
+ import { existsSync as existsSync6, readFileSync as readFileSync8, rmSync as rmSync4, readdirSync as readdirSync4, writeFileSync as writeFileSync6 } from "fs";
1076
1224
  import { resolve as resolve6, dirname as dirname5 } from "path";
1077
1225
  var removeFiles = (basePath, relativePaths) => {
1078
1226
  const deleted = [];
@@ -1081,23 +1229,26 @@ var removeFiles = (basePath, relativePaths) => {
1081
1229
  const notFound = [];
1082
1230
  for (const rel of relativePaths) {
1083
1231
  const absPath = resolve6(basePath, rel);
1084
- if (!existsSync5(absPath)) {
1232
+ if (!existsSync6(absPath)) {
1085
1233
  notFound.push(rel);
1086
1234
  continue;
1087
1235
  }
1088
- const content = readFileSync7(absPath, "utf-8");
1089
- if (!isManagedFile(content)) {
1090
- if (hasAiOpsSection(content)) {
1091
- const stripped = stripAiOpsSection(content);
1092
- writeFileSync5(absPath, stripped, "utf-8");
1093
- cleaned.push(rel);
1236
+ const content = readFileSync8(absPath, "utf-8");
1237
+ if (hasAiOpsSection(content)) {
1238
+ const stripped = stripAiOpsSection(content);
1239
+ if (stripped.trim().length === 0) {
1240
+ rmSync4(absPath);
1241
+ deleted.push(rel);
1094
1242
  } else {
1095
- skipped.push(rel);
1243
+ writeFileSync6(absPath, stripped, "utf-8");
1244
+ cleaned.push(rel);
1096
1245
  }
1097
- continue;
1246
+ } else if (hasLegacyHeader(content)) {
1247
+ rmSync4(absPath);
1248
+ deleted.push(rel);
1249
+ } else {
1250
+ skipped.push(rel);
1098
1251
  }
1099
- rmSync(absPath);
1100
- deleted.push(rel);
1101
1252
  }
1102
1253
  return { deleted, cleaned, skipped, notFound };
1103
1254
  };
@@ -1105,11 +1256,11 @@ var cleanEmptyDirs = (basePath, dirs) => {
1105
1256
  const removed = [];
1106
1257
  for (const dir of dirs) {
1107
1258
  const absDir = resolve6(basePath, dir);
1108
- if (!existsSync5(absDir)) continue;
1259
+ if (!existsSync6(absDir)) continue;
1109
1260
  try {
1110
1261
  const entries = readdirSync4(absDir);
1111
1262
  if (entries.length === 0) {
1112
- rmSync(absDir, { recursive: true });
1263
+ rmSync4(absDir, { recursive: true });
1113
1264
  removed.push(dir);
1114
1265
  }
1115
1266
  } catch {
@@ -1129,64 +1280,85 @@ var collectManagedDirs = (relativePaths) => {
1129
1280
  };
1130
1281
 
1131
1282
  // src/commands/uninstall.ts
1283
+ var SETTINGS_PATHS = /* @__PURE__ */ new Set([".claude/settings.local.json", ".gemini/settings.json"]);
1132
1284
  var uninstallCommand = async () => {
1133
1285
  const basePath = resolveBasePath();
1134
1286
  const manifestPath = resolveManifestPath(basePath);
1135
- p6.intro("ai-ops uninstall");
1287
+ p7.intro("ai-ops uninstall");
1136
1288
  const manifest = readManifest(manifestPath);
1137
1289
  if (!manifest) {
1138
- p6.log.error("manifest\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 ai-ops init\uC744 \uC2E4\uD589\uD558\uC138\uC694.");
1290
+ p7.log.error("manifest\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 ai-ops init\uC744 \uC2E4\uD589\uD558\uC138\uC694.");
1139
1291
  process.exit(1);
1140
1292
  }
1141
1293
  const targetFiles = [
1142
1294
  ...manifest.installed_files ?? inferInstalledFiles(manifest),
1143
1295
  ...manifest.appended_files ?? []
1144
- ];
1296
+ ].filter((f) => !SETTINGS_PATHS.has(f));
1145
1297
  if (targetFiles.length === 0) {
1146
- p6.log.warn("\uC0AD\uC81C\uD560 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
1147
- p6.outro("ai-ops uninstall \uC644\uB8CC");
1298
+ p7.log.warn("\uC0AD\uC81C\uD560 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
1299
+ p7.outro("ai-ops uninstall \uC644\uB8CC");
1148
1300
  return;
1149
1301
  }
1150
- p6.log.info(`\uC0AD\uC81C \uB300\uC0C1 \uD30C\uC77C (${targetFiles.length}\uAC1C):
1302
+ p7.log.info(`\uC0AD\uC81C \uB300\uC0C1 \uD30C\uC77C (${targetFiles.length}\uAC1C):
1151
1303
  ${targetFiles.map((f) => ` ${f}`).join("\n")}`);
1152
- const confirmed = await p6.confirm({
1304
+ const confirmed = await p7.confirm({
1153
1305
  message: "\uC704 \uD30C\uC77C\uACFC manifest\uB97C \uBAA8\uB450 \uC0AD\uC81C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
1154
1306
  initialValue: false
1155
1307
  });
1156
- if (p6.isCancel(confirmed) || !confirmed) {
1157
- p6.cancel("\uCDE8\uC18C\uB428");
1308
+ if (p7.isCancel(confirmed) || !confirmed) {
1309
+ p7.cancel("\uCDE8\uC18C\uB428");
1158
1310
  process.exit(0);
1159
1311
  }
1312
+ const settingsMessages = [];
1313
+ if (manifest.settings?.claude) {
1314
+ const status = uninstallClaudeSettings(basePath, manifest.settings.claude);
1315
+ if (status === "deleted") settingsMessages.push("\uC0AD\uC81C: .claude/settings.local.json");
1316
+ else if (status === "cleaned") settingsMessages.push("ai-ops \uD0A4 \uC81C\uAC70 (\uC0AC\uC6A9\uC790 \uC124\uC815 \uBCF4\uC874): .claude/settings.local.json");
1317
+ }
1318
+ if (manifest.settings?.gemini) {
1319
+ const status = uninstallGeminiSettings(basePath, manifest.settings.gemini);
1320
+ if (status === "deleted") settingsMessages.push("\uC0AD\uC81C: .gemini/settings.json");
1321
+ else if (status === "cleaned") settingsMessages.push("ai-ops \uD0A4 \uC81C\uAC70 (\uC0AC\uC6A9\uC790 \uC124\uC815 \uBCF4\uC874): .gemini/settings.json");
1322
+ }
1323
+ if (manifest.settings?.prettierignore) {
1324
+ const status = uninstallPrettierIgnore(basePath);
1325
+ if (status === "deleted") settingsMessages.push("\uC0AD\uC81C: .prettierignore");
1326
+ else if (status === "cleaned") settingsMessages.push("ai-ops \uC139\uC158 \uC81C\uAC70 (\uC0AC\uC6A9\uC790 \uB0B4\uC6A9 \uBCF4\uC874): .prettierignore");
1327
+ }
1160
1328
  const result = removeFiles(basePath, targetFiles);
1161
1329
  const dirs = collectManagedDirs(targetFiles);
1162
1330
  const removedDirs = cleanEmptyDirs(basePath, dirs);
1163
- rmSync2(manifestPath, { force: true });
1331
+ rmSync5(manifestPath, { force: true });
1164
1332
  if (result.deleted.length > 0) {
1165
- p6.log.success(`\uC0AD\uC81C \uC644\uB8CC (${result.deleted.length}\uAC1C):
1333
+ p7.log.success(`\uC0AD\uC81C \uC644\uB8CC (${result.deleted.length}\uAC1C):
1166
1334
  ${result.deleted.map((f) => ` ${f}`).join("\n")}`);
1167
1335
  }
1168
1336
  if (result.cleaned.length > 0) {
1169
- p6.log.success(
1337
+ p7.log.success(
1170
1338
  `\uC139\uC158 \uC81C\uAC70 \uC644\uB8CC (\uC0AC\uC6A9\uC790 \uB0B4\uC6A9 \uBCF4\uC874, ${result.cleaned.length}\uAC1C):
1171
1339
  ${result.cleaned.map((f) => ` ${f}`).join("\n")}`
1172
1340
  );
1173
1341
  }
1174
1342
  if (result.skipped.length > 0) {
1175
- p6.log.warn(
1343
+ p7.log.warn(
1176
1344
  `\uAC74\uB108\uB700 (non-managed \uD30C\uC77C \uBCF4\uD638, ${result.skipped.length}\uAC1C):
1177
1345
  ${result.skipped.map((f) => ` ${f}`).join("\n")}`
1178
1346
  );
1179
1347
  }
1180
1348
  if (result.notFound.length > 0) {
1181
- p6.log.info(`\uC774\uBBF8 \uC5C6\uC74C (${result.notFound.length}\uAC1C):
1349
+ p7.log.info(`\uC774\uBBF8 \uC5C6\uC74C (${result.notFound.length}\uAC1C):
1182
1350
  ${result.notFound.map((f) => ` ${f}`).join("\n")}`);
1183
1351
  }
1184
1352
  if (removedDirs.length > 0) {
1185
- p6.log.info(`\uBE48 \uB514\uB809\uD1A0\uB9AC \uC815\uB9AC (${removedDirs.length}\uAC1C):
1353
+ p7.log.info(`\uBE48 \uB514\uB809\uD1A0\uB9AC \uC815\uB9AC (${removedDirs.length}\uAC1C):
1186
1354
  ${removedDirs.map((d) => ` ${d}`).join("\n")}`);
1187
1355
  }
1188
- p6.log.success(`manifest \uC0AD\uC81C: ${MANIFEST_FILENAME}`);
1189
- p6.outro("ai-ops uninstall \uC644\uB8CC");
1356
+ if (settingsMessages.length > 0) {
1357
+ p7.log.success(`\uC124\uC815 \uD30C\uC77C \uCC98\uB9AC:
1358
+ ${settingsMessages.map((m) => ` ${m}`).join("\n")}`);
1359
+ }
1360
+ p7.log.success(`manifest \uC0AD\uC81C: ${MANIFEST_FILENAME}`);
1361
+ p7.outro("ai-ops uninstall \uC644\uB8CC");
1190
1362
  };
1191
1363
 
1192
1364
  // src/bin/index.ts