ai-ops-cli 0.1.15 → 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),
@@ -239,7 +240,7 @@ var partitionRules = (rules) => {
239
240
  return { global, domain };
240
241
  };
241
242
  var renderFrontmatter = (paths) => {
242
- const lines = paths.map((p7) => ` - "${p7}"`).join("\n");
243
+ const lines = paths.map((p8) => ` - "${p8}"`).join("\n");
243
244
  return `---
244
245
  paths:
245
246
  ${lines}
@@ -333,20 +334,7 @@ var buildManifest = (params) => ManifestSchema.parse({
333
334
  var MANAGED_MARKER = "<!-- managed by ai-ops -->";
334
335
  var SECTION_START = "<!-- ai-ops:start -->";
335
336
  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
- };
337
+ var hasLegacyHeader = (content) => content.includes(MANAGED_MARKER);
350
338
  var wrapWithSection = (content, meta) => {
351
339
  const metaLine = `<!-- sourceHash: ${meta.sourceHash} | generatedAt: ${meta.generatedAt} -->`;
352
340
  return `${SECTION_START}
@@ -370,7 +358,7 @@ var replaceAiOpsSection = (existing, newSection) => {
370
358
  if (startIdx === -1 || endIdx === -1) return existing;
371
359
  const before = existing.slice(0, startIdx).trimEnd();
372
360
  const after = existing.slice(endIdx + SECTION_END.length).trimStart();
373
- return before + "\n\n" + newSection + (after ? "\n\n" + after : "") + "\n";
361
+ return [before, newSection, after].filter(Boolean).join("\n\n") + "\n";
374
362
  };
375
363
 
376
364
  // src/core/manifest-io.ts
@@ -415,7 +403,7 @@ var buildInstallPlan = (params) => {
415
403
  if (toolId === "claude-code" && renderResult.tool === "claude-code") {
416
404
  return renderResult.files.map(({ relativePath, content }) => ({
417
405
  relativePath,
418
- content: wrapWithHeader(content, meta)
406
+ content: wrapWithSection(content, meta)
419
407
  }));
420
408
  }
421
409
  if (toolId === "codex" && renderResult.tool === "codex" || toolId === "gemini" && renderResult.tool === "gemini") {
@@ -425,13 +413,13 @@ var buildInstallPlan = (params) => {
425
413
  const rootContent = toolId === "codex" ? renderResult.rootContent + CODEX_PLAN_SECTION : renderResult.rootContent;
426
414
  actions.push({
427
415
  relativePath: join3(config.dir, config.rootFileName),
428
- content: wrapWithHeader(rootContent, meta)
416
+ content: wrapWithSection(rootContent, meta)
429
417
  });
430
418
  }
431
419
  if (renderResult.domainContent) {
432
420
  actions.push({
433
421
  relativePath: join3(config.dir, config.domainFileName),
434
- content: wrapWithHeader(renderResult.domainContent, meta)
422
+ content: wrapWithSection(renderResult.domainContent, meta)
435
423
  });
436
424
  }
437
425
  return actions;
@@ -536,32 +524,31 @@ var listWorkspaceCandidates = (basePath) => {
536
524
  // src/lib/install.ts
537
525
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
538
526
  import { dirname as dirname4, resolve as resolve5 } from "path";
539
- var installFiles = (basePath, actions, meta) => {
527
+ var installFiles = (basePath, actions, _meta) => {
540
528
  const written = [];
541
529
  const appended = [];
542
530
  const skipped = [];
543
531
  for (const action of actions) {
544
532
  const absPath = resolve5(basePath, action.relativePath);
545
- 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 {
546
538
  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);
539
+ if (hasAiOpsSection(existing)) {
540
+ const updated = replaceAiOpsSection(existing, action.content);
553
541
  writeFileSync2(absPath, updated, "utf-8");
554
- 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);
555
547
  } else {
556
- const sectionContent = wrapWithSection(stripManagedHeader(action.content), meta);
557
- const updated = existing.trimEnd() + "\n\n" + sectionContent + "\n";
548
+ const updated = existing.trimEnd() + "\n\n" + action.content + "\n";
558
549
  writeFileSync2(absPath, updated, "utf-8");
559
550
  appended.push(action.relativePath);
560
551
  }
561
- } else {
562
- mkdirSync2(dirname4(absPath), { recursive: true });
563
- writeFileSync2(absPath, action.content, "utf-8");
564
- written.push(action.relativePath);
565
552
  }
566
553
  }
567
554
  return { written, appended, skipped };
@@ -569,8 +556,40 @@ var installFiles = (basePath, actions, meta) => {
569
556
 
570
557
  // src/lib/gemini-settings.ts
571
558
  import * as p from "@clack/prompts";
572
- 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";
573
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
574
593
  var SETTING_GROUPS = [
575
594
  {
576
595
  value: "ui",
@@ -597,17 +616,6 @@ var SETTING_GROUPS = [
597
616
  patch: { experimental: { jitContext: true, plan: true } }
598
617
  }
599
618
  ];
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
619
  var promptGeminiSettings = async () => {
612
620
  const wantSettings = await p.confirm({
613
621
  message: "Gemini CLI \uC124\uC815 \uD30C\uC77C(.gemini/settings.json)\uC744 \uC124\uCE58\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
@@ -647,10 +655,33 @@ var installGeminiSettings = (basePath, selectedValues) => {
647
655
  mkdirSync3(settingsDir, { recursive: true });
648
656
  writeFileSync3(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
649
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
+ };
650
681
 
651
682
  // src/lib/claude-settings.ts
652
683
  import * as p2 from "@clack/prompts";
653
- 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";
654
685
  import { join as join8 } from "path";
655
686
  var SETTING_GROUPS2 = [
656
687
  {
@@ -666,17 +697,6 @@ var SETTING_GROUPS2 = [
666
697
  patch: { plansDirectory: "./.claude/plans" }
667
698
  }
668
699
  ];
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
700
  var promptClaudeSettings = async () => {
681
701
  const wantSettings = await p2.confirm({
682
702
  message: "Claude Code \uC124\uC815 \uD30C\uC77C(.claude/settings.local.json)\uC744 \uC124\uCE58\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
@@ -711,11 +731,131 @@ var installClaudeSettings = (basePath, selectedValues) => {
711
731
  for (const val of selectedValues) {
712
732
  const group = SETTING_GROUPS2.find((g) => g.value === val);
713
733
  if (!group) continue;
714
- merged = deepMerge2(merged, group.patch);
734
+ merged = deepMerge(merged, group.patch);
715
735
  }
716
736
  mkdirSync4(settingsDir, { recursive: true });
717
737
  writeFileSync4(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
718
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
+ };
719
859
 
720
860
  // src/commands/init.ts
721
861
  var TOOL_OPTIONS = [
@@ -732,7 +872,7 @@ var deduplicateRules = (rules) => {
732
872
  });
733
873
  };
734
874
  var selectPresetAndFineTune = async (workspaceName, presets, allRules) => {
735
- const preset = await p3.select({
875
+ const preset = await p4.select({
736
876
  message: `[${workspaceName}] \uD504\uB9AC\uC14B\uC744 \uC120\uD0DD\uD558\uC138\uC694`,
737
877
  options: presets.map((pr) => ({
738
878
  value: pr,
@@ -740,25 +880,25 @@ var selectPresetAndFineTune = async (workspaceName, presets, allRules) => {
740
880
  hint: pr.description
741
881
  }))
742
882
  });
743
- if (p3.isCancel(preset)) return null;
883
+ if (p4.isCancel(preset)) return null;
744
884
  const presetRuleGroups = resolvePresetRuleGroups(preset, allRules);
745
885
  const globalGroups = presetRuleGroups.filter((group) => group.rules.every(isGlobalRule));
746
886
  const domainGroups = presetRuleGroups.filter((group) => !group.rules.every(isGlobalRule));
747
887
  const globalGroupIds = globalGroups.map((group) => group.id);
748
888
  const globalRules = globalGroupIds.length > 0 ? resolvePresetRules({ ...preset, rules: globalGroupIds }, allRules) : [];
749
889
  if (globalRules.length > 0) {
750
- 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)`);
751
891
  }
752
892
  if (domainGroups.length === 0) {
753
893
  return { workspace: workspaceName, preset, finalRules: resolvePresetRules(preset, allRules) };
754
894
  }
755
- const selectedDomain = await p3.multiselect({
895
+ const selectedDomain = await p4.multiselect({
756
896
  message: `[${workspaceName}] \uB3C4\uBA54\uC778 \uADDC\uCE59 \uC120\uD0DD (\uD574\uC81C\uD558\uC5EC \uC81C\uC678)`,
757
897
  options: domainGroups.map((group) => ({ value: group.id, label: group.id })),
758
898
  initialValues: domainGroups.map((group) => group.id),
759
899
  required: false
760
900
  });
761
- if (p3.isCancel(selectedDomain)) return null;
901
+ if (p4.isCancel(selectedDomain)) return null;
762
902
  const selectedLogicalRuleIds = [...globalGroupIds, ...selectedDomain];
763
903
  return {
764
904
  workspace: workspaceName,
@@ -774,8 +914,8 @@ var installHierarchicalMonorepo = (toolId, mappings, basePath, meta) => {
774
914
  const { global } = partitionRules(allRules);
775
915
  if (global.length > 0) {
776
916
  const rootAction = {
777
- relativePath: join9(config.dir, config.rootFileName),
778
- content: wrapWithHeader(renderRulesToMarkdown(global), meta)
917
+ relativePath: join10(config.dir, config.rootFileName),
918
+ content: wrapWithSection(renderRulesToMarkdown(global), meta)
779
919
  };
780
920
  const r = installFiles(basePath, [rootAction], meta);
781
921
  written.push(...r.written);
@@ -785,8 +925,8 @@ var installHierarchicalMonorepo = (toolId, mappings, basePath, meta) => {
785
925
  const { domain } = partitionRules(mapping.finalRules);
786
926
  if (domain.length === 0) continue;
787
927
  const domainAction = {
788
- relativePath: join9(mapping.workspace, config.domainFileName),
789
- content: wrapWithHeader(renderRulesToMarkdown(domain), meta)
928
+ relativePath: join10(mapping.workspace, config.domainFileName),
929
+ content: wrapWithSection(renderRulesToMarkdown(domain), meta)
790
930
  };
791
931
  const r = installFiles(basePath, [domainAction], meta);
792
932
  written.push(...r.written);
@@ -808,22 +948,22 @@ var installClaudeCodeMonorepo = (mappings, basePath, meta) => {
808
948
  var initCommand = async () => {
809
949
  const basePath = resolveBasePath();
810
950
  const rulesDir = resolveRulesDir();
811
- p3.intro("ai-ops init");
812
- const selectedTools = await p3.multiselect({
951
+ p4.intro("ai-ops init");
952
+ const selectedTools = await p4.multiselect({
813
953
  message: "AI \uB3C4\uAD6C\uB97C \uC120\uD0DD\uD558\uC138\uC694",
814
954
  options: TOOL_OPTIONS,
815
955
  required: true
816
956
  });
817
- if (p3.isCancel(selectedTools)) {
818
- p3.cancel("\uCDE8\uC18C\uB428");
957
+ if (p4.isCancel(selectedTools)) {
958
+ p4.cancel("\uCDE8\uC18C\uB428");
819
959
  process.exit(0);
820
960
  }
821
- const isMonorepo = await p3.confirm({
961
+ const isMonorepo = await p4.confirm({
822
962
  message: "\uBAA8\uB178\uB808\uD3EC \uD504\uB85C\uC81D\uD2B8\uC785\uB2C8\uAE4C?",
823
963
  initialValue: false
824
964
  });
825
- if (p3.isCancel(isMonorepo)) {
826
- p3.cancel("\uCDE8\uC18C\uB428");
965
+ if (p4.isCancel(isMonorepo)) {
966
+ p4.cancel("\uCDE8\uC18C\uB428");
827
967
  process.exit(0);
828
968
  }
829
969
  const allRules = loadAllRules(rulesDir);
@@ -833,25 +973,25 @@ var initCommand = async () => {
833
973
  if (!isMonorepo) {
834
974
  const mapping = await selectPresetAndFineTune(".", presets, allRules);
835
975
  if (!mapping) {
836
- p3.cancel("\uCDE8\uC18C\uB428");
976
+ p4.cancel("\uCDE8\uC18C\uB428");
837
977
  process.exit(0);
838
978
  }
839
979
  mappings.push(mapping);
840
980
  } else {
841
981
  const candidates = listWorkspaceCandidates(basePath);
842
- const selectedWorkspaces = await p3.multiselect({
982
+ const selectedWorkspaces = await p4.multiselect({
843
983
  message: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uC120\uD0DD\uD558\uC138\uC694",
844
984
  options: candidates.map((c) => ({ value: c, label: c })),
845
985
  required: true
846
986
  });
847
- if (p3.isCancel(selectedWorkspaces)) {
848
- p3.cancel("\uCDE8\uC18C\uB428");
987
+ if (p4.isCancel(selectedWorkspaces)) {
988
+ p4.cancel("\uCDE8\uC18C\uB428");
849
989
  process.exit(0);
850
990
  }
851
991
  for (const ws of selectedWorkspaces) {
852
992
  const mapping = await selectPresetAndFineTune(ws, presets, allRules);
853
993
  if (!mapping) {
854
- p3.cancel("\uCDE8\uC18C\uB428");
994
+ p4.cancel("\uCDE8\uC18C\uB428");
855
995
  process.exit(0);
856
996
  }
857
997
  mappings.push(mapping);
@@ -859,7 +999,8 @@ var initCommand = async () => {
859
999
  }
860
1000
  const geminiSettingValues = selectedTools.includes("gemini") ? await promptGeminiSettings() : null;
861
1001
  const claudeSettingValues = selectedTools.includes("claude-code") ? await promptClaudeSettings() : null;
862
- const s = p3.spinner();
1002
+ const wantPrettierIgnore = await promptPrettierIgnore();
1003
+ const s = p4.spinner();
863
1004
  s.start("\uADDC\uCE59 \uC124\uCE58 \uC911...");
864
1005
  const meta = { sourceHash, generatedAt: (/* @__PURE__ */ new Date()).toISOString() };
865
1006
  const allInstalledFiles = [];
@@ -885,11 +1026,12 @@ var initCommand = async () => {
885
1026
  }
886
1027
  if (geminiSettingValues && geminiSettingValues.length > 0) {
887
1028
  installGeminiSettings(basePath, geminiSettingValues);
888
- allInstalledFiles.push(".gemini/settings.json");
889
1029
  }
890
1030
  if (claudeSettingValues && claudeSettingValues.length > 0) {
891
1031
  installClaudeSettings(basePath, claudeSettingValues);
892
- allInstalledFiles.push(".claude/settings.local.json");
1032
+ }
1033
+ if (wantPrettierIgnore) {
1034
+ installPrettierIgnore(basePath);
893
1035
  }
894
1036
  s.stop("\uADDC\uCE59 \uC124\uCE58 \uC644\uB8CC");
895
1037
  const allInstalledRuleIds = deduplicateRules(mappings.flatMap((m) => m.finalRules)).map((r) => r.id);
@@ -904,32 +1046,33 @@ var initCommand = async () => {
904
1046
  installedRules: allInstalledRuleIds,
905
1047
  installedFiles: allInstalledFiles,
906
1048
  appendedFiles: allAppended,
907
- settings: claudeSettingValues || geminiSettingValues ? {
1049
+ settings: claudeSettingValues || geminiSettingValues || wantPrettierIgnore ? {
908
1050
  claude: claudeSettingValues ? [...claudeSettingValues] : void 0,
909
- gemini: geminiSettingValues ? [...geminiSettingValues] : void 0
1051
+ gemini: geminiSettingValues ? [...geminiSettingValues] : void 0,
1052
+ prettierignore: wantPrettierIgnore || void 0
910
1053
  } : void 0,
911
1054
  cliVersion: getCliVersion(),
912
1055
  sourceHash
913
1056
  });
914
1057
  writeManifest(resolveManifestPath(basePath), manifest);
915
1058
  if (allAppended.length > 0) {
916
- 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):
917
1060
  ${allAppended.map((f) => ` ${f}`).join("\n")}`);
918
1061
  }
919
- p3.log.success(`\uC124\uCE58\uB41C \uADDC\uCE59: ${allInstalledRuleIds.length}\uAC1C`);
920
- 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");
921
1064
  };
922
1065
 
923
1066
  // src/commands/update.ts
924
- import * as p4 from "@clack/prompts";
925
- import { join as join10 } from "path";
1067
+ import * as p5 from "@clack/prompts";
1068
+ import { join as join11 } from "path";
926
1069
  var updateCommand = async (opts) => {
927
1070
  const basePath = resolveBasePath();
928
1071
  const manifestPath = resolveManifestPath(basePath);
929
- p4.intro("ai-ops update");
1072
+ p5.intro("ai-ops update");
930
1073
  const manifest = readManifest(manifestPath);
931
1074
  if (!manifest) {
932
- 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.");
933
1076
  process.exit(1);
934
1077
  }
935
1078
  const rulesDir = resolveRulesDir();
@@ -942,11 +1085,11 @@ var updateCommand = async (opts) => {
942
1085
  currentCliVersion: cliVersion
943
1086
  });
944
1087
  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");
1088
+ p5.log.info("\uBCC0\uACBD \uC0AC\uD56D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
1089
+ p5.outro("ai-ops update \uC644\uB8CC");
947
1090
  return;
948
1091
  }
949
- const s = p4.spinner();
1092
+ const s = p5.spinner();
950
1093
  s.start("\uADDC\uCE59 \uAC31\uC2E0 \uC911...");
951
1094
  const allRules = loadAllRules(rulesDir);
952
1095
  const meta = { sourceHash, generatedAt: (/* @__PURE__ */ new Date()).toISOString() };
@@ -975,8 +1118,8 @@ var updateCommand = async (opts) => {
975
1118
  const { global } = partitionRules(allRulesToInstall);
976
1119
  if (global.length > 0) {
977
1120
  const rootAction = {
978
- relativePath: join10(config.dir, config.rootFileName),
979
- content: wrapWithHeader(renderRulesToMarkdown(global), meta)
1121
+ relativePath: join11(config.dir, config.rootFileName),
1122
+ content: wrapWithSection(renderRulesToMarkdown(global), meta)
980
1123
  };
981
1124
  const r = installFiles(basePath, [rootAction], meta);
982
1125
  allInstalledFiles.push(...r.written);
@@ -988,8 +1131,8 @@ var updateCommand = async (opts) => {
988
1131
  const { domain } = partitionRules(wsRules);
989
1132
  if (domain.length === 0) continue;
990
1133
  const domainAction = {
991
- relativePath: join10(ws, config.domainFileName),
992
- content: wrapWithHeader(renderRulesToMarkdown(domain), meta)
1134
+ relativePath: join11(ws, config.domainFileName),
1135
+ content: wrapWithSection(renderRulesToMarkdown(domain), meta)
993
1136
  };
994
1137
  const r = installFiles(basePath, [domainAction], meta);
995
1138
  allInstalledFiles.push(...r.written);
@@ -1015,6 +1158,9 @@ var updateCommand = async (opts) => {
1015
1158
  if (manifest.settings?.gemini) {
1016
1159
  installGeminiSettings(basePath, manifest.settings.gemini);
1017
1160
  }
1161
+ if (manifest.settings?.prettierignore) {
1162
+ installPrettierIgnore(basePath);
1163
+ }
1018
1164
  const newManifest = buildManifest({
1019
1165
  tools: manifest.tools,
1020
1166
  scope: manifest.scope,
@@ -1025,24 +1171,25 @@ var updateCommand = async (opts) => {
1025
1171
  appendedFiles: allAppended.length > 0 ? allAppended : manifest.appended_files,
1026
1172
  settings: manifest.settings ? {
1027
1173
  claude: manifest.settings.claude,
1028
- gemini: manifest.settings.gemini
1174
+ gemini: manifest.settings.gemini,
1175
+ prettierignore: manifest.settings.prettierignore
1029
1176
  } : void 0,
1030
1177
  cliVersion,
1031
1178
  sourceHash
1032
1179
  });
1033
1180
  writeManifest(manifestPath, newManifest);
1034
1181
  s.stop("\uADDC\uCE59 \uAC31\uC2E0 \uC644\uB8CC");
1035
- p4.outro("ai-ops update \uC644\uB8CC");
1182
+ p5.outro("ai-ops update \uC644\uB8CC");
1036
1183
  };
1037
1184
 
1038
1185
  // src/commands/diff.ts
1039
- import * as p5 from "@clack/prompts";
1186
+ import * as p6 from "@clack/prompts";
1040
1187
  var diffCommand = async () => {
1041
1188
  const basePath = resolveBasePath();
1042
- p5.intro("ai-ops diff");
1189
+ p6.intro("ai-ops diff");
1043
1190
  const manifest = readManifest(resolveManifestPath(basePath));
1044
1191
  if (!manifest) {
1045
- 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.");
1046
1193
  process.exit(1);
1047
1194
  }
1048
1195
  const sourceHash = computeSourceHash(resolveRulesDir());
@@ -1052,27 +1199,27 @@ var diffCommand = async () => {
1052
1199
  currentSourceHash: sourceHash
1053
1200
  });
1054
1201
  if (result.status === "up-to-date") {
1055
- 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.");
1056
1203
  } else {
1057
1204
  if (result.sourceChanged) {
1058
- 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}`);
1059
1206
  }
1060
1207
  if (result.added.length > 0) {
1061
- p5.log.info(`\uCD94\uAC00\uB41C \uADDC\uCE59: ${result.added.join(", ")}`);
1208
+ p6.log.info(`\uCD94\uAC00\uB41C \uADDC\uCE59: ${result.added.join(", ")}`);
1062
1209
  }
1063
1210
  if (result.removed.length > 0) {
1064
- p5.log.info(`\uC81C\uAC70\uB41C \uADDC\uCE59: ${result.removed.join(", ")}`);
1211
+ p6.log.info(`\uC81C\uAC70\uB41C \uADDC\uCE59: ${result.removed.join(", ")}`);
1065
1212
  }
1066
1213
  }
1067
- p5.outro("ai-ops diff \uC644\uB8CC");
1214
+ p6.outro("ai-ops diff \uC644\uB8CC");
1068
1215
  };
1069
1216
 
1070
1217
  // src/commands/uninstall.ts
1071
- import * as p6 from "@clack/prompts";
1072
- import { rmSync as rmSync2 } from "fs";
1218
+ import * as p7 from "@clack/prompts";
1219
+ import { rmSync as rmSync5 } from "fs";
1073
1220
 
1074
1221
  // src/lib/uninstall.ts
1075
- import { existsSync as existsSync5, readFileSync as readFileSync7, rmSync, readdirSync as readdirSync4, writeFileSync as writeFileSync5 } from "fs";
1222
+ import { existsSync as existsSync6, readFileSync as readFileSync8, rmSync as rmSync4, readdirSync as readdirSync4, writeFileSync as writeFileSync6 } from "fs";
1076
1223
  import { resolve as resolve6, dirname as dirname5 } from "path";
1077
1224
  var removeFiles = (basePath, relativePaths) => {
1078
1225
  const deleted = [];
@@ -1081,23 +1228,26 @@ var removeFiles = (basePath, relativePaths) => {
1081
1228
  const notFound = [];
1082
1229
  for (const rel of relativePaths) {
1083
1230
  const absPath = resolve6(basePath, rel);
1084
- if (!existsSync5(absPath)) {
1231
+ if (!existsSync6(absPath)) {
1085
1232
  notFound.push(rel);
1086
1233
  continue;
1087
1234
  }
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);
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);
1094
1241
  } else {
1095
- skipped.push(rel);
1242
+ writeFileSync6(absPath, stripped, "utf-8");
1243
+ cleaned.push(rel);
1096
1244
  }
1097
- continue;
1245
+ } else if (hasLegacyHeader(content)) {
1246
+ rmSync4(absPath);
1247
+ deleted.push(rel);
1248
+ } else {
1249
+ skipped.push(rel);
1098
1250
  }
1099
- rmSync(absPath);
1100
- deleted.push(rel);
1101
1251
  }
1102
1252
  return { deleted, cleaned, skipped, notFound };
1103
1253
  };
@@ -1105,11 +1255,11 @@ var cleanEmptyDirs = (basePath, dirs) => {
1105
1255
  const removed = [];
1106
1256
  for (const dir of dirs) {
1107
1257
  const absDir = resolve6(basePath, dir);
1108
- if (!existsSync5(absDir)) continue;
1258
+ if (!existsSync6(absDir)) continue;
1109
1259
  try {
1110
1260
  const entries = readdirSync4(absDir);
1111
1261
  if (entries.length === 0) {
1112
- rmSync(absDir, { recursive: true });
1262
+ rmSync4(absDir, { recursive: true });
1113
1263
  removed.push(dir);
1114
1264
  }
1115
1265
  } catch {
@@ -1129,64 +1279,85 @@ var collectManagedDirs = (relativePaths) => {
1129
1279
  };
1130
1280
 
1131
1281
  // src/commands/uninstall.ts
1282
+ var SETTINGS_PATHS = /* @__PURE__ */ new Set([".claude/settings.local.json", ".gemini/settings.json"]);
1132
1283
  var uninstallCommand = async () => {
1133
1284
  const basePath = resolveBasePath();
1134
1285
  const manifestPath = resolveManifestPath(basePath);
1135
- p6.intro("ai-ops uninstall");
1286
+ p7.intro("ai-ops uninstall");
1136
1287
  const manifest = readManifest(manifestPath);
1137
1288
  if (!manifest) {
1138
- 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.");
1139
1290
  process.exit(1);
1140
1291
  }
1141
1292
  const targetFiles = [
1142
1293
  ...manifest.installed_files ?? inferInstalledFiles(manifest),
1143
1294
  ...manifest.appended_files ?? []
1144
- ];
1295
+ ].filter((f) => !SETTINGS_PATHS.has(f));
1145
1296
  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");
1297
+ p7.log.warn("\uC0AD\uC81C\uD560 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
1298
+ p7.outro("ai-ops uninstall \uC644\uB8CC");
1148
1299
  return;
1149
1300
  }
1150
- 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):
1151
1302
  ${targetFiles.map((f) => ` ${f}`).join("\n")}`);
1152
- const confirmed = await p6.confirm({
1303
+ const confirmed = await p7.confirm({
1153
1304
  message: "\uC704 \uD30C\uC77C\uACFC manifest\uB97C \uBAA8\uB450 \uC0AD\uC81C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
1154
1305
  initialValue: false
1155
1306
  });
1156
- if (p6.isCancel(confirmed) || !confirmed) {
1157
- p6.cancel("\uCDE8\uC18C\uB428");
1307
+ if (p7.isCancel(confirmed) || !confirmed) {
1308
+ p7.cancel("\uCDE8\uC18C\uB428");
1158
1309
  process.exit(0);
1159
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
+ }
1160
1327
  const result = removeFiles(basePath, targetFiles);
1161
1328
  const dirs = collectManagedDirs(targetFiles);
1162
1329
  const removedDirs = cleanEmptyDirs(basePath, dirs);
1163
- rmSync2(manifestPath, { force: true });
1330
+ rmSync5(manifestPath, { force: true });
1164
1331
  if (result.deleted.length > 0) {
1165
- p6.log.success(`\uC0AD\uC81C \uC644\uB8CC (${result.deleted.length}\uAC1C):
1332
+ p7.log.success(`\uC0AD\uC81C \uC644\uB8CC (${result.deleted.length}\uAC1C):
1166
1333
  ${result.deleted.map((f) => ` ${f}`).join("\n")}`);
1167
1334
  }
1168
1335
  if (result.cleaned.length > 0) {
1169
- p6.log.success(
1336
+ p7.log.success(
1170
1337
  `\uC139\uC158 \uC81C\uAC70 \uC644\uB8CC (\uC0AC\uC6A9\uC790 \uB0B4\uC6A9 \uBCF4\uC874, ${result.cleaned.length}\uAC1C):
1171
1338
  ${result.cleaned.map((f) => ` ${f}`).join("\n")}`
1172
1339
  );
1173
1340
  }
1174
1341
  if (result.skipped.length > 0) {
1175
- p6.log.warn(
1342
+ p7.log.warn(
1176
1343
  `\uAC74\uB108\uB700 (non-managed \uD30C\uC77C \uBCF4\uD638, ${result.skipped.length}\uAC1C):
1177
1344
  ${result.skipped.map((f) => ` ${f}`).join("\n")}`
1178
1345
  );
1179
1346
  }
1180
1347
  if (result.notFound.length > 0) {
1181
- p6.log.info(`\uC774\uBBF8 \uC5C6\uC74C (${result.notFound.length}\uAC1C):
1348
+ p7.log.info(`\uC774\uBBF8 \uC5C6\uC74C (${result.notFound.length}\uAC1C):
1182
1349
  ${result.notFound.map((f) => ` ${f}`).join("\n")}`);
1183
1350
  }
1184
1351
  if (removedDirs.length > 0) {
1185
- 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):
1186
1353
  ${removedDirs.map((d) => ` ${d}`).join("\n")}`);
1187
1354
  }
1188
- p6.log.success(`manifest \uC0AD\uC81C: ${MANIFEST_FILENAME}`);
1189
- 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");
1190
1361
  };
1191
1362
 
1192
1363
  // src/bin/index.ts