ai-ops-cli 0.1.24 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +107 -45
  2. package/data/presets.yaml +0 -23
  3. package/data/rules/communication.yaml +3 -2
  4. package/data/skills/README.md +157 -0
  5. package/data/skills/reference-skills/ai-llm-python-runtime/SKILL.md +10 -0
  6. package/data/skills/reference-skills/ai-llm-python-runtime/references/reference.md +26 -0
  7. package/data/skills/reference-skills/backend-python-fastapi-runtime/SKILL.md +10 -0
  8. package/data/skills/reference-skills/backend-python-fastapi-runtime/references/reference.md +43 -0
  9. package/data/skills/reference-skills/backend-service-standards/SKILL.md +10 -0
  10. package/data/skills/reference-skills/backend-service-standards/references/reference.md +29 -0
  11. package/data/skills/reference-skills/backend-ts-nestjs-runtime/SKILL.md +10 -0
  12. package/data/skills/reference-skills/backend-ts-nestjs-runtime/references/reference.md +43 -0
  13. package/data/skills/reference-skills/data-pipeline-python-performance/SKILL.md +10 -0
  14. package/data/skills/reference-skills/data-pipeline-python-performance/references/reference.md +24 -0
  15. package/data/skills/reference-skills/db-prisma-postgresql/SKILL.md +10 -0
  16. package/data/skills/reference-skills/db-prisma-postgresql/references/reference.md +25 -0
  17. package/data/skills/reference-skills/db-sqlalchemy-postgresql/SKILL.md +10 -0
  18. package/data/skills/reference-skills/db-sqlalchemy-postgresql/references/reference.md +25 -0
  19. package/data/skills/reference-skills/frontend-app-flutter-runtime/SKILL.md +10 -0
  20. package/data/skills/reference-skills/frontend-app-flutter-runtime/references/reference.md +50 -0
  21. package/data/skills/reference-skills/frontend-web-react-next-runtime/SKILL.md +10 -0
  22. package/data/skills/reference-skills/frontend-web-react-next-runtime/references/reference.md +51 -0
  23. package/data/skills/reference-skills/frontend-web-shadcn-ui/SKILL.md +10 -0
  24. package/data/skills/reference-skills/frontend-web-shadcn-ui/references/reference.md +26 -0
  25. package/data/skills/reference-skills/graphql-client-integration/SKILL.md +10 -0
  26. package/data/skills/reference-skills/graphql-client-integration/references/reference.md +22 -0
  27. package/data/skills/reference-skills/graphql-contract/SKILL.md +10 -0
  28. package/data/skills/reference-skills/graphql-contract/references/reference.md +22 -0
  29. package/data/skills/reference-skills/graphql-server-runtime/SKILL.md +10 -0
  30. package/data/skills/reference-skills/graphql-server-runtime/references/reference.md +40 -0
  31. package/data/skills/reference-skills/python-language/SKILL.md +10 -0
  32. package/data/skills/reference-skills/python-language/references/reference.md +26 -0
  33. package/data/skills/reference-skills/typescript-language/SKILL.md +10 -0
  34. package/data/skills/reference-skills/typescript-language/references/reference.md +23 -0
  35. package/data/skills/skill-registry.json +148 -0
  36. package/data/skills/task-skills/skill-load-check/SKILL.md +8 -0
  37. package/data/skills/task-skills/skill-load-check/scripts/loaded.js +1 -0
  38. package/dist/bin/index.js +1325 -423
  39. package/dist/bin/index.js.map +1 -1
  40. package/package.json +1 -1
  41. package/data/rules/ai-llm-python.yaml +0 -35
  42. package/data/rules/data-pipeline-python.yaml +0 -34
  43. package/data/rules/engineering-standards.yaml +0 -39
  44. package/data/rules/fastapi.yaml +0 -34
  45. package/data/rules/flutter.yaml +0 -40
  46. package/data/rules/graphql-client-app.yaml +0 -29
  47. package/data/rules/graphql-client-web.yaml +0 -30
  48. package/data/rules/graphql-core.yaml +0 -31
  49. package/data/rules/graphql-server.yaml +0 -33
  50. package/data/rules/libs-backend-python.yaml +0 -35
  51. package/data/rules/libs-backend-ts.yaml +0 -36
  52. package/data/rules/libs-frontend-app.yaml +0 -39
  53. package/data/rules/libs-frontend-web.yaml +0 -39
  54. package/data/rules/nestjs-graphql.yaml +0 -31
  55. package/data/rules/nestjs.yaml +0 -26
  56. package/data/rules/nextjs.yaml +0 -34
  57. package/data/rules/prisma-postgresql.yaml +0 -30
  58. package/data/rules/python.yaml +0 -31
  59. package/data/rules/react-typescript.yaml +0 -11
  60. package/data/rules/shadcn-ui.yaml +0 -36
  61. package/data/rules/sqlalchemy.yaml +0 -32
  62. package/data/rules/typescript.yaml +0 -22
package/dist/bin/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import { Command } from "commander";
5
5
 
6
6
  // src/commands/init.ts
7
- import * as p4 from "@clack/prompts";
7
+ import * as p3 from "@clack/prompts";
8
8
 
9
9
  // src/core/schemas/rule.schema.ts
10
10
  import { z } from "zod";
@@ -28,6 +28,7 @@ var RuleSchema = z.object({
28
28
  tags: z.array(z.string().min(1)),
29
29
  /** 0-100. 높을수록 생성 파일 상단 배치 (U-shaped attention 최적화) */
30
30
  priority: z.number().int().min(0).max(100),
31
+ supported_tools: z.array(z.string().min(1)).min(1).default(["claude-code", "codex", "gemini"]),
31
32
  content: RuleContentSchema
32
33
  }).strict();
33
34
 
@@ -39,53 +40,132 @@ var PresetSchema = z2.object({
39
40
  rules: z2.array(z2.string().min(1)).min(1)
40
41
  }).strict();
41
42
 
42
- // src/core/schemas/manifest.schema.ts
43
+ // src/core/schemas/skill.schema.ts
43
44
  import { z as z3 } from "zod";
44
- var SettingsConfigSchema = z3.object({
45
- claude: z3.array(z3.string().min(1)).optional(),
46
- gemini: z3.array(z3.string().min(1)).optional(),
47
- prettierignore: z3.boolean().optional()
45
+ var SKILL_KIND = {
46
+ REFERENCE: "reference",
47
+ TASK: "task"
48
+ };
49
+ var SKILL_SCOPE = {
50
+ PROJECT: "project",
51
+ USER: "user"
52
+ };
53
+ var SKILL_TOOL = {
54
+ CLAUDE_CODE: "claude-code",
55
+ CODEX: "codex",
56
+ GEMINI: "gemini"
57
+ };
58
+ var SkillKindSchema = z3.union([z3.literal(SKILL_KIND.REFERENCE), z3.literal(SKILL_KIND.TASK)]);
59
+ var SkillScopeSchema = z3.union([z3.literal(SKILL_SCOPE.PROJECT), z3.literal(SKILL_SCOPE.USER)]);
60
+ var SkillToolSchema = z3.union([
61
+ z3.literal(SKILL_TOOL.CLAUDE_CODE),
62
+ z3.literal(SKILL_TOOL.CODEX),
63
+ z3.literal(SKILL_TOOL.GEMINI)
64
+ ]);
65
+ var SkillFileSchema = z3.object({
66
+ path: z3.string().min(1),
67
+ content: z3.string()
68
+ }).strict();
69
+ var SkillFrontmatterSchema = z3.object({
70
+ name: z3.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "name must be kebab-case"),
71
+ description: z3.string().min(1)
72
+ }).passthrough();
73
+ var InstalledSkillSchema = z3.object({
74
+ id: z3.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "id must be kebab-case"),
75
+ kind: SkillKindSchema,
76
+ tools: z3.array(SkillToolSchema).min(1),
77
+ scope: SkillScopeSchema,
78
+ installed_paths: z3.array(z3.string().min(1)).min(1),
79
+ sourceHash: z3.string().regex(/^[a-f0-9]{6}$/, "sourceHash must be 6 lowercase hex chars")
80
+ }).strip();
81
+
82
+ // src/core/schemas/skill-catalog.schema.ts
83
+ import { z as z4 } from "zod";
84
+ var SkillIdSchema = z4.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "id must be kebab-case");
85
+ var SkillCatalogPathSchema = z4.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*(?:\/[a-z0-9]+(?:-[a-z0-9]+)*)+$/, "source_path must be relative kebab-case path");
86
+ var SkillCatalogEntrySchema = z4.object({
87
+ id: SkillIdSchema,
88
+ kind: SkillKindSchema,
89
+ supported_tools: z4.array(SkillToolSchema).min(1),
90
+ install_scopes: z4.array(SkillScopeSchema).min(1),
91
+ groups: z4.array(z4.string().min(1)),
92
+ included_in_presets: z4.array(z4.string().min(1)),
93
+ source_path: SkillCatalogPathSchema
94
+ }).strict().superRefine((entry, ctx) => {
95
+ const expectedPrefix = entry.kind === "reference" ? "reference-skills/" : "task-skills/";
96
+ if (!entry.source_path.startsWith(expectedPrefix)) {
97
+ ctx.addIssue({
98
+ code: z4.ZodIssueCode.custom,
99
+ path: ["source_path"],
100
+ message: `source_path must start with ${expectedPrefix}`
101
+ });
102
+ }
103
+ });
104
+ var SkillCatalogSchema = z4.object({
105
+ skills: z4.array(SkillCatalogEntrySchema)
106
+ }).strict();
107
+
108
+ // src/core/schemas/skill-registry.schema.ts
109
+ import { z as z5 } from "zod";
110
+ var SkillRegistrySchema = z5.object({
111
+ skills: z5.array(InstalledSkillSchema),
112
+ cliVersion: z5.string().optional(),
113
+ generatedAt: z5.string().datetime({ offset: true })
48
114
  }).strict();
49
- var WorkspaceEntrySchema = z3.object({
50
- preset: z3.string().min(1),
51
- rules: z3.array(z3.string().min(1))
115
+
116
+ // src/core/schemas/manifest.schema.ts
117
+ import { z as z6 } from "zod";
118
+ var SettingsConfigSchema = z6.object({
119
+ claude: z6.array(z6.string().min(1)).optional(),
120
+ gemini: z6.array(z6.string().min(1)).optional(),
121
+ prettierignore: z6.boolean().optional()
52
122
  }).strict();
53
- var ManifestSchema = z3.object({
54
- tools: z3.array(z3.string().min(1)).min(1),
55
- scope: z3.literal("project"),
123
+ var WorkspaceEntrySchema = z6.object({
124
+ preset: z6.string().min(1),
125
+ rules: z6.array(z6.string().min(1))
126
+ }).strict();
127
+ var ManifestSchema = z6.object({
128
+ tools: z6.array(z6.string().min(1)).min(1),
129
+ scope: z6.literal("project"),
56
130
  /** 비모노레포 단일 preset */
57
- preset: z3.string().min(1).optional(),
131
+ preset: z6.string().min(1).optional(),
58
132
  /** 모노레포: workspace path → { preset, rules } */
59
- workspaces: z3.record(z3.string(), WorkspaceEntrySchema).optional(),
60
- installed_rules: z3.array(z3.string().min(1)),
133
+ workspaces: z6.record(z6.string(), WorkspaceEntrySchema).optional(),
134
+ installed_rules: z6.array(z6.string().min(1)),
61
135
  /** 실제 디스크에 쓰여진 파일 상대 경로 목록 (uninstall용). 기존 manifest 호환성 위해 optional */
62
- installed_files: z3.array(z3.string().min(1)).optional(),
136
+ installed_files: z6.array(z6.string().min(1)).optional(),
137
+ /** skill 설치 루트 디렉토리 목록 */
138
+ installed_skills: z6.array(InstalledSkillSchema).optional(),
63
139
  /** non-managed 파일에 섹션을 append한 경우 추적 (uninstall 시 섹션만 제거) */
64
- appended_files: z3.array(z3.string().min(1)).optional(),
140
+ appended_files: z6.array(z6.string().min(1)).optional(),
65
141
  /** init 시 선택된 settings 항목 — update 시 재생성에 사용 */
66
142
  settings: SettingsConfigSchema.optional(),
67
143
  /** init/update 실행 시점의 CLI 패키지 버전 — 버전 변경 감지에 사용 */
68
- cliVersion: z3.string().optional(),
144
+ cliVersion: z6.string().optional(),
69
145
  /** SSOT 데이터 파일들의 deterministic SHA-256 해시 (6자리 hex). diff/update 판단 기준 */
70
- sourceHash: z3.string().regex(/^[a-f0-9]{6}$/, "sourceHash must be 6 lowercase hex chars"),
71
- generatedAt: z3.string().datetime({ offset: true })
146
+ sourceHash: z6.string().regex(/^[a-f0-9]{6}$/, "sourceHash must be 6 lowercase hex chars"),
147
+ generatedAt: z6.string().datetime({ offset: true })
72
148
  }).strict();
73
149
 
74
150
  // src/core/loader.ts
75
151
  import { readFileSync, readdirSync } from "fs";
76
- import { resolve } from "path";
152
+ import { join, resolve } from "path";
153
+ import { parse as parse2 } from "yaml";
154
+
155
+ // src/core/frontmatter.ts
77
156
  import { parse } from "yaml";
78
- var PRESET_RULE_BUNDLES = {
79
- "frontend-web": {
80
- graphql: ["graphql-core", "graphql-client-web"]
81
- },
82
- "frontend-app": {
83
- graphql: ["graphql-core", "graphql-client-app"]
84
- },
85
- "backend-ts": {
86
- graphql: ["graphql-core", "graphql-server"]
157
+ var parseMarkdownFrontmatter = (content) => {
158
+ const match = content.match(/^---\n([\s\S]*?)\n---\n?/);
159
+ if (!match) {
160
+ throw new Error("Missing YAML frontmatter");
87
161
  }
162
+ return {
163
+ frontmatter: parse(match[1]),
164
+ body: content.slice(match[0].length)
165
+ };
88
166
  };
167
+
168
+ // src/core/loader.ts
89
169
  var sortRulesByPriority = (rules) => [...rules].sort((a, b) => b.priority - a.priority);
90
170
  var deduplicateRulesById = (rules) => {
91
171
  const seen = /* @__PURE__ */ new Set();
@@ -95,11 +175,6 @@ var deduplicateRulesById = (rules) => {
95
175
  return true;
96
176
  });
97
177
  };
98
- var resolveBundledRuleIds = (presetId, logicalRuleId) => {
99
- const presetBundles = PRESET_RULE_BUNDLES[presetId];
100
- if (!presetBundles) return [logicalRuleId];
101
- return presetBundles[logicalRuleId] ?? [logicalRuleId];
102
- };
103
178
  var resolveRuleById = (ruleId, allRules, context) => {
104
179
  const found = allRules.find((rule) => rule.id === ruleId);
105
180
  if (!found) {
@@ -109,33 +184,80 @@ var resolveRuleById = (ruleId, allRules, context) => {
109
184
  return found;
110
185
  };
111
186
  var parseRawPresets = (raw) => Object.entries(raw).map(([id, value]) => PresetSchema.parse({ id, ...value }));
112
- var resolvePresetRuleGroups = (preset, allRules) => preset.rules.map((logicalRuleId) => {
113
- const bundledRuleIds = resolveBundledRuleIds(preset.id, logicalRuleId);
114
- const rules = bundledRuleIds.map((ruleId) => resolveRuleById(ruleId, allRules, `${preset.id}:${logicalRuleId}`));
115
- return { id: logicalRuleId, rules };
116
- });
117
187
  var resolvePresetRules = (preset, allRules) => {
118
- const groups = resolvePresetRuleGroups(preset, allRules);
119
- const resolved = deduplicateRulesById(groups.flatMap((group) => group.rules));
120
- return sortRulesByPriority(resolved);
188
+ const resolved = preset.rules.map((ruleId) => resolveRuleById(ruleId, allRules, preset.id));
189
+ return sortRulesByPriority(deduplicateRulesById(resolved));
190
+ };
191
+ var resolvePresetSkills = (preset, allSkills) => {
192
+ return allSkills.filter((skill) => skill.included_in_presets.includes(preset.id)).sort((a, b) => a.id.localeCompare(b.id));
121
193
  };
122
194
  var loadRuleFile = (filePath) => {
123
195
  const raw = readFileSync(filePath, "utf-8");
124
- return RuleSchema.parse(parse(raw));
196
+ return RuleSchema.parse(parse2(raw));
197
+ };
198
+ var loadSkillDirectoryFiles = (skillDir) => {
199
+ const files = [];
200
+ const walk = (relativeDir = "") => {
201
+ const absDir = relativeDir.length > 0 ? join(skillDir, relativeDir) : skillDir;
202
+ const entries = readdirSync(absDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
203
+ for (const entry of entries) {
204
+ const nextRelativePath = relativeDir.length > 0 ? join(relativeDir, entry.name) : entry.name;
205
+ if (entry.isDirectory()) {
206
+ walk(nextRelativePath);
207
+ continue;
208
+ }
209
+ files.push({
210
+ path: nextRelativePath,
211
+ content: readFileSync(join(skillDir, nextRelativePath), "utf-8")
212
+ });
213
+ }
214
+ };
215
+ walk();
216
+ return files;
125
217
  };
126
218
  var loadAllRules = (rulesDir) => {
127
219
  const files = readdirSync(rulesDir).filter((f) => f.endsWith(".yaml")).sort();
128
220
  const rules = files.map((f) => loadRuleFile(resolve(rulesDir, f)));
129
221
  return sortRulesByPriority(rules);
130
222
  };
223
+ var loadSkillCatalog = (skillsDir) => SkillCatalogSchema.parse(JSON.parse(readFileSync(resolve(skillsDir, "skill-registry.json"), "utf-8")));
224
+ var loadAllSkills = (skillsDir) => {
225
+ const catalog = loadSkillCatalog(skillsDir);
226
+ const entries = [...catalog.skills].sort((a, b) => a.id.localeCompare(b.id));
227
+ return entries.map((entry) => {
228
+ const directory = resolve(skillsDir, entry.source_path);
229
+ const skillMdPath = join(directory, "SKILL.md");
230
+ const rawSkillMd = readFileSync(skillMdPath, "utf-8");
231
+ const { frontmatter } = parseMarkdownFrontmatter(rawSkillMd);
232
+ const parsed = SkillFrontmatterSchema.parse(frontmatter);
233
+ if (parsed.name !== entry.id) {
234
+ throw new Error(`Skill directory and frontmatter name mismatch: ${entry.id} != ${parsed.name}`);
235
+ }
236
+ const files = loadSkillDirectoryFiles(directory);
237
+ if (entry.kind === "reference" && !files.some((file) => file.path === "references/reference.md")) {
238
+ throw new Error(`Reference skill must include references/reference.md: ${parsed.name}`);
239
+ }
240
+ return {
241
+ id: entry.id,
242
+ kind: entry.kind,
243
+ description: parsed.description,
244
+ supported_tools: [...entry.supported_tools],
245
+ install_scopes: [...entry.install_scopes],
246
+ groups: [...entry.groups],
247
+ included_in_presets: [...entry.included_in_presets],
248
+ directory,
249
+ files
250
+ };
251
+ });
252
+ };
131
253
  var loadPresets = (presetsPath) => {
132
254
  const raw = readFileSync(presetsPath, "utf-8");
133
- const data = parse(raw);
255
+ const data = parse2(raw);
134
256
  return parseRawPresets(data);
135
257
  };
136
258
 
137
259
  // src/core/renderer.ts
138
- import { join } from "path";
260
+ import { join as join2 } from "path";
139
261
 
140
262
  // src/core/tool-output.ts
141
263
  var GLOBAL_CATEGORIES = ["persona", "communication", "philosophy", "convention", "standard"];
@@ -225,7 +347,7 @@ var renderRuleToMarkdown = (rule) => {
225
347
  }
226
348
  return sections.join("\n\n");
227
349
  };
228
- var renderRulesToMarkdown = (rules) => rules.map(renderRuleToMarkdown).join("\n\n---\n\n");
350
+ var renderRulesToMarkdown = (rules) => rules.map((rule) => renderRuleToMarkdown(rule)).join("\n\n---\n\n");
229
351
  var isGlobalRule = (rule) => GLOBAL_CATEGORIES.includes(rule.category);
230
352
  var partitionRules = (rules) => {
231
353
  const global = [];
@@ -261,14 +383,14 @@ var renderForTool = (toolId, rules, workspaceMappings) => {
261
383
  const { rulesDir, fileExtension } = config;
262
384
  if (!workspaceMappings || workspaceMappings.length === 0) {
263
385
  const files = rules.map((rule) => ({
264
- relativePath: join(rulesDir, `${rule.id}${fileExtension}`),
386
+ relativePath: join2(rulesDir, `${rule.id}${fileExtension}`),
265
387
  content: renderClaudeCodeRule(rule)
266
388
  }));
267
389
  return { tool: "claude-code", files };
268
390
  }
269
391
  const { global: global2, domain: domain2 } = partitionRules(rules);
270
392
  const globalFiles = global2.map((rule) => ({
271
- relativePath: join(rulesDir, `${rule.id}${fileExtension}`),
393
+ relativePath: join2(rulesDir, `${rule.id}${fileExtension}`),
272
394
  content: renderRuleToMarkdown(rule)
273
395
  // global은 frontmatter 불필요
274
396
  }));
@@ -277,7 +399,7 @@ var renderForTool = (toolId, rules, workspaceMappings) => {
277
399
  const wsRules = domain2.filter((r) => ws.ruleIds.includes(r.id));
278
400
  if (wsRules.length === 0) continue;
279
401
  workspaceFiles.push({
280
- relativePath: join(ws.path, "CLAUDE.md"),
402
+ relativePath: join2(ws.path, "CLAUDE.md"),
281
403
  content: renderRulesToMarkdown(wsRules)
282
404
  });
283
405
  }
@@ -295,7 +417,10 @@ var renderForTool = (toolId, rules, workspaceMappings) => {
295
417
  for (const ws of workspaceMappings) {
296
418
  const wsRules = domain.filter((r) => ws.ruleIds.includes(r.id));
297
419
  if (wsRules.length === 0) continue;
298
- domainFiles.push({ workspacePath: ws.path, content: renderRulesToMarkdown(wsRules) });
420
+ domainFiles.push({
421
+ workspacePath: ws.path,
422
+ content: renderRulesToMarkdown(wsRules)
423
+ });
299
424
  }
300
425
  if (toolId === "codex") {
301
426
  return { tool: "codex", rootContent, domainFiles };
@@ -303,10 +428,13 @@ var renderForTool = (toolId, rules, workspaceMappings) => {
303
428
  return { tool: "gemini", rootContent, domainFiles };
304
429
  };
305
430
 
431
+ // src/core/skill-renderer.ts
432
+ import { join as join4 } from "path";
433
+
306
434
  // src/core/source-hash.ts
307
435
  import { createHash } from "crypto";
308
- import { readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
309
- import { dirname, resolve as resolve2 } from "path";
436
+ import { existsSync, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
437
+ import { dirname, join as join3, resolve as resolve2 } from "path";
310
438
  import { fileURLToPath } from "url";
311
439
  var __dirname = dirname(fileURLToPath(import.meta.url));
312
440
  var getCliVersion = () => {
@@ -319,11 +447,50 @@ var getCliVersion = () => {
319
447
  }
320
448
  };
321
449
  var computeHash = (contents) => createHash("sha256").update(contents.join("")).digest("hex").slice(0, 6);
322
- var computeSourceHash = (rulesDir) => {
323
- const files = readdirSync2(rulesDir).filter((f) => f.endsWith(".yaml")).sort();
324
- const contents = files.map((f) => readFileSync2(resolve2(rulesDir, f), "utf-8"));
325
- return computeHash(contents);
450
+ var loadSortedFileContents = (dirPath) => {
451
+ const files = readdirSync2(dirPath).filter((f) => f.endsWith(".yaml")).sort();
452
+ return files.map((f) => readFileSync2(resolve2(dirPath, f), "utf-8"));
326
453
  };
454
+ var loadDirectoryContents = (baseDir) => {
455
+ const contents = [];
456
+ const walk = (relativeDir = "") => {
457
+ const absDir = relativeDir.length > 0 ? join3(baseDir, relativeDir) : baseDir;
458
+ const entries = readdirSync2(absDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
459
+ for (const entry of entries) {
460
+ const nextRelativePath = relativeDir.length > 0 ? join3(relativeDir, entry.name) : entry.name;
461
+ if (entry.isDirectory()) {
462
+ walk(nextRelativePath);
463
+ continue;
464
+ }
465
+ contents.push(`${nextRelativePath}:${readFileSync2(join3(baseDir, nextRelativePath), "utf-8")}`);
466
+ }
467
+ };
468
+ walk();
469
+ return contents;
470
+ };
471
+ var loadSkillTreeContents = (skillsDir) => {
472
+ const contents = [];
473
+ const registryPath = resolve2(skillsDir, "skill-registry.json");
474
+ if (existsSync(registryPath)) {
475
+ contents.push(`skill-registry.json:${readFileSync2(registryPath, "utf-8")}`);
476
+ }
477
+ for (const directoryName of ["reference-skills", "task-skills"]) {
478
+ const directoryPath = resolve2(skillsDir, directoryName);
479
+ if (!existsSync(directoryPath)) {
480
+ continue;
481
+ }
482
+ const directoryContents = loadDirectoryContents(directoryPath).map((content) => `${directoryName}/${content}`);
483
+ contents.push(...directoryContents);
484
+ }
485
+ return contents;
486
+ };
487
+ var computeSourceHash = (dataDir) => {
488
+ const ruleContents = loadSortedFileContents(resolve2(dataDir, "rules"));
489
+ const skillContents = loadSkillTreeContents(resolve2(dataDir, "skills"));
490
+ const presetsContent = readFileSync2(resolve2(dataDir, "presets.yaml"), "utf-8");
491
+ return computeHash([...ruleContents, ...skillContents, presetsContent]);
492
+ };
493
+ var computeInstalledSkillHash = (params) => computeHash([params.kind, params.description, ...[...params.tools].sort(), ...[...params.files].sort()]);
327
494
  var buildManifest = (params) => ManifestSchema.parse({
328
495
  tools: [...params.tools],
329
496
  scope: params.scope,
@@ -331,16 +498,71 @@ var buildManifest = (params) => ManifestSchema.parse({
331
498
  workspaces: params.workspaces,
332
499
  installed_rules: [...params.installedRules],
333
500
  installed_files: params.installedFiles ? [...params.installedFiles] : void 0,
501
+ installed_skills: params.installedSkills ? [...params.installedSkills] : void 0,
334
502
  appended_files: params.appendedFiles && params.appendedFiles.length > 0 ? [...params.appendedFiles] : void 0,
335
503
  settings: params.settings ? {
336
504
  claude: params.settings.claude ? [...params.settings.claude] : void 0,
337
- gemini: params.settings.gemini ? [...params.settings.gemini] : void 0
505
+ gemini: params.settings.gemini ? [...params.settings.gemini] : void 0,
506
+ prettierignore: params.settings.prettierignore
338
507
  } : void 0,
339
508
  cliVersion: params.cliVersion,
340
509
  sourceHash: params.sourceHash,
341
510
  generatedAt: (/* @__PURE__ */ new Date()).toISOString()
342
511
  });
343
512
 
513
+ // src/core/skill-renderer.ts
514
+ var AGENT_SKILLS_DIR = ".agents/skills";
515
+ var CLAUDE_SKILLS_DIR = ".claude/skills";
516
+ var buildRootDirs = (skillId, toolIds) => {
517
+ const dirs = [];
518
+ if (toolIds.some((toolId) => toolId === "codex" || toolId === "gemini")) {
519
+ dirs.push(join4(AGENT_SKILLS_DIR, skillId));
520
+ }
521
+ if (toolIds.includes("claude-code")) {
522
+ dirs.push(join4(CLAUDE_SKILLS_DIR, skillId));
523
+ }
524
+ return dirs;
525
+ };
526
+ var normalizeSelectedTools = (skill, requestedTools) => {
527
+ const supportedToolSet = new Set(skill.supported_tools);
528
+ return requestedTools.filter((toolId) => supportedToolSet.has(toolId));
529
+ };
530
+ var buildSkillInstallPlan = (params) => {
531
+ const selectedTools = normalizeSelectedTools(params.skill, params.requestedTools);
532
+ if (selectedTools.length === 0) {
533
+ throw new Error(`Skill ${params.skill.id} does not support the requested tools`);
534
+ }
535
+ const rootDirs = buildRootDirs(params.skill.id, selectedTools);
536
+ const skillHash = computeInstalledSkillHash({
537
+ kind: params.skill.kind,
538
+ description: params.skill.description,
539
+ tools: selectedTools,
540
+ files: params.skill.files.map((file) => `${file.path}:${file.content}`)
541
+ });
542
+ const packages = rootDirs.map((rootDir) => {
543
+ const files = params.skill.files.map((file) => ({
544
+ relativePath: join4(rootDir, file.path),
545
+ content: file.content
546
+ }));
547
+ return {
548
+ skillId: params.skill.id,
549
+ rootDir,
550
+ files
551
+ };
552
+ });
553
+ return {
554
+ packages,
555
+ installedSkill: {
556
+ id: params.skill.id,
557
+ kind: params.skill.kind,
558
+ tools: selectedTools,
559
+ scope: params.scope,
560
+ installed_paths: rootDirs,
561
+ sourceHash: skillHash
562
+ }
563
+ };
564
+ };
565
+
344
566
  // src/core/managed-header.ts
345
567
  var MANAGED_MARKER = "<!-- managed by ai-ops -->";
346
568
  var SECTION_START = "<!-- ai-ops:start -->";
@@ -374,11 +596,11 @@ var replaceAiOpsSection = (existing, newSection) => {
374
596
 
375
597
  // src/core/manifest-io.ts
376
598
  import { mkdirSync, readFileSync as readFileSync3, writeFileSync } from "fs";
377
- import { dirname as dirname2, join as join2 } from "path";
599
+ import { dirname as dirname2, join as join5 } from "path";
378
600
  var MANIFEST_FILENAME = ".ai-ops-manifest.json";
379
601
  var parseManifest = (json) => ManifestSchema.parse(JSON.parse(json));
380
602
  var serializeManifest = (manifest) => JSON.stringify(manifest, null, 2) + "\n";
381
- var resolveManifestPath = (basePath) => join2(basePath, MANIFEST_FILENAME);
603
+ var resolveManifestPath = (basePath) => join5(basePath, MANIFEST_FILENAME);
382
604
  var readManifest = (manifestPath) => {
383
605
  let raw;
384
606
  try {
@@ -393,6 +615,144 @@ var writeManifest = (manifestPath, manifest) => {
393
615
  writeFileSync(manifestPath, serializeManifest(manifest), "utf-8");
394
616
  };
395
617
 
618
+ // src/core/manifest-resolution.ts
619
+ var LEGACY_SKILL_ID_MAP = {
620
+ "engineering-standards-pack": "backend-service-standards"
621
+ };
622
+ var LEGACY_EXTERNALIZED_RULE_SKILL_MAP = {
623
+ "engineering-standards": "backend-service-standards",
624
+ typescript: "typescript-language",
625
+ python: "python-language",
626
+ "react-typescript": "frontend-web-react-next-runtime",
627
+ nextjs: "frontend-web-react-next-runtime",
628
+ "libs-frontend-web": "frontend-web-react-next-runtime",
629
+ "shadcn-ui": "frontend-web-shadcn-ui",
630
+ flutter: "frontend-app-flutter-runtime",
631
+ "libs-frontend-app": "frontend-app-flutter-runtime",
632
+ nestjs: "backend-ts-nestjs-runtime",
633
+ "libs-backend-ts": "backend-ts-nestjs-runtime",
634
+ fastapi: "backend-python-fastapi-runtime",
635
+ "libs-backend-python": "backend-python-fastapi-runtime",
636
+ "graphql-core": "graphql-contract",
637
+ "graphql-client-web": "graphql-client-integration",
638
+ "graphql-client-app": "graphql-client-integration",
639
+ "graphql-server": "graphql-server-runtime",
640
+ "nestjs-graphql": "graphql-server-runtime",
641
+ "prisma-postgresql": "db-prisma-postgresql",
642
+ sqlalchemy: "db-sqlalchemy-postgresql",
643
+ "ai-llm-python": "ai-llm-python-runtime",
644
+ "data-pipeline-python": "data-pipeline-python-performance"
645
+ };
646
+ var resolveCanonicalSkillId = (skillId) => LEGACY_SKILL_ID_MAP[skillId] ?? skillId;
647
+ var resolveRulesFromIds = (ruleIds, allRules) => {
648
+ const ruleMap = new Map(allRules.map((rule) => [rule.id, rule]));
649
+ const seen = /* @__PURE__ */ new Set();
650
+ const resolved = ruleIds.flatMap((ruleId) => {
651
+ const rule = ruleMap.get(ruleId);
652
+ if (!rule || seen.has(rule.id)) {
653
+ return [];
654
+ }
655
+ seen.add(rule.id);
656
+ return [rule];
657
+ });
658
+ return [...resolved].sort((a, b) => b.priority - a.priority);
659
+ };
660
+ var resolvePresetById = (presetId, presets) => {
661
+ if (presetId === void 0) {
662
+ return void 0;
663
+ }
664
+ return presets.find((preset) => preset.id === presetId);
665
+ };
666
+ var resolveManifestRules = (params) => {
667
+ const { manifest, allRules, presets } = params;
668
+ if (manifest.workspaces) {
669
+ const resolvedWorkspaces = Object.fromEntries(
670
+ Object.entries(manifest.workspaces).map(([workspacePath, entry]) => {
671
+ const preset2 = resolvePresetById(entry.preset, presets);
672
+ const rules = preset2 ? resolvePresetRules(preset2, allRules) : resolveRulesFromIds(entry.rules, allRules);
673
+ return [
674
+ workspacePath,
675
+ {
676
+ preset: entry.preset,
677
+ rules: rules.map((rule) => rule.id)
678
+ }
679
+ ];
680
+ })
681
+ );
682
+ const installedRules2 = resolveRulesFromIds(
683
+ Object.values(resolvedWorkspaces).flatMap((entry) => entry.rules),
684
+ allRules
685
+ );
686
+ return {
687
+ installedRules: installedRules2,
688
+ workspaces: resolvedWorkspaces
689
+ };
690
+ }
691
+ const preset = resolvePresetById(manifest.preset, presets);
692
+ const installedRules = preset ? resolvePresetRules(preset, allRules) : resolveRulesFromIds(manifest.installed_rules, allRules);
693
+ return {
694
+ installedRules
695
+ };
696
+ };
697
+ var resolveManifestProjectSkills = (params) => {
698
+ const { manifest, allSkills } = params;
699
+ const skillMap = new Map(allSkills.map((skill) => [skill.id, skill]));
700
+ const targets = [];
701
+ const seen = /* @__PURE__ */ new Set();
702
+ for (const installedSkill of manifest.installed_skills ?? []) {
703
+ const canonicalSkillId = resolveCanonicalSkillId(installedSkill.id);
704
+ const skill = skillMap.get(canonicalSkillId);
705
+ if (!skill) {
706
+ throw new Error(`Skill not found during manifest resolution: ${installedSkill.id}`);
707
+ }
708
+ if (seen.has(skill.id)) {
709
+ continue;
710
+ }
711
+ seen.add(skill.id);
712
+ targets.push({
713
+ skill,
714
+ requestedTools: [...installedSkill.tools]
715
+ });
716
+ }
717
+ for (const ruleId of manifest.installed_rules) {
718
+ const mappedSkillId = LEGACY_EXTERNALIZED_RULE_SKILL_MAP[ruleId];
719
+ if (!mappedSkillId || seen.has(mappedSkillId)) {
720
+ continue;
721
+ }
722
+ const skill = skillMap.get(mappedSkillId);
723
+ if (!skill) {
724
+ throw new Error(`Skill not found during legacy rule migration: ${mappedSkillId}`);
725
+ }
726
+ seen.add(mappedSkillId);
727
+ targets.push({
728
+ skill,
729
+ requestedTools: [...manifest.tools]
730
+ });
731
+ }
732
+ return targets;
733
+ };
734
+
735
+ // src/core/skill-registry-io.ts
736
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
737
+ import { dirname as dirname3, join as join6 } from "path";
738
+ var SKILL_REGISTRY_FILENAME = "skills-manifest.json";
739
+ var parseSkillRegistry = (json) => SkillRegistrySchema.parse(JSON.parse(json));
740
+ var serializeSkillRegistry = (registry) => JSON.stringify(registry, null, 2) + "\n";
741
+ var resolveSkillRegistryPath = (userBasePath) => join6(userBasePath, ".ai-ops", SKILL_REGISTRY_FILENAME);
742
+ var readSkillRegistry = (registryPath) => {
743
+ let raw;
744
+ try {
745
+ raw = readFileSync4(registryPath, "utf-8");
746
+ } catch {
747
+ return null;
748
+ }
749
+ return parseSkillRegistry(raw);
750
+ };
751
+ var writeSkillRegistry = (registryPath, registry) => {
752
+ mkdirSync2(dirname3(registryPath), { recursive: true });
753
+ writeFileSync2(registryPath, serializeSkillRegistry(registry), "utf-8");
754
+ };
755
+
396
756
  // src/core/diff.ts
397
757
  var computeDiff = (params) => {
398
758
  const { previous, currentRules, currentSourceHash, currentCliVersion } = params;
@@ -407,7 +767,7 @@ var computeDiff = (params) => {
407
767
  };
408
768
 
409
769
  // src/core/install-plan.ts
410
- import { join as join3 } from "path";
770
+ import { join as join7 } from "path";
411
771
  var CODEX_PLAN_BODY = "## 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.";
412
772
  var buildInstallPlan = (params) => {
413
773
  const { toolId, renderResult, meta } = params;
@@ -422,12 +782,12 @@ var buildInstallPlan = (params) => {
422
782
  const actions = [];
423
783
  const rootContent = renderResult.rootContent ? renderResult.rootContent + "\n\n---\n\n" + CODEX_PLAN_BODY : CODEX_PLAN_BODY;
424
784
  actions.push({
425
- relativePath: join3(config.dir, config.rootFileName),
785
+ relativePath: join7(config.dir, config.rootFileName),
426
786
  content: wrapWithSection(rootContent, meta)
427
787
  });
428
788
  for (const df of renderResult.domainFiles) {
429
789
  actions.push({
430
- relativePath: join3(df.workspacePath, config.domainFileName),
790
+ relativePath: join7(df.workspacePath, config.domainFileName),
431
791
  content: wrapWithSection(df.content, meta)
432
792
  });
433
793
  }
@@ -438,13 +798,13 @@ var buildInstallPlan = (params) => {
438
798
  const actions = [];
439
799
  if (renderResult.rootContent) {
440
800
  actions.push({
441
- relativePath: join3(config.dir, config.rootFileName),
801
+ relativePath: join7(config.dir, config.rootFileName),
442
802
  content: wrapWithSection(renderResult.rootContent, meta)
443
803
  });
444
804
  }
445
805
  for (const df of renderResult.domainFiles) {
446
806
  actions.push({
447
- relativePath: join3(df.workspacePath, config.domainFileName),
807
+ relativePath: join7(df.workspacePath, config.domainFileName),
448
808
  content: wrapWithSection(df.content, meta)
449
809
  });
450
810
  }
@@ -454,7 +814,7 @@ var buildInstallPlan = (params) => {
454
814
  };
455
815
 
456
816
  // src/core/uninstall-plan.ts
457
- import { join as join4 } from "path";
817
+ import { join as join8 } from "path";
458
818
  var inferInstalledFiles = (manifest) => {
459
819
  const files = [];
460
820
  const isMonorepo = manifest.workspaces !== void 0;
@@ -462,27 +822,27 @@ var inferInstalledFiles = (manifest) => {
462
822
  if (toolId === "claude-code") {
463
823
  const config = TOOL_OUTPUT_MAP["claude-code"];
464
824
  for (const ruleId of manifest.installed_rules) {
465
- files.push(join4(config.rulesDir, `${ruleId}${config.fileExtension}`));
825
+ files.push(join8(config.rulesDir, `${ruleId}${config.fileExtension}`));
466
826
  }
467
827
  } else if (toolId === "codex") {
468
828
  const config = TOOL_OUTPUT_MAP["codex"];
469
829
  if (!isMonorepo) {
470
- files.push(join4(config.dir, config.rootFileName));
471
- files.push(join4(config.dir, config.domainFileName));
830
+ files.push(join8(config.dir, config.rootFileName));
831
+ files.push(join8(config.dir, config.domainFileName));
472
832
  } else {
473
- files.push(join4(config.dir, config.rootFileName));
833
+ files.push(join8(config.dir, config.rootFileName));
474
834
  for (const ws of Object.keys(manifest.workspaces ?? {})) {
475
- files.push(join4(ws, config.domainFileName));
835
+ files.push(join8(ws, config.domainFileName));
476
836
  }
477
837
  }
478
838
  } else if (toolId === "gemini") {
479
839
  const config = TOOL_OUTPUT_MAP["gemini"];
480
840
  if (!isMonorepo) {
481
- files.push(join4(config.dir, config.rootFileName));
841
+ files.push(join8(config.dir, config.rootFileName));
482
842
  } else {
483
- files.push(join4(config.dir, config.rootFileName));
843
+ files.push(join8(config.dir, config.rootFileName));
484
844
  for (const ws of Object.keys(manifest.workspaces ?? {})) {
485
- files.push(join4(ws, config.domainFileName));
845
+ files.push(join8(ws, config.domainFileName));
486
846
  }
487
847
  }
488
848
  }
@@ -491,20 +851,23 @@ var inferInstalledFiles = (manifest) => {
491
851
  };
492
852
 
493
853
  // src/core/paths.ts
494
- import { dirname as dirname3, resolve as resolve3 } from "path";
854
+ import { dirname as dirname4, resolve as resolve3 } from "path";
495
855
  import { fileURLToPath as fileURLToPath2 } from "url";
496
- var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
856
+ var __dirname2 = dirname4(fileURLToPath2(import.meta.url));
497
857
  var COMPILER_DATA_DIR = resolve3(__dirname2, "..", "..", "data");
498
858
 
499
859
  // src/lib/paths.ts
500
- import { join as join5 } from "path";
501
- var resolveRulesDir = () => join5(COMPILER_DATA_DIR, "rules");
502
- var resolvePresetsPath = () => join5(COMPILER_DATA_DIR, "presets.yaml");
860
+ import { join as join9 } from "path";
861
+ var resolveCompilerDataDir = () => COMPILER_DATA_DIR;
862
+ var resolveRulesDir = () => join9(COMPILER_DATA_DIR, "rules");
863
+ var resolveSkillsDir = () => join9(COMPILER_DATA_DIR, "skills");
864
+ var resolvePresetsPath = () => join9(COMPILER_DATA_DIR, "presets.yaml");
503
865
  var resolveBasePath = () => process.cwd();
866
+ var resolveUserBasePath = () => process.env.AI_OPS_HOME ?? process.env.HOME ?? process.cwd();
504
867
 
505
868
  // src/lib/workspace.ts
506
- import { existsSync, readdirSync as readdirSync3, statSync } from "fs";
507
- import { join as join6, resolve as resolve4 } from "path";
869
+ import { existsSync as existsSync2, readdirSync as readdirSync3, statSync } from "fs";
870
+ import { join as join10, resolve as resolve4 } from "path";
508
871
  var EXCLUDE_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", ".turbo", ".cache", "coverage"]);
509
872
  var isVisibleDir = (basePath, name) => {
510
873
  if (name.startsWith(".") || EXCLUDE_DIRS.has(name)) return false;
@@ -524,7 +887,7 @@ var PROJECT_MANIFESTS = [
524
887
  "go.mod"
525
888
  // Go
526
889
  ];
527
- var isWorkspaceRoot = (dirPath) => PROJECT_MANIFESTS.some((f) => existsSync(join6(dirPath, f)));
890
+ var isWorkspaceRoot = (dirPath) => PROJECT_MANIFESTS.some((f) => existsSync2(join10(dirPath, f)));
528
891
  var listWorkspaceCandidates = (basePath) => {
529
892
  const topLevel = readdirSync3(basePath).filter((name) => isVisibleDir(basePath, name));
530
893
  const candidates = [];
@@ -537,7 +900,7 @@ var listWorkspaceCandidates = (basePath) => {
537
900
  const wsChildren = children.filter((name) => isWorkspaceRoot(resolve4(subPath, name)));
538
901
  if (wsChildren.length > 0) {
539
902
  for (const child of wsChildren) {
540
- candidates.push(join6(dir, child));
903
+ candidates.push(join10(dir, child));
541
904
  }
542
905
  } else {
543
906
  candidates.push(dir);
@@ -548,31 +911,31 @@ var listWorkspaceCandidates = (basePath) => {
548
911
  };
549
912
 
550
913
  // src/lib/install.ts
551
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
552
- import { dirname as dirname4, resolve as resolve5 } from "path";
914
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
915
+ import { dirname as dirname5, resolve as resolve5 } from "path";
553
916
  var installFiles = (basePath, actions, _meta) => {
554
917
  const written = [];
555
918
  const appended = [];
556
919
  const skipped = [];
557
920
  for (const action of actions) {
558
921
  const absPath = resolve5(basePath, action.relativePath);
559
- if (!existsSync2(absPath)) {
560
- mkdirSync2(dirname4(absPath), { recursive: true });
561
- writeFileSync2(absPath, action.content + "\n", "utf-8");
922
+ if (!existsSync3(absPath)) {
923
+ mkdirSync3(dirname5(absPath), { recursive: true });
924
+ writeFileSync3(absPath, action.content + "\n", "utf-8");
562
925
  written.push(action.relativePath);
563
926
  } else {
564
- const existing = readFileSync4(absPath, "utf-8");
927
+ const existing = readFileSync5(absPath, "utf-8");
565
928
  if (hasAiOpsSection(existing)) {
566
929
  const updated = replaceAiOpsSection(existing, action.content);
567
- writeFileSync2(absPath, updated, "utf-8");
930
+ writeFileSync3(absPath, updated, "utf-8");
568
931
  const stripped = stripAiOpsSection(existing);
569
932
  (stripped.trim().length > 0 ? appended : written).push(action.relativePath);
570
933
  } else if (hasLegacyHeader(existing)) {
571
- writeFileSync2(absPath, action.content + "\n", "utf-8");
934
+ writeFileSync3(absPath, action.content + "\n", "utf-8");
572
935
  written.push(action.relativePath);
573
936
  } else {
574
937
  const updated = existing.trimEnd() + "\n\n" + action.content + "\n";
575
- writeFileSync2(absPath, updated, "utf-8");
938
+ writeFileSync3(absPath, updated, "utf-8");
576
939
  appended.push(action.relativePath);
577
940
  }
578
941
  }
@@ -580,10 +943,40 @@ var installFiles = (basePath, actions, _meta) => {
580
943
  return { written, appended, skipped };
581
944
  };
582
945
 
583
- // src/lib/gemini-settings.ts
946
+ // src/lib/skill-install.ts
947
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4, rmSync, writeFileSync as writeFileSync4 } from "fs";
948
+ import { dirname as dirname6, resolve as resolve6 } from "path";
949
+ var installSkillPackages = (basePath, packages) => {
950
+ const writtenRoots = [];
951
+ for (const skillPackage of packages) {
952
+ const absRoot = resolve6(basePath, skillPackage.rootDir);
953
+ if (existsSync4(absRoot)) {
954
+ rmSync(absRoot, { recursive: true, force: true });
955
+ }
956
+ for (const file of skillPackage.files) {
957
+ const absPath = resolve6(basePath, file.relativePath);
958
+ mkdirSync4(dirname6(absPath), { recursive: true });
959
+ writeFileSync4(absPath, file.content + "\n", "utf-8");
960
+ }
961
+ writtenRoots.push(skillPackage.rootDir);
962
+ }
963
+ return writtenRoots;
964
+ };
965
+ var removeDirectories = (basePath, relativeDirs) => {
966
+ const removed = [];
967
+ for (const relativeDir of relativeDirs) {
968
+ const absPath = resolve6(basePath, relativeDir);
969
+ if (!existsSync4(absPath)) continue;
970
+ rmSync(absPath, { recursive: true, force: true });
971
+ removed.push(relativeDir);
972
+ }
973
+ return removed;
974
+ };
975
+
976
+ // src/lib/tool-settings.ts
584
977
  import * as p from "@clack/prompts";
585
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync3 } from "fs";
586
- import { join as join7 } from "path";
978
+ import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync6, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
979
+ import { join as join11 } from "path";
587
980
 
588
981
  // src/lib/deep-merge.util.ts
589
982
  var deepMerge = (base, patch) => {
@@ -615,100 +1008,106 @@ var deepRemoveKeys = (base, patch) => {
615
1008
  return result;
616
1009
  };
617
1010
 
618
- // src/lib/gemini-settings.ts
619
- var SETTING_GROUPS = [
620
- {
621
- value: "ui",
622
- label: "UI \u2014 \uC904 \uBC88\uD638 \uC228\uAE30\uAE30",
623
- hint: "ui.showLineNumbers: false \u2014 \uCF54\uB4DC \uBCF5\uC0AC \uC2DC \uC904 \uBC88\uD638\uAC00 \uD3EC\uD568\uB418\uC9C0 \uC54A\uB3C4\uB85D \uBE44\uD65C\uC131\uD654",
624
- patch: { ui: { showLineNumbers: false } }
625
- },
626
- {
627
- value: "plan",
628
- label: "Plan \u2014 \uACC4\uD68D \uD30C\uC77C \uC800\uC7A5 \uBC0F \uBAA8\uB378 \uB77C\uC6B0\uD305",
629
- hint: "general.plan.directory: .gemini/plans, modelRouting: true \u2014 AI \uACC4\uD68D\uC744 \uD30C\uC77C\uB85C \uC800\uC7A5\uD558\uACE0 \uD0DC\uC2A4\uD06C\uBCC4 \uCD5C\uC801 \uBAA8\uB378 \uC790\uB3D9 \uC120\uD0DD",
630
- patch: { general: { plan: { directory: ".gemini/plans", modelRouting: true } } }
631
- },
632
- {
633
- value: "sessionRetention",
634
- label: "Session Retention \u2014 \uC138\uC158 30\uC77C \uBCF4\uC874",
635
- hint: "general.sessionRetention.maxAge: 30d \u2014 \uC774\uC804 \uB300\uD654 \uCEE8\uD14D\uC2A4\uD2B8\uB97C 30\uC77C\uAC04 \uC720\uC9C0",
636
- patch: { general: { sessionRetention: { maxAge: "30d" } } }
637
- },
638
- {
639
- value: "experimental",
640
- label: "Experimental \u2014 JIT \uCEE8\uD14D\uC2A4\uD2B8 + Plan \uAE30\uB2A5",
641
- hint: "experimental.jitContext: true, plan: true \u2014 \uC11C\uBE0C\uB514\uB809\uD1A0\uB9AC \uCEE8\uD14D\uC2A4\uD2B8 \uC9C0\uC5F0 \uB85C\uB529 \uBC0F \uACC4\uD68D \uAE30\uB2A5 \uC2E4\uD5D8\uC801 \uD65C\uC131\uD654",
642
- patch: { experimental: { jitContext: true, plan: true } }
643
- }
644
- ];
645
- var promptGeminiSettings = async () => {
646
- const wantSettings = await p.confirm({
647
- message: "Gemini CLI \uC124\uC815 \uD30C\uC77C(.gemini/settings.json)\uC744 \uC124\uCE58\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
648
- initialValue: true
649
- });
650
- if (p.isCancel(wantSettings) || !wantSettings) return null;
1011
+ // src/lib/prompt-control.ts
1012
+ var PROMPT_CANCELLED = /* @__PURE__ */ Symbol("prompt-cancelled");
1013
+ var isPromptCancelled = (value) => value === PROMPT_CANCELLED;
1014
+
1015
+ // src/lib/tool-settings.ts
1016
+ var promptToolSettings = async (config) => {
1017
+ const want = await p.confirm({ message: config.promptMessage, initialValue: true });
1018
+ if (p.isCancel(want)) return PROMPT_CANCELLED;
1019
+ if (!want) return null;
651
1020
  const selected = await p.multiselect({
652
1021
  message: "\uC124\uCE58\uD560 \uC124\uC815 \uD56D\uBAA9\uC744 \uC120\uD0DD\uD558\uC138\uC694 (\uC2A4\uD398\uC774\uC2A4\uB85C \uD1A0\uAE00)",
653
- options: SETTING_GROUPS.map((g) => ({
654
- value: g.value,
655
- label: g.label,
656
- hint: g.hint
657
- })),
658
- initialValues: SETTING_GROUPS.map((g) => g.value),
1022
+ options: config.groups.map((g) => ({ value: g.value, label: g.label, hint: g.hint })),
1023
+ initialValues: config.groups.map((g) => g.value),
659
1024
  required: false
660
1025
  });
661
- if (p.isCancel(selected)) return null;
1026
+ if (p.isCancel(selected)) return PROMPT_CANCELLED;
662
1027
  return selected;
663
1028
  };
664
- var installGeminiSettings = (basePath, selectedValues) => {
1029
+ var installToolSettings = (basePath, selectedValues, config) => {
665
1030
  if (selectedValues.length === 0) return;
666
- const settingsDir = join7(basePath, ".gemini");
667
- const settingsPath = join7(settingsDir, "settings.json");
1031
+ const settingsDir = join11(basePath, config.dirName);
1032
+ const settingsPath = join11(settingsDir, config.fileName);
668
1033
  let existing = {};
669
- if (existsSync3(settingsPath)) {
1034
+ if (existsSync5(settingsPath)) {
670
1035
  try {
671
- existing = JSON.parse(readFileSync5(settingsPath, "utf-8"));
1036
+ existing = JSON.parse(readFileSync6(settingsPath, "utf-8"));
672
1037
  } catch {
673
1038
  }
674
1039
  }
675
1040
  let merged = existing;
676
1041
  for (const val of selectedValues) {
677
- const group = SETTING_GROUPS.find((g) => g.value === val);
1042
+ const group = config.groups.find((g) => g.value === val);
678
1043
  if (!group) continue;
679
1044
  merged = deepMerge(merged, group.patch);
680
1045
  }
681
- mkdirSync3(settingsDir, { recursive: true });
682
- writeFileSync3(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
1046
+ mkdirSync5(settingsDir, { recursive: true });
1047
+ writeFileSync5(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
683
1048
  };
684
- var uninstallGeminiSettings = (basePath, selectedValues) => {
685
- const settingsPath = join7(basePath, ".gemini", "settings.json");
686
- if (!existsSync3(settingsPath)) return "notFound";
1049
+ var uninstallToolSettings = (basePath, selectedValues, config) => {
1050
+ const settingsPath = join11(basePath, config.dirName, config.fileName);
1051
+ if (!existsSync5(settingsPath)) return "notFound";
687
1052
  let existing = {};
688
1053
  try {
689
- existing = JSON.parse(readFileSync5(settingsPath, "utf-8"));
1054
+ existing = JSON.parse(readFileSync6(settingsPath, "utf-8"));
690
1055
  } catch {
691
- rmSync(settingsPath, { force: true });
1056
+ rmSync2(settingsPath, { force: true });
692
1057
  return "deleted";
693
1058
  }
694
1059
  let result = existing;
695
1060
  for (const val of selectedValues) {
696
- const group = SETTING_GROUPS.find((g) => g.value === val);
1061
+ const group = config.groups.find((g) => g.value === val);
697
1062
  if (!group) continue;
698
1063
  result = deepRemoveKeys(result, group.patch);
699
1064
  }
700
1065
  if (Object.keys(result).length === 0) {
701
- rmSync(settingsPath, { force: true });
1066
+ rmSync2(settingsPath, { force: true });
702
1067
  return "deleted";
703
1068
  }
704
- writeFileSync3(settingsPath, JSON.stringify(result, null, 2) + "\n", "utf-8");
1069
+ writeFileSync5(settingsPath, JSON.stringify(result, null, 2) + "\n", "utf-8");
705
1070
  return "cleaned";
706
1071
  };
707
1072
 
1073
+ // src/lib/gemini-settings.ts
1074
+ var SETTING_GROUPS = [
1075
+ {
1076
+ value: "ui",
1077
+ label: "UI \u2014 \uC904 \uBC88\uD638 \uC228\uAE30\uAE30",
1078
+ hint: "ui.showLineNumbers: false \u2014 \uCF54\uB4DC \uBCF5\uC0AC \uC2DC \uC904 \uBC88\uD638\uAC00 \uD3EC\uD568\uB418\uC9C0 \uC54A\uB3C4\uB85D \uBE44\uD65C\uC131\uD654",
1079
+ patch: { ui: { showLineNumbers: false } }
1080
+ },
1081
+ {
1082
+ value: "plan",
1083
+ label: "Plan \u2014 \uACC4\uD68D \uD30C\uC77C \uC800\uC7A5 \uBC0F \uBAA8\uB378 \uB77C\uC6B0\uD305",
1084
+ hint: "general.plan.directory: .gemini/plans, modelRouting: true \u2014 AI \uACC4\uD68D\uC744 \uD30C\uC77C\uB85C \uC800\uC7A5\uD558\uACE0 \uD0DC\uC2A4\uD06C\uBCC4 \uCD5C\uC801 \uBAA8\uB378 \uC790\uB3D9 \uC120\uD0DD",
1085
+ patch: { general: { plan: { directory: ".gemini/plans", modelRouting: true } } }
1086
+ },
1087
+ {
1088
+ value: "sessionRetention",
1089
+ label: "Session Retention \u2014 \uC138\uC158 30\uC77C \uBCF4\uC874",
1090
+ hint: "general.sessionRetention.maxAge: 30d \u2014 \uC774\uC804 \uB300\uD654 \uCEE8\uD14D\uC2A4\uD2B8\uB97C 30\uC77C\uAC04 \uC720\uC9C0",
1091
+ patch: { general: { sessionRetention: { maxAge: "30d" } } }
1092
+ },
1093
+ {
1094
+ value: "experimental",
1095
+ label: "Experimental \u2014 JIT \uCEE8\uD14D\uC2A4\uD2B8 + Plan \uAE30\uB2A5",
1096
+ hint: "experimental.jitContext: true, plan: true \u2014 \uC11C\uBE0C\uB514\uB809\uD1A0\uB9AC \uCEE8\uD14D\uC2A4\uD2B8 \uC9C0\uC5F0 \uB85C\uB529 \uBC0F \uACC4\uD68D \uAE30\uB2A5 \uC2E4\uD5D8\uC801 \uD65C\uC131\uD654",
1097
+ patch: { experimental: { jitContext: true, plan: true } }
1098
+ }
1099
+ ];
1100
+ var CONFIG = {
1101
+ dirName: ".gemini",
1102
+ fileName: "settings.json",
1103
+ promptMessage: "Gemini CLI \uC124\uC815 \uD30C\uC77C(.gemini/settings.json)\uC744 \uC124\uCE58\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
1104
+ groups: SETTING_GROUPS
1105
+ };
1106
+ var promptGeminiSettings = () => promptToolSettings(CONFIG);
1107
+ var installGeminiSettings = (basePath, selectedValues) => installToolSettings(basePath, selectedValues, CONFIG);
1108
+ var uninstallGeminiSettings = (basePath, selectedValues) => uninstallToolSettings(basePath, selectedValues, CONFIG);
1109
+
708
1110
  // src/lib/claude-settings.ts
709
- import * as p2 from "@clack/prompts";
710
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync6, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
711
- import { join as join8 } from "path";
712
1111
  var SETTING_GROUPS2 = [
713
1112
  {
714
1113
  value: "model",
@@ -723,73 +1122,20 @@ var SETTING_GROUPS2 = [
723
1122
  patch: { plansDirectory: "./.claude/plans" }
724
1123
  }
725
1124
  ];
726
- var promptClaudeSettings = async () => {
727
- const wantSettings = await p2.confirm({
728
- message: "Claude Code \uC124\uC815 \uD30C\uC77C(.claude/settings.local.json)\uC744 \uC124\uCE58\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
729
- initialValue: true
730
- });
731
- if (p2.isCancel(wantSettings) || !wantSettings) return null;
732
- const selected = await p2.multiselect({
733
- message: "\uC124\uCE58\uD560 \uC124\uC815 \uD56D\uBAA9\uC744 \uC120\uD0DD\uD558\uC138\uC694 (\uC2A4\uD398\uC774\uC2A4\uB85C \uD1A0\uAE00)",
734
- options: SETTING_GROUPS2.map((g) => ({
735
- value: g.value,
736
- label: g.label,
737
- hint: g.hint
738
- })),
739
- initialValues: SETTING_GROUPS2.map((g) => g.value),
740
- required: false
741
- });
742
- if (p2.isCancel(selected)) return null;
743
- return selected;
744
- };
745
- var installClaudeSettings = (basePath, selectedValues) => {
746
- if (selectedValues.length === 0) return;
747
- const settingsDir = join8(basePath, ".claude");
748
- const settingsPath = join8(settingsDir, "settings.local.json");
749
- let existing = {};
750
- if (existsSync4(settingsPath)) {
751
- try {
752
- existing = JSON.parse(readFileSync6(settingsPath, "utf-8"));
753
- } catch {
754
- }
755
- }
756
- let merged = existing;
757
- for (const val of selectedValues) {
758
- const group = SETTING_GROUPS2.find((g) => g.value === val);
759
- if (!group) continue;
760
- merged = deepMerge(merged, group.patch);
761
- }
762
- mkdirSync4(settingsDir, { recursive: true });
763
- writeFileSync4(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
764
- };
765
- var uninstallClaudeSettings = (basePath, selectedValues) => {
766
- const settingsPath = join8(basePath, ".claude", "settings.local.json");
767
- if (!existsSync4(settingsPath)) return "notFound";
768
- let existing = {};
769
- try {
770
- existing = JSON.parse(readFileSync6(settingsPath, "utf-8"));
771
- } catch {
772
- rmSync2(settingsPath, { force: true });
773
- return "deleted";
774
- }
775
- let result = existing;
776
- for (const val of selectedValues) {
777
- const group = SETTING_GROUPS2.find((g) => g.value === val);
778
- if (!group) continue;
779
- result = deepRemoveKeys(result, group.patch);
780
- }
781
- if (Object.keys(result).length === 0) {
782
- rmSync2(settingsPath, { force: true });
783
- return "deleted";
784
- }
785
- writeFileSync4(settingsPath, JSON.stringify(result, null, 2) + "\n", "utf-8");
786
- return "cleaned";
1125
+ var CONFIG2 = {
1126
+ dirName: ".claude",
1127
+ fileName: "settings.local.json",
1128
+ promptMessage: "Claude Code \uC124\uC815 \uD30C\uC77C(.claude/settings.local.json)\uC744 \uC124\uCE58\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
1129
+ groups: SETTING_GROUPS2
787
1130
  };
1131
+ var promptClaudeSettings = () => promptToolSettings(CONFIG2);
1132
+ var installClaudeSettings = (basePath, selectedValues) => installToolSettings(basePath, selectedValues, CONFIG2);
1133
+ var uninstallClaudeSettings = (basePath, selectedValues) => uninstallToolSettings(basePath, selectedValues, CONFIG2);
788
1134
 
789
1135
  // src/lib/prettier-ignore.ts
790
- import * as p3 from "@clack/prompts";
791
- import { existsSync as existsSync5, readFileSync as readFileSync7, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "fs";
792
- import { join as join9 } from "path";
1136
+ import * as p2 from "@clack/prompts";
1137
+ import { existsSync as existsSync6, readFileSync as readFileSync7, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "fs";
1138
+ import { join as join12 } from "path";
793
1139
  var PRETTIER_IGNORE_CONTENT = `# CLAUDE
794
1140
  .claude/rules/
795
1141
  **/CLAUDE.md
@@ -847,31 +1193,31 @@ var stripAiOpsSection2 = (content) => {
847
1193
  return result.join("\n");
848
1194
  };
849
1195
  var promptPrettierIgnore = async () => {
850
- const want = await p3.confirm({
1196
+ const want = await p2.confirm({
851
1197
  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)",
852
1198
  initialValue: false
853
1199
  });
854
- if (p3.isCancel(want)) return false;
1200
+ if (p2.isCancel(want)) return PROMPT_CANCELLED;
855
1201
  return want;
856
1202
  };
857
1203
  var installPrettierIgnore = (basePath) => {
858
- const filePath = join9(basePath, ".prettierignore");
1204
+ const filePath = join12(basePath, ".prettierignore");
859
1205
  const section = wrapSection(PRETTIER_IGNORE_CONTENT);
860
- if (!existsSync5(filePath)) {
861
- writeFileSync5(filePath, section + "\n", "utf-8");
1206
+ if (!existsSync6(filePath)) {
1207
+ writeFileSync6(filePath, section + "\n", "utf-8");
862
1208
  return;
863
1209
  }
864
1210
  const existing = readFileSync7(filePath, "utf-8");
865
1211
  if (hasAiOpsSection2(existing)) {
866
- writeFileSync5(filePath, replaceSection(existing, PRETTIER_IGNORE_CONTENT), "utf-8");
1212
+ writeFileSync6(filePath, replaceSection(existing, PRETTIER_IGNORE_CONTENT), "utf-8");
867
1213
  return;
868
1214
  }
869
1215
  const separator = existing.endsWith("\n") ? "\n" : "\n\n";
870
- writeFileSync5(filePath, existing + separator + section + "\n", "utf-8");
1216
+ writeFileSync6(filePath, existing + separator + section + "\n", "utf-8");
871
1217
  };
872
1218
  var uninstallPrettierIgnore = (basePath) => {
873
- const filePath = join9(basePath, ".prettierignore");
874
- if (!existsSync5(filePath)) return "notFound";
1219
+ const filePath = join12(basePath, ".prettierignore");
1220
+ if (!existsSync6(filePath)) return "notFound";
875
1221
  const existing = readFileSync7(filePath, "utf-8");
876
1222
  if (!hasAiOpsSection2(existing)) return "notFound";
877
1223
  const stripped = stripAiOpsSection2(existing).trim();
@@ -879,10 +1225,54 @@ var uninstallPrettierIgnore = (basePath) => {
879
1225
  rmSync3(filePath, { force: true });
880
1226
  return "deleted";
881
1227
  }
882
- writeFileSync5(filePath, stripped + "\n", "utf-8");
1228
+ writeFileSync6(filePath, stripped + "\n", "utf-8");
883
1229
  return "cleaned";
884
1230
  };
885
1231
 
1232
+ // src/lib/skill-state.ts
1233
+ var resolveSkillScope = (params) => {
1234
+ if (params.scope !== void 0) {
1235
+ if (params.scope === "user") return "user";
1236
+ if (params.scope === "project") return "project";
1237
+ throw new Error(`Unsupported scope: ${params.scope}`);
1238
+ }
1239
+ if (params.project) return "project";
1240
+ return "user";
1241
+ };
1242
+ var resolveRequestedTools = (params) => {
1243
+ if (params.requested === void 0 || params.requested.length === 0) {
1244
+ return [...params.supported];
1245
+ }
1246
+ const supportedSet = new Set(params.supported);
1247
+ const invalid = params.requested.filter((tool) => !supportedSet.has(tool));
1248
+ if (invalid.length > 0) {
1249
+ throw new Error(`Unsupported tools requested: ${invalid.join(", ")}`);
1250
+ }
1251
+ return [...params.requested];
1252
+ };
1253
+ var TOOL_ORDER = [SKILL_TOOL.CLAUDE_CODE, SKILL_TOOL.CODEX, SKILL_TOOL.GEMINI];
1254
+ var mergeSkillTools = (params) => {
1255
+ const merged = /* @__PURE__ */ new Set([...params.existing ?? [], ...params.requested]);
1256
+ return TOOL_ORDER.filter((tool) => merged.has(tool));
1257
+ };
1258
+ var subtractSkillTools = (params) => {
1259
+ const installed = new Set(params.installed ?? []);
1260
+ return params.requested.filter((tool) => !installed.has(tool));
1261
+ };
1262
+ var upsertInstalledSkill = (installedSkills, nextSkill) => {
1263
+ const nextSkillId = resolveCanonicalSkillId(nextSkill.id);
1264
+ const remaining = installedSkills.filter((skill) => resolveCanonicalSkillId(skill.id) !== nextSkillId);
1265
+ return [...remaining, nextSkill];
1266
+ };
1267
+ var removeInstalledSkill = (installedSkills, skillId) => {
1268
+ const targetSkillId = resolveCanonicalSkillId(skillId);
1269
+ return installedSkills.filter((skill) => resolveCanonicalSkillId(skill.id) !== targetSkillId);
1270
+ };
1271
+ var findInstalledSkill = (installedSkills, skillId) => {
1272
+ const targetSkillId = resolveCanonicalSkillId(skillId);
1273
+ return installedSkills.find((skill) => resolveCanonicalSkillId(skill.id) === targetSkillId);
1274
+ };
1275
+
886
1276
  // src/commands/init.ts
887
1277
  var TOOL_OPTIONS = [
888
1278
  { value: "claude-code", label: "Claude Code" },
@@ -891,203 +1281,393 @@ var TOOL_OPTIONS = [
891
1281
  ];
892
1282
  var deduplicateRules = (rules) => {
893
1283
  const seen = /* @__PURE__ */ new Set();
894
- return rules.filter((r) => {
895
- if (seen.has(r.id)) return false;
896
- seen.add(r.id);
1284
+ return rules.filter((rule) => {
1285
+ if (seen.has(rule.id)) return false;
1286
+ seen.add(rule.id);
897
1287
  return true;
898
1288
  });
899
1289
  };
900
- var selectPresetAndFineTune = async (workspaceName, presets, allRules) => {
901
- const preset = await p4.select({
1290
+ var formatToolList = (toolIds) => toolIds.join(", ");
1291
+ var deduplicateSkillTargets = (targets) => {
1292
+ const merged = /* @__PURE__ */ new Map();
1293
+ for (const target of targets) {
1294
+ const previous = merged.get(target.skill.id);
1295
+ if (!previous) {
1296
+ merged.set(target.skill.id, {
1297
+ skill: target.skill,
1298
+ requestedTools: [...target.requestedTools]
1299
+ });
1300
+ continue;
1301
+ }
1302
+ merged.set(target.skill.id, {
1303
+ skill: target.skill,
1304
+ requestedTools: mergeSkillTools({
1305
+ existing: previous.requestedTools,
1306
+ requested: target.requestedTools
1307
+ })
1308
+ });
1309
+ }
1310
+ return [...merged.values()].sort((a, b) => a.skill.id.localeCompare(b.skill.id));
1311
+ };
1312
+ var resolveSupportedRequestedTools = (skill, selectedTools) => selectedTools.filter((toolId) => skill.supported_tools.includes(toolId));
1313
+ var partitionPresetSkills = (params) => {
1314
+ const globalSkills = [];
1315
+ const installableSkills = [];
1316
+ for (const skill of resolvePresetSkills(params.preset, params.allSkills)) {
1317
+ if (skill.kind !== "reference") {
1318
+ continue;
1319
+ }
1320
+ const supportedRequestedTools = resolveSupportedRequestedTools(skill, params.selectedTools);
1321
+ if (supportedRequestedTools.length === 0) {
1322
+ continue;
1323
+ }
1324
+ const installedGlobalSkill = findInstalledSkill(params.globalInstalledSkills, skill.id);
1325
+ const availableTools = installedGlobalSkill ? supportedRequestedTools.filter((toolId) => installedGlobalSkill.tools.includes(toolId)) : [];
1326
+ const requestedTools = subtractSkillTools({
1327
+ requested: supportedRequestedTools,
1328
+ installed: availableTools
1329
+ });
1330
+ if (requestedTools.length === 0) {
1331
+ globalSkills.push({
1332
+ skill,
1333
+ availableTools
1334
+ });
1335
+ continue;
1336
+ }
1337
+ installableSkills.push({
1338
+ skill,
1339
+ requestedTools,
1340
+ globalTools: availableTools
1341
+ });
1342
+ }
1343
+ return {
1344
+ globalSkills,
1345
+ installableSkills
1346
+ };
1347
+ };
1348
+ var selectPresetAndFineTune = async (workspaceName, presets, allRules, allSkills, selectedTools, globalInstalledSkills) => {
1349
+ const preset = await p3.select({
902
1350
  message: `[${workspaceName}] \uD504\uB9AC\uC14B\uC744 \uC120\uD0DD\uD558\uC138\uC694`,
903
- options: presets.map((pr) => ({
904
- value: pr,
905
- label: pr.id,
906
- hint: pr.description
1351
+ options: presets.map((candidate) => ({
1352
+ value: candidate,
1353
+ label: candidate.id,
1354
+ hint: candidate.description
907
1355
  }))
908
1356
  });
909
- if (p4.isCancel(preset)) return null;
910
- const presetRuleGroups = resolvePresetRuleGroups(preset, allRules);
911
- const globalGroups = presetRuleGroups.filter((group) => group.rules.every(isGlobalRule));
912
- const domainGroups = presetRuleGroups.filter((group) => !group.rules.every(isGlobalRule));
913
- const globalGroupIds = globalGroups.map((group) => group.id);
914
- const globalRules = globalGroupIds.length > 0 ? resolvePresetRules({ ...preset, rules: globalGroupIds }, allRules) : [];
915
- if (globalRules.length > 0) {
916
- p4.note(globalRules.map((r) => ` \u2713 ${r.id}`).join("\n"), `[${workspaceName}] \uAE30\uBCF8 \uADDC\uCE59 (\uC7A0\uAE08)`);
917
- }
918
- if (domainGroups.length === 0) {
919
- return { workspace: workspaceName, preset, finalRules: resolvePresetRules(preset, allRules) };
920
- }
921
- const selectedDomain = await p4.multiselect({
922
- message: `[${workspaceName}] \uB3C4\uBA54\uC778 \uADDC\uCE59 \uC120\uD0DD (\uD574\uC81C\uD558\uC5EC \uC81C\uC678)`,
923
- options: domainGroups.map((group) => ({ value: group.id, label: group.id })),
924
- initialValues: domainGroups.map((group) => group.id),
1357
+ if (p3.isCancel(preset)) return null;
1358
+ const finalRules = resolvePresetRules(preset, allRules);
1359
+ if (finalRules.length > 0) {
1360
+ p3.note(finalRules.map((rule) => ` \u2713 ${rule.id}`).join("\n"), `[${workspaceName}] core rules (\uC7A0\uAE08)`);
1361
+ }
1362
+ const { globalSkills, installableSkills } = partitionPresetSkills({
1363
+ preset,
1364
+ allSkills,
1365
+ selectedTools,
1366
+ globalInstalledSkills
1367
+ });
1368
+ if (globalSkills.length > 0) {
1369
+ const globalLines = globalSkills.map(
1370
+ ({ skill, availableTools }) => ` \u2713 ${skill.id} (${formatToolList(availableTools)})`
1371
+ );
1372
+ p3.note(globalLines.join("\n"), `[${workspaceName}] already available globally`);
1373
+ }
1374
+ if (installableSkills.length === 0) {
1375
+ p3.note(" \uC0C8\uB85C \uC124\uCE58\uD560 reference skill\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.", `[${workspaceName}] installable reference skills`);
1376
+ return {
1377
+ workspace: workspaceName,
1378
+ preset,
1379
+ finalRules,
1380
+ finalSkillTargets: []
1381
+ };
1382
+ }
1383
+ const selectedSkillIds = await p3.multiselect({
1384
+ message: `[${workspaceName}] installable reference skills \uC120\uD0DD`,
1385
+ options: installableSkills.map(({ skill, requestedTools, globalTools }) => ({
1386
+ value: skill.id,
1387
+ label: skill.id,
1388
+ hint: globalTools.length > 0 ? `global: ${formatToolList(globalTools)} / install: ${formatToolList(requestedTools)}` : `${skill.description} / install: ${formatToolList(requestedTools)}`
1389
+ })),
1390
+ initialValues: installableSkills.map(({ skill }) => skill.id),
925
1391
  required: false
926
1392
  });
927
- if (p4.isCancel(selectedDomain)) return null;
928
- const selectedLogicalRuleIds = [...globalGroupIds, ...selectedDomain];
1393
+ if (p3.isCancel(selectedSkillIds)) return null;
1394
+ const selectedSkillSet = new Set(selectedSkillIds);
929
1395
  return {
930
1396
  workspace: workspaceName,
931
1397
  preset,
932
- finalRules: resolvePresetRules({ ...preset, rules: selectedLogicalRuleIds }, allRules)
1398
+ finalRules,
1399
+ finalSkillTargets: installableSkills.filter(({ skill }) => selectedSkillSet.has(skill.id)).map(({ skill, requestedTools }) => ({
1400
+ skill,
1401
+ requestedTools
1402
+ }))
933
1403
  };
934
1404
  };
1405
+ var selectInitSkillScope = async () => {
1406
+ const scope = await p3.select({
1407
+ message: "\uC120\uD0DD\uB41C skills\uB97C \uC5B4\uB514\uC5D0 \uC124\uCE58\uD560\uAE4C\uC694?",
1408
+ options: [
1409
+ { value: "user", label: "user (global)", hint: "\uAE30\uBCF8\uAC12. \uC5EC\uB7EC \uD504\uB85C\uC81D\uD2B8\uC5D0\uC11C \uC7AC\uC0AC\uC6A9" },
1410
+ { value: "project", label: "project", hint: "\uD604\uC7AC \uD504\uB85C\uC81D\uD2B8\uC5D0\uB9CC \uC124\uCE58" }
1411
+ ]
1412
+ });
1413
+ return p3.isCancel(scope) ? null : scope;
1414
+ };
935
1415
  var initCommand = async () => {
936
1416
  const basePath = resolveBasePath();
1417
+ const userBasePath = resolveUserBasePath();
937
1418
  const rulesDir = resolveRulesDir();
938
- p4.intro("ai-ops init");
939
- const selectedTools = await p4.multiselect({
940
- message: "AI \uB3C4\uAD6C\uB97C \uC120\uD0DD\uD558\uC138\uC694",
941
- options: TOOL_OPTIONS,
942
- required: true
943
- });
944
- if (p4.isCancel(selectedTools)) {
945
- p4.cancel("\uCDE8\uC18C\uB428");
946
- process.exit(0);
947
- }
948
- const isMonorepo = await p4.confirm({
949
- message: "\uBAA8\uB178\uB808\uD3EC \uD504\uB85C\uC81D\uD2B8\uC785\uB2C8\uAE4C?",
950
- initialValue: false
951
- });
952
- if (p4.isCancel(isMonorepo)) {
953
- p4.cancel("\uCDE8\uC18C\uB428");
954
- process.exit(0);
955
- }
956
- const allRules = loadAllRules(rulesDir);
957
- const presets = loadPresets(resolvePresetsPath());
958
- const sourceHash = computeSourceHash(rulesDir);
959
- const mappings = [];
960
- if (!isMonorepo) {
961
- const mapping = await selectPresetAndFineTune(".", presets, allRules);
962
- if (!mapping) {
963
- p4.cancel("\uCDE8\uC18C\uB428");
964
- process.exit(0);
1419
+ const skillsDir = resolveSkillsDir();
1420
+ const spinner3 = p3.spinner();
1421
+ let spinnerStarted = false;
1422
+ const cancelInit = (params) => {
1423
+ if (spinnerStarted) {
1424
+ spinner3.stop("\uC124\uCE58 \uC911\uB2E8\uB428");
1425
+ spinnerStarted = false;
965
1426
  }
966
- mappings.push(mapping);
967
- } else {
968
- const candidates = listWorkspaceCandidates(basePath);
969
- const selectedWorkspaces = await p4.multiselect({
970
- message: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uC120\uD0DD\uD558\uC138\uC694",
971
- options: candidates.map((c) => ({ value: c, label: c })),
1427
+ p3.cancel(params?.message ?? "\uCDE8\uC18C\uB428");
1428
+ process.exit(params?.exitCode ?? 0);
1429
+ };
1430
+ const handleSigint = () => cancelInit({
1431
+ message: "\uC0AC\uC6A9\uC790 \uC694\uCCAD\uC73C\uB85C init\uC774 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4.",
1432
+ exitCode: 130
1433
+ });
1434
+ process.once("SIGINT", handleSigint);
1435
+ try {
1436
+ p3.intro("ai-ops init");
1437
+ const selectedTools = await p3.multiselect({
1438
+ message: "AI \uB3C4\uAD6C\uB97C \uC120\uD0DD\uD558\uC138\uC694",
1439
+ options: TOOL_OPTIONS,
972
1440
  required: true
973
1441
  });
974
- if (p4.isCancel(selectedWorkspaces)) {
975
- p4.cancel("\uCDE8\uC18C\uB428");
976
- process.exit(0);
1442
+ if (p3.isCancel(selectedTools)) {
1443
+ cancelInit();
977
1444
  }
978
- for (const ws of selectedWorkspaces) {
979
- const mapping = await selectPresetAndFineTune(ws, presets, allRules);
980
- if (!mapping) {
981
- p4.cancel("\uCDE8\uC18C\uB428");
982
- process.exit(0);
983
- }
984
- mappings.push(mapping);
1445
+ const isMonorepo = await p3.confirm({
1446
+ message: "\uBAA8\uB178\uB808\uD3EC \uD504\uB85C\uC81D\uD2B8\uC785\uB2C8\uAE4C?",
1447
+ initialValue: false
1448
+ });
1449
+ if (p3.isCancel(isMonorepo)) {
1450
+ cancelInit();
985
1451
  }
986
- }
987
- const geminiSettingValues = selectedTools.includes("gemini") ? await promptGeminiSettings() : null;
988
- const claudeSettingValues = selectedTools.includes("claude-code") ? await promptClaudeSettings() : null;
989
- const wantPrettierIgnore = await promptPrettierIgnore();
990
- const s = p4.spinner();
991
- s.start("\uADDC\uCE59 \uC124\uCE58 \uC911...");
992
- const meta = { sourceHash, generatedAt: (/* @__PURE__ */ new Date()).toISOString() };
993
- const allInstalledFiles = [];
994
- const allAppended = [];
995
- for (const toolId of selectedTools) {
996
- if (isMonorepo) {
997
- const allRules2 = deduplicateRules(mappings.flatMap((m) => m.finalRules));
998
- const workspaceMappings = mappings.map((m) => ({
999
- path: m.workspace,
1000
- ruleIds: m.finalRules.map((r) => r.id)
1001
- }));
1002
- const renderResult = renderForTool(toolId, allRules2, workspaceMappings);
1003
- const actions = buildInstallPlan({ toolId, renderResult, meta });
1004
- const result = installFiles(basePath, actions, meta);
1005
- allInstalledFiles.push(...result.written);
1006
- allAppended.push(...result.appended);
1452
+ const allRules = loadAllRules(rulesDir);
1453
+ const allSkills = loadAllSkills(skillsDir);
1454
+ const presets = loadPresets(resolvePresetsPath());
1455
+ const sourceHash = computeSourceHash(resolveCompilerDataDir());
1456
+ const globalInstalledSkills = readSkillRegistry(resolveSkillRegistryPath(userBasePath))?.skills ?? [];
1457
+ const mappings = [];
1458
+ if (!isMonorepo) {
1459
+ const mapping = await selectPresetAndFineTune(
1460
+ ".",
1461
+ presets,
1462
+ allRules,
1463
+ allSkills,
1464
+ selectedTools,
1465
+ globalInstalledSkills
1466
+ ) ?? cancelInit();
1467
+ mappings.push(mapping);
1007
1468
  } else {
1008
- const renderResult = renderForTool(toolId, mappings[0].finalRules);
1009
- const actions = buildInstallPlan({ toolId, renderResult, meta });
1010
- const result = installFiles(basePath, actions, meta);
1011
- allInstalledFiles.push(...result.written);
1012
- allAppended.push(...result.appended);
1469
+ const candidates = listWorkspaceCandidates(basePath);
1470
+ const selectedWorkspaces = await p3.multiselect({
1471
+ message: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uC120\uD0DD\uD558\uC138\uC694",
1472
+ options: candidates.map((candidate) => ({ value: candidate, label: candidate })),
1473
+ required: true
1474
+ });
1475
+ if (p3.isCancel(selectedWorkspaces)) {
1476
+ cancelInit();
1477
+ }
1478
+ for (const workspace of selectedWorkspaces) {
1479
+ const mapping = await selectPresetAndFineTune(
1480
+ workspace,
1481
+ presets,
1482
+ allRules,
1483
+ allSkills,
1484
+ selectedTools,
1485
+ globalInstalledSkills
1486
+ ) ?? cancelInit();
1487
+ mappings.push(mapping);
1488
+ }
1013
1489
  }
1490
+ const selectedSkillTargets = deduplicateSkillTargets(mappings.flatMap((mapping) => mapping.finalSkillTargets));
1491
+ const skillScope = selectedSkillTargets.length > 0 ? await selectInitSkillScope() : null;
1492
+ if (selectedSkillTargets.length > 0 && skillScope === null) {
1493
+ cancelInit();
1494
+ }
1495
+ const geminiSettingValues = selectedTools.includes("gemini") ? await promptGeminiSettings() : null;
1496
+ const resolvedGeminiSettingValues = isPromptCancelled(geminiSettingValues) ? cancelInit() : geminiSettingValues;
1497
+ const claudeSettingValues = selectedTools.includes("claude-code") ? await promptClaudeSettings() : null;
1498
+ const resolvedClaudeSettingValues = isPromptCancelled(claudeSettingValues) ? cancelInit() : claudeSettingValues;
1499
+ const wantPrettierIgnore = await promptPrettierIgnore();
1500
+ const resolvedWantPrettierIgnore = isPromptCancelled(wantPrettierIgnore) ? cancelInit() : wantPrettierIgnore;
1501
+ spinner3.start("\uADDC\uCE59 \uC124\uCE58 \uC911...");
1502
+ spinnerStarted = true;
1503
+ const meta = { sourceHash, generatedAt: (/* @__PURE__ */ new Date()).toISOString() };
1504
+ const allInstalledFiles = [];
1505
+ const allAppended = [];
1506
+ const selectedRuleIds = deduplicateRules(mappings.flatMap((mapping) => mapping.finalRules)).map((rule) => rule.id);
1507
+ let projectInstalledSkills = [];
1508
+ if (selectedSkillTargets.length > 0 && skillScope !== null) {
1509
+ const skillBasePath = skillScope === "project" ? basePath : userBasePath;
1510
+ const installedSkills = selectedSkillTargets.map(({ skill, requestedTools }) => {
1511
+ const existingUserSkill = skillScope === "user" ? findInstalledSkill(globalInstalledSkills, skill.id) : void 0;
1512
+ const nextRequestedTools = skillScope === "user" ? mergeSkillTools({
1513
+ existing: existingUserSkill?.tools,
1514
+ requested: requestedTools
1515
+ }) : requestedTools;
1516
+ const { packages, installedSkill } = buildSkillInstallPlan({
1517
+ skill,
1518
+ requestedTools: nextRequestedTools,
1519
+ scope: skillScope
1520
+ });
1521
+ installSkillPackages(skillBasePath, packages);
1522
+ return installedSkill;
1523
+ });
1524
+ if (skillScope === "project") {
1525
+ projectInstalledSkills = installedSkills;
1526
+ } else {
1527
+ const registryPath = resolveSkillRegistryPath(skillBasePath);
1528
+ const previous = readSkillRegistry(registryPath);
1529
+ const nextSkills = installedSkills.reduce(
1530
+ (acc, installedSkill) => upsertInstalledSkill(acc, installedSkill),
1531
+ previous?.skills ?? []
1532
+ );
1533
+ writeSkillRegistry(registryPath, {
1534
+ skills: nextSkills,
1535
+ cliVersion: getCliVersion(),
1536
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
1537
+ });
1538
+ }
1539
+ }
1540
+ for (const toolId of selectedTools) {
1541
+ if (isMonorepo) {
1542
+ const allWorkspaceRules = deduplicateRules(mappings.flatMap((mapping) => mapping.finalRules));
1543
+ const workspaceMappings = mappings.map((mapping) => ({
1544
+ path: mapping.workspace,
1545
+ ruleIds: mapping.finalRules.map((rule) => rule.id)
1546
+ }));
1547
+ const renderResult = renderForTool(toolId, allWorkspaceRules, workspaceMappings);
1548
+ const actions = buildInstallPlan({ toolId, renderResult, meta });
1549
+ const result = installFiles(basePath, actions, meta);
1550
+ allInstalledFiles.push(...result.written);
1551
+ allAppended.push(...result.appended);
1552
+ } else {
1553
+ const renderResult = renderForTool(toolId, mappings[0].finalRules);
1554
+ const actions = buildInstallPlan({ toolId, renderResult, meta });
1555
+ const result = installFiles(basePath, actions, meta);
1556
+ allInstalledFiles.push(...result.written);
1557
+ allAppended.push(...result.appended);
1558
+ }
1559
+ }
1560
+ if (resolvedGeminiSettingValues && resolvedGeminiSettingValues.length > 0) {
1561
+ installGeminiSettings(basePath, resolvedGeminiSettingValues);
1562
+ }
1563
+ if (resolvedClaudeSettingValues && resolvedClaudeSettingValues.length > 0) {
1564
+ installClaudeSettings(basePath, resolvedClaudeSettingValues);
1565
+ }
1566
+ if (resolvedWantPrettierIgnore) {
1567
+ installPrettierIgnore(basePath);
1568
+ }
1569
+ spinner3.stop("\uADDC\uCE59 \uC124\uCE58 \uC644\uB8CC");
1570
+ spinnerStarted = false;
1571
+ const workspacesRecord = isMonorepo ? Object.fromEntries(
1572
+ mappings.map((mapping) => [
1573
+ mapping.workspace,
1574
+ {
1575
+ preset: mapping.preset.id,
1576
+ rules: mapping.finalRules.map((rule) => rule.id)
1577
+ }
1578
+ ])
1579
+ ) : void 0;
1580
+ const manifest = buildManifest({
1581
+ tools: selectedTools,
1582
+ scope: "project",
1583
+ preset: !isMonorepo ? mappings[0].preset.id : void 0,
1584
+ workspaces: workspacesRecord,
1585
+ installedRules: selectedRuleIds,
1586
+ installedFiles: allInstalledFiles,
1587
+ installedSkills: projectInstalledSkills,
1588
+ appendedFiles: allAppended,
1589
+ settings: resolvedClaudeSettingValues || resolvedGeminiSettingValues || resolvedWantPrettierIgnore ? {
1590
+ claude: resolvedClaudeSettingValues ? [...resolvedClaudeSettingValues] : void 0,
1591
+ gemini: resolvedGeminiSettingValues ? [...resolvedGeminiSettingValues] : void 0,
1592
+ prettierignore: resolvedWantPrettierIgnore || void 0
1593
+ } : void 0,
1594
+ cliVersion: getCliVersion(),
1595
+ sourceHash
1596
+ });
1597
+ writeManifest(resolveManifestPath(basePath), manifest);
1598
+ if (allAppended.length > 0) {
1599
+ p3.log.info(`\uAE30\uC874 \uD30C\uC77C\uC5D0 \uC139\uC158 \uCD94\uAC00\uB428 (\uB0B4\uC6A9 \uBCF4\uC874):
1600
+ ${allAppended.map((file) => ` ${file}`).join("\n")}`);
1601
+ }
1602
+ p3.log.success(`\uC124\uCE58\uB41C core rules: ${selectedRuleIds.length}\uAC1C`);
1603
+ p3.log.success(`\uC124\uCE58\uB41C skills: ${selectedSkillTargets.length}\uAC1C${skillScope ? ` (${skillScope})` : ""}`);
1604
+ if (selectedSkillTargets.length > 0 && skillScope === "user") {
1605
+ p3.log.info("global skill\uC740 ai-ops uninstall \uB300\uC0C1\uC774 \uC544\uB2D9\uB2C8\uB2E4. ai-ops skill uninstall\uC73C\uB85C \uC81C\uAC70\uD558\uC138\uC694.");
1606
+ }
1607
+ p3.outro("ai-ops init \uC644\uB8CC");
1608
+ } finally {
1609
+ process.off("SIGINT", handleSigint);
1014
1610
  }
1015
- if (geminiSettingValues && geminiSettingValues.length > 0) {
1016
- installGeminiSettings(basePath, geminiSettingValues);
1017
- }
1018
- if (claudeSettingValues && claudeSettingValues.length > 0) {
1019
- installClaudeSettings(basePath, claudeSettingValues);
1020
- }
1021
- if (wantPrettierIgnore) {
1022
- installPrettierIgnore(basePath);
1023
- }
1024
- s.stop("\uADDC\uCE59 \uC124\uCE58 \uC644\uB8CC");
1025
- const allInstalledRuleIds = deduplicateRules(mappings.flatMap((m) => m.finalRules)).map((r) => r.id);
1026
- const workspacesRecord = isMonorepo ? Object.fromEntries(
1027
- mappings.map((m) => [m.workspace, { preset: m.preset.id, rules: m.finalRules.map((r) => r.id) }])
1028
- ) : void 0;
1029
- const manifest = buildManifest({
1030
- tools: selectedTools,
1031
- scope: "project",
1032
- preset: !isMonorepo ? mappings[0].preset.id : void 0,
1033
- workspaces: workspacesRecord,
1034
- installedRules: allInstalledRuleIds,
1035
- installedFiles: allInstalledFiles,
1036
- appendedFiles: allAppended,
1037
- settings: claudeSettingValues || geminiSettingValues || wantPrettierIgnore ? {
1038
- claude: claudeSettingValues ? [...claudeSettingValues] : void 0,
1039
- gemini: geminiSettingValues ? [...geminiSettingValues] : void 0,
1040
- prettierignore: wantPrettierIgnore || void 0
1041
- } : void 0,
1042
- cliVersion: getCliVersion(),
1043
- sourceHash
1044
- });
1045
- writeManifest(resolveManifestPath(basePath), manifest);
1046
- if (allAppended.length > 0) {
1047
- p4.log.info(`\uAE30\uC874 \uD30C\uC77C\uC5D0 \uC139\uC158 \uCD94\uAC00\uB428 (\uB0B4\uC6A9 \uBCF4\uC874):
1048
- ${allAppended.map((f) => ` ${f}`).join("\n")}`);
1049
- }
1050
- p4.log.success(`\uC124\uCE58\uB41C \uADDC\uCE59: ${allInstalledRuleIds.length}\uAC1C`);
1051
- p4.outro("ai-ops init \uC644\uB8CC");
1052
1611
  };
1053
1612
 
1054
1613
  // src/commands/update.ts
1055
- import * as p5 from "@clack/prompts";
1614
+ import * as p4 from "@clack/prompts";
1056
1615
  var updateCommand = async (opts) => {
1057
1616
  const basePath = resolveBasePath();
1058
1617
  const manifestPath = resolveManifestPath(basePath);
1059
- p5.intro("ai-ops update");
1618
+ p4.intro("ai-ops update");
1060
1619
  const manifest = readManifest(manifestPath);
1061
1620
  if (!manifest) {
1062
- p5.log.error("manifest\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 ai-ops init\uC744 \uC2E4\uD589\uD558\uC138\uC694.");
1621
+ p4.log.error("manifest\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 ai-ops init\uC744 \uC2E4\uD589\uD558\uC138\uC694.");
1063
1622
  process.exit(1);
1064
1623
  }
1065
1624
  const rulesDir = resolveRulesDir();
1066
- const sourceHash = computeSourceHash(rulesDir);
1625
+ const skillsDir = resolveSkillsDir();
1626
+ const presetsPath = resolvePresetsPath();
1627
+ const sourceHash = computeSourceHash(resolveCompilerDataDir());
1067
1628
  const cliVersion = getCliVersion();
1629
+ const allRules = loadAllRules(rulesDir);
1630
+ const allSkills = loadAllSkills(skillsDir);
1631
+ const presets = loadPresets(presetsPath);
1632
+ const resolvedRules = resolveManifestRules({
1633
+ manifest,
1634
+ allRules,
1635
+ presets
1636
+ });
1637
+ const resolvedSkills = resolveManifestProjectSkills({
1638
+ manifest,
1639
+ allSkills
1640
+ });
1068
1641
  const diffResult = computeDiff({
1069
1642
  previous: manifest,
1070
- currentRules: manifest.installed_rules,
1643
+ currentRules: resolvedRules.installedRules.map((rule) => rule.id),
1071
1644
  currentSourceHash: sourceHash,
1072
1645
  currentCliVersion: cliVersion
1073
1646
  });
1074
1647
  if (diffResult.status === "up-to-date" && !opts.force) {
1075
- p5.log.info("\uBCC0\uACBD \uC0AC\uD56D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
1076
- p5.outro("ai-ops update \uC644\uB8CC");
1648
+ p4.log.info("\uBCC0\uACBD \uC0AC\uD56D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
1649
+ p4.outro("ai-ops update \uC644\uB8CC");
1077
1650
  return;
1078
1651
  }
1079
- const s = p5.spinner();
1652
+ const s = p4.spinner();
1080
1653
  s.start("\uADDC\uCE59 \uAC31\uC2E0 \uC911...");
1081
- const allRules = loadAllRules(rulesDir);
1082
1654
  const meta = { sourceHash, generatedAt: (/* @__PURE__ */ new Date()).toISOString() };
1083
1655
  const allInstalledFiles = [];
1084
1656
  const allAppended = [];
1657
+ const installedSkills = resolvedSkills.map(({ skill, requestedTools }) => {
1658
+ const { packages, installedSkill } = buildSkillInstallPlan({
1659
+ skill,
1660
+ requestedTools,
1661
+ scope: "project"
1662
+ });
1663
+ installSkillPackages(basePath, packages);
1664
+ return installedSkill;
1665
+ });
1085
1666
  if (manifest.workspaces) {
1086
- const workspaceEntries = Object.entries(manifest.workspaces);
1667
+ const workspaceEntries = Object.entries(resolvedRules.workspaces ?? {});
1087
1668
  for (const toolIdStr of manifest.tools) {
1088
1669
  const toolId = toolIdStr;
1089
- const allInstalledRuleSet = new Set(manifest.installed_rules);
1090
- const rulesToInstall = allRules.filter((r2) => allInstalledRuleSet.has(r2.id));
1670
+ const rulesToInstall = resolvedRules.installedRules;
1091
1671
  const workspaceMappings = workspaceEntries.map(([path, entry]) => ({
1092
1672
  path,
1093
1673
  ruleIds: entry.rules
@@ -1099,8 +1679,7 @@ var updateCommand = async (opts) => {
1099
1679
  allAppended.push(...r.appended);
1100
1680
  }
1101
1681
  } else {
1102
- const installedRuleSet = new Set(manifest.installed_rules);
1103
- const rulesToInstall = allRules.filter((r) => installedRuleSet.has(r.id));
1682
+ const rulesToInstall = resolvedRules.installedRules;
1104
1683
  for (const toolIdStr of manifest.tools) {
1105
1684
  const toolId = toolIdStr;
1106
1685
  const renderResult = renderForTool(toolId, rulesToInstall);
@@ -1123,9 +1702,10 @@ var updateCommand = async (opts) => {
1123
1702
  tools: manifest.tools,
1124
1703
  scope: manifest.scope,
1125
1704
  preset: manifest.preset,
1126
- workspaces: manifest.workspaces,
1127
- installedRules: manifest.installed_rules,
1705
+ workspaces: resolvedRules.workspaces,
1706
+ installedRules: resolvedRules.installedRules.map((rule) => rule.id),
1128
1707
  installedFiles: allInstalledFiles.length > 0 ? allInstalledFiles : manifest.installed_files,
1708
+ installedSkills,
1129
1709
  appendedFiles: allAppended.length > 0 ? allAppended : manifest.appended_files,
1130
1710
  settings: manifest.settings ? {
1131
1711
  claude: manifest.settings.claude,
@@ -1137,56 +1717,83 @@ var updateCommand = async (opts) => {
1137
1717
  });
1138
1718
  writeManifest(manifestPath, newManifest);
1139
1719
  s.stop("\uADDC\uCE59 \uAC31\uC2E0 \uC644\uB8CC");
1140
- p5.outro("ai-ops update \uC644\uB8CC");
1720
+ p4.outro("ai-ops update \uC644\uB8CC");
1141
1721
  };
1142
1722
 
1143
1723
  // src/commands/diff.ts
1144
- import * as p6 from "@clack/prompts";
1724
+ import * as p5 from "@clack/prompts";
1145
1725
  var diffCommand = async () => {
1146
1726
  const basePath = resolveBasePath();
1147
- p6.intro("ai-ops diff");
1727
+ p5.intro("ai-ops diff");
1148
1728
  const manifest = readManifest(resolveManifestPath(basePath));
1149
1729
  if (!manifest) {
1150
- p6.log.error("manifest\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 ai-ops init\uC744 \uC2E4\uD589\uD558\uC138\uC694.");
1730
+ p5.log.error("manifest\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 ai-ops init\uC744 \uC2E4\uD589\uD558\uC138\uC694.");
1151
1731
  process.exit(1);
1152
1732
  }
1153
- const sourceHash = computeSourceHash(resolveRulesDir());
1733
+ const sourceHash = computeSourceHash(resolveCompilerDataDir());
1734
+ const allRules = loadAllRules(resolveRulesDir());
1735
+ const allSkills = loadAllSkills(resolveSkillsDir());
1736
+ const presets = loadPresets(resolvePresetsPath());
1737
+ const resolvedRules = resolveManifestRules({
1738
+ manifest,
1739
+ allRules,
1740
+ presets
1741
+ });
1742
+ const resolvedSkills = resolveManifestProjectSkills({
1743
+ manifest,
1744
+ allSkills
1745
+ });
1154
1746
  const result = computeDiff({
1155
1747
  previous: manifest,
1156
- currentRules: manifest.installed_rules,
1748
+ currentRules: resolvedRules.installedRules.map((rule) => rule.id),
1157
1749
  currentSourceHash: sourceHash
1158
1750
  });
1751
+ const skillLines = resolvedSkills.map(({ skill, requestedTools }) => {
1752
+ const { installedSkill: next } = buildSkillInstallPlan({
1753
+ skill,
1754
+ requestedTools,
1755
+ scope: "project"
1756
+ });
1757
+ const previous = (manifest.installed_skills ?? []).find((installedSkill) => installedSkill.id === skill.id);
1758
+ const previousHash = previous?.sourceHash ?? "legacy";
1759
+ const changed = previousHash !== next.sourceHash;
1760
+ return `- ${skill.id}: ${changed ? "changed" : "up-to-date"} (${previousHash} -> ${next.sourceHash})`;
1761
+ });
1159
1762
  if (result.status === "up-to-date") {
1160
- p6.log.success("\uBCC0\uACBD \uC0AC\uD56D \uC5C6\uC74C. \uCD5C\uC2E0 \uC0C1\uD0DC\uC785\uB2C8\uB2E4.");
1763
+ p5.log.success("\uBCC0\uACBD \uC0AC\uD56D \uC5C6\uC74C. \uCD5C\uC2E0 \uC0C1\uD0DC\uC785\uB2C8\uB2E4.");
1161
1764
  } else {
1162
1765
  if (result.sourceChanged) {
1163
- p6.log.warn(`\uC18C\uC2A4 \uBCC0\uACBD \uAC10\uC9C0: ${manifest.sourceHash} \u2192 ${sourceHash}`);
1766
+ p5.log.warn(`\uC18C\uC2A4 \uBCC0\uACBD \uAC10\uC9C0: ${manifest.sourceHash} \u2192 ${sourceHash}`);
1164
1767
  }
1165
1768
  if (result.added.length > 0) {
1166
- p6.log.info(`\uCD94\uAC00\uB41C \uADDC\uCE59: ${result.added.join(", ")}`);
1769
+ p5.log.info(`\uCD94\uAC00\uB41C \uADDC\uCE59: ${result.added.join(", ")}`);
1167
1770
  }
1168
1771
  if (result.removed.length > 0) {
1169
- p6.log.info(`\uC81C\uAC70\uB41C \uADDC\uCE59: ${result.removed.join(", ")}`);
1772
+ p5.log.info(`\uC81C\uAC70\uB41C \uADDC\uCE59: ${result.removed.join(", ")}`);
1170
1773
  }
1171
1774
  }
1172
- p6.outro("ai-ops diff \uC644\uB8CC");
1775
+ if (skillLines.length > 0) {
1776
+ p5.log.info(`project skills:
1777
+ ${skillLines.map((line) => ` ${line}`).join("\n")}`);
1778
+ }
1779
+ p5.outro("ai-ops diff \uC644\uB8CC");
1173
1780
  };
1174
1781
 
1175
1782
  // src/commands/uninstall.ts
1176
- import * as p7 from "@clack/prompts";
1783
+ import * as p6 from "@clack/prompts";
1177
1784
  import { rmSync as rmSync5 } from "fs";
1178
1785
 
1179
1786
  // src/lib/uninstall.ts
1180
- import { existsSync as existsSync6, readFileSync as readFileSync8, rmSync as rmSync4, readdirSync as readdirSync4, writeFileSync as writeFileSync6 } from "fs";
1181
- import { resolve as resolve6, dirname as dirname5 } from "path";
1787
+ import { existsSync as existsSync7, readFileSync as readFileSync8, rmSync as rmSync4, readdirSync as readdirSync4, writeFileSync as writeFileSync7 } from "fs";
1788
+ import { resolve as resolve7, dirname as dirname7 } from "path";
1182
1789
  var removeFiles = (basePath, relativePaths) => {
1183
1790
  const deleted = [];
1184
1791
  const cleaned = [];
1185
1792
  const skipped = [];
1186
1793
  const notFound = [];
1187
1794
  for (const rel of relativePaths) {
1188
- const absPath = resolve6(basePath, rel);
1189
- if (!existsSync6(absPath)) {
1795
+ const absPath = resolve7(basePath, rel);
1796
+ if (!existsSync7(absPath)) {
1190
1797
  notFound.push(rel);
1191
1798
  continue;
1192
1799
  }
@@ -1197,7 +1804,7 @@ var removeFiles = (basePath, relativePaths) => {
1197
1804
  rmSync4(absPath);
1198
1805
  deleted.push(rel);
1199
1806
  } else {
1200
- writeFileSync6(absPath, stripped, "utf-8");
1807
+ writeFileSync7(absPath, stripped, "utf-8");
1201
1808
  cleaned.push(rel);
1202
1809
  }
1203
1810
  } else if (hasLegacyHeader(content)) {
@@ -1212,8 +1819,8 @@ var removeFiles = (basePath, relativePaths) => {
1212
1819
  var cleanEmptyDirs = (basePath, dirs) => {
1213
1820
  const removed = [];
1214
1821
  for (const dir of dirs) {
1215
- const absDir = resolve6(basePath, dir);
1216
- if (!existsSync6(absDir)) continue;
1822
+ const absDir = resolve7(basePath, dir);
1823
+ if (!existsSync7(absDir)) continue;
1217
1824
  try {
1218
1825
  const entries = readdirSync4(absDir);
1219
1826
  if (entries.length === 0) {
@@ -1228,7 +1835,7 @@ var cleanEmptyDirs = (basePath, dirs) => {
1228
1835
  var collectManagedDirs = (relativePaths) => {
1229
1836
  const dirs = /* @__PURE__ */ new Set();
1230
1837
  for (const rel of relativePaths) {
1231
- const dir = dirname5(rel);
1838
+ const dir = dirname7(rel);
1232
1839
  if (dir !== ".") {
1233
1840
  dirs.add(dir);
1234
1841
  }
@@ -1241,36 +1848,46 @@ var SETTINGS_PATHS = /* @__PURE__ */ new Set([".claude/settings.local.json", ".g
1241
1848
  var uninstallCommand = async () => {
1242
1849
  const basePath = resolveBasePath();
1243
1850
  const manifestPath = resolveManifestPath(basePath);
1244
- p7.intro("ai-ops uninstall");
1851
+ p6.intro("ai-ops uninstall");
1245
1852
  const manifest = readManifest(manifestPath);
1246
1853
  if (!manifest) {
1247
- p7.log.error("manifest\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 ai-ops init\uC744 \uC2E4\uD589\uD558\uC138\uC694.");
1854
+ p6.log.error("manifest\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 ai-ops init\uC744 \uC2E4\uD589\uD558\uC138\uC694.");
1248
1855
  process.exit(1);
1249
1856
  }
1250
1857
  const targetFiles = [
1251
1858
  ...manifest.installed_files ?? inferInstalledFiles(manifest),
1252
1859
  ...manifest.appended_files ?? []
1253
1860
  ].filter((f) => !SETTINGS_PATHS.has(f));
1254
- if (targetFiles.length === 0) {
1255
- p7.log.warn("\uC0AD\uC81C\uD560 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
1256
- p7.outro("ai-ops uninstall \uC644\uB8CC");
1861
+ const targetSkillDirs = (manifest.installed_skills ?? []).flatMap((skill) => skill.installed_paths);
1862
+ if (targetFiles.length === 0 && targetSkillDirs.length === 0) {
1863
+ p6.log.warn("\uC0AD\uC81C\uD560 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
1864
+ p6.outro("ai-ops uninstall \uC644\uB8CC");
1257
1865
  return;
1258
1866
  }
1259
- p7.log.info(`\uC0AD\uC81C \uB300\uC0C1 \uD30C\uC77C (${targetFiles.length}\uAC1C):
1867
+ if (targetFiles.length > 0) {
1868
+ p6.log.info(`\uC0AD\uC81C \uB300\uC0C1 \uD30C\uC77C (${targetFiles.length}\uAC1C):
1260
1869
  ${targetFiles.map((f) => ` ${f}`).join("\n")}`);
1261
- const confirmed = await p7.confirm({
1870
+ }
1871
+ if (targetSkillDirs.length > 0) {
1872
+ p6.log.info(
1873
+ `\uC0AD\uC81C \uB300\uC0C1 skill \uB514\uB809\uD1A0\uB9AC (${targetSkillDirs.length}\uAC1C):
1874
+ ${targetSkillDirs.map((f) => ` ${f}`).join("\n")}`
1875
+ );
1876
+ }
1877
+ const confirmed = await p6.confirm({
1262
1878
  message: "\uC704 \uD30C\uC77C\uACFC manifest\uB97C \uBAA8\uB450 \uC0AD\uC81C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
1263
1879
  initialValue: false
1264
1880
  });
1265
- if (p7.isCancel(confirmed) || !confirmed) {
1266
- p7.cancel("\uCDE8\uC18C\uB428");
1881
+ if (p6.isCancel(confirmed) || !confirmed) {
1882
+ p6.cancel("\uCDE8\uC18C\uB428");
1267
1883
  process.exit(0);
1268
1884
  }
1269
1885
  const settingsMessages = [];
1270
1886
  if (manifest.settings?.claude) {
1271
1887
  const status = uninstallClaudeSettings(basePath, manifest.settings.claude);
1272
1888
  if (status === "deleted") settingsMessages.push("\uC0AD\uC81C: .claude/settings.local.json");
1273
- else if (status === "cleaned") settingsMessages.push("ai-ops \uD0A4 \uC81C\uAC70 (\uC0AC\uC6A9\uC790 \uC124\uC815 \uBCF4\uC874): .claude/settings.local.json");
1889
+ else if (status === "cleaned")
1890
+ settingsMessages.push("ai-ops \uD0A4 \uC81C\uAC70 (\uC0AC\uC6A9\uC790 \uC124\uC815 \uBCF4\uC874): .claude/settings.local.json");
1274
1891
  }
1275
1892
  if (manifest.settings?.gemini) {
1276
1893
  const status = uninstallGeminiSettings(basePath, manifest.settings.gemini);
@@ -1281,54 +1898,339 @@ ${targetFiles.map((f) => ` ${f}`).join("\n")}`);
1281
1898
  if (prettierStatus === "deleted") settingsMessages.push("\uC0AD\uC81C: .prettierignore");
1282
1899
  else if (prettierStatus === "cleaned") settingsMessages.push("ai-ops \uC139\uC158 \uC81C\uAC70 (\uC0AC\uC6A9\uC790 \uB0B4\uC6A9 \uBCF4\uC874): .prettierignore");
1283
1900
  const result = removeFiles(basePath, targetFiles);
1901
+ const removedSkillDirs = removeDirectories(basePath, targetSkillDirs);
1284
1902
  const dirs = collectManagedDirs(targetFiles);
1285
1903
  const removedDirs = cleanEmptyDirs(basePath, dirs);
1286
1904
  rmSync5(manifestPath, { force: true });
1287
1905
  if (result.deleted.length > 0) {
1288
- p7.log.success(`\uC0AD\uC81C \uC644\uB8CC (${result.deleted.length}\uAC1C):
1906
+ p6.log.success(`\uC0AD\uC81C \uC644\uB8CC (${result.deleted.length}\uAC1C):
1289
1907
  ${result.deleted.map((f) => ` ${f}`).join("\n")}`);
1290
1908
  }
1291
1909
  if (result.cleaned.length > 0) {
1292
- p7.log.success(
1910
+ p6.log.success(
1293
1911
  `\uC139\uC158 \uC81C\uAC70 \uC644\uB8CC (\uC0AC\uC6A9\uC790 \uB0B4\uC6A9 \uBCF4\uC874, ${result.cleaned.length}\uAC1C):
1294
1912
  ${result.cleaned.map((f) => ` ${f}`).join("\n")}`
1295
1913
  );
1296
1914
  }
1297
1915
  if (result.skipped.length > 0) {
1298
- p7.log.warn(
1916
+ p6.log.warn(
1299
1917
  `\uAC74\uB108\uB700 (non-managed \uD30C\uC77C \uBCF4\uD638, ${result.skipped.length}\uAC1C):
1300
1918
  ${result.skipped.map((f) => ` ${f}`).join("\n")}`
1301
1919
  );
1302
1920
  }
1303
1921
  if (result.notFound.length > 0) {
1304
- p7.log.info(`\uC774\uBBF8 \uC5C6\uC74C (${result.notFound.length}\uAC1C):
1922
+ p6.log.info(`\uC774\uBBF8 \uC5C6\uC74C (${result.notFound.length}\uAC1C):
1305
1923
  ${result.notFound.map((f) => ` ${f}`).join("\n")}`);
1306
1924
  }
1307
1925
  if (removedDirs.length > 0) {
1308
- p7.log.info(`\uBE48 \uB514\uB809\uD1A0\uB9AC \uC815\uB9AC (${removedDirs.length}\uAC1C):
1926
+ p6.log.info(`\uBE48 \uB514\uB809\uD1A0\uB9AC \uC815\uB9AC (${removedDirs.length}\uAC1C):
1309
1927
  ${removedDirs.map((d) => ` ${d}`).join("\n")}`);
1310
1928
  }
1929
+ if (removedSkillDirs.length > 0) {
1930
+ p6.log.success(
1931
+ `skill \uB514\uB809\uD1A0\uB9AC \uC0AD\uC81C (${removedSkillDirs.length}\uAC1C):
1932
+ ${removedSkillDirs.map((d) => ` ${d}`).join("\n")}`
1933
+ );
1934
+ }
1311
1935
  if (settingsMessages.length > 0) {
1312
- p7.log.success(`\uC124\uC815 \uD30C\uC77C \uCC98\uB9AC:
1936
+ p6.log.success(`\uC124\uC815 \uD30C\uC77C \uCC98\uB9AC:
1313
1937
  ${settingsMessages.map((m) => ` ${m}`).join("\n")}`);
1314
1938
  }
1315
- p7.log.success(`manifest \uC0AD\uC81C: ${MANIFEST_FILENAME}`);
1316
- p7.outro("ai-ops uninstall \uC644\uB8CC");
1939
+ p6.log.success(`manifest \uC0AD\uC81C: ${MANIFEST_FILENAME}`);
1940
+ p6.outro("ai-ops uninstall \uC644\uB8CC");
1317
1941
  };
1318
1942
 
1319
- // src/bin/index.ts
1320
- var program = new Command();
1321
- var ensureNoDeprecatedScopeFlag = (argv) => {
1322
- if (argv.some((arg) => arg === "--scope" || arg.startsWith("--scope="))) {
1323
- console.error("`--scope` is no longer supported. ai-ops is now project-only.");
1324
- process.exit(1);
1943
+ // src/commands/skill.ts
1944
+ import * as p7 from "@clack/prompts";
1945
+ import { rmSync as rmSync6 } from "fs";
1946
+ var resolveScopeContext = (opts) => {
1947
+ const scope = resolveSkillScope(opts);
1948
+ return {
1949
+ scope,
1950
+ basePath: scope === "project" ? resolveBasePath() : resolveUserBasePath()
1951
+ };
1952
+ };
1953
+ var loadCompilerInputs = () => {
1954
+ const compilerDataDir = resolveCompilerDataDir();
1955
+ return {
1956
+ allSkills: loadAllSkills(resolveSkillsDir()),
1957
+ sourceHash: computeSourceHash(compilerDataDir),
1958
+ cliVersion: getCliVersion()
1959
+ };
1960
+ };
1961
+ var resolveSkillById = (skills, skillId) => {
1962
+ const canonicalSkillId = resolveCanonicalSkillId(skillId);
1963
+ const skill = skills.find((candidate) => candidate.id === canonicalSkillId);
1964
+ if (!skill) {
1965
+ throw new Error(`Unknown skill: ${skillId}`);
1325
1966
  }
1967
+ return skill;
1326
1968
  };
1969
+ var assertScopeAllowed = (skill, scope) => {
1970
+ if (!skill.install_scopes.includes(scope)) {
1971
+ throw new Error(`Skill ${skill.id} does not support ${scope} scope`);
1972
+ }
1973
+ };
1974
+ var writeProjectSkillState = (params) => {
1975
+ const manifestPath = resolveManifestPath(params.basePath);
1976
+ const previous = readManifest(manifestPath);
1977
+ const installedSkills = params.removeSkillId ? removeInstalledSkill(previous?.installed_skills ?? [], params.removeSkillId) : params.nextSkill ? upsertInstalledSkill(previous?.installed_skills ?? [], params.nextSkill) : previous?.installed_skills ?? [];
1978
+ const nextTools = params.nextSkill !== void 0 ? [.../* @__PURE__ */ new Set([...previous?.tools ?? [], ...params.nextSkill.tools])] : previous?.tools ?? [];
1979
+ const hasProjectState = (previous?.installed_rules.length ?? 0) > 0 || (previous?.installed_files?.length ?? 0) > 0 || (previous?.appended_files?.length ?? 0) > 0 || installedSkills.length > 0 || previous?.settings !== void 0;
1980
+ if (!hasProjectState) {
1981
+ rmSync6(manifestPath, { force: true });
1982
+ return;
1983
+ }
1984
+ const manifest = buildManifest({
1985
+ tools: nextTools.length > 0 ? nextTools : params.nextSkill?.tools ?? ["codex"],
1986
+ scope: "project",
1987
+ preset: previous?.preset,
1988
+ workspaces: previous?.workspaces,
1989
+ installedRules: previous?.installed_rules ?? [],
1990
+ installedFiles: previous?.installed_files,
1991
+ installedSkills,
1992
+ appendedFiles: previous?.appended_files,
1993
+ settings: previous?.settings,
1994
+ cliVersion: params.cliVersion,
1995
+ sourceHash: params.sourceHash
1996
+ });
1997
+ writeManifest(manifestPath, manifest);
1998
+ };
1999
+ var writeUserSkillState = (params) => {
2000
+ const registryPath = resolveSkillRegistryPath(params.basePath);
2001
+ const previous = readSkillRegistry(registryPath);
2002
+ const skills = params.removeSkillId ? removeInstalledSkill(previous?.skills ?? [], params.removeSkillId) : params.nextSkill ? upsertInstalledSkill(previous?.skills ?? [], params.nextSkill) : previous?.skills ?? [];
2003
+ if (skills.length === 0) {
2004
+ rmSync6(registryPath, { force: true });
2005
+ return;
2006
+ }
2007
+ writeSkillRegistry(registryPath, {
2008
+ skills,
2009
+ cliVersion: params.cliVersion,
2010
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
2011
+ });
2012
+ };
2013
+ var readInstalledSkills = (scope, basePath) => {
2014
+ if (scope === "project") {
2015
+ return (readManifest(resolveManifestPath(basePath))?.installed_skills ?? []).map((installedSkill) => ({
2016
+ ...installedSkill,
2017
+ id: resolveCanonicalSkillId(installedSkill.id)
2018
+ }));
2019
+ }
2020
+ return (readSkillRegistry(resolveSkillRegistryPath(basePath))?.skills ?? []).map((installedSkill) => ({
2021
+ ...installedSkill,
2022
+ id: resolveCanonicalSkillId(installedSkill.id)
2023
+ }));
2024
+ };
2025
+ var installSkill = (params) => {
2026
+ const installedSkills = readInstalledSkills(params.scope, params.basePath);
2027
+ const existingInstalledSkill = findInstalledSkill(installedSkills, params.skill.id);
2028
+ const nextRequestedTools = mergeSkillTools({
2029
+ existing: existingInstalledSkill?.tools,
2030
+ requested: params.requestedTools
2031
+ });
2032
+ const { packages, installedSkill } = buildSkillInstallPlan({
2033
+ skill: params.skill,
2034
+ requestedTools: nextRequestedTools,
2035
+ scope: params.scope
2036
+ });
2037
+ installSkillPackages(params.basePath, packages);
2038
+ if (params.scope === "project") {
2039
+ writeProjectSkillState({
2040
+ basePath: params.basePath,
2041
+ sourceHash: params.sourceHash,
2042
+ cliVersion: params.cliVersion,
2043
+ nextSkill: installedSkill
2044
+ });
2045
+ } else {
2046
+ writeUserSkillState({
2047
+ basePath: params.basePath,
2048
+ cliVersion: params.cliVersion,
2049
+ nextSkill: installedSkill
2050
+ });
2051
+ }
2052
+ return installedSkill;
2053
+ };
2054
+ var skillListCommand = async (opts) => {
2055
+ const { scope, basePath } = resolveScopeContext(opts);
2056
+ const { allSkills } = loadCompilerInputs();
2057
+ const installedSkills = readInstalledSkills(scope, basePath);
2058
+ p7.intro(`ai-ops skill list (${scope})`);
2059
+ const sections = [
2060
+ { kind: "reference", title: "reference skills" },
2061
+ { kind: "task", title: "task skills" }
2062
+ ].map(({ kind, title }) => {
2063
+ const lines = allSkills.filter((skill) => skill.kind === kind).map((skill) => {
2064
+ const installed = findInstalledSkill(installedSkills, skill.id);
2065
+ const suffix = installed ? `installed for ${installed.tools.join(", ")}` : "not installed";
2066
+ return `- ${skill.id} (${skill.install_scopes.join(", ")}) - ${suffix}`;
2067
+ });
2068
+ if (lines.length === 0) {
2069
+ return null;
2070
+ }
2071
+ return `${title}
2072
+ ${lines.join("\n")}`;
2073
+ }).filter((section) => section !== null);
2074
+ p7.log.info(sections.join("\n\n"));
2075
+ p7.outro("ai-ops skill list \uC644\uB8CC");
2076
+ };
2077
+ var skillInstallCommand = async (skillId, opts) => {
2078
+ const { scope, basePath } = resolveScopeContext(opts);
2079
+ const { allSkills, sourceHash, cliVersion } = loadCompilerInputs();
2080
+ const skill = resolveSkillById(allSkills, skillId);
2081
+ assertScopeAllowed(skill, scope);
2082
+ const requestedTools = resolveRequestedTools({ requested: opts.tool, supported: skill.supported_tools });
2083
+ p7.intro(`ai-ops skill install ${skillId}`);
2084
+ const installedSkill = installSkill({
2085
+ skill,
2086
+ requestedTools,
2087
+ scope,
2088
+ basePath,
2089
+ cliVersion,
2090
+ sourceHash
2091
+ });
2092
+ p7.log.success(`\uC124\uCE58 \uC644\uB8CC: ${installedSkill.id} (${installedSkill.installed_paths.join(", ")})`);
2093
+ p7.outro("ai-ops skill install \uC644\uB8CC");
2094
+ };
2095
+ var skillDiffCommand = async (skillId, opts) => {
2096
+ const { scope, basePath } = resolveScopeContext(opts);
2097
+ const { allSkills } = loadCompilerInputs();
2098
+ const installedSkills = readInstalledSkills(scope, basePath);
2099
+ const targets = skillId ? installedSkills.filter((skill) => skill.id === skillId) : installedSkills;
2100
+ p7.intro(`ai-ops skill diff (${scope})`);
2101
+ if (targets.length === 0) {
2102
+ p7.log.warn("\uBE44\uAD50\uD560 \uC124\uCE58\uB41C skill\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
2103
+ p7.outro("ai-ops skill diff \uC644\uB8CC");
2104
+ return;
2105
+ }
2106
+ const lines = targets.map((installedSkill) => {
2107
+ const skill = resolveSkillById(allSkills, installedSkill.id);
2108
+ const { installedSkill: next } = buildSkillInstallPlan({
2109
+ skill,
2110
+ requestedTools: installedSkill.tools,
2111
+ scope
2112
+ });
2113
+ const changed = next.sourceHash !== installedSkill.sourceHash;
2114
+ return `- ${installedSkill.id}: ${changed ? "changed" : "up-to-date"} (${installedSkill.sourceHash} -> ${next.sourceHash})`;
2115
+ });
2116
+ p7.log.info(lines.join("\n"));
2117
+ p7.outro("ai-ops skill diff \uC644\uB8CC");
2118
+ };
2119
+ var skillUpdateCommand = async (skillId, opts) => {
2120
+ const { scope, basePath } = resolveScopeContext(opts);
2121
+ const { allSkills, sourceHash, cliVersion } = loadCompilerInputs();
2122
+ const installedSkills = readInstalledSkills(scope, basePath);
2123
+ const targets = skillId ? installedSkills.filter((skill) => skill.id === skillId) : installedSkills;
2124
+ p7.intro(`ai-ops skill update (${scope})`);
2125
+ if (targets.length === 0) {
2126
+ p7.log.warn("\uAC31\uC2E0\uD560 \uC124\uCE58\uB41C skill\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
2127
+ p7.outro("ai-ops skill update \uC644\uB8CC");
2128
+ return;
2129
+ }
2130
+ const nextInstalledSkills = targets.map((installedSkill) => {
2131
+ const skill = resolveSkillById(allSkills, installedSkill.id);
2132
+ const { packages, installedSkill: next } = buildSkillInstallPlan({
2133
+ skill,
2134
+ requestedTools: installedSkill.tools,
2135
+ scope
2136
+ });
2137
+ installSkillPackages(basePath, packages);
2138
+ return next;
2139
+ });
2140
+ if (scope === "project") {
2141
+ const manifestPath = resolveManifestPath(basePath);
2142
+ const previous = readManifest(manifestPath);
2143
+ if (!previous) {
2144
+ p7.log.error("project manifest\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
2145
+ process.exit(1);
2146
+ }
2147
+ const untouched = (previous.installed_skills ?? []).filter(
2148
+ (installedSkill) => !nextInstalledSkills.some((next) => next.id === installedSkill.id)
2149
+ );
2150
+ writeManifest(
2151
+ manifestPath,
2152
+ buildManifest({
2153
+ tools: previous.tools,
2154
+ scope: previous.scope,
2155
+ preset: previous.preset,
2156
+ workspaces: previous.workspaces,
2157
+ installedRules: previous.installed_rules,
2158
+ installedFiles: previous.installed_files,
2159
+ installedSkills: [...untouched, ...nextInstalledSkills],
2160
+ appendedFiles: previous.appended_files,
2161
+ settings: previous.settings,
2162
+ cliVersion,
2163
+ sourceHash
2164
+ })
2165
+ );
2166
+ } else {
2167
+ const registryPath = resolveSkillRegistryPath(basePath);
2168
+ const previous = readSkillRegistry(registryPath);
2169
+ const untouched = (previous?.skills ?? []).filter(
2170
+ (installedSkill) => !nextInstalledSkills.some((next) => next.id === installedSkill.id)
2171
+ );
2172
+ writeSkillRegistry(registryPath, {
2173
+ skills: [...untouched, ...nextInstalledSkills],
2174
+ cliVersion,
2175
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
2176
+ });
2177
+ }
2178
+ p7.log.success(`\uAC31\uC2E0 \uC644\uB8CC: ${nextInstalledSkills.map((skill) => skill.id).join(", ")}`);
2179
+ p7.outro("ai-ops skill update \uC644\uB8CC");
2180
+ };
2181
+ var skillUninstallCommand = async (skillId, opts) => {
2182
+ const { scope, basePath } = resolveScopeContext(opts);
2183
+ const { sourceHash, cliVersion } = loadCompilerInputs();
2184
+ const installedSkills = readInstalledSkills(scope, basePath);
2185
+ const installedSkill = findInstalledSkill(installedSkills, skillId);
2186
+ p7.intro(`ai-ops skill uninstall ${skillId}`);
2187
+ if (!installedSkill) {
2188
+ p7.log.warn("\uC124\uCE58\uB41C skill\uC744 \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
2189
+ p7.outro("ai-ops skill uninstall \uC644\uB8CC");
2190
+ return;
2191
+ }
2192
+ const removed = removeDirectories(basePath, installedSkill.installed_paths);
2193
+ if (scope === "project") {
2194
+ writeProjectSkillState({
2195
+ basePath,
2196
+ sourceHash,
2197
+ cliVersion,
2198
+ removeSkillId: skillId
2199
+ });
2200
+ } else {
2201
+ writeUserSkillState({
2202
+ basePath,
2203
+ cliVersion,
2204
+ removeSkillId: skillId
2205
+ });
2206
+ }
2207
+ p7.log.success(`\uC81C\uAC70 \uC644\uB8CC: ${removed.join(", ")}`);
2208
+ p7.outro("ai-ops skill uninstall \uC644\uB8CC");
2209
+ };
2210
+
2211
+ // src/bin/index.ts
2212
+ var program = new Command();
1327
2213
  program.name("ai-ops").description("AI \uC5D0\uC774\uC804\uD2B8 \uADDC\uCE59 \uC2A4\uCE90\uD3F4\uB354").version("0.1.0");
1328
2214
  program.command("init").description("AI \uADDC\uCE59 \uCD08\uAE30 \uC124\uCE58").action(() => initCommand());
1329
2215
  program.command("update").description("\uAE30\uC874 manifest \uAE30\uBC18 \uADDC\uCE59 \uAC31\uC2E0").option("--force", "\uBCC0\uACBD \uC5C6\uC5B4\uB3C4 \uAC15\uC81C \uC7AC\uC124\uCE58", false).action((opts) => updateCommand(opts));
1330
2216
  program.command("diff").description("\uC124\uCE58\uB41C \uADDC\uCE59\uACFC \uCD5C\uC2E0 \uC18C\uC2A4 \uBE44\uAD50").action(() => diffCommand());
1331
2217
  program.command("uninstall").description("\uC124\uCE58\uB41C \uADDC\uCE59 \uD30C\uC77C \uBC0F manifest \uC81C\uAC70").action(() => uninstallCommand());
1332
- ensureNoDeprecatedScopeFlag(process.argv);
2218
+ var skillCommand = program.command("skill").description("\uC5D0\uC774\uC804\uD2B8 skill \uC124\uCE58/\uC870\uD68C/\uAC31\uC2E0");
2219
+ var applySkillScopeOptions = (command) => command.option("-g, --global", "user scope\uC5D0 \uC124\uCE58/\uC870\uD68C").option("--project", "project scope\uC5D0 \uC124\uCE58/\uC870\uD68C").option("--scope <scope>", "explicit scope (user|project)").option("--tool <tool...>", "\uB300\uC0C1 \uB3C4\uAD6C \uC9C0\uC815");
2220
+ applySkillScopeOptions(skillCommand.command("list").description("\uC0AC\uC6A9 \uAC00\uB2A5\uD55C skill \uBAA9\uB85D")).action(
2221
+ (opts) => skillListCommand(opts)
2222
+ );
2223
+ applySkillScopeOptions(skillCommand.command("install <skillId>").description("skill \uC124\uCE58")).action(
2224
+ (skillId, opts) => skillInstallCommand(skillId, opts)
2225
+ );
2226
+ applySkillScopeOptions(skillCommand.command("diff [skillId]").description("skill \uBCC0\uACBD \uBE44\uAD50")).action(
2227
+ (skillId, opts) => skillDiffCommand(skillId, opts)
2228
+ );
2229
+ applySkillScopeOptions(skillCommand.command("update [skillId]").description("skill \uAC31\uC2E0")).action(
2230
+ (skillId, opts) => skillUpdateCommand(skillId, opts)
2231
+ );
2232
+ applySkillScopeOptions(skillCommand.command("uninstall <skillId>").description("skill \uC81C\uAC70")).action(
2233
+ (skillId, opts) => skillUninstallCommand(skillId, opts)
2234
+ );
1333
2235
  program.parse();
1334
2236
  //# sourceMappingURL=index.js.map