ai-ops-cli 0.1.23 → 0.2.0

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/plan-mode.yaml +4 -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 +1264 -308
  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
@@ -16,7 +16,7 @@ var DecisionTableEntrySchema = z.object({
16
16
  }).strict();
17
17
  var RuleContentSchema = z.object({
18
18
  /** Anti-pattern 규칙 ('하지 마라'). guidelines보다 항상 상단 렌더링 */
19
- constraints: z.array(z.string().min(1)),
19
+ constraints: z.array(z.string().min(1)).default([]),
20
20
  /** Positive 규칙 ('해라') */
21
21
  guidelines: z.array(z.string().min(1)),
22
22
  /** 조건부 규칙. when→then→avoid 구조 */
@@ -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)
48
106
  }).strict();
49
- var WorkspaceEntrySchema = z3.object({
50
- preset: z3.string().min(1),
51
- rules: z3.array(z3.string().min(1))
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 })
52
114
  }).strict();
53
- var ManifestSchema = z3.object({
54
- tools: z3.array(z3.string().min(1)).min(1),
55
- scope: z3.literal("project"),
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()
122
+ }).strict();
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 = [];
@@ -240,7 +362,7 @@ var partitionRules = (rules) => {
240
362
  return { global, domain };
241
363
  };
242
364
  var renderFrontmatter = (paths) => {
243
- const lines = paths.map((p8) => ` - "${p8}"`).join("\n");
365
+ const lines = paths.map((p9) => ` - "${p9}"`).join("\n");
244
366
  return `---
245
367
  paths:
246
368
  ${lines}
@@ -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
 
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
+
583
976
  // src/lib/gemini-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,6 +1008,10 @@ var deepRemoveKeys = (base, patch) => {
615
1008
  return result;
616
1009
  };
617
1010
 
1011
+ // src/lib/prompt-control.ts
1012
+ var PROMPT_CANCELLED = /* @__PURE__ */ Symbol("prompt-cancelled");
1013
+ var isPromptCancelled = (value) => value === PROMPT_CANCELLED;
1014
+
618
1015
  // src/lib/gemini-settings.ts
619
1016
  var SETTING_GROUPS = [
620
1017
  {
@@ -647,7 +1044,8 @@ var promptGeminiSettings = async () => {
647
1044
  message: "Gemini CLI \uC124\uC815 \uD30C\uC77C(.gemini/settings.json)\uC744 \uC124\uCE58\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
648
1045
  initialValue: true
649
1046
  });
650
- if (p.isCancel(wantSettings) || !wantSettings) return null;
1047
+ if (p.isCancel(wantSettings)) return PROMPT_CANCELLED;
1048
+ if (!wantSettings) return null;
651
1049
  const selected = await p.multiselect({
652
1050
  message: "\uC124\uCE58\uD560 \uC124\uC815 \uD56D\uBAA9\uC744 \uC120\uD0DD\uD558\uC138\uC694 (\uC2A4\uD398\uC774\uC2A4\uB85C \uD1A0\uAE00)",
653
1051
  options: SETTING_GROUPS.map((g) => ({
@@ -658,17 +1056,17 @@ var promptGeminiSettings = async () => {
658
1056
  initialValues: SETTING_GROUPS.map((g) => g.value),
659
1057
  required: false
660
1058
  });
661
- if (p.isCancel(selected)) return null;
1059
+ if (p.isCancel(selected)) return PROMPT_CANCELLED;
662
1060
  return selected;
663
1061
  };
664
1062
  var installGeminiSettings = (basePath, selectedValues) => {
665
1063
  if (selectedValues.length === 0) return;
666
- const settingsDir = join7(basePath, ".gemini");
667
- const settingsPath = join7(settingsDir, "settings.json");
1064
+ const settingsDir = join11(basePath, ".gemini");
1065
+ const settingsPath = join11(settingsDir, "settings.json");
668
1066
  let existing = {};
669
- if (existsSync3(settingsPath)) {
1067
+ if (existsSync5(settingsPath)) {
670
1068
  try {
671
- existing = JSON.parse(readFileSync5(settingsPath, "utf-8"));
1069
+ existing = JSON.parse(readFileSync6(settingsPath, "utf-8"));
672
1070
  } catch {
673
1071
  }
674
1072
  }
@@ -678,37 +1076,40 @@ var installGeminiSettings = (basePath, selectedValues) => {
678
1076
  if (!group) continue;
679
1077
  merged = deepMerge(merged, group.patch);
680
1078
  }
681
- mkdirSync3(settingsDir, { recursive: true });
682
- writeFileSync3(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
1079
+ mkdirSync5(settingsDir, { recursive: true });
1080
+ writeFileSync5(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
683
1081
  };
684
1082
  var uninstallGeminiSettings = (basePath, selectedValues) => {
685
- const settingsPath = join7(basePath, ".gemini", "settings.json");
686
- if (!existsSync3(settingsPath)) return "notFound";
1083
+ const settingsPath = join11(basePath, ".gemini", "settings.json");
1084
+ if (!existsSync5(settingsPath)) return "notFound";
687
1085
  let existing = {};
688
1086
  try {
689
- existing = JSON.parse(readFileSync5(settingsPath, "utf-8"));
1087
+ existing = JSON.parse(readFileSync6(settingsPath, "utf-8"));
690
1088
  } catch {
691
- rmSync(settingsPath, { force: true });
1089
+ rmSync2(settingsPath, { force: true });
692
1090
  return "deleted";
693
1091
  }
694
1092
  let result = existing;
695
1093
  for (const val of selectedValues) {
696
1094
  const group = SETTING_GROUPS.find((g) => g.value === val);
697
1095
  if (!group) continue;
698
- result = deepRemoveKeys(result, group.patch);
1096
+ result = deepRemoveKeys(
1097
+ result,
1098
+ group.patch
1099
+ );
699
1100
  }
700
1101
  if (Object.keys(result).length === 0) {
701
- rmSync(settingsPath, { force: true });
1102
+ rmSync2(settingsPath, { force: true });
702
1103
  return "deleted";
703
1104
  }
704
- writeFileSync3(settingsPath, JSON.stringify(result, null, 2) + "\n", "utf-8");
1105
+ writeFileSync5(settingsPath, JSON.stringify(result, null, 2) + "\n", "utf-8");
705
1106
  return "cleaned";
706
1107
  };
707
1108
 
708
1109
  // src/lib/claude-settings.ts
709
1110
  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";
1111
+ import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync7, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "fs";
1112
+ import { join as join12 } from "path";
712
1113
  var SETTING_GROUPS2 = [
713
1114
  {
714
1115
  value: "model",
@@ -728,7 +1129,8 @@ var promptClaudeSettings = async () => {
728
1129
  message: "Claude Code \uC124\uC815 \uD30C\uC77C(.claude/settings.local.json)\uC744 \uC124\uCE58\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
729
1130
  initialValue: true
730
1131
  });
731
- if (p2.isCancel(wantSettings) || !wantSettings) return null;
1132
+ if (p2.isCancel(wantSettings)) return PROMPT_CANCELLED;
1133
+ if (!wantSettings) return null;
732
1134
  const selected = await p2.multiselect({
733
1135
  message: "\uC124\uCE58\uD560 \uC124\uC815 \uD56D\uBAA9\uC744 \uC120\uD0DD\uD558\uC138\uC694 (\uC2A4\uD398\uC774\uC2A4\uB85C \uD1A0\uAE00)",
734
1136
  options: SETTING_GROUPS2.map((g) => ({
@@ -739,17 +1141,17 @@ var promptClaudeSettings = async () => {
739
1141
  initialValues: SETTING_GROUPS2.map((g) => g.value),
740
1142
  required: false
741
1143
  });
742
- if (p2.isCancel(selected)) return null;
1144
+ if (p2.isCancel(selected)) return PROMPT_CANCELLED;
743
1145
  return selected;
744
1146
  };
745
1147
  var installClaudeSettings = (basePath, selectedValues) => {
746
1148
  if (selectedValues.length === 0) return;
747
- const settingsDir = join8(basePath, ".claude");
748
- const settingsPath = join8(settingsDir, "settings.local.json");
1149
+ const settingsDir = join12(basePath, ".claude");
1150
+ const settingsPath = join12(settingsDir, "settings.local.json");
749
1151
  let existing = {};
750
- if (existsSync4(settingsPath)) {
1152
+ if (existsSync6(settingsPath)) {
751
1153
  try {
752
- existing = JSON.parse(readFileSync6(settingsPath, "utf-8"));
1154
+ existing = JSON.parse(readFileSync7(settingsPath, "utf-8"));
753
1155
  } catch {
754
1156
  }
755
1157
  }
@@ -759,37 +1161,40 @@ var installClaudeSettings = (basePath, selectedValues) => {
759
1161
  if (!group) continue;
760
1162
  merged = deepMerge(merged, group.patch);
761
1163
  }
762
- mkdirSync4(settingsDir, { recursive: true });
763
- writeFileSync4(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
1164
+ mkdirSync6(settingsDir, { recursive: true });
1165
+ writeFileSync6(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
764
1166
  };
765
1167
  var uninstallClaudeSettings = (basePath, selectedValues) => {
766
- const settingsPath = join8(basePath, ".claude", "settings.local.json");
767
- if (!existsSync4(settingsPath)) return "notFound";
1168
+ const settingsPath = join12(basePath, ".claude", "settings.local.json");
1169
+ if (!existsSync6(settingsPath)) return "notFound";
768
1170
  let existing = {};
769
1171
  try {
770
- existing = JSON.parse(readFileSync6(settingsPath, "utf-8"));
1172
+ existing = JSON.parse(readFileSync7(settingsPath, "utf-8"));
771
1173
  } catch {
772
- rmSync2(settingsPath, { force: true });
1174
+ rmSync3(settingsPath, { force: true });
773
1175
  return "deleted";
774
1176
  }
775
1177
  let result = existing;
776
1178
  for (const val of selectedValues) {
777
1179
  const group = SETTING_GROUPS2.find((g) => g.value === val);
778
1180
  if (!group) continue;
779
- result = deepRemoveKeys(result, group.patch);
1181
+ result = deepRemoveKeys(
1182
+ result,
1183
+ group.patch
1184
+ );
780
1185
  }
781
1186
  if (Object.keys(result).length === 0) {
782
- rmSync2(settingsPath, { force: true });
1187
+ rmSync3(settingsPath, { force: true });
783
1188
  return "deleted";
784
1189
  }
785
- writeFileSync4(settingsPath, JSON.stringify(result, null, 2) + "\n", "utf-8");
1190
+ writeFileSync6(settingsPath, JSON.stringify(result, null, 2) + "\n", "utf-8");
786
1191
  return "cleaned";
787
1192
  };
788
1193
 
789
1194
  // src/lib/prettier-ignore.ts
790
1195
  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";
1196
+ import { existsSync as existsSync7, readFileSync as readFileSync8, rmSync as rmSync4, writeFileSync as writeFileSync7 } from "fs";
1197
+ import { join as join13 } from "path";
793
1198
  var PRETTIER_IGNORE_CONTENT = `# CLAUDE
794
1199
  .claude/rules/
795
1200
  **/CLAUDE.md
@@ -851,38 +1256,82 @@ var promptPrettierIgnore = async () => {
851
1256
  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
1257
  initialValue: false
853
1258
  });
854
- if (p3.isCancel(want)) return false;
1259
+ if (p3.isCancel(want)) return PROMPT_CANCELLED;
855
1260
  return want;
856
1261
  };
857
1262
  var installPrettierIgnore = (basePath) => {
858
- const filePath = join9(basePath, ".prettierignore");
1263
+ const filePath = join13(basePath, ".prettierignore");
859
1264
  const section = wrapSection(PRETTIER_IGNORE_CONTENT);
860
- if (!existsSync5(filePath)) {
861
- writeFileSync5(filePath, section + "\n", "utf-8");
1265
+ if (!existsSync7(filePath)) {
1266
+ writeFileSync7(filePath, section + "\n", "utf-8");
862
1267
  return;
863
1268
  }
864
- const existing = readFileSync7(filePath, "utf-8");
1269
+ const existing = readFileSync8(filePath, "utf-8");
865
1270
  if (hasAiOpsSection2(existing)) {
866
- writeFileSync5(filePath, replaceSection(existing, PRETTIER_IGNORE_CONTENT), "utf-8");
1271
+ writeFileSync7(filePath, replaceSection(existing, PRETTIER_IGNORE_CONTENT), "utf-8");
867
1272
  return;
868
1273
  }
869
1274
  const separator = existing.endsWith("\n") ? "\n" : "\n\n";
870
- writeFileSync5(filePath, existing + separator + section + "\n", "utf-8");
1275
+ writeFileSync7(filePath, existing + separator + section + "\n", "utf-8");
871
1276
  };
872
1277
  var uninstallPrettierIgnore = (basePath) => {
873
- const filePath = join9(basePath, ".prettierignore");
874
- if (!existsSync5(filePath)) return "notFound";
875
- const existing = readFileSync7(filePath, "utf-8");
1278
+ const filePath = join13(basePath, ".prettierignore");
1279
+ if (!existsSync7(filePath)) return "notFound";
1280
+ const existing = readFileSync8(filePath, "utf-8");
876
1281
  if (!hasAiOpsSection2(existing)) return "notFound";
877
1282
  const stripped = stripAiOpsSection2(existing).trim();
878
1283
  if (stripped.length === 0) {
879
- rmSync3(filePath, { force: true });
1284
+ rmSync4(filePath, { force: true });
880
1285
  return "deleted";
881
1286
  }
882
- writeFileSync5(filePath, stripped + "\n", "utf-8");
1287
+ writeFileSync7(filePath, stripped + "\n", "utf-8");
883
1288
  return "cleaned";
884
1289
  };
885
1290
 
1291
+ // src/lib/skill-state.ts
1292
+ var resolveSkillScope = (params) => {
1293
+ if (params.scope !== void 0) {
1294
+ if (params.scope === "user") return "user";
1295
+ if (params.scope === "project") return "project";
1296
+ throw new Error(`Unsupported scope: ${params.scope}`);
1297
+ }
1298
+ if (params.project) return "project";
1299
+ return "user";
1300
+ };
1301
+ var resolveRequestedTools = (params) => {
1302
+ if (params.requested === void 0 || params.requested.length === 0) {
1303
+ return [...params.supported];
1304
+ }
1305
+ const supportedSet = new Set(params.supported);
1306
+ const invalid = params.requested.filter((tool) => !supportedSet.has(tool));
1307
+ if (invalid.length > 0) {
1308
+ throw new Error(`Unsupported tools requested: ${invalid.join(", ")}`);
1309
+ }
1310
+ return [...params.requested];
1311
+ };
1312
+ var TOOL_ORDER = [SKILL_TOOL.CLAUDE_CODE, SKILL_TOOL.CODEX, SKILL_TOOL.GEMINI];
1313
+ var mergeSkillTools = (params) => {
1314
+ const merged = /* @__PURE__ */ new Set([...params.existing ?? [], ...params.requested]);
1315
+ return TOOL_ORDER.filter((tool) => merged.has(tool));
1316
+ };
1317
+ var subtractSkillTools = (params) => {
1318
+ const installed = new Set(params.installed ?? []);
1319
+ return params.requested.filter((tool) => !installed.has(tool));
1320
+ };
1321
+ var upsertInstalledSkill = (installedSkills, nextSkill) => {
1322
+ const nextSkillId = resolveCanonicalSkillId(nextSkill.id);
1323
+ const remaining = installedSkills.filter((skill) => resolveCanonicalSkillId(skill.id) !== nextSkillId);
1324
+ return [...remaining, nextSkill];
1325
+ };
1326
+ var removeInstalledSkill = (installedSkills, skillId) => {
1327
+ const targetSkillId = resolveCanonicalSkillId(skillId);
1328
+ return installedSkills.filter((skill) => resolveCanonicalSkillId(skill.id) !== targetSkillId);
1329
+ };
1330
+ var findInstalledSkill = (installedSkills, skillId) => {
1331
+ const targetSkillId = resolveCanonicalSkillId(skillId);
1332
+ return installedSkills.find((skill) => resolveCanonicalSkillId(skill.id) === targetSkillId);
1333
+ };
1334
+
886
1335
  // src/commands/init.ts
887
1336
  var TOOL_OPTIONS = [
888
1337
  { value: "claude-code", label: "Claude Code" },
@@ -891,164 +1340,333 @@ var TOOL_OPTIONS = [
891
1340
  ];
892
1341
  var deduplicateRules = (rules) => {
893
1342
  const seen = /* @__PURE__ */ new Set();
894
- return rules.filter((r) => {
895
- if (seen.has(r.id)) return false;
896
- seen.add(r.id);
1343
+ return rules.filter((rule) => {
1344
+ if (seen.has(rule.id)) return false;
1345
+ seen.add(rule.id);
897
1346
  return true;
898
1347
  });
899
1348
  };
900
- var selectPresetAndFineTune = async (workspaceName, presets, allRules) => {
1349
+ var formatToolList = (toolIds) => toolIds.join(", ");
1350
+ var deduplicateSkillTargets = (targets) => {
1351
+ const merged = /* @__PURE__ */ new Map();
1352
+ for (const target of targets) {
1353
+ const previous = merged.get(target.skill.id);
1354
+ if (!previous) {
1355
+ merged.set(target.skill.id, {
1356
+ skill: target.skill,
1357
+ requestedTools: [...target.requestedTools]
1358
+ });
1359
+ continue;
1360
+ }
1361
+ merged.set(target.skill.id, {
1362
+ skill: target.skill,
1363
+ requestedTools: mergeSkillTools({
1364
+ existing: previous.requestedTools,
1365
+ requested: target.requestedTools
1366
+ })
1367
+ });
1368
+ }
1369
+ return [...merged.values()].sort((a, b) => a.skill.id.localeCompare(b.skill.id));
1370
+ };
1371
+ var resolveSupportedRequestedTools = (skill, selectedTools) => selectedTools.filter((toolId) => skill.supported_tools.includes(toolId));
1372
+ var partitionPresetSkills = (params) => {
1373
+ const globalSkills = [];
1374
+ const installableSkills = [];
1375
+ for (const skill of resolvePresetSkills(params.preset, params.allSkills)) {
1376
+ if (skill.kind !== "reference") {
1377
+ continue;
1378
+ }
1379
+ const supportedRequestedTools = resolveSupportedRequestedTools(skill, params.selectedTools);
1380
+ if (supportedRequestedTools.length === 0) {
1381
+ continue;
1382
+ }
1383
+ const installedGlobalSkill = findInstalledSkill(params.globalInstalledSkills, skill.id);
1384
+ const availableTools = installedGlobalSkill ? supportedRequestedTools.filter((toolId) => installedGlobalSkill.tools.includes(toolId)) : [];
1385
+ const requestedTools = subtractSkillTools({
1386
+ requested: supportedRequestedTools,
1387
+ installed: availableTools
1388
+ });
1389
+ if (requestedTools.length === 0) {
1390
+ globalSkills.push({
1391
+ skill,
1392
+ availableTools
1393
+ });
1394
+ continue;
1395
+ }
1396
+ installableSkills.push({
1397
+ skill,
1398
+ requestedTools,
1399
+ globalTools: availableTools
1400
+ });
1401
+ }
1402
+ return {
1403
+ globalSkills,
1404
+ installableSkills
1405
+ };
1406
+ };
1407
+ var selectPresetAndFineTune = async (workspaceName, presets, allRules, allSkills, selectedTools, globalInstalledSkills) => {
901
1408
  const preset = await p4.select({
902
1409
  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
1410
+ options: presets.map((candidate) => ({
1411
+ value: candidate,
1412
+ label: candidate.id,
1413
+ hint: candidate.description
907
1414
  }))
908
1415
  });
909
1416
  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),
1417
+ const finalRules = resolvePresetRules(preset, allRules);
1418
+ if (finalRules.length > 0) {
1419
+ p4.note(finalRules.map((rule) => ` \u2713 ${rule.id}`).join("\n"), `[${workspaceName}] core rules (\uC7A0\uAE08)`);
1420
+ }
1421
+ const { globalSkills, installableSkills } = partitionPresetSkills({
1422
+ preset,
1423
+ allSkills,
1424
+ selectedTools,
1425
+ globalInstalledSkills
1426
+ });
1427
+ if (globalSkills.length > 0) {
1428
+ const globalLines = globalSkills.map(
1429
+ ({ skill, availableTools }) => ` \u2713 ${skill.id} (${formatToolList(availableTools)})`
1430
+ );
1431
+ p4.note(globalLines.join("\n"), `[${workspaceName}] already available globally`);
1432
+ }
1433
+ if (installableSkills.length === 0) {
1434
+ p4.note(" \uC0C8\uB85C \uC124\uCE58\uD560 reference skill\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.", `[${workspaceName}] installable reference skills`);
1435
+ return {
1436
+ workspace: workspaceName,
1437
+ preset,
1438
+ finalRules,
1439
+ finalSkillTargets: []
1440
+ };
1441
+ }
1442
+ const selectedSkillIds = await p4.multiselect({
1443
+ message: `[${workspaceName}] installable reference skills \uC120\uD0DD`,
1444
+ options: installableSkills.map(({ skill, requestedTools, globalTools }) => ({
1445
+ value: skill.id,
1446
+ label: skill.id,
1447
+ hint: globalTools.length > 0 ? `global: ${formatToolList(globalTools)} / install: ${formatToolList(requestedTools)}` : `${skill.description} / install: ${formatToolList(requestedTools)}`
1448
+ })),
1449
+ initialValues: installableSkills.map(({ skill }) => skill.id),
925
1450
  required: false
926
1451
  });
927
- if (p4.isCancel(selectedDomain)) return null;
928
- const selectedLogicalRuleIds = [...globalGroupIds, ...selectedDomain];
1452
+ if (p4.isCancel(selectedSkillIds)) return null;
1453
+ const selectedSkillSet = new Set(selectedSkillIds);
929
1454
  return {
930
1455
  workspace: workspaceName,
931
1456
  preset,
932
- finalRules: resolvePresetRules({ ...preset, rules: selectedLogicalRuleIds }, allRules)
1457
+ finalRules,
1458
+ finalSkillTargets: installableSkills.filter(({ skill }) => selectedSkillSet.has(skill.id)).map(({ skill, requestedTools }) => ({
1459
+ skill,
1460
+ requestedTools
1461
+ }))
933
1462
  };
934
1463
  };
1464
+ var selectInitSkillScope = async () => {
1465
+ const scope = await p4.select({
1466
+ message: "\uC120\uD0DD\uB41C skills\uB97C \uC5B4\uB514\uC5D0 \uC124\uCE58\uD560\uAE4C\uC694?",
1467
+ options: [
1468
+ { value: "user", label: "user (global)", hint: "\uAE30\uBCF8\uAC12. \uC5EC\uB7EC \uD504\uB85C\uC81D\uD2B8\uC5D0\uC11C \uC7AC\uC0AC\uC6A9" },
1469
+ { value: "project", label: "project", hint: "\uD604\uC7AC \uD504\uB85C\uC81D\uD2B8\uC5D0\uB9CC \uC124\uCE58" }
1470
+ ]
1471
+ });
1472
+ return p4.isCancel(scope) ? null : scope;
1473
+ };
935
1474
  var initCommand = async () => {
936
1475
  const basePath = resolveBasePath();
1476
+ const userBasePath = resolveUserBasePath();
937
1477
  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);
1478
+ const skillsDir = resolveSkillsDir();
1479
+ const spinner3 = p4.spinner();
1480
+ let spinnerStarted = false;
1481
+ const cancelInit = (params) => {
1482
+ if (spinnerStarted) {
1483
+ spinner3.stop("\uC124\uCE58 \uC911\uB2E8\uB428");
1484
+ spinnerStarted = false;
965
1485
  }
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 })),
1486
+ p4.cancel(params?.message ?? "\uCDE8\uC18C\uB428");
1487
+ process.exit(params?.exitCode ?? 0);
1488
+ };
1489
+ const handleSigint = () => cancelInit({
1490
+ message: "\uC0AC\uC6A9\uC790 \uC694\uCCAD\uC73C\uB85C init\uC774 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4.",
1491
+ exitCode: 130
1492
+ });
1493
+ process.once("SIGINT", handleSigint);
1494
+ try {
1495
+ p4.intro("ai-ops init");
1496
+ const selectedTools = await p4.multiselect({
1497
+ message: "AI \uB3C4\uAD6C\uB97C \uC120\uD0DD\uD558\uC138\uC694",
1498
+ options: TOOL_OPTIONS,
972
1499
  required: true
973
1500
  });
974
- if (p4.isCancel(selectedWorkspaces)) {
975
- p4.cancel("\uCDE8\uC18C\uB428");
976
- process.exit(0);
1501
+ if (p4.isCancel(selectedTools)) {
1502
+ cancelInit();
977
1503
  }
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);
1504
+ const isMonorepo = await p4.confirm({
1505
+ message: "\uBAA8\uB178\uB808\uD3EC \uD504\uB85C\uC81D\uD2B8\uC785\uB2C8\uAE4C?",
1506
+ initialValue: false
1507
+ });
1508
+ if (p4.isCancel(isMonorepo)) {
1509
+ cancelInit();
985
1510
  }
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);
1511
+ const allRules = loadAllRules(rulesDir);
1512
+ const allSkills = loadAllSkills(skillsDir);
1513
+ const presets = loadPresets(resolvePresetsPath());
1514
+ const sourceHash = computeSourceHash(resolveCompilerDataDir());
1515
+ const globalInstalledSkills = readSkillRegistry(resolveSkillRegistryPath(userBasePath))?.skills ?? [];
1516
+ const mappings = [];
1517
+ if (!isMonorepo) {
1518
+ const mapping = await selectPresetAndFineTune(
1519
+ ".",
1520
+ presets,
1521
+ allRules,
1522
+ allSkills,
1523
+ selectedTools,
1524
+ globalInstalledSkills
1525
+ ) ?? cancelInit();
1526
+ mappings.push(mapping);
1007
1527
  } 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);
1528
+ const candidates = listWorkspaceCandidates(basePath);
1529
+ const selectedWorkspaces = await p4.multiselect({
1530
+ message: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uC120\uD0DD\uD558\uC138\uC694",
1531
+ options: candidates.map((candidate) => ({ value: candidate, label: candidate })),
1532
+ required: true
1533
+ });
1534
+ if (p4.isCancel(selectedWorkspaces)) {
1535
+ cancelInit();
1536
+ }
1537
+ for (const workspace of selectedWorkspaces) {
1538
+ const mapping = await selectPresetAndFineTune(
1539
+ workspace,
1540
+ presets,
1541
+ allRules,
1542
+ allSkills,
1543
+ selectedTools,
1544
+ globalInstalledSkills
1545
+ ) ?? cancelInit();
1546
+ mappings.push(mapping);
1547
+ }
1013
1548
  }
1549
+ const selectedSkillTargets = deduplicateSkillTargets(mappings.flatMap((mapping) => mapping.finalSkillTargets));
1550
+ const skillScope = selectedSkillTargets.length > 0 ? await selectInitSkillScope() : null;
1551
+ if (selectedSkillTargets.length > 0 && skillScope === null) {
1552
+ cancelInit();
1553
+ }
1554
+ const geminiSettingValues = selectedTools.includes("gemini") ? await promptGeminiSettings() : null;
1555
+ const resolvedGeminiSettingValues = isPromptCancelled(geminiSettingValues) ? cancelInit() : geminiSettingValues;
1556
+ const claudeSettingValues = selectedTools.includes("claude-code") ? await promptClaudeSettings() : null;
1557
+ const resolvedClaudeSettingValues = isPromptCancelled(claudeSettingValues) ? cancelInit() : claudeSettingValues;
1558
+ const wantPrettierIgnore = await promptPrettierIgnore();
1559
+ const resolvedWantPrettierIgnore = isPromptCancelled(wantPrettierIgnore) ? cancelInit() : wantPrettierIgnore;
1560
+ spinner3.start("\uADDC\uCE59 \uC124\uCE58 \uC911...");
1561
+ spinnerStarted = true;
1562
+ const meta = { sourceHash, generatedAt: (/* @__PURE__ */ new Date()).toISOString() };
1563
+ const allInstalledFiles = [];
1564
+ const allAppended = [];
1565
+ const selectedRuleIds = deduplicateRules(mappings.flatMap((mapping) => mapping.finalRules)).map((rule) => rule.id);
1566
+ let projectInstalledSkills = [];
1567
+ if (selectedSkillTargets.length > 0 && skillScope !== null) {
1568
+ const skillBasePath = skillScope === "project" ? basePath : userBasePath;
1569
+ const installedSkills = selectedSkillTargets.map(({ skill, requestedTools }) => {
1570
+ const existingUserSkill = skillScope === "user" ? findInstalledSkill(globalInstalledSkills, skill.id) : void 0;
1571
+ const nextRequestedTools = skillScope === "user" ? mergeSkillTools({
1572
+ existing: existingUserSkill?.tools,
1573
+ requested: requestedTools
1574
+ }) : requestedTools;
1575
+ const { packages, installedSkill } = buildSkillInstallPlan({
1576
+ skill,
1577
+ requestedTools: nextRequestedTools,
1578
+ scope: skillScope
1579
+ });
1580
+ installSkillPackages(skillBasePath, packages);
1581
+ return installedSkill;
1582
+ });
1583
+ if (skillScope === "project") {
1584
+ projectInstalledSkills = installedSkills;
1585
+ } else {
1586
+ const registryPath = resolveSkillRegistryPath(skillBasePath);
1587
+ const previous = readSkillRegistry(registryPath);
1588
+ const nextSkills = installedSkills.reduce(
1589
+ (acc, installedSkill) => upsertInstalledSkill(acc, installedSkill),
1590
+ previous?.skills ?? []
1591
+ );
1592
+ writeSkillRegistry(registryPath, {
1593
+ skills: nextSkills,
1594
+ cliVersion: getCliVersion(),
1595
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
1596
+ });
1597
+ }
1598
+ }
1599
+ for (const toolId of selectedTools) {
1600
+ if (isMonorepo) {
1601
+ const allWorkspaceRules = deduplicateRules(mappings.flatMap((mapping) => mapping.finalRules));
1602
+ const workspaceMappings = mappings.map((mapping) => ({
1603
+ path: mapping.workspace,
1604
+ ruleIds: mapping.finalRules.map((rule) => rule.id)
1605
+ }));
1606
+ const renderResult = renderForTool(toolId, allWorkspaceRules, workspaceMappings);
1607
+ const actions = buildInstallPlan({ toolId, renderResult, meta });
1608
+ const result = installFiles(basePath, actions, meta);
1609
+ allInstalledFiles.push(...result.written);
1610
+ allAppended.push(...result.appended);
1611
+ } else {
1612
+ const renderResult = renderForTool(toolId, mappings[0].finalRules);
1613
+ const actions = buildInstallPlan({ toolId, renderResult, meta });
1614
+ const result = installFiles(basePath, actions, meta);
1615
+ allInstalledFiles.push(...result.written);
1616
+ allAppended.push(...result.appended);
1617
+ }
1618
+ }
1619
+ if (resolvedGeminiSettingValues && resolvedGeminiSettingValues.length > 0) {
1620
+ installGeminiSettings(basePath, resolvedGeminiSettingValues);
1621
+ }
1622
+ if (resolvedClaudeSettingValues && resolvedClaudeSettingValues.length > 0) {
1623
+ installClaudeSettings(basePath, resolvedClaudeSettingValues);
1624
+ }
1625
+ if (resolvedWantPrettierIgnore) {
1626
+ installPrettierIgnore(basePath);
1627
+ }
1628
+ spinner3.stop("\uADDC\uCE59 \uC124\uCE58 \uC644\uB8CC");
1629
+ spinnerStarted = false;
1630
+ const workspacesRecord = isMonorepo ? Object.fromEntries(
1631
+ mappings.map((mapping) => [
1632
+ mapping.workspace,
1633
+ {
1634
+ preset: mapping.preset.id,
1635
+ rules: mapping.finalRules.map((rule) => rule.id)
1636
+ }
1637
+ ])
1638
+ ) : void 0;
1639
+ const manifest = buildManifest({
1640
+ tools: selectedTools,
1641
+ scope: "project",
1642
+ preset: !isMonorepo ? mappings[0].preset.id : void 0,
1643
+ workspaces: workspacesRecord,
1644
+ installedRules: selectedRuleIds,
1645
+ installedFiles: allInstalledFiles,
1646
+ installedSkills: projectInstalledSkills,
1647
+ appendedFiles: allAppended,
1648
+ settings: resolvedClaudeSettingValues || resolvedGeminiSettingValues || resolvedWantPrettierIgnore ? {
1649
+ claude: resolvedClaudeSettingValues ? [...resolvedClaudeSettingValues] : void 0,
1650
+ gemini: resolvedGeminiSettingValues ? [...resolvedGeminiSettingValues] : void 0,
1651
+ prettierignore: resolvedWantPrettierIgnore || void 0
1652
+ } : void 0,
1653
+ cliVersion: getCliVersion(),
1654
+ sourceHash
1655
+ });
1656
+ writeManifest(resolveManifestPath(basePath), manifest);
1657
+ if (allAppended.length > 0) {
1658
+ p4.log.info(`\uAE30\uC874 \uD30C\uC77C\uC5D0 \uC139\uC158 \uCD94\uAC00\uB428 (\uB0B4\uC6A9 \uBCF4\uC874):
1659
+ ${allAppended.map((file) => ` ${file}`).join("\n")}`);
1660
+ }
1661
+ p4.log.success(`\uC124\uCE58\uB41C core rules: ${selectedRuleIds.length}\uAC1C`);
1662
+ p4.log.success(`\uC124\uCE58\uB41C skills: ${selectedSkillTargets.length}\uAC1C${skillScope ? ` (${skillScope})` : ""}`);
1663
+ if (selectedSkillTargets.length > 0 && skillScope === "user") {
1664
+ p4.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.");
1665
+ }
1666
+ p4.outro("ai-ops init \uC644\uB8CC");
1667
+ } finally {
1668
+ process.off("SIGINT", handleSigint);
1014
1669
  }
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
1670
  };
1053
1671
 
1054
1672
  // src/commands/update.ts
@@ -1063,11 +1681,25 @@ var updateCommand = async (opts) => {
1063
1681
  process.exit(1);
1064
1682
  }
1065
1683
  const rulesDir = resolveRulesDir();
1066
- const sourceHash = computeSourceHash(rulesDir);
1684
+ const skillsDir = resolveSkillsDir();
1685
+ const presetsPath = resolvePresetsPath();
1686
+ const sourceHash = computeSourceHash(resolveCompilerDataDir());
1067
1687
  const cliVersion = getCliVersion();
1688
+ const allRules = loadAllRules(rulesDir);
1689
+ const allSkills = loadAllSkills(skillsDir);
1690
+ const presets = loadPresets(presetsPath);
1691
+ const resolvedRules = resolveManifestRules({
1692
+ manifest,
1693
+ allRules,
1694
+ presets
1695
+ });
1696
+ const resolvedSkills = resolveManifestProjectSkills({
1697
+ manifest,
1698
+ allSkills
1699
+ });
1068
1700
  const diffResult = computeDiff({
1069
1701
  previous: manifest,
1070
- currentRules: manifest.installed_rules,
1702
+ currentRules: resolvedRules.installedRules.map((rule) => rule.id),
1071
1703
  currentSourceHash: sourceHash,
1072
1704
  currentCliVersion: cliVersion
1073
1705
  });
@@ -1078,16 +1710,23 @@ var updateCommand = async (opts) => {
1078
1710
  }
1079
1711
  const s = p5.spinner();
1080
1712
  s.start("\uADDC\uCE59 \uAC31\uC2E0 \uC911...");
1081
- const allRules = loadAllRules(rulesDir);
1082
1713
  const meta = { sourceHash, generatedAt: (/* @__PURE__ */ new Date()).toISOString() };
1083
1714
  const allInstalledFiles = [];
1084
1715
  const allAppended = [];
1716
+ const installedSkills = resolvedSkills.map(({ skill, requestedTools }) => {
1717
+ const { packages, installedSkill } = buildSkillInstallPlan({
1718
+ skill,
1719
+ requestedTools,
1720
+ scope: "project"
1721
+ });
1722
+ installSkillPackages(basePath, packages);
1723
+ return installedSkill;
1724
+ });
1085
1725
  if (manifest.workspaces) {
1086
- const workspaceEntries = Object.entries(manifest.workspaces);
1726
+ const workspaceEntries = Object.entries(resolvedRules.workspaces ?? {});
1087
1727
  for (const toolIdStr of manifest.tools) {
1088
1728
  const toolId = toolIdStr;
1089
- const allInstalledRuleSet = new Set(manifest.installed_rules);
1090
- const rulesToInstall = allRules.filter((r2) => allInstalledRuleSet.has(r2.id));
1729
+ const rulesToInstall = resolvedRules.installedRules;
1091
1730
  const workspaceMappings = workspaceEntries.map(([path, entry]) => ({
1092
1731
  path,
1093
1732
  ruleIds: entry.rules
@@ -1099,8 +1738,7 @@ var updateCommand = async (opts) => {
1099
1738
  allAppended.push(...r.appended);
1100
1739
  }
1101
1740
  } else {
1102
- const installedRuleSet = new Set(manifest.installed_rules);
1103
- const rulesToInstall = allRules.filter((r) => installedRuleSet.has(r.id));
1741
+ const rulesToInstall = resolvedRules.installedRules;
1104
1742
  for (const toolIdStr of manifest.tools) {
1105
1743
  const toolId = toolIdStr;
1106
1744
  const renderResult = renderForTool(toolId, rulesToInstall);
@@ -1123,9 +1761,10 @@ var updateCommand = async (opts) => {
1123
1761
  tools: manifest.tools,
1124
1762
  scope: manifest.scope,
1125
1763
  preset: manifest.preset,
1126
- workspaces: manifest.workspaces,
1127
- installedRules: manifest.installed_rules,
1764
+ workspaces: resolvedRules.workspaces,
1765
+ installedRules: resolvedRules.installedRules.map((rule) => rule.id),
1128
1766
  installedFiles: allInstalledFiles.length > 0 ? allInstalledFiles : manifest.installed_files,
1767
+ installedSkills,
1129
1768
  appendedFiles: allAppended.length > 0 ? allAppended : manifest.appended_files,
1130
1769
  settings: manifest.settings ? {
1131
1770
  claude: manifest.settings.claude,
@@ -1150,12 +1789,35 @@ var diffCommand = async () => {
1150
1789
  p6.log.error("manifest\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 ai-ops init\uC744 \uC2E4\uD589\uD558\uC138\uC694.");
1151
1790
  process.exit(1);
1152
1791
  }
1153
- const sourceHash = computeSourceHash(resolveRulesDir());
1792
+ const sourceHash = computeSourceHash(resolveCompilerDataDir());
1793
+ const allRules = loadAllRules(resolveRulesDir());
1794
+ const allSkills = loadAllSkills(resolveSkillsDir());
1795
+ const presets = loadPresets(resolvePresetsPath());
1796
+ const resolvedRules = resolveManifestRules({
1797
+ manifest,
1798
+ allRules,
1799
+ presets
1800
+ });
1801
+ const resolvedSkills = resolveManifestProjectSkills({
1802
+ manifest,
1803
+ allSkills
1804
+ });
1154
1805
  const result = computeDiff({
1155
1806
  previous: manifest,
1156
- currentRules: manifest.installed_rules,
1807
+ currentRules: resolvedRules.installedRules.map((rule) => rule.id),
1157
1808
  currentSourceHash: sourceHash
1158
1809
  });
1810
+ const skillLines = resolvedSkills.map(({ skill, requestedTools }) => {
1811
+ const { installedSkill: next } = buildSkillInstallPlan({
1812
+ skill,
1813
+ requestedTools,
1814
+ scope: "project"
1815
+ });
1816
+ const previous = (manifest.installed_skills ?? []).find((installedSkill) => installedSkill.id === skill.id);
1817
+ const previousHash = previous?.sourceHash ?? "legacy";
1818
+ const changed = previousHash !== next.sourceHash;
1819
+ return `- ${skill.id}: ${changed ? "changed" : "up-to-date"} (${previousHash} -> ${next.sourceHash})`;
1820
+ });
1159
1821
  if (result.status === "up-to-date") {
1160
1822
  p6.log.success("\uBCC0\uACBD \uC0AC\uD56D \uC5C6\uC74C. \uCD5C\uC2E0 \uC0C1\uD0DC\uC785\uB2C8\uB2E4.");
1161
1823
  } else {
@@ -1169,39 +1831,43 @@ var diffCommand = async () => {
1169
1831
  p6.log.info(`\uC81C\uAC70\uB41C \uADDC\uCE59: ${result.removed.join(", ")}`);
1170
1832
  }
1171
1833
  }
1834
+ if (skillLines.length > 0) {
1835
+ p6.log.info(`project skills:
1836
+ ${skillLines.map((line) => ` ${line}`).join("\n")}`);
1837
+ }
1172
1838
  p6.outro("ai-ops diff \uC644\uB8CC");
1173
1839
  };
1174
1840
 
1175
1841
  // src/commands/uninstall.ts
1176
1842
  import * as p7 from "@clack/prompts";
1177
- import { rmSync as rmSync5 } from "fs";
1843
+ import { rmSync as rmSync6 } from "fs";
1178
1844
 
1179
1845
  // 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";
1846
+ import { existsSync as existsSync8, readFileSync as readFileSync9, rmSync as rmSync5, readdirSync as readdirSync4, writeFileSync as writeFileSync8 } from "fs";
1847
+ import { resolve as resolve7, dirname as dirname7 } from "path";
1182
1848
  var removeFiles = (basePath, relativePaths) => {
1183
1849
  const deleted = [];
1184
1850
  const cleaned = [];
1185
1851
  const skipped = [];
1186
1852
  const notFound = [];
1187
1853
  for (const rel of relativePaths) {
1188
- const absPath = resolve6(basePath, rel);
1189
- if (!existsSync6(absPath)) {
1854
+ const absPath = resolve7(basePath, rel);
1855
+ if (!existsSync8(absPath)) {
1190
1856
  notFound.push(rel);
1191
1857
  continue;
1192
1858
  }
1193
- const content = readFileSync8(absPath, "utf-8");
1859
+ const content = readFileSync9(absPath, "utf-8");
1194
1860
  if (hasAiOpsSection(content)) {
1195
1861
  const stripped = stripAiOpsSection(content);
1196
1862
  if (stripped.trim().length === 0) {
1197
- rmSync4(absPath);
1863
+ rmSync5(absPath);
1198
1864
  deleted.push(rel);
1199
1865
  } else {
1200
- writeFileSync6(absPath, stripped, "utf-8");
1866
+ writeFileSync8(absPath, stripped, "utf-8");
1201
1867
  cleaned.push(rel);
1202
1868
  }
1203
1869
  } else if (hasLegacyHeader(content)) {
1204
- rmSync4(absPath);
1870
+ rmSync5(absPath);
1205
1871
  deleted.push(rel);
1206
1872
  } else {
1207
1873
  skipped.push(rel);
@@ -1212,12 +1878,12 @@ var removeFiles = (basePath, relativePaths) => {
1212
1878
  var cleanEmptyDirs = (basePath, dirs) => {
1213
1879
  const removed = [];
1214
1880
  for (const dir of dirs) {
1215
- const absDir = resolve6(basePath, dir);
1216
- if (!existsSync6(absDir)) continue;
1881
+ const absDir = resolve7(basePath, dir);
1882
+ if (!existsSync8(absDir)) continue;
1217
1883
  try {
1218
1884
  const entries = readdirSync4(absDir);
1219
1885
  if (entries.length === 0) {
1220
- rmSync4(absDir, { recursive: true });
1886
+ rmSync5(absDir, { recursive: true });
1221
1887
  removed.push(dir);
1222
1888
  }
1223
1889
  } catch {
@@ -1228,7 +1894,7 @@ var cleanEmptyDirs = (basePath, dirs) => {
1228
1894
  var collectManagedDirs = (relativePaths) => {
1229
1895
  const dirs = /* @__PURE__ */ new Set();
1230
1896
  for (const rel of relativePaths) {
1231
- const dir = dirname5(rel);
1897
+ const dir = dirname7(rel);
1232
1898
  if (dir !== ".") {
1233
1899
  dirs.add(dir);
1234
1900
  }
@@ -1251,13 +1917,20 @@ var uninstallCommand = async () => {
1251
1917
  ...manifest.installed_files ?? inferInstalledFiles(manifest),
1252
1918
  ...manifest.appended_files ?? []
1253
1919
  ].filter((f) => !SETTINGS_PATHS.has(f));
1254
- if (targetFiles.length === 0) {
1920
+ const targetSkillDirs = (manifest.installed_skills ?? []).flatMap((skill) => skill.installed_paths);
1921
+ if (targetFiles.length === 0 && targetSkillDirs.length === 0) {
1255
1922
  p7.log.warn("\uC0AD\uC81C\uD560 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
1256
1923
  p7.outro("ai-ops uninstall \uC644\uB8CC");
1257
1924
  return;
1258
1925
  }
1259
- p7.log.info(`\uC0AD\uC81C \uB300\uC0C1 \uD30C\uC77C (${targetFiles.length}\uAC1C):
1926
+ if (targetFiles.length > 0) {
1927
+ p7.log.info(`\uC0AD\uC81C \uB300\uC0C1 \uD30C\uC77C (${targetFiles.length}\uAC1C):
1260
1928
  ${targetFiles.map((f) => ` ${f}`).join("\n")}`);
1929
+ }
1930
+ if (targetSkillDirs.length > 0) {
1931
+ p7.log.info(`\uC0AD\uC81C \uB300\uC0C1 skill \uB514\uB809\uD1A0\uB9AC (${targetSkillDirs.length}\uAC1C):
1932
+ ${targetSkillDirs.map((f) => ` ${f}`).join("\n")}`);
1933
+ }
1261
1934
  const confirmed = await p7.confirm({
1262
1935
  message: "\uC704 \uD30C\uC77C\uACFC manifest\uB97C \uBAA8\uB450 \uC0AD\uC81C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
1263
1936
  initialValue: false
@@ -1281,9 +1954,10 @@ ${targetFiles.map((f) => ` ${f}`).join("\n")}`);
1281
1954
  if (prettierStatus === "deleted") settingsMessages.push("\uC0AD\uC81C: .prettierignore");
1282
1955
  else if (prettierStatus === "cleaned") settingsMessages.push("ai-ops \uC139\uC158 \uC81C\uAC70 (\uC0AC\uC6A9\uC790 \uB0B4\uC6A9 \uBCF4\uC874): .prettierignore");
1283
1956
  const result = removeFiles(basePath, targetFiles);
1957
+ const removedSkillDirs = removeDirectories(basePath, targetSkillDirs);
1284
1958
  const dirs = collectManagedDirs(targetFiles);
1285
1959
  const removedDirs = cleanEmptyDirs(basePath, dirs);
1286
- rmSync5(manifestPath, { force: true });
1960
+ rmSync6(manifestPath, { force: true });
1287
1961
  if (result.deleted.length > 0) {
1288
1962
  p7.log.success(`\uC0AD\uC81C \uC644\uB8CC (${result.deleted.length}\uAC1C):
1289
1963
  ${result.deleted.map((f) => ` ${f}`).join("\n")}`);
@@ -1307,6 +1981,10 @@ ${result.notFound.map((f) => ` ${f}`).join("\n")}`);
1307
1981
  if (removedDirs.length > 0) {
1308
1982
  p7.log.info(`\uBE48 \uB514\uB809\uD1A0\uB9AC \uC815\uB9AC (${removedDirs.length}\uAC1C):
1309
1983
  ${removedDirs.map((d) => ` ${d}`).join("\n")}`);
1984
+ }
1985
+ if (removedSkillDirs.length > 0) {
1986
+ p7.log.success(`skill \uB514\uB809\uD1A0\uB9AC \uC0AD\uC81C (${removedSkillDirs.length}\uAC1C):
1987
+ ${removedSkillDirs.map((d) => ` ${d}`).join("\n")}`);
1310
1988
  }
1311
1989
  if (settingsMessages.length > 0) {
1312
1990
  p7.log.success(`\uC124\uC815 \uD30C\uC77C \uCC98\uB9AC:
@@ -1316,19 +1994,297 @@ ${settingsMessages.map((m) => ` ${m}`).join("\n")}`);
1316
1994
  p7.outro("ai-ops uninstall \uC644\uB8CC");
1317
1995
  };
1318
1996
 
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);
1997
+ // src/commands/skill.ts
1998
+ import * as p8 from "@clack/prompts";
1999
+ import { rmSync as rmSync7 } from "fs";
2000
+ var resolveScopeContext = (opts) => {
2001
+ const scope = resolveSkillScope(opts);
2002
+ return {
2003
+ scope,
2004
+ basePath: scope === "project" ? resolveBasePath() : resolveUserBasePath()
2005
+ };
2006
+ };
2007
+ var loadCompilerInputs = () => {
2008
+ const compilerDataDir = resolveCompilerDataDir();
2009
+ return {
2010
+ allSkills: loadAllSkills(resolveSkillsDir()),
2011
+ sourceHash: computeSourceHash(compilerDataDir),
2012
+ cliVersion: getCliVersion()
2013
+ };
2014
+ };
2015
+ var resolveSkillById = (skills, skillId) => {
2016
+ const canonicalSkillId = resolveCanonicalSkillId(skillId);
2017
+ const skill = skills.find((candidate) => candidate.id === canonicalSkillId);
2018
+ if (!skill) {
2019
+ throw new Error(`Unknown skill: ${skillId}`);
2020
+ }
2021
+ return skill;
2022
+ };
2023
+ var assertScopeAllowed = (skill, scope) => {
2024
+ if (!skill.install_scopes.includes(scope)) {
2025
+ throw new Error(`Skill ${skill.id} does not support ${scope} scope`);
1325
2026
  }
1326
2027
  };
2028
+ var writeProjectSkillState = (params) => {
2029
+ const manifestPath = resolveManifestPath(params.basePath);
2030
+ const previous = readManifest(manifestPath);
2031
+ const installedSkills = params.removeSkillId ? removeInstalledSkill(previous?.installed_skills ?? [], params.removeSkillId) : params.nextSkill ? upsertInstalledSkill(previous?.installed_skills ?? [], params.nextSkill) : previous?.installed_skills ?? [];
2032
+ const nextTools = params.nextSkill !== void 0 ? [.../* @__PURE__ */ new Set([...previous?.tools ?? [], ...params.nextSkill.tools])] : previous?.tools ?? [];
2033
+ 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;
2034
+ if (!hasProjectState) {
2035
+ rmSync7(manifestPath, { force: true });
2036
+ return;
2037
+ }
2038
+ const manifest = buildManifest({
2039
+ tools: nextTools.length > 0 ? nextTools : params.nextSkill?.tools ?? ["codex"],
2040
+ scope: "project",
2041
+ preset: previous?.preset,
2042
+ workspaces: previous?.workspaces,
2043
+ installedRules: previous?.installed_rules ?? [],
2044
+ installedFiles: previous?.installed_files,
2045
+ installedSkills,
2046
+ appendedFiles: previous?.appended_files,
2047
+ settings: previous?.settings,
2048
+ cliVersion: params.cliVersion,
2049
+ sourceHash: params.sourceHash
2050
+ });
2051
+ writeManifest(manifestPath, manifest);
2052
+ };
2053
+ var writeUserSkillState = (params) => {
2054
+ const registryPath = resolveSkillRegistryPath(params.basePath);
2055
+ const previous = readSkillRegistry(registryPath);
2056
+ const skills = params.removeSkillId ? removeInstalledSkill(previous?.skills ?? [], params.removeSkillId) : params.nextSkill ? upsertInstalledSkill(previous?.skills ?? [], params.nextSkill) : previous?.skills ?? [];
2057
+ if (skills.length === 0) {
2058
+ rmSync7(registryPath, { force: true });
2059
+ return;
2060
+ }
2061
+ writeSkillRegistry(registryPath, {
2062
+ skills,
2063
+ cliVersion: params.cliVersion,
2064
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
2065
+ });
2066
+ };
2067
+ var readInstalledSkills = (scope, basePath) => {
2068
+ if (scope === "project") {
2069
+ return (readManifest(resolveManifestPath(basePath))?.installed_skills ?? []).map((installedSkill) => ({
2070
+ ...installedSkill,
2071
+ id: resolveCanonicalSkillId(installedSkill.id)
2072
+ }));
2073
+ }
2074
+ return (readSkillRegistry(resolveSkillRegistryPath(basePath))?.skills ?? []).map((installedSkill) => ({
2075
+ ...installedSkill,
2076
+ id: resolveCanonicalSkillId(installedSkill.id)
2077
+ }));
2078
+ };
2079
+ var installSkill = (params) => {
2080
+ const installedSkills = readInstalledSkills(params.scope, params.basePath);
2081
+ const existingInstalledSkill = findInstalledSkill(installedSkills, params.skill.id);
2082
+ const nextRequestedTools = mergeSkillTools({
2083
+ existing: existingInstalledSkill?.tools,
2084
+ requested: params.requestedTools
2085
+ });
2086
+ const { packages, installedSkill } = buildSkillInstallPlan({
2087
+ skill: params.skill,
2088
+ requestedTools: nextRequestedTools,
2089
+ scope: params.scope
2090
+ });
2091
+ installSkillPackages(params.basePath, packages);
2092
+ if (params.scope === "project") {
2093
+ writeProjectSkillState({
2094
+ basePath: params.basePath,
2095
+ sourceHash: params.sourceHash,
2096
+ cliVersion: params.cliVersion,
2097
+ nextSkill: installedSkill
2098
+ });
2099
+ } else {
2100
+ writeUserSkillState({
2101
+ basePath: params.basePath,
2102
+ cliVersion: params.cliVersion,
2103
+ nextSkill: installedSkill
2104
+ });
2105
+ }
2106
+ return installedSkill;
2107
+ };
2108
+ var skillListCommand = async (opts) => {
2109
+ const { scope, basePath } = resolveScopeContext(opts);
2110
+ const { allSkills } = loadCompilerInputs();
2111
+ const installedSkills = readInstalledSkills(scope, basePath);
2112
+ p8.intro(`ai-ops skill list (${scope})`);
2113
+ const sections = [
2114
+ { kind: "reference", title: "reference skills" },
2115
+ { kind: "task", title: "task skills" }
2116
+ ].map(({ kind, title }) => {
2117
+ const lines = allSkills.filter((skill) => skill.kind === kind).map((skill) => {
2118
+ const installed = findInstalledSkill(installedSkills, skill.id);
2119
+ const suffix = installed ? `installed for ${installed.tools.join(", ")}` : "not installed";
2120
+ return `- ${skill.id} (${skill.install_scopes.join(", ")}) - ${suffix}`;
2121
+ });
2122
+ if (lines.length === 0) {
2123
+ return null;
2124
+ }
2125
+ return `${title}
2126
+ ${lines.join("\n")}`;
2127
+ }).filter((section) => section !== null);
2128
+ p8.log.info(sections.join("\n\n"));
2129
+ p8.outro("ai-ops skill list \uC644\uB8CC");
2130
+ };
2131
+ var skillInstallCommand = async (skillId, opts) => {
2132
+ const { scope, basePath } = resolveScopeContext(opts);
2133
+ const { allSkills, sourceHash, cliVersion } = loadCompilerInputs();
2134
+ const skill = resolveSkillById(allSkills, skillId);
2135
+ assertScopeAllowed(skill, scope);
2136
+ const requestedTools = resolveRequestedTools({ requested: opts.tool, supported: skill.supported_tools });
2137
+ p8.intro(`ai-ops skill install ${skillId}`);
2138
+ const installedSkill = installSkill({
2139
+ skill,
2140
+ requestedTools,
2141
+ scope,
2142
+ basePath,
2143
+ cliVersion,
2144
+ sourceHash
2145
+ });
2146
+ p8.log.success(`\uC124\uCE58 \uC644\uB8CC: ${installedSkill.id} (${installedSkill.installed_paths.join(", ")})`);
2147
+ p8.outro("ai-ops skill install \uC644\uB8CC");
2148
+ };
2149
+ var skillDiffCommand = async (skillId, opts) => {
2150
+ const { scope, basePath } = resolveScopeContext(opts);
2151
+ const { allSkills } = loadCompilerInputs();
2152
+ const installedSkills = readInstalledSkills(scope, basePath);
2153
+ const targets = skillId ? installedSkills.filter((skill) => skill.id === skillId) : installedSkills;
2154
+ p8.intro(`ai-ops skill diff (${scope})`);
2155
+ if (targets.length === 0) {
2156
+ p8.log.warn("\uBE44\uAD50\uD560 \uC124\uCE58\uB41C skill\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
2157
+ p8.outro("ai-ops skill diff \uC644\uB8CC");
2158
+ return;
2159
+ }
2160
+ const lines = targets.map((installedSkill) => {
2161
+ const skill = resolveSkillById(allSkills, installedSkill.id);
2162
+ const { installedSkill: next } = buildSkillInstallPlan({
2163
+ skill,
2164
+ requestedTools: installedSkill.tools,
2165
+ scope
2166
+ });
2167
+ const changed = next.sourceHash !== installedSkill.sourceHash;
2168
+ return `- ${installedSkill.id}: ${changed ? "changed" : "up-to-date"} (${installedSkill.sourceHash} -> ${next.sourceHash})`;
2169
+ });
2170
+ p8.log.info(lines.join("\n"));
2171
+ p8.outro("ai-ops skill diff \uC644\uB8CC");
2172
+ };
2173
+ var skillUpdateCommand = async (skillId, opts) => {
2174
+ const { scope, basePath } = resolveScopeContext(opts);
2175
+ const { allSkills, sourceHash, cliVersion } = loadCompilerInputs();
2176
+ const installedSkills = readInstalledSkills(scope, basePath);
2177
+ const targets = skillId ? installedSkills.filter((skill) => skill.id === skillId) : installedSkills;
2178
+ p8.intro(`ai-ops skill update (${scope})`);
2179
+ if (targets.length === 0) {
2180
+ p8.log.warn("\uAC31\uC2E0\uD560 \uC124\uCE58\uB41C skill\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
2181
+ p8.outro("ai-ops skill update \uC644\uB8CC");
2182
+ return;
2183
+ }
2184
+ const nextInstalledSkills = targets.map((installedSkill) => {
2185
+ const skill = resolveSkillById(allSkills, installedSkill.id);
2186
+ const { packages, installedSkill: next } = buildSkillInstallPlan({
2187
+ skill,
2188
+ requestedTools: installedSkill.tools,
2189
+ scope
2190
+ });
2191
+ installSkillPackages(basePath, packages);
2192
+ return next;
2193
+ });
2194
+ if (scope === "project") {
2195
+ const manifestPath = resolveManifestPath(basePath);
2196
+ const previous = readManifest(manifestPath);
2197
+ if (!previous) {
2198
+ p8.log.error("project manifest\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
2199
+ process.exit(1);
2200
+ }
2201
+ const untouched = (previous.installed_skills ?? []).filter(
2202
+ (installedSkill) => !nextInstalledSkills.some((next) => next.id === installedSkill.id)
2203
+ );
2204
+ writeManifest(
2205
+ manifestPath,
2206
+ buildManifest({
2207
+ tools: previous.tools,
2208
+ scope: previous.scope,
2209
+ preset: previous.preset,
2210
+ workspaces: previous.workspaces,
2211
+ installedRules: previous.installed_rules,
2212
+ installedFiles: previous.installed_files,
2213
+ installedSkills: [...untouched, ...nextInstalledSkills],
2214
+ appendedFiles: previous.appended_files,
2215
+ settings: previous.settings,
2216
+ cliVersion,
2217
+ sourceHash
2218
+ })
2219
+ );
2220
+ } else {
2221
+ const registryPath = resolveSkillRegistryPath(basePath);
2222
+ const previous = readSkillRegistry(registryPath);
2223
+ const untouched = (previous?.skills ?? []).filter(
2224
+ (installedSkill) => !nextInstalledSkills.some((next) => next.id === installedSkill.id)
2225
+ );
2226
+ writeSkillRegistry(registryPath, {
2227
+ skills: [...untouched, ...nextInstalledSkills],
2228
+ cliVersion,
2229
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
2230
+ });
2231
+ }
2232
+ p8.log.success(`\uAC31\uC2E0 \uC644\uB8CC: ${nextInstalledSkills.map((skill) => skill.id).join(", ")}`);
2233
+ p8.outro("ai-ops skill update \uC644\uB8CC");
2234
+ };
2235
+ var skillUninstallCommand = async (skillId, opts) => {
2236
+ const { scope, basePath } = resolveScopeContext(opts);
2237
+ const { sourceHash, cliVersion } = loadCompilerInputs();
2238
+ const installedSkills = readInstalledSkills(scope, basePath);
2239
+ const installedSkill = findInstalledSkill(installedSkills, skillId);
2240
+ p8.intro(`ai-ops skill uninstall ${skillId}`);
2241
+ if (!installedSkill) {
2242
+ p8.log.warn("\uC124\uCE58\uB41C skill\uC744 \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
2243
+ p8.outro("ai-ops skill uninstall \uC644\uB8CC");
2244
+ return;
2245
+ }
2246
+ const removed = removeDirectories(basePath, installedSkill.installed_paths);
2247
+ if (scope === "project") {
2248
+ writeProjectSkillState({
2249
+ basePath,
2250
+ sourceHash,
2251
+ cliVersion,
2252
+ removeSkillId: skillId
2253
+ });
2254
+ } else {
2255
+ writeUserSkillState({
2256
+ basePath,
2257
+ cliVersion,
2258
+ removeSkillId: skillId
2259
+ });
2260
+ }
2261
+ p8.log.success(`\uC81C\uAC70 \uC644\uB8CC: ${removed.join(", ")}`);
2262
+ p8.outro("ai-ops skill uninstall \uC644\uB8CC");
2263
+ };
2264
+
2265
+ // src/bin/index.ts
2266
+ var program = new Command();
1327
2267
  program.name("ai-ops").description("AI \uC5D0\uC774\uC804\uD2B8 \uADDC\uCE59 \uC2A4\uCE90\uD3F4\uB354").version("0.1.0");
1328
2268
  program.command("init").description("AI \uADDC\uCE59 \uCD08\uAE30 \uC124\uCE58").action(() => initCommand());
1329
2269
  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
2270
  program.command("diff").description("\uC124\uCE58\uB41C \uADDC\uCE59\uACFC \uCD5C\uC2E0 \uC18C\uC2A4 \uBE44\uAD50").action(() => diffCommand());
1331
2271
  program.command("uninstall").description("\uC124\uCE58\uB41C \uADDC\uCE59 \uD30C\uC77C \uBC0F manifest \uC81C\uAC70").action(() => uninstallCommand());
1332
- ensureNoDeprecatedScopeFlag(process.argv);
2272
+ var skillCommand = program.command("skill").description("\uC5D0\uC774\uC804\uD2B8 skill \uC124\uCE58/\uC870\uD68C/\uAC31\uC2E0");
2273
+ 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");
2274
+ applySkillScopeOptions(skillCommand.command("list").description("\uC0AC\uC6A9 \uAC00\uB2A5\uD55C skill \uBAA9\uB85D")).action(
2275
+ (opts) => skillListCommand(opts)
2276
+ );
2277
+ applySkillScopeOptions(skillCommand.command("install <skillId>").description("skill \uC124\uCE58")).action(
2278
+ (skillId, opts) => skillInstallCommand(skillId, opts)
2279
+ );
2280
+ applySkillScopeOptions(skillCommand.command("diff [skillId]").description("skill \uBCC0\uACBD \uBE44\uAD50")).action(
2281
+ (skillId, opts) => skillDiffCommand(skillId, opts)
2282
+ );
2283
+ applySkillScopeOptions(skillCommand.command("update [skillId]").description("skill \uAC31\uC2E0")).action(
2284
+ (skillId, opts) => skillUpdateCommand(skillId, opts)
2285
+ );
2286
+ applySkillScopeOptions(skillCommand.command("uninstall <skillId>").description("skill \uC81C\uAC70")).action(
2287
+ (skillId, opts) => skillUninstallCommand(skillId, opts)
2288
+ );
1333
2289
  program.parse();
1334
2290
  //# sourceMappingURL=index.js.map