ai-ops-cli 0.2.4 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.ko.md +170 -0
  2. package/README.md +109 -163
  3. package/data/context-layer/AGENTS.md +29 -0
  4. package/data/context-layer/CLAUDE.md +14 -0
  5. package/data/context-layer/GEMINI.md +14 -0
  6. package/data/context-layer/docs/agent/checks/impact-checklist.md +16 -0
  7. package/data/context-layer/docs/agent/checks/review-checklist.md +17 -0
  8. package/data/context-layer/docs/agent/maps/codebase-map.md +16 -0
  9. package/data/context-layer/docs/agent/rules/doc-update-rules.md +22 -0
  10. package/data/context-layer/docs/agent/rules/routing-rules.md +22 -0
  11. package/data/context-layer/docs/agent/rules/stop-rules.md +20 -0
  12. package/data/context-layer/docs/agent/workflow.md +25 -0
  13. package/data/context-layer/docs/business/business-rules.md +16 -0
  14. package/data/context-layer/docs/docs-status.md +14 -0
  15. package/data/packs/pack-registry.json +8 -0
  16. package/data/packs/spec-lifecycle/docs/specs/README.ko.md +26 -0
  17. package/data/packs/spec-lifecycle/docs/specs/README.md +26 -0
  18. package/data/packs/spec-lifecycle/docs/specs/baseline/.gitkeep +1 -0
  19. package/data/packs/spec-lifecycle/docs/specs/initial-build/.gitkeep +1 -0
  20. package/data/skills/README.ko.md +182 -0
  21. package/data/skills/README.md +27 -2
  22. package/data/skills/reference-skills/frontend-app-flutter-runtime/references/reference.md +4 -0
  23. package/data/skills/reference-skills/frontend-web-react-next-runtime/references/reference.md +6 -0
  24. package/data/skills/reference-skills/graphql-client-integration/references/reference.md +3 -0
  25. package/data/skills/skill-registry.json +64 -16
  26. package/data/skills/task-skills/doc-impact-reviewer/SKILL.md +101 -0
  27. package/data/skills/task-skills/doc-impact-reviewer/agents/openai.yaml +6 -0
  28. package/data/skills/task-skills/spec-baseline-sync/SKILL.md +134 -0
  29. package/data/skills/task-skills/spec-baseline-sync/agents/openai.yaml +6 -0
  30. package/data/skills/task-skills/spec-baseline-sync/references/template.md +14 -0
  31. package/data/skills/task-skills/spec-product-01-idea-to-brief/SKILL.md +78 -0
  32. package/data/skills/task-skills/spec-product-01-idea-to-brief/agents/openai.yaml +6 -0
  33. package/data/skills/task-skills/spec-product-01-idea-to-brief/references/template.md +36 -0
  34. package/data/skills/task-skills/spec-product-02-brief-to-technical-context/SKILL.md +91 -0
  35. package/data/skills/task-skills/spec-product-02-brief-to-technical-context/agents/openai.yaml +6 -0
  36. package/data/skills/task-skills/spec-product-02-brief-to-technical-context/references/template.md +58 -0
  37. package/data/skills/task-skills/spec-product-03-brief-to-product-spec/SKILL.md +85 -0
  38. package/data/skills/task-skills/spec-product-03-brief-to-product-spec/agents/openai.yaml +6 -0
  39. package/data/skills/task-skills/spec-product-03-brief-to-product-spec/references/template.md +41 -0
  40. package/data/skills/task-skills/spec-product-04-product-spec-to-ui-spec/SKILL.md +93 -0
  41. package/data/skills/task-skills/spec-product-04-product-spec-to-ui-spec/agents/openai.yaml +6 -0
  42. package/data/skills/task-skills/spec-product-04-product-spec-to-ui-spec/references/stitch-prompt-template.md +41 -0
  43. package/data/skills/task-skills/spec-product-04-product-spec-to-ui-spec/references/ui-spec-template.md +39 -0
  44. package/data/skills/task-skills/spec-product-05-spec-to-work-packets/SKILL.md +157 -0
  45. package/data/skills/task-skills/spec-product-05-spec-to-work-packets/agents/openai.yaml +6 -0
  46. package/data/skills/task-skills/spec-product-05-spec-to-work-packets/references/stitch-html-review.md +25 -0
  47. package/data/skills/task-skills/spec-product-05-spec-to-work-packets/references/work-packet-template.md +67 -0
  48. package/data/skills/task-skills/spec-shared-glossary-sync/SKILL.md +102 -0
  49. package/data/skills/task-skills/spec-shared-glossary-sync/agents/openai.yaml +6 -0
  50. package/data/skills/task-skills/spec-shared-glossary-sync/references/checklist.md +36 -0
  51. package/data/skills/task-skills/spec-shared-glossary-sync/references/template.md +58 -0
  52. package/data/subagents/README.ko.md +47 -0
  53. package/data/subagents/README.md +47 -0
  54. package/data/subagents/security-gate/PROMPT.md +18 -0
  55. package/data/subagents/security-gate/claude.frontmatter.yaml +8 -0
  56. package/data/subagents/security-gate/codex.frontmatter.toml +6 -0
  57. package/data/subagents/security-gate/gemini.frontmatter.yaml +6 -0
  58. package/data/subagents/security-reviewer/PROMPT.md +17 -0
  59. package/data/subagents/security-reviewer/claude.frontmatter.yaml +9 -0
  60. package/data/subagents/security-reviewer/codex.frontmatter.toml +6 -0
  61. package/data/subagents/security-reviewer/gemini.frontmatter.yaml +6 -0
  62. package/data/subagents/subagent-registry.json +14 -0
  63. package/dist/bin/index.js +2101 -1712
  64. package/dist/bin/index.js.map +1 -1
  65. package/package.json +2 -2
package/dist/bin/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import { Command } from "commander";
5
5
 
6
6
  // src/commands/init.ts
7
- import * as p3 from "@clack/prompts";
7
+ import * as p2 from "@clack/prompts";
8
8
 
9
9
  // src/core/schemas/rule.schema.ts
10
10
  import { z } from "zod";
@@ -46,17 +46,12 @@ var SKILL_KIND = {
46
46
  REFERENCE: "reference",
47
47
  TASK: "task"
48
48
  };
49
- var SKILL_SCOPE = {
50
- PROJECT: "project",
51
- USER: "user"
52
- };
53
49
  var SKILL_TOOL = {
54
50
  CLAUDE_CODE: "claude-code",
55
51
  CODEX: "codex",
56
52
  GEMINI: "gemini"
57
53
  };
58
54
  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
55
  var SkillToolSchema = z3.union([
61
56
  z3.literal(SKILL_TOOL.CLAUDE_CODE),
62
57
  z3.literal(SKILL_TOOL.CODEX),
@@ -74,7 +69,6 @@ var InstalledSkillSchema = z3.object({
74
69
  id: z3.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "id must be kebab-case"),
75
70
  kind: SkillKindSchema,
76
71
  tools: z3.array(SkillToolSchema).min(1),
77
- scope: SkillScopeSchema,
78
72
  installed_paths: z3.array(z3.string().min(1)).min(1),
79
73
  sourceHash: z3.string().regex(/^[a-f0-9]{6}$/, "sourceHash must be 6 lowercase hex chars")
80
74
  }).strip();
@@ -87,7 +81,6 @@ var SkillCatalogEntrySchema = z4.object({
87
81
  id: SkillIdSchema,
88
82
  kind: SkillKindSchema,
89
83
  supported_tools: z4.array(SkillToolSchema).min(1),
90
- install_scopes: z4.array(SkillScopeSchema).min(1),
91
84
  groups: z4.array(z4.string().min(1)),
92
85
  included_in_presets: z4.array(z4.string().min(1)),
93
86
  source_path: SkillCatalogPathSchema
@@ -113,43 +106,216 @@ var SkillRegistrySchema = z5.object({
113
106
  generatedAt: z5.string().datetime({ offset: true })
114
107
  }).strict();
115
108
 
116
- // src/core/schemas/manifest.schema.ts
109
+ // src/core/schemas/subagent.schema.ts
110
+ import { z as z7 } from "zod";
111
+
112
+ // src/core/schemas/project-layer.schema.ts
117
113
  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()
114
+ var ProjectLayerToolSchema = z6.enum(["claude-code", "codex", "gemini"]);
115
+ var ProjectLayerDocumentStatusSchema = z6.enum(["Active", "Reserved", "Draft", "Archived"]);
116
+ var ShortHashSchema = z6.string().regex(/^[a-f0-9]{6}$/, "hash must be 6 lowercase hex chars");
117
+ var isSafeProjectLayerPath = (value) => {
118
+ if (value.length === 0) return false;
119
+ if (value.includes("\0")) return false;
120
+ if (value.includes("\\")) return false;
121
+ if (value.startsWith("/")) return false;
122
+ if (/^[A-Za-z]:/.test(value)) return false;
123
+ const segments = value.split("/");
124
+ return segments.every((segment) => segment.length > 0 && segment !== "." && segment !== "..");
125
+ };
126
+ var ProjectLayerPathSchema = z6.string().min(1).refine(isSafeProjectLayerPath, "path must be a safe project-relative path");
127
+ var ProjectLayerFrontmatterSchema = z6.object({
128
+ status: ProjectLayerDocumentStatusSchema,
129
+ layer: z6.string().min(1),
130
+ owner: z6.string().min(1),
131
+ read_when: z6.array(z6.string().min(1)).min(1),
132
+ update_when: z6.array(z6.string().min(1)).min(1)
133
+ }).strict();
134
+ var ProjectLayerManagedFileSchema = z6.object({
135
+ path: ProjectLayerPathSchema,
136
+ sourceHash: ShortHashSchema
137
+ }).strict();
138
+ var ProjectLayerProjectFileSchema = z6.object({
139
+ path: ProjectLayerPathSchema,
140
+ templateHash: ShortHashSchema,
141
+ created: z6.boolean()
142
+ }).strict();
143
+ var ProjectLayerPackFileRecordSchema = z6.object({
144
+ path: ProjectLayerPathSchema,
145
+ sourceHash: ShortHashSchema
146
+ }).strict();
147
+ var ProjectLayerPackRecordSchema = z6.object({
148
+ id: z6.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "id must be kebab-case"),
149
+ sourceHash: ShortHashSchema,
150
+ documents: z6.array(ProjectLayerPackFileRecordSchema),
151
+ files: z6.array(ProjectLayerPackFileRecordSchema),
152
+ installedAt: z6.string().datetime({ offset: true })
153
+ }).strict();
154
+ var ProjectLayerManifestSchema = z6.object({
155
+ schemaVersion: z6.literal(1),
156
+ kind: z6.literal("project-operating-layer"),
157
+ tools: z6.array(ProjectLayerToolSchema).min(1),
158
+ managed_files: z6.array(ProjectLayerManagedFileSchema),
159
+ project_files: z6.array(ProjectLayerProjectFileSchema),
160
+ packs: z6.array(ProjectLayerPackRecordSchema).default([]),
161
+ settings: z6.record(z6.unknown()),
162
+ sourceHash: ShortHashSchema,
163
+ cliVersion: z6.string().min(1),
164
+ generatedAt: z6.string().datetime({ offset: true })
165
+ }).strict();
166
+ var ProjectLayerContextDocumentSchema = ProjectLayerFrontmatterSchema.extend({
167
+ path: ProjectLayerPathSchema,
168
+ contentHash: ShortHashSchema
169
+ }).strict();
170
+ var ProjectLayerContextIndexSchema = z6.object({
171
+ schemaVersion: z6.literal(1),
172
+ kind: z6.literal("context-layer-index"),
173
+ documents: z6.array(ProjectLayerContextDocumentSchema),
174
+ generatedAt: z6.string().datetime({ offset: true })
175
+ }).strict();
176
+
177
+ // src/core/subagent-paths.ts
178
+ import { join } from "path";
179
+ var SUBAGENT_TOOL_OUTPUTS = {
180
+ "claude-code": {
181
+ dir: ".claude/agents",
182
+ extension: ".md"
183
+ },
184
+ codex: {
185
+ dir: ".codex/agents",
186
+ extension: ".toml"
187
+ },
188
+ gemini: {
189
+ dir: ".gemini/agents",
190
+ extension: ".md"
191
+ }
192
+ };
193
+ var buildSubagentRelativePath = (subagentId, toolId) => {
194
+ const output = SUBAGENT_TOOL_OUTPUTS[toolId];
195
+ return join(output.dir, `${subagentId}${output.extension}`);
196
+ };
197
+
198
+ // src/core/schemas/subagent.schema.ts
199
+ var SubagentIdSchema = z7.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "id must be kebab-case");
200
+ var SubagentMarkdownFrontmatterSchema = z7.object({
201
+ name: SubagentIdSchema,
202
+ description: z7.string().min(1)
203
+ }).passthrough();
204
+ var TomlValueSchema = z7.union([z7.string(), z7.number(), z7.boolean(), z7.array(z7.string())]);
205
+ var SubagentInstalledPathSchema = z7.string().min(1).refine(isSafeProjectLayerPath, "installed path must be safe relative path");
206
+ var CodexSubagentFrontmatterSchema = z7.object({
207
+ name: SubagentIdSchema,
208
+ description: z7.string().min(1),
209
+ skill_names: z7.array(SubagentIdSchema).optional()
210
+ }).catchall(TomlValueSchema);
211
+ var InstalledSubagentSchema = z7.object({
212
+ id: SubagentIdSchema,
213
+ tools: z7.array(SkillToolSchema).min(1),
214
+ installed_paths: z7.array(SubagentInstalledPathSchema).min(1),
215
+ sourceHash: z7.string().regex(/^[a-f0-9]{6}$/, "sourceHash must be 6 lowercase hex chars")
216
+ }).strip().superRefine((subagent, ctx) => {
217
+ const expectedPaths = new Set(subagent.tools.map((tool) => buildSubagentRelativePath(subagent.id, tool)));
218
+ const installedPaths = new Set(subagent.installed_paths);
219
+ if (installedPaths.size !== subagent.installed_paths.length) {
220
+ ctx.addIssue({
221
+ code: z7.ZodIssueCode.custom,
222
+ path: ["installed_paths"],
223
+ message: "installed_paths must not contain duplicates"
224
+ });
225
+ return;
226
+ }
227
+ if (installedPaths.size !== expectedPaths.size) {
228
+ ctx.addIssue({
229
+ code: z7.ZodIssueCode.custom,
230
+ path: ["installed_paths"],
231
+ message: "installed_paths must match id and tools"
232
+ });
233
+ return;
234
+ }
235
+ for (const installedPath of installedPaths) {
236
+ if (!expectedPaths.has(installedPath)) {
237
+ ctx.addIssue({
238
+ code: z7.ZodIssueCode.custom,
239
+ path: ["installed_paths"],
240
+ message: "installed_paths must match id and tools"
241
+ });
242
+ return;
243
+ }
244
+ }
245
+ });
246
+
247
+ // src/core/schemas/subagent-catalog.schema.ts
248
+ import { z as z8 } from "zod";
249
+ var SubagentCatalogPathSchema = z8.string().regex(
250
+ /^[a-z0-9]+(?:-[a-z0-9]+)*(?:\/[a-z0-9]+(?:-[a-z0-9]+)*)*$/,
251
+ "source_path must be relative kebab-case path"
252
+ );
253
+ var SubagentCatalogEntrySchema = z8.object({
254
+ id: SubagentIdSchema,
255
+ supported_tools: z8.array(SkillToolSchema).min(1),
256
+ source_path: SubagentCatalogPathSchema
257
+ }).strict();
258
+ var SubagentCatalogSchema = z8.object({
259
+ subagents: z8.array(SubagentCatalogEntrySchema)
260
+ }).strict();
261
+
262
+ // src/core/schemas/subagent-manifest.schema.ts
263
+ import { z as z9 } from "zod";
264
+ var SubagentManifestSchema = z9.object({
265
+ subagents: z9.array(InstalledSubagentSchema),
266
+ cliVersion: z9.string().optional(),
267
+ generatedAt: z9.string().datetime({ offset: true })
268
+ }).strict();
269
+
270
+ // src/core/schemas/pack.schema.ts
271
+ import { z as z10 } from "zod";
272
+ var PackIdSchema = z10.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "id must be kebab-case");
273
+ var PackSourcePathSchema = z10.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*(?:\/[a-z0-9]+(?:-[a-z0-9]+)*)*$/, "source_path must be relative kebab-case path");
274
+ var PackCatalogEntrySchema = z10.object({
275
+ id: PackIdSchema,
276
+ source_path: PackSourcePathSchema
277
+ }).strict();
278
+ var PackCatalogSchema = z10.object({
279
+ packs: z10.array(PackCatalogEntrySchema)
280
+ }).strict();
281
+
282
+ // src/core/schemas/manifest.schema.ts
283
+ import { z as z11 } from "zod";
284
+ var SettingsConfigSchema = z11.object({
285
+ claude: z11.array(z11.string().min(1)).optional(),
286
+ gemini: z11.array(z11.string().min(1)).optional(),
287
+ prettierignore: z11.boolean().optional()
122
288
  }).strict();
123
- var WorkspaceEntrySchema = z6.object({
124
- preset: z6.string().min(1),
125
- rules: z6.array(z6.string().min(1))
289
+ var WorkspaceEntrySchema = z11.object({
290
+ preset: z11.string().min(1),
291
+ rules: z11.array(z11.string().min(1))
126
292
  }).strict();
127
- var ManifestSchema = z6.object({
128
- tools: z6.array(z6.string().min(1)).min(1),
129
- scope: z6.literal("project"),
293
+ var ManifestSchema = z11.object({
294
+ tools: z11.array(z11.string().min(1)).min(1),
295
+ scope: z11.literal("project"),
130
296
  /** 비모노레포 단일 preset */
131
- preset: z6.string().min(1).optional(),
297
+ preset: z11.string().min(1).optional(),
132
298
  /** 모노레포: workspace path → { preset, rules } */
133
- workspaces: z6.record(z6.string(), WorkspaceEntrySchema).optional(),
134
- installed_rules: z6.array(z6.string().min(1)),
299
+ workspaces: z11.record(z11.string(), WorkspaceEntrySchema).optional(),
300
+ installed_rules: z11.array(z11.string().min(1)),
135
301
  /** 실제 디스크에 쓰여진 파일 상대 경로 목록 (uninstall용). 기존 manifest 호환성 위해 optional */
136
- installed_files: z6.array(z6.string().min(1)).optional(),
302
+ installed_files: z11.array(z11.string().min(1)).optional(),
137
303
  /** skill 설치 루트 디렉토리 목록 */
138
- installed_skills: z6.array(InstalledSkillSchema).optional(),
304
+ installed_skills: z11.array(InstalledSkillSchema).optional(),
139
305
  /** non-managed 파일에 섹션을 append한 경우 추적 (uninstall 시 섹션만 제거) */
140
- appended_files: z6.array(z6.string().min(1)).optional(),
306
+ appended_files: z11.array(z11.string().min(1)).optional(),
141
307
  /** init 시 선택된 settings 항목 — update 시 재생성에 사용 */
142
308
  settings: SettingsConfigSchema.optional(),
143
309
  /** init/update 실행 시점의 CLI 패키지 버전 — 버전 변경 감지에 사용 */
144
- cliVersion: z6.string().optional(),
310
+ cliVersion: z11.string().optional(),
145
311
  /** SSOT 데이터 파일들의 deterministic SHA-256 해시 (6자리 hex). diff/update 판단 기준 */
146
- sourceHash: z6.string().regex(/^[a-f0-9]{6}$/, "sourceHash must be 6 lowercase hex chars"),
147
- generatedAt: z6.string().datetime({ offset: true })
312
+ sourceHash: z11.string().regex(/^[a-f0-9]{6}$/, "sourceHash must be 6 lowercase hex chars"),
313
+ generatedAt: z11.string().datetime({ offset: true })
148
314
  }).strict();
149
315
 
150
316
  // src/core/loader.ts
151
317
  import { readFileSync, readdirSync } from "fs";
152
- import { join, resolve } from "path";
318
+ import { join as join2, resolve } from "path";
153
319
  import { parse as parse2 } from "yaml";
154
320
 
155
321
  // src/core/frontmatter.ts
@@ -165,68 +331,83 @@ var parseMarkdownFrontmatter = (content) => {
165
331
  };
166
332
  };
167
333
 
168
- // src/core/loader.ts
169
- var sortRulesByPriority = (rules) => [...rules].sort((a, b) => b.priority - a.priority);
170
- var deduplicateRulesById = (rules) => {
171
- const seen = /* @__PURE__ */ new Set();
172
- return rules.filter((rule) => {
173
- if (seen.has(rule.id)) return false;
174
- seen.add(rule.id);
175
- return true;
176
- });
177
- };
178
- var resolveRuleById = (ruleId, allRules, context) => {
179
- const found = allRules.find((rule) => rule.id === ruleId);
180
- if (!found) {
181
- const suffix = context ? ` (from ${context})` : "";
182
- throw new Error(`Rule not found: ${ruleId}${suffix}`);
334
+ // src/core/subagent-toml.ts
335
+ var parseTomlValue = (value) => {
336
+ const trimmed = value.trim();
337
+ if (trimmed.startsWith('"') || trimmed.startsWith("[")) {
338
+ const parsed = JSON.parse(trimmed);
339
+ if (typeof parsed === "string") {
340
+ return parsed;
341
+ }
342
+ if (Array.isArray(parsed) && parsed.every((item) => typeof item === "string")) {
343
+ return parsed;
344
+ }
345
+ throw new Error(`Unsupported TOML value: ${value}`);
183
346
  }
184
- return found;
185
- };
186
- var parseRawPresets = (raw) => Object.entries(raw).map(([id, value]) => PresetSchema.parse({ id, ...value }));
187
- var resolvePresetRules = (preset, allRules) => {
188
- const resolved = preset.rules.map((ruleId) => resolveRuleById(ruleId, allRules, preset.id));
189
- return sortRulesByPriority(deduplicateRulesById(resolved));
347
+ if (trimmed === "true") return true;
348
+ if (trimmed === "false") return false;
349
+ const numericValue = Number(trimmed);
350
+ if (Number.isFinite(numericValue)) {
351
+ return numericValue;
352
+ }
353
+ throw new Error(`Unsupported TOML value: ${value}`);
190
354
  };
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));
355
+ var parseFlatToml = (content) => {
356
+ const result = {};
357
+ const lines = content.split("\n");
358
+ for (const line of lines) {
359
+ const trimmed = line.trim();
360
+ if (trimmed.length === 0 || trimmed.startsWith("#")) {
361
+ continue;
362
+ }
363
+ const match = trimmed.match(/^([A-Za-z0-9_-]+)\s*=\s*(.+)$/);
364
+ if (!match) {
365
+ throw new Error(`Unsupported TOML line: ${line}`);
366
+ }
367
+ const [, key, value] = match;
368
+ result[key] = parseTomlValue(value);
369
+ }
370
+ return result;
193
371
  };
194
- var loadRuleFile = (filePath) => {
195
- const raw = readFileSync(filePath, "utf-8");
196
- return RuleSchema.parse(parse2(raw));
372
+ var renderTomlValue = (value) => {
373
+ if (typeof value === "string") {
374
+ return JSON.stringify(value);
375
+ }
376
+ if (typeof value === "number" || typeof value === "boolean") {
377
+ return String(value);
378
+ }
379
+ return `[${value.map((item) => JSON.stringify(item)).join(", ")}]`;
197
380
  };
381
+ var renderFlatToml = (entries) => entries.map(([key, value]) => `${key} = ${renderTomlValue(value)}`).join("\n");
382
+
383
+ // src/core/loader.ts
198
384
  var loadSkillDirectoryFiles = (skillDir) => {
199
385
  const files = [];
200
386
  const walk = (relativeDir = "") => {
201
- const absDir = relativeDir.length > 0 ? join(skillDir, relativeDir) : skillDir;
387
+ const absDir = relativeDir.length > 0 ? join2(skillDir, relativeDir) : skillDir;
202
388
  const entries = readdirSync(absDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
203
389
  for (const entry of entries) {
204
- const nextRelativePath = relativeDir.length > 0 ? join(relativeDir, entry.name) : entry.name;
390
+ const nextRelativePath = relativeDir.length > 0 ? join2(relativeDir, entry.name) : entry.name;
205
391
  if (entry.isDirectory()) {
206
392
  walk(nextRelativePath);
207
393
  continue;
208
394
  }
209
395
  files.push({
210
396
  path: nextRelativePath,
211
- content: readFileSync(join(skillDir, nextRelativePath), "utf-8")
397
+ content: readFileSync(join2(skillDir, nextRelativePath), "utf-8")
212
398
  });
213
399
  }
214
400
  };
215
401
  walk();
216
402
  return files;
217
403
  };
218
- var loadAllRules = (rulesDir) => {
219
- const files = readdirSync(rulesDir).filter((f) => f.endsWith(".yaml")).sort();
220
- const rules = files.map((f) => loadRuleFile(resolve(rulesDir, f)));
221
- return sortRulesByPriority(rules);
222
- };
223
404
  var loadSkillCatalog = (skillsDir) => SkillCatalogSchema.parse(JSON.parse(readFileSync(resolve(skillsDir, "skill-registry.json"), "utf-8")));
224
405
  var loadAllSkills = (skillsDir) => {
225
406
  const catalog = loadSkillCatalog(skillsDir);
226
407
  const entries = [...catalog.skills].sort((a, b) => a.id.localeCompare(b.id));
227
408
  return entries.map((entry) => {
228
409
  const directory = resolve(skillsDir, entry.source_path);
229
- const skillMdPath = join(directory, "SKILL.md");
410
+ const skillMdPath = join2(directory, "SKILL.md");
230
411
  const rawSkillMd = readFileSync(skillMdPath, "utf-8");
231
412
  const { frontmatter } = parseMarkdownFrontmatter(rawSkillMd);
232
413
  const parsed = SkillFrontmatterSchema.parse(frontmatter);
@@ -242,7 +423,6 @@ var loadAllSkills = (skillsDir) => {
242
423
  kind: entry.kind,
243
424
  description: parsed.description,
244
425
  supported_tools: [...entry.supported_tools],
245
- install_scopes: [...entry.install_scopes],
246
426
  groups: [...entry.groups],
247
427
  included_in_presets: [...entry.included_in_presets],
248
428
  directory,
@@ -250,191 +430,69 @@ var loadAllSkills = (skillsDir) => {
250
430
  };
251
431
  });
252
432
  };
253
- var loadPresets = (presetsPath) => {
254
- const raw = readFileSync(presetsPath, "utf-8");
255
- const data = parse2(raw);
256
- return parseRawPresets(data);
257
- };
258
-
259
- // src/core/renderer.ts
260
- import { join as join2 } from "path";
261
-
262
- // src/core/tool-output.ts
263
- var GLOBAL_CATEGORIES = ["persona", "communication", "philosophy", "convention", "standard"];
264
- var CLAUDE_CODE_PATH_GLOBS = {
265
- typescript: ["**/*.ts", "**/*.tsx"],
266
- "react-typescript": ["**/*.tsx", "**/*.jsx"],
267
- nextjs: ["**/app/**", "next.config.*", "**/middleware.ts"],
268
- nestjs: ["**/*.module.ts", "**/*.controller.ts", "**/*.service.ts"],
269
- "nestjs-graphql": ["**/*.resolver.ts"],
270
- "graphql-core": ["**/*.graphql", "**/*.gql"],
271
- "graphql-client-web": ["**/*.graphql", "**/*.gql", "**/*.tsx", "**/*.ts"],
272
- "graphql-client-app": ["**/*.graphql", "**/*.gql", "lib/**/*.dart"],
273
- "graphql-server": ["**/*.graphql", "**/*.gql", "**/*.resolver.ts"],
274
- "prisma-postgresql": ["prisma/**", "**/*.prisma"],
275
- "shadcn-ui": ["**/components/ui/**"],
276
- flutter: ["lib/**/*.dart"],
277
- python: ["**/*.py"],
278
- fastapi: ["**/routers/**", "**/main.py"],
279
- sqlalchemy: ["**/models/**/*.py", "alembic/**"],
280
- "data-pipeline-python": ["**/pipelines/**", "**/etl/**"],
281
- "ai-llm-python": ["**/agents/**", "**/chains/**"],
282
- "libs-frontend-web": ["**/*.tsx", "**/*.ts"],
283
- "libs-frontend-app": ["lib/**/*.dart"],
284
- "libs-backend-ts": ["**/*.ts"],
285
- "libs-backend-python": ["**/*.py"]
286
- };
287
- var TOOL_OUTPUT_MAP = {
288
- "claude-code": {
289
- mode: "multi-file",
290
- rulesDir: ".claude/rules",
291
- fileExtension: ".md",
292
- // single: path-scoped (paths: frontmatter) / monorepo: hierarchical ({workspace}/CLAUDE.md)
293
- contextStrategy: "hybrid"
294
- },
295
- codex: {
296
- mode: "multi-file",
297
- dir: "",
298
- rootFileName: "AGENTS.md",
299
- // global 룰
300
- domainFileName: "AGENTS.override.md",
301
- // domain 룰 (하위 폴더)
302
- contextStrategy: "hierarchical"
303
- // 루트 + 하위 폴더 JIT
304
- },
305
- gemini: {
306
- mode: "multi-file",
307
- dir: "",
308
- rootFileName: "GEMINI.md",
309
- // global 룰 (루트)
310
- domainFileName: "GEMINI.md",
311
- // domain 룰 (하위 폴더)
312
- contextStrategy: "hierarchical"
313
- // 루트 + 하위 폴더 JIT
433
+ var readRequiredTextFile = (filePath) => {
434
+ try {
435
+ return readFileSync(filePath, "utf-8");
436
+ } catch (error) {
437
+ const cause = error instanceof Error ? `: ${error.message}` : "";
438
+ throw new Error(`Required subagent source file is missing: ${filePath}${cause}`);
314
439
  }
315
440
  };
316
-
317
- // src/core/renderer.ts
318
- var ruleIdToTitle = (id) => id.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
319
- var renderDecisionTable = (entries) => {
320
- const escape = (s) => s.replace(/\|/g, "|");
321
- const hasAvoid = entries.some((e) => e.avoid !== void 0);
322
- const header = hasAvoid ? "| When | Then | Avoid |\n|------|------|-------|" : "| When | Then |\n|------|------|";
323
- const rows = entries.map((e) => {
324
- const when = escape(e.when);
325
- const then = escape(e.then);
326
- if (hasAvoid) {
327
- const avoid = e.avoid ? escape(e.avoid) : "";
328
- return `| ${when} | ${then} | ${avoid} |`;
329
- }
330
- return `| ${when} | ${then} |`;
331
- });
332
- return [header, ...rows].join("\n");
333
- };
334
- var renderRuleToMarkdown = (rule) => {
335
- const sections = [`# ${ruleIdToTitle(rule.id)}`];
336
- if (rule.content.constraints.length > 0) {
337
- sections.push("## Constraints");
338
- sections.push(rule.content.constraints.map((c) => `- ${c}`).join("\n"));
339
- }
340
- if (rule.content.guidelines.length > 0) {
341
- sections.push("## Guidelines");
342
- sections.push(rule.content.guidelines.map((g) => `- ${g}`).join("\n"));
343
- }
344
- if (rule.content.decision_table && rule.content.decision_table.length > 0) {
345
- sections.push("## Decision Table");
346
- sections.push(renderDecisionTable(rule.content.decision_table));
347
- }
348
- return sections.join("\n\n");
349
- };
350
- var renderRulesToMarkdown = (rules) => rules.map((rule) => renderRuleToMarkdown(rule)).join("\n\n---\n\n");
351
- var isGlobalRule = (rule) => GLOBAL_CATEGORIES.includes(rule.category);
352
- var partitionRules = (rules) => {
353
- const global = [];
354
- const domain = [];
355
- for (const rule of rules) {
356
- if (isGlobalRule(rule)) {
357
- global.push(rule);
358
- } else {
359
- domain.push(rule);
360
- }
441
+ var assertSubagentFrontmatterName = (params) => {
442
+ if (params.name !== params.id) {
443
+ throw new Error(`Subagent ${params.tool} frontmatter name mismatch: ${params.id} != ${params.name}`);
361
444
  }
362
- return { global, domain };
363
445
  };
364
- var renderFrontmatter = (paths) => {
365
- const lines = paths.map((p9) => ` - "${p9}"`).join("\n");
366
- return `---
367
- paths:
368
- ${lines}
369
- ---`;
446
+ var loadSubagentCatalog = (subagentsDir) => SubagentCatalogSchema.parse(JSON.parse(readFileSync(resolve(subagentsDir, "subagent-registry.json"), "utf-8")));
447
+ var loadAllSubagents = (subagentsDir) => {
448
+ const catalog = loadSubagentCatalog(subagentsDir);
449
+ const entries = [...catalog.subagents].sort((a, b) => a.id.localeCompare(b.id));
450
+ return entries.map((entry) => {
451
+ const directory = resolve(subagentsDir, entry.source_path);
452
+ const prompt = readRequiredTextFile(join2(directory, "PROMPT.md"));
453
+ const claudeRaw = readRequiredTextFile(join2(directory, "claude.frontmatter.yaml"));
454
+ const codexRaw = readRequiredTextFile(join2(directory, "codex.frontmatter.toml"));
455
+ const geminiRaw = readRequiredTextFile(join2(directory, "gemini.frontmatter.yaml"));
456
+ const claude = SubagentMarkdownFrontmatterSchema.parse(parse2(claudeRaw));
457
+ const codex = CodexSubagentFrontmatterSchema.parse(parseFlatToml(codexRaw));
458
+ const gemini = SubagentMarkdownFrontmatterSchema.parse(parse2(geminiRaw));
459
+ assertSubagentFrontmatterName({ id: entry.id, tool: "claude", name: claude.name });
460
+ assertSubagentFrontmatterName({ id: entry.id, tool: "codex", name: codex.name });
461
+ assertSubagentFrontmatterName({ id: entry.id, tool: "gemini", name: gemini.name });
462
+ return {
463
+ id: entry.id,
464
+ supported_tools: [...entry.supported_tools],
465
+ source_path: entry.source_path,
466
+ directory,
467
+ prompt,
468
+ frontmatter: {
469
+ claude: {
470
+ raw: claudeRaw,
471
+ parsed: claude
472
+ },
473
+ codex: {
474
+ raw: codexRaw,
475
+ parsed: codex
476
+ },
477
+ gemini: {
478
+ raw: geminiRaw,
479
+ parsed: gemini
480
+ }
481
+ }
482
+ };
483
+ });
370
484
  };
371
- var renderClaudeCodeRule = (rule) => {
372
- const globs = CLAUDE_CODE_PATH_GLOBS[rule.id];
373
- if (!isGlobalRule(rule) && globs !== void 0) {
374
- return `${renderFrontmatter(globs)}
375
485
 
376
- ${renderRuleToMarkdown(rule)}`;
377
- }
378
- return renderRuleToMarkdown(rule);
379
- };
380
- var renderForTool = (toolId, rules, workspaceMappings) => {
381
- const config = TOOL_OUTPUT_MAP[toolId];
382
- if (toolId === "claude-code") {
383
- const { rulesDir, fileExtension } = config;
384
- if (!workspaceMappings || workspaceMappings.length === 0) {
385
- const files = rules.map((rule) => ({
386
- relativePath: join2(rulesDir, `${rule.id}${fileExtension}`),
387
- content: renderClaudeCodeRule(rule)
388
- }));
389
- return { tool: "claude-code", files };
390
- }
391
- const { global: global2, domain: domain2 } = partitionRules(rules);
392
- const globalFiles = global2.map((rule) => ({
393
- relativePath: join2(rulesDir, `${rule.id}${fileExtension}`),
394
- content: renderRuleToMarkdown(rule)
395
- // global은 frontmatter 불필요
396
- }));
397
- const workspaceFiles = [];
398
- for (const ws of workspaceMappings) {
399
- const wsRules = domain2.filter((r) => ws.ruleIds.includes(r.id));
400
- if (wsRules.length === 0) continue;
401
- workspaceFiles.push({
402
- relativePath: join2(ws.path, "CLAUDE.md"),
403
- content: renderRulesToMarkdown(wsRules)
404
- });
405
- }
406
- return { tool: "claude-code", files: [...globalFiles, ...workspaceFiles] };
407
- }
408
- if (!workspaceMappings || workspaceMappings.length === 0) {
409
- const rootContent2 = renderRulesToMarkdown(rules);
410
- const domainFiles2 = [];
411
- if (toolId === "codex") return { tool: "codex", rootContent: rootContent2, domainFiles: domainFiles2 };
412
- return { tool: "gemini", rootContent: rootContent2, domainFiles: domainFiles2 };
413
- }
414
- const { global, domain } = partitionRules(rules);
415
- const rootContent = renderRulesToMarkdown(global);
416
- const domainFiles = [];
417
- for (const ws of workspaceMappings) {
418
- const wsRules = domain.filter((r) => ws.ruleIds.includes(r.id));
419
- if (wsRules.length === 0) continue;
420
- domainFiles.push({
421
- workspacePath: ws.path,
422
- content: renderRulesToMarkdown(wsRules)
423
- });
424
- }
425
- if (toolId === "codex") {
426
- return { tool: "codex", rootContent, domainFiles };
427
- }
428
- return { tool: "gemini", rootContent, domainFiles };
429
- };
486
+ // src/core/renderer.ts
487
+ import { join as join3 } from "path";
430
488
 
431
489
  // src/core/skill-renderer.ts
432
- import { join as join4 } from "path";
490
+ import { join as join5 } from "path";
433
491
 
434
492
  // src/core/source-hash.ts
435
493
  import { createHash } from "crypto";
436
494
  import { existsSync, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
437
- import { dirname, join as join3, resolve as resolve2 } from "path";
495
+ import { dirname, join as join4, resolve as resolve2 } from "path";
438
496
  import { fileURLToPath } from "url";
439
497
  var __dirname = dirname(fileURLToPath(import.meta.url));
440
498
  var getCliVersion = () => {
@@ -447,68 +505,8 @@ var getCliVersion = () => {
447
505
  }
448
506
  };
449
507
  var computeHash = (contents) => createHash("sha256").update(contents.join("")).digest("hex").slice(0, 6);
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"));
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
508
  var computeInstalledSkillHash = (params) => computeHash([params.kind, params.description, ...[...params.tools].sort(), ...[...params.files].sort()]);
494
- var buildManifest = (params) => ManifestSchema.parse({
495
- tools: [...params.tools],
496
- scope: params.scope,
497
- preset: params.preset,
498
- workspaces: params.workspaces,
499
- installed_rules: [...params.installedRules],
500
- installed_files: params.installedFiles ? [...params.installedFiles] : void 0,
501
- installed_skills: params.installedSkills ? [...params.installedSkills] : void 0,
502
- appended_files: params.appendedFiles && params.appendedFiles.length > 0 ? [...params.appendedFiles] : void 0,
503
- settings: params.settings ? {
504
- claude: params.settings.claude ? [...params.settings.claude] : void 0,
505
- gemini: params.settings.gemini ? [...params.settings.gemini] : void 0,
506
- prettierignore: params.settings.prettierignore
507
- } : void 0,
508
- cliVersion: params.cliVersion,
509
- sourceHash: params.sourceHash,
510
- generatedAt: (/* @__PURE__ */ new Date()).toISOString()
511
- });
509
+ var computeInstalledSubagentHash = (params) => computeHash([params.id, params.prompt, ...[...params.tools].sort(), ...[...params.metadataFiles].sort()]);
512
510
 
513
511
  // src/core/skill-renderer.ts
514
512
  var AGENT_SKILLS_DIR = ".agents/skills";
@@ -516,10 +514,10 @@ var CLAUDE_SKILLS_DIR = ".claude/skills";
516
514
  var buildRootDirs = (skillId, toolIds) => {
517
515
  const dirs = [];
518
516
  if (toolIds.some((toolId) => toolId === "codex" || toolId === "gemini")) {
519
- dirs.push(join4(AGENT_SKILLS_DIR, skillId));
517
+ dirs.push(join5(AGENT_SKILLS_DIR, skillId));
520
518
  }
521
519
  if (toolIds.includes("claude-code")) {
522
- dirs.push(join4(CLAUDE_SKILLS_DIR, skillId));
520
+ dirs.push(join5(CLAUDE_SKILLS_DIR, skillId));
523
521
  }
524
522
  return dirs;
525
523
  };
@@ -541,7 +539,7 @@ var buildSkillInstallPlan = (params) => {
541
539
  });
542
540
  const packages = rootDirs.map((rootDir) => {
543
541
  const files = params.skill.files.map((file) => ({
544
- relativePath: join4(rootDir, file.path),
542
+ relativePath: join5(rootDir, file.path),
545
543
  content: file.content
546
544
  }));
547
545
  return {
@@ -556,15 +554,137 @@ var buildSkillInstallPlan = (params) => {
556
554
  id: params.skill.id,
557
555
  kind: params.skill.kind,
558
556
  tools: selectedTools,
559
- scope: params.scope,
560
557
  installed_paths: rootDirs,
561
558
  sourceHash: skillHash
562
559
  }
563
560
  };
564
561
  };
565
562
 
563
+ // src/core/subagent-renderer.ts
564
+ import { resolve as resolve3 } from "path";
565
+ var normalizeSelectedTools2 = (subagent, requestedTools) => {
566
+ const supportedToolSet = new Set(subagent.supported_tools);
567
+ return requestedTools.filter((toolId) => supportedToolSet.has(toolId));
568
+ };
569
+ var renderMarkdownSubagent = (params) => `---
570
+ ${params.rawFrontmatter.trimEnd()}
571
+ ---
572
+
573
+ ${params.prompt.trimEnd()}
574
+ `;
575
+ var getCodexTomlEntries = (frontmatter) => Object.entries(frontmatter).filter(
576
+ (entry) => entry[0] !== "skill_names" && (typeof entry[1] === "string" || typeof entry[1] === "number" || typeof entry[1] === "boolean" || Array.isArray(entry[1]) && entry[1].every((item) => typeof item === "string"))
577
+ );
578
+ var renderCodexSubagent = (params) => {
579
+ const metadata = renderFlatToml(getCodexTomlEntries(params.frontmatter));
580
+ const skills = (params.frontmatter.skill_names ?? []).map((skillName) => {
581
+ const skillPath = resolve3(params.userBasePath, ".agents", "skills", skillName, "SKILL.md");
582
+ return `[[skills.config]]
583
+ path = ${JSON.stringify(skillPath)}
584
+ enabled = true`;
585
+ });
586
+ const sections = [
587
+ metadata,
588
+ `developer_instructions = ${JSON.stringify(params.prompt.trimEnd())}`,
589
+ ...skills
590
+ ].filter((section) => section.length > 0);
591
+ return sections.join("\n\n") + "\n";
592
+ };
593
+ var renderSubagentForTool = (params) => {
594
+ if (params.toolId === "claude-code") {
595
+ return renderMarkdownSubagent({
596
+ rawFrontmatter: params.subagent.frontmatter.claude.raw,
597
+ prompt: params.subagent.prompt
598
+ });
599
+ }
600
+ if (params.toolId === "gemini") {
601
+ return renderMarkdownSubagent({
602
+ rawFrontmatter: params.subagent.frontmatter.gemini.raw,
603
+ prompt: params.subagent.prompt
604
+ });
605
+ }
606
+ return renderCodexSubagent({
607
+ frontmatter: params.subagent.frontmatter.codex.parsed,
608
+ prompt: params.subagent.prompt,
609
+ userBasePath: params.userBasePath
610
+ });
611
+ };
612
+ var getSelectedMetadataFiles = (subagent, selectedTools) => selectedTools.map((toolId) => {
613
+ if (toolId === "claude-code") return `claude:${subagent.frontmatter.claude.raw}`;
614
+ if (toolId === "gemini") return `gemini:${subagent.frontmatter.gemini.raw}`;
615
+ return `codex:${subagent.frontmatter.codex.raw}`;
616
+ });
617
+ var getStringArray = (value) => {
618
+ if (!Array.isArray(value)) {
619
+ return [];
620
+ }
621
+ return value.filter((item) => typeof item === "string");
622
+ };
623
+ var buildRequiredSubagentSkills = (params) => {
624
+ const required = [];
625
+ if (params.selectedTools.includes("codex")) {
626
+ for (const skillName of params.subagent.frontmatter.codex.parsed.skill_names ?? []) {
627
+ required.push({
628
+ tool: "codex",
629
+ skillName,
630
+ path: resolve3(params.userBasePath, ".agents", "skills", skillName, "SKILL.md")
631
+ });
632
+ }
633
+ }
634
+ if (params.selectedTools.includes("claude-code")) {
635
+ for (const skillName of getStringArray(params.subagent.frontmatter.claude.parsed["skills"])) {
636
+ required.push({
637
+ tool: "claude-code",
638
+ skillName,
639
+ path: resolve3(params.userBasePath, ".claude", "skills", skillName, "SKILL.md")
640
+ });
641
+ }
642
+ }
643
+ return required;
644
+ };
645
+ var buildSubagentInstallPlan = (params) => {
646
+ const selectedTools = normalizeSelectedTools2(params.subagent, params.requestedTools);
647
+ if (selectedTools.length === 0) {
648
+ throw new Error(`Subagent ${params.subagent.id} does not support the requested tools`);
649
+ }
650
+ const files = selectedTools.map((toolId) => ({
651
+ relativePath: buildSubagentRelativePath(params.subagent.id, toolId),
652
+ content: renderSubagentForTool({
653
+ subagent: params.subagent,
654
+ toolId,
655
+ userBasePath: params.userBasePath
656
+ })
657
+ }));
658
+ const subagentHash = computeInstalledSubagentHash({
659
+ id: params.subagent.id,
660
+ tools: selectedTools,
661
+ prompt: params.subagent.prompt,
662
+ metadataFiles: getSelectedMetadataFiles(params.subagent, selectedTools)
663
+ });
664
+ return {
665
+ packages: [
666
+ {
667
+ subagentId: params.subagent.id,
668
+ files
669
+ }
670
+ ],
671
+ installedSubagent: {
672
+ id: params.subagent.id,
673
+ tools: selectedTools,
674
+ installed_paths: files.map((file) => file.relativePath),
675
+ sourceHash: subagentHash
676
+ },
677
+ requiredSkills: buildRequiredSubagentSkills({
678
+ subagent: params.subagent,
679
+ selectedTools,
680
+ userBasePath: params.userBasePath
681
+ })
682
+ };
683
+ };
684
+
566
685
  // src/core/managed-header.ts
567
686
  var MANAGED_MARKER = "<!-- managed by ai-ops -->";
687
+ var META_PATTERN = /^<!-- sourceHash: ([a-f0-9]{6}) \| generatedAt: (.+) -->$/;
568
688
  var SECTION_START = "<!-- ai-ops:start -->";
569
689
  var SECTION_END = "<!-- ai-ops:end -->";
570
690
  var hasLegacyHeader = (content) => content.includes(MANAGED_MARKER);
@@ -585,6 +705,15 @@ var stripAiOpsSection = (content) => {
585
705
  const after = content.slice(endIdx + SECTION_END.length).trimStart();
586
706
  return before + (after ? "\n\n" + after : "") + "\n";
587
707
  };
708
+ var extractAiOpsSectionContent = (content) => {
709
+ const startIdx = content.indexOf(SECTION_START);
710
+ const endIdx = content.indexOf(SECTION_END);
711
+ if (startIdx === -1 || endIdx === -1) return null;
712
+ const section = content.slice(startIdx + SECTION_START.length, endIdx).trim();
713
+ const lines = section.split("\n");
714
+ const [, ...contentLines] = lines;
715
+ return contentLines.join("\n").trimStart();
716
+ };
588
717
  var replaceAiOpsSection = (existing, newSection) => {
589
718
  const startIdx = existing.indexOf(SECTION_START);
590
719
  const endIdx = existing.indexOf(SECTION_END);
@@ -593,152 +722,33 @@ var replaceAiOpsSection = (existing, newSection) => {
593
722
  const after = existing.slice(endIdx + SECTION_END.length).trimStart();
594
723
  return [before, newSection, after].filter(Boolean).join("\n\n") + "\n";
595
724
  };
725
+ var parseAiOpsMeta = (content) => {
726
+ const startIdx = content.indexOf(SECTION_START);
727
+ if (startIdx === -1) return null;
728
+ const lines = content.slice(startIdx).split("\n");
729
+ const metaLine = lines[1] ?? "";
730
+ const match = META_PATTERN.exec(metaLine);
731
+ if (!match) return null;
732
+ return { sourceHash: match[1], generatedAt: match[2] };
733
+ };
596
734
 
597
735
  // src/core/manifest-io.ts
598
736
  import { mkdirSync, readFileSync as readFileSync3, writeFileSync } from "fs";
599
- import { dirname as dirname2, join as join5 } from "path";
600
- var MANIFEST_FILENAME = ".ai-ops-manifest.json";
601
- var parseManifest = (json) => ManifestSchema.parse(JSON.parse(json));
602
- var serializeManifest = (manifest) => JSON.stringify(manifest, null, 2) + "\n";
603
- var resolveManifestPath = (basePath) => join5(basePath, MANIFEST_FILENAME);
604
- var readManifest = (manifestPath) => {
605
- let raw;
606
- try {
607
- raw = readFileSync3(manifestPath, "utf-8");
608
- } catch {
609
- return null;
610
- }
611
- return parseManifest(raw);
612
- };
613
- var writeManifest = (manifestPath, manifest) => {
614
- mkdirSync(dirname2(manifestPath), { recursive: true });
615
- writeFileSync(manifestPath, serializeManifest(manifest), "utf-8");
616
- };
737
+ import { dirname as dirname2, join as join6 } from "path";
617
738
 
618
739
  // src/core/manifest-resolution.ts
619
740
  var LEGACY_SKILL_ID_MAP = {
620
741
  "engineering-standards-pack": "backend-service-standards"
621
742
  };
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
743
  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
744
 
735
745
  // src/core/skill-registry-io.ts
736
746
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
737
- import { dirname as dirname3, join as join6 } from "path";
747
+ import { dirname as dirname3, join as join7 } from "path";
738
748
  var SKILL_REGISTRY_FILENAME = "skills-manifest.json";
739
749
  var parseSkillRegistry = (json) => SkillRegistrySchema.parse(JSON.parse(json));
740
750
  var serializeSkillRegistry = (registry) => JSON.stringify(registry, null, 2) + "\n";
741
- var resolveSkillRegistryPath = (userBasePath) => join6(userBasePath, ".ai-ops", SKILL_REGISTRY_FILENAME);
751
+ var resolveSkillRegistryPath = (userBasePath) => join7(userBasePath, ".ai-ops", SKILL_REGISTRY_FILENAME);
742
752
  var readSkillRegistry = (registryPath) => {
743
753
  let raw;
744
754
  try {
@@ -753,1245 +763,1356 @@ var writeSkillRegistry = (registryPath, registry) => {
753
763
  writeFileSync2(registryPath, serializeSkillRegistry(registry), "utf-8");
754
764
  };
755
765
 
756
- // src/core/diff.ts
757
- var computeDiff = (params) => {
758
- const { previous, currentRules, currentSourceHash, currentCliVersion } = params;
759
- const previousSet = new Set(previous.installed_rules);
760
- const currentSet = new Set(currentRules);
761
- const added = currentRules.filter((id) => !previousSet.has(id));
762
- const removed = previous.installed_rules.filter((id) => !currentSet.has(id));
763
- const sourceChanged = previous.sourceHash !== currentSourceHash;
764
- const versionChanged = previous.cliVersion !== void 0 && currentCliVersion !== void 0 && previous.cliVersion !== currentCliVersion;
765
- const status = added.length > 0 || removed.length > 0 || sourceChanged || versionChanged ? "changed" : "up-to-date";
766
- return { status, added, removed, sourceChanged, versionChanged };
767
- };
768
-
769
- // src/core/install-plan.ts
770
- import { join as join7 } from "path";
771
- var CODEX_PLAN_BODY = "## Plan Snapshot (Plan mode only)\n\n- This rule applies only when Codex is running in `collaboration_mode=Plan`.\n- Before implementation (file edits/creates, installs, commits) and before leaving Plan mode, save the latest plan content to `.codex/plans/YYYYMMDD_<topic>.md`.\n- In `Default` mode, do not automatically create or update plan files.";
772
- var buildInstallPlan = (params) => {
773
- const { toolId, renderResult, meta } = params;
774
- if (toolId === "claude-code" && renderResult.tool === "claude-code") {
775
- return renderResult.files.map(({ relativePath, content }) => ({
776
- relativePath,
777
- content: wrapWithSection(content, meta)
778
- }));
779
- }
780
- if (toolId === "codex" && renderResult.tool === "codex") {
781
- const config = TOOL_OUTPUT_MAP["codex"];
782
- const actions = [];
783
- const rootContent = renderResult.rootContent ? renderResult.rootContent + "\n\n---\n\n" + CODEX_PLAN_BODY : CODEX_PLAN_BODY;
784
- actions.push({
785
- relativePath: join7(config.dir, config.rootFileName),
786
- content: wrapWithSection(rootContent, meta)
787
- });
788
- for (const df of renderResult.domainFiles) {
789
- actions.push({
790
- relativePath: join7(df.workspacePath, config.domainFileName),
791
- content: wrapWithSection(df.content, meta)
792
- });
793
- }
794
- return actions;
795
- }
796
- if (toolId === "gemini" && renderResult.tool === "gemini") {
797
- const config = TOOL_OUTPUT_MAP["gemini"];
798
- const actions = [];
799
- if (renderResult.rootContent) {
800
- actions.push({
801
- relativePath: join7(config.dir, config.rootFileName),
802
- content: wrapWithSection(renderResult.rootContent, meta)
803
- });
804
- }
805
- for (const df of renderResult.domainFiles) {
806
- actions.push({
807
- relativePath: join7(df.workspacePath, config.domainFileName),
808
- content: wrapWithSection(df.content, meta)
809
- });
810
- }
811
- return actions;
766
+ // src/core/subagent-manifest-io.ts
767
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
768
+ import { dirname as dirname4, join as join8 } from "path";
769
+ var SUBAGENT_MANIFEST_FILENAME = "subagents-manifest.json";
770
+ var parseSubagentManifest = (json) => SubagentManifestSchema.parse(JSON.parse(json));
771
+ var serializeSubagentManifest = (manifest) => JSON.stringify(manifest, null, 2) + "\n";
772
+ var resolveSubagentManifestPath = (userBasePath) => join8(userBasePath, ".ai-ops", SUBAGENT_MANIFEST_FILENAME);
773
+ var readSubagentManifest = (manifestPath) => {
774
+ let raw;
775
+ try {
776
+ raw = readFileSync5(manifestPath, "utf-8");
777
+ } catch {
778
+ return null;
812
779
  }
813
- return [];
780
+ return parseSubagentManifest(raw);
781
+ };
782
+ var writeSubagentManifest = (manifestPath, manifest) => {
783
+ mkdirSync3(dirname4(manifestPath), { recursive: true });
784
+ writeFileSync3(manifestPath, serializeSubagentManifest(manifest), "utf-8");
814
785
  };
815
786
 
816
- // src/data/spec-readme.ts
817
- var SPEC_README_TEMPLATE = `# Specs
818
-
819
- \uC774 \uB514\uB809\uD1A0\uB9AC\uB294 AI \uD611\uC5C5 \uAE30\uBC18 spec \uD30C\uC774\uD504\uB77C\uC778\uC744 \uAD00\uB9AC\uD569\uB2C8\uB2E4.
820
-
821
- ## \uB514\uB809\uD1A0\uB9AC \uAD6C\uC870
822
-
823
- \`\`\`
824
- specs/
825
- \u251C\u2500\u2500 baseline/ # \uAE30\uC900 spec \uBB38\uC11C (\uCD08\uAE30 \uC694\uAD6C\uC0AC\uD56D, \uD655\uC815\uB41C \uC2A4\uD399)
826
- \u2514\u2500\u2500 delta/ # \uBCC0\uACBD spec \uBB38\uC11C (\uAE30\uC900 \uB300\uBE44 \uCD94\uAC00/\uC218\uC815 \uC0AC\uD56D)
827
- \`\`\`
828
-
829
- ## \uC0AC\uC6A9 \uBC29\uBC95
830
-
831
- ### baseline
832
-
833
- \uD504\uB85C\uC81D\uD2B8\uC758 \uCD08\uAE30 \uB610\uB294 \uD655\uC815\uB41C \uC2A4\uD399 \uBB38\uC11C\uB97C \`baseline/\` \uB514\uB809\uD1A0\uB9AC\uC5D0 \uC800\uC7A5\uD569\uB2C8\uB2E4.
834
-
835
- - \uD30C\uC77C\uBA85: \`<feature-name>.md\` (kebab-case)
836
- - \uB0B4\uC6A9: \uC694\uAD6C\uC0AC\uD56D, \uB3C4\uBA54\uC778 \uC6A9\uC5B4, \uC81C\uC57D \uC870\uAC74 \uB4F1
837
-
838
- ### delta
839
-
840
- \uAE30\uC900 \uC2A4\uD399 \uB300\uBE44 \uBCC0\uACBD\uB418\uB294 \uB0B4\uC6A9\uC744 \`delta/\` \uB514\uB809\uD1A0\uB9AC\uC5D0 \uC800\uC7A5\uD569\uB2C8\uB2E4.
841
-
842
- - \uD30C\uC77C\uBA85: \`<YYYYMMDD>-<feature-name>.md\`
843
- - \uB0B4\uC6A9: \uBCC0\uACBD \uC774\uC720, \uBCC0\uACBD \uC804/\uD6C4 \uBE44\uAD50, \uC601\uD5A5 \uBC94\uC704 \uB4F1
844
- `;
845
-
846
- // src/core/spec-plan.ts
847
- var buildSpecInitPlan = () => [
848
- { relativePath: "specs/README.md", content: SPEC_README_TEMPLATE },
849
- { relativePath: "specs/baseline/.gitkeep", content: "" },
850
- { relativePath: "specs/delta/.gitkeep", content: "" }
851
- ];
787
+ // src/core/install-plan.ts
788
+ import { join as join9 } from "path";
852
789
 
853
- // src/core/uninstall-plan.ts
854
- import { join as join8 } from "path";
855
- var inferInstalledFiles = (manifest) => {
856
- const files = [];
857
- const isMonorepo = manifest.workspaces !== void 0;
858
- for (const toolId of manifest.tools) {
859
- if (toolId === "claude-code") {
860
- const config = TOOL_OUTPUT_MAP["claude-code"];
861
- for (const ruleId of manifest.installed_rules) {
862
- files.push(join8(config.rulesDir, `${ruleId}${config.fileExtension}`));
863
- }
864
- } else if (toolId === "codex") {
865
- const config = TOOL_OUTPUT_MAP["codex"];
866
- if (!isMonorepo) {
867
- files.push(join8(config.dir, config.rootFileName));
868
- files.push(join8(config.dir, config.domainFileName));
869
- } else {
870
- files.push(join8(config.dir, config.rootFileName));
871
- for (const ws of Object.keys(manifest.workspaces ?? {})) {
872
- files.push(join8(ws, config.domainFileName));
873
- }
874
- }
875
- } else if (toolId === "gemini") {
876
- const config = TOOL_OUTPUT_MAP["gemini"];
877
- if (!isMonorepo) {
878
- files.push(join8(config.dir, config.rootFileName));
879
- } else {
880
- files.push(join8(config.dir, config.rootFileName));
881
- for (const ws of Object.keys(manifest.workspaces ?? {})) {
882
- files.push(join8(ws, config.domainFileName));
883
- }
884
- }
885
- }
886
- }
887
- return [...new Set(files)];
888
- };
790
+ // src/core/project-layer.ts
791
+ import { existsSync as existsSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync6, readdirSync as readdirSync3, rmSync, writeFileSync as writeFileSync4 } from "fs";
792
+ import { dirname as dirname6, isAbsolute, join as join10, relative, resolve as resolve5 } from "path";
889
793
 
890
794
  // src/core/paths.ts
891
- import { dirname as dirname4, resolve as resolve3 } from "path";
795
+ import { dirname as dirname5, resolve as resolve4 } from "path";
892
796
  import { fileURLToPath as fileURLToPath2 } from "url";
893
- var __dirname2 = dirname4(fileURLToPath2(import.meta.url));
894
- var COMPILER_DATA_DIR = resolve3(__dirname2, "..", "..", "data");
797
+ var __dirname2 = dirname5(fileURLToPath2(import.meta.url));
798
+ var COMPILER_DATA_DIR = resolve4(__dirname2, "..", "..", "data");
895
799
 
896
- // src/lib/paths.ts
897
- import { join as join9 } from "path";
898
- var resolveCompilerDataDir = () => COMPILER_DATA_DIR;
899
- var resolveRulesDir = () => join9(COMPILER_DATA_DIR, "rules");
900
- var resolveSkillsDir = () => join9(COMPILER_DATA_DIR, "skills");
901
- var resolvePresetsPath = () => join9(COMPILER_DATA_DIR, "presets.yaml");
902
- var resolveBasePath = () => process.cwd();
903
- var resolveUserBasePath = () => process.env.AI_OPS_HOME ?? process.env.HOME ?? process.cwd();
904
-
905
- // src/lib/workspace.ts
906
- import { existsSync as existsSync2, readdirSync as readdirSync3, statSync } from "fs";
907
- import { join as join10, resolve as resolve4 } from "path";
908
- var EXCLUDE_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", ".turbo", ".cache", "coverage"]);
909
- var isVisibleDir = (basePath, name) => {
910
- if (name.startsWith(".") || EXCLUDE_DIRS.has(name)) return false;
911
- return statSync(resolve4(basePath, name)).isDirectory();
912
- };
913
- var PROJECT_MANIFESTS = [
914
- "package.json",
915
- // Node.js / JS / TS
916
- "pubspec.yaml",
917
- // Flutter / Dart
918
- "pyproject.toml",
919
- // Python (modern)
920
- "setup.py",
921
- // Python (legacy)
922
- "Cargo.toml",
923
- // Rust
924
- "go.mod"
925
- // Go
800
+ // src/core/project-layer.ts
801
+ var PROJECT_LAYER_MANIFEST_RELATIVE_PATH = ".ai-ops/manifest.json";
802
+ var PROJECT_LAYER_CONTEXT_INDEX_RELATIVE_PATH = ".ai-ops/context-layer.json";
803
+ var CONTEXT_LAYER_DATA_DIR = join10(COMPILER_DATA_DIR, "context-layer");
804
+ var TOOL_ORDER = ["codex", "gemini", "claude-code"];
805
+ var DEFAULT_TOOLS = TOOL_ORDER;
806
+ var TEMPLATE_PATHS = [
807
+ "AGENTS.md",
808
+ "GEMINI.md",
809
+ "CLAUDE.md",
810
+ "docs/agent/workflow.md",
811
+ "docs/agent/rules/routing-rules.md",
812
+ "docs/agent/rules/doc-update-rules.md",
813
+ "docs/agent/rules/stop-rules.md",
814
+ "docs/agent/checks/impact-checklist.md",
815
+ "docs/agent/checks/review-checklist.md",
816
+ "docs/agent/maps/codebase-map.md",
817
+ "docs/business/business-rules.md",
818
+ "docs/docs-status.md"
926
819
  ];
927
- var isWorkspaceRoot = (dirPath) => PROJECT_MANIFESTS.some((f) => existsSync2(join10(dirPath, f)));
928
- var listWorkspaceCandidates = (basePath) => {
929
- const topLevel = readdirSync3(basePath).filter((name) => isVisibleDir(basePath, name));
930
- const candidates = [];
931
- for (const dir of topLevel) {
932
- const subPath = resolve4(basePath, dir);
933
- if (isWorkspaceRoot(subPath)) {
934
- candidates.push(dir);
935
- } else {
936
- const children = readdirSync3(subPath).filter((name) => isVisibleDir(subPath, name));
937
- const wsChildren = children.filter((name) => isWorkspaceRoot(resolve4(subPath, name)));
938
- if (wsChildren.length > 0) {
939
- for (const child of wsChildren) {
940
- candidates.push(join10(dir, child));
941
- }
942
- } else {
943
- candidates.push(dir);
944
- }
945
- }
946
- }
947
- return candidates.sort();
820
+ var PROJECT_OWNED_PATHS = /* @__PURE__ */ new Set([
821
+ "docs/docs-status.md",
822
+ "docs/agent/maps/codebase-map.md",
823
+ "docs/business/business-rules.md"
824
+ ]);
825
+ var RESERVED_DOCUMENT_WARNINGS = [
826
+ "\uD310\uB2E8 \uADFC\uAC70\uB85C \uC0AC\uC6A9\uD558\uC9C0 \uB9C8\uC138\uC694",
827
+ "Do not use this document as current decision-making evidence"
828
+ ];
829
+ var resolveProjectLayerManifestPath = (basePath) => join10(basePath, PROJECT_LAYER_MANIFEST_RELATIVE_PATH);
830
+ var resolveProjectLayerContextIndexPath = (basePath) => join10(basePath, PROJECT_LAYER_CONTEXT_INDEX_RELATIVE_PATH);
831
+ var resolveTemplatePath = (relativePath) => join10(CONTEXT_LAYER_DATA_DIR, relativePath);
832
+ var toRelativeDir = (relativePath) => dirname6(relativePath);
833
+ var resolveProjectLayerFilePath = (basePath, relativePath) => {
834
+ if (!isSafeProjectLayerPath(relativePath)) {
835
+ throw new Error(`Unsafe project layer path: ${relativePath}`);
836
+ }
837
+ const absoluteBasePath = resolve5(basePath);
838
+ const absolutePath = resolve5(absoluteBasePath, relativePath);
839
+ const relativeFromBase = relative(absoluteBasePath, absolutePath);
840
+ if (relativeFromBase === "" || relativeFromBase.startsWith("..") || isAbsolute(relativeFromBase)) {
841
+ throw new Error(`Unsafe project layer path: ${relativePath}`);
842
+ }
843
+ return absolutePath;
844
+ };
845
+ var parseProjectLayerManifest = (json) => ProjectLayerManifestSchema.parse(JSON.parse(json));
846
+ var serializeProjectLayerManifest = (manifest) => JSON.stringify(manifest, null, 2) + "\n";
847
+ var parseProjectLayerContextIndex = (json) => ProjectLayerContextIndexSchema.parse(JSON.parse(json));
848
+ var serializeProjectLayerContextIndex = (contextIndex) => JSON.stringify(contextIndex, null, 2) + "\n";
849
+ var parseProjectLayerFrontmatter = (content) => {
850
+ const { frontmatter } = parseMarkdownFrontmatter(content);
851
+ return ProjectLayerFrontmatterSchema.parse(frontmatter);
852
+ };
853
+ var parseProjectLayerDocument = (path, rawContent) => {
854
+ const managedContent = extractAiOpsSectionContent(rawContent);
855
+ const content = managedContent ?? rawContent;
856
+ const frontmatter = parseProjectLayerFrontmatter(content);
857
+ return {
858
+ path,
859
+ status: frontmatter.status,
860
+ layer: frontmatter.layer,
861
+ owner: frontmatter.owner,
862
+ read_when: frontmatter.read_when,
863
+ update_when: frontmatter.update_when,
864
+ contentHash: computeHash([content.trimEnd()]),
865
+ content
866
+ };
948
867
  };
949
-
950
- // src/lib/install.ts
951
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
952
- import { dirname as dirname5, resolve as resolve5 } from "path";
953
- var installFiles = (basePath, actions, _meta) => {
954
- const written = [];
955
- const appended = [];
956
- const skipped = [];
957
- for (const action of actions) {
958
- const absPath = resolve5(basePath, action.relativePath);
959
- if (!existsSync3(absPath)) {
960
- mkdirSync3(dirname5(absPath), { recursive: true });
961
- writeFileSync3(absPath, action.content + "\n", "utf-8");
962
- written.push(action.relativePath);
963
- } else {
964
- const existing = readFileSync5(absPath, "utf-8");
965
- if (hasAiOpsSection(existing)) {
966
- const updated = replaceAiOpsSection(existing, action.content);
967
- writeFileSync3(absPath, updated, "utf-8");
968
- const stripped = stripAiOpsSection(existing);
969
- (stripped.trim().length > 0 ? appended : written).push(action.relativePath);
970
- } else if (hasLegacyHeader(existing)) {
971
- writeFileSync3(absPath, action.content + "\n", "utf-8");
972
- written.push(action.relativePath);
973
- } else {
974
- const updated = existing.trimEnd() + "\n\n" + action.content + "\n";
975
- writeFileSync3(absPath, updated, "utf-8");
976
- appended.push(action.relativePath);
868
+ var parseDocsStatusEntries = (content) => {
869
+ const document = parseProjectLayerDocument("docs/docs-status.md", content);
870
+ const rows = document.content.split("\n").filter((line) => line.trim().startsWith("|")).map((line) => line.trim());
871
+ return rows.flatMap((line) => {
872
+ const cells = line.split("|").map((cell) => cell.trim()).filter((cell) => cell.length > 0);
873
+ if (cells.length < 3) return [];
874
+ if (cells[0] === "path") return [];
875
+ if (cells[0].startsWith("---")) return [];
876
+ return [
877
+ {
878
+ path: cells[0],
879
+ status: cells[1],
880
+ owner: cells[2]
977
881
  }
978
- }
882
+ ];
883
+ });
884
+ };
885
+ var resolveProjectLayerTools = (requestedTools) => {
886
+ if (requestedTools === void 0 || requestedTools.length === 0) {
887
+ return [...DEFAULT_TOOLS];
979
888
  }
980
- return { written, appended, skipped };
889
+ const parsedTools = requestedTools.map((tool) => ProjectLayerToolSchema.parse(tool));
890
+ const toolSet = new Set(parsedTools);
891
+ return TOOL_ORDER.filter((tool) => toolSet.has(tool));
981
892
  };
982
-
983
- // src/lib/skill-install.ts
984
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, rmSync, writeFileSync as writeFileSync4 } from "fs";
985
- import { dirname as dirname6, resolve as resolve6 } from "path";
986
- var installSkillPackages = (basePath, packages) => {
987
- const writtenRoots = [];
988
- for (const skillPackage of packages) {
989
- const absRoot = resolve6(basePath, skillPackage.rootDir);
990
- if (existsSync4(absRoot)) {
991
- rmSync(absRoot, { recursive: true, force: true });
992
- }
993
- for (const file of skillPackage.files) {
994
- const absPath = resolve6(basePath, file.relativePath);
995
- mkdirSync4(dirname6(absPath), { recursive: true });
996
- writeFileSync4(absPath, file.content + "\n", "utf-8");
997
- }
998
- writtenRoots.push(skillPackage.rootDir);
893
+ var shouldIncludeTemplate = (relativePath, tools) => {
894
+ if (relativePath === "GEMINI.md") return tools.includes("gemini");
895
+ if (relativePath === "CLAUDE.md") return tools.includes("claude-code");
896
+ return true;
897
+ };
898
+ var buildDocsStatusRows = (specs) => specs.map((spec) => `| ${spec.path} | ${spec.frontmatter.status} | ${spec.frontmatter.owner} |`).join("\n");
899
+ var includesReservedDocumentWarning = (content) => RESERVED_DOCUMENT_WARNINGS.some((warning) => content.includes(warning));
900
+ var loadTemplateSpec = (relativePath, content) => {
901
+ const frontmatter = parseProjectLayerFrontmatter(content);
902
+ const ownership = PROJECT_OWNED_PATHS.has(relativePath) ? "project" : "managed";
903
+ if (frontmatter.status === "Reserved" && !includesReservedDocumentWarning(content)) {
904
+ throw new Error(`Reserved template must include warning text: ${relativePath}`);
999
905
  }
1000
- return writtenRoots;
906
+ return {
907
+ path: relativePath,
908
+ content,
909
+ ownership,
910
+ frontmatter,
911
+ contentHash: computeHash([content.trimEnd()])
912
+ };
1001
913
  };
1002
- var removeDirectories = (basePath, relativeDirs) => {
1003
- const removed = [];
1004
- for (const relativeDir of relativeDirs) {
1005
- const absPath = resolve6(basePath, relativeDir);
1006
- if (!existsSync4(absPath)) continue;
1007
- rmSync(absPath, { recursive: true, force: true });
1008
- removed.push(relativeDir);
914
+ var loadProjectLayerTemplateSpecs = (tools) => {
915
+ const selectedPaths = TEMPLATE_PATHS.filter((relativePath) => shouldIncludeTemplate(relativePath, tools));
916
+ const nonStatusSpecs = selectedPaths.filter((relativePath) => relativePath !== "docs/docs-status.md").map((relativePath) => loadTemplateSpec(relativePath, readFileSync6(resolveTemplatePath(relativePath), "utf-8")));
917
+ const statusTemplate = readFileSync6(resolveTemplatePath("docs/docs-status.md"), "utf-8");
918
+ const statusPlaceholderSpec = loadTemplateSpec("docs/docs-status.md", statusTemplate);
919
+ const specsForStatus = [...nonStatusSpecs, statusPlaceholderSpec].sort((a, b) => a.path.localeCompare(b.path));
920
+ const statusContent = statusTemplate.replace("{{documents_table}}", buildDocsStatusRows(specsForStatus));
921
+ const statusSpec = loadTemplateSpec("docs/docs-status.md", statusContent);
922
+ return [...nonStatusSpecs, statusSpec].sort((a, b) => a.path.localeCompare(b.path));
923
+ };
924
+ var computeProjectLayerSourceHash = (specs) => computeHash(specs.map((spec) => `${spec.path}:${spec.content}`));
925
+ var readProjectLayerManifest = (basePath) => {
926
+ try {
927
+ return parseProjectLayerManifest(readFileSync6(resolveProjectLayerManifestPath(basePath), "utf-8"));
928
+ } catch (error) {
929
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
930
+ return null;
931
+ }
932
+ throw error;
1009
933
  }
1010
- return removed;
1011
934
  };
1012
-
1013
- // src/lib/tool-settings.ts
1014
- import * as p from "@clack/prompts";
1015
- import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync6, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
1016
- import { join as join11 } from "path";
1017
-
1018
- // src/lib/deep-merge.util.ts
1019
- var deepMerge = (base, patch) => {
1020
- const result = { ...base };
1021
- for (const [key, value] of Object.entries(patch)) {
1022
- if (value !== null && typeof value === "object" && !Array.isArray(value) && typeof result[key] === "object" && result[key] !== null && !Array.isArray(result[key])) {
1023
- result[key] = deepMerge(result[key], value);
1024
- } else {
1025
- result[key] = value;
935
+ var writeProjectLayerManifest = (basePath, manifest) => {
936
+ const manifestPath = resolveProjectLayerManifestPath(basePath);
937
+ mkdirSync4(dirname6(manifestPath), { recursive: true });
938
+ writeFileSync4(manifestPath, serializeProjectLayerManifest(manifest), "utf-8");
939
+ };
940
+ var readProjectLayerContextIndex = (basePath) => {
941
+ try {
942
+ return parseProjectLayerContextIndex(readFileSync6(resolveProjectLayerContextIndexPath(basePath), "utf-8"));
943
+ } catch (error) {
944
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
945
+ return null;
1026
946
  }
947
+ throw error;
1027
948
  }
1028
- return result;
1029
949
  };
1030
- var deepRemoveKeys = (base, patch) => {
1031
- const result = { ...base };
1032
- for (const [key, value] of Object.entries(patch)) {
1033
- if (!(key in result)) continue;
1034
- if (value !== null && typeof value === "object" && !Array.isArray(value) && typeof result[key] === "object" && result[key] !== null && !Array.isArray(result[key])) {
1035
- const nested = deepRemoveKeys(result[key], value);
1036
- if (Object.keys(nested).length === 0) {
1037
- delete result[key];
950
+ var writeProjectLayerContextIndex = (basePath, contextIndex) => {
951
+ const contextIndexPath = resolveProjectLayerContextIndexPath(basePath);
952
+ mkdirSync4(dirname6(contextIndexPath), { recursive: true });
953
+ writeFileSync4(contextIndexPath, serializeProjectLayerContextIndex(contextIndex), "utf-8");
954
+ };
955
+ var installManagedFiles = (basePath, specs, meta) => {
956
+ const written = [];
957
+ const appended = [];
958
+ for (const spec of specs) {
959
+ const absolutePath = resolveProjectLayerFilePath(basePath, spec.path);
960
+ const wrappedContent = wrapWithSection(spec.content, meta);
961
+ if (!existsSync2(absolutePath)) {
962
+ mkdirSync4(dirname6(absolutePath), { recursive: true });
963
+ writeFileSync4(absolutePath, wrappedContent + "\n", "utf-8");
964
+ written.push(spec.path);
965
+ continue;
966
+ }
967
+ const existing = readFileSync6(absolutePath, "utf-8");
968
+ if (hasAiOpsSection(existing)) {
969
+ writeFileSync4(absolutePath, replaceAiOpsSection(existing, wrappedContent), "utf-8");
970
+ const stripped = stripAiOpsSection(existing);
971
+ (stripped.trim().length > 0 ? appended : written).push(spec.path);
972
+ continue;
973
+ }
974
+ if (hasLegacyHeader(existing)) {
975
+ writeFileSync4(absolutePath, wrappedContent + "\n", "utf-8");
976
+ written.push(spec.path);
977
+ continue;
978
+ }
979
+ writeFileSync4(absolutePath, existing.trimEnd() + "\n\n" + wrappedContent + "\n", "utf-8");
980
+ appended.push(spec.path);
981
+ }
982
+ return { written, appended };
983
+ };
984
+ var installProjectFiles = (params) => {
985
+ const records = [];
986
+ const created = [];
987
+ const refreshed = [];
988
+ const preserved = [];
989
+ const previousByPath = new Map((params.previousProjectFiles ?? []).map((file) => [file.path, file]));
990
+ for (const spec of params.specs) {
991
+ const absolutePath = resolveProjectLayerFilePath(params.basePath, spec.path);
992
+ const previous = previousByPath.get(spec.path);
993
+ if (!existsSync2(absolutePath)) {
994
+ mkdirSync4(dirname6(absolutePath), { recursive: true });
995
+ writeFileSync4(absolutePath, spec.content + "\n", "utf-8");
996
+ created.push(spec.path);
997
+ records.push({
998
+ path: spec.path,
999
+ templateHash: spec.contentHash,
1000
+ created: true
1001
+ });
1002
+ continue;
1003
+ }
1004
+ const existingContent = readFileSync6(absolutePath, "utf-8").trimEnd();
1005
+ const existingHash = computeHash([existingContent]);
1006
+ if (previous?.created === true && existingHash === previous.templateHash) {
1007
+ if (existingHash !== spec.contentHash) {
1008
+ writeFileSync4(absolutePath, spec.content + "\n", "utf-8");
1009
+ refreshed.push(spec.path);
1038
1010
  } else {
1039
- result[key] = nested;
1011
+ preserved.push(spec.path);
1040
1012
  }
1041
- } else {
1042
- delete result[key];
1013
+ records.push({
1014
+ path: spec.path,
1015
+ templateHash: spec.contentHash,
1016
+ created: true
1017
+ });
1018
+ continue;
1043
1019
  }
1020
+ preserved.push(spec.path);
1021
+ records.push({
1022
+ path: spec.path,
1023
+ templateHash: previous?.templateHash ?? spec.contentHash,
1024
+ created: previous?.created ?? false
1025
+ });
1044
1026
  }
1045
- return result;
1027
+ return { records, created, refreshed, preserved };
1028
+ };
1029
+ var buildContextIndexFromDisk = (params) => {
1030
+ const documents = params.documentPaths.map(
1031
+ (path) => parseProjectLayerDocument(path, readFileSync6(resolveProjectLayerFilePath(params.basePath, path), "utf-8"))
1032
+ );
1033
+ return ProjectLayerContextIndexSchema.parse({
1034
+ schemaVersion: 1,
1035
+ kind: "context-layer-index",
1036
+ documents: documents.map(({ content: _content, ...document }) => document),
1037
+ generatedAt: params.generatedAt
1038
+ });
1046
1039
  };
1047
-
1048
- // src/lib/prompt-control.ts
1049
- var PROMPT_CANCELLED = /* @__PURE__ */ Symbol("prompt-cancelled");
1050
- var isPromptCancelled = (value) => value === PROMPT_CANCELLED;
1051
-
1052
- // src/lib/tool-settings.ts
1053
- var promptToolSettings = async (config) => {
1054
- const want = await p.confirm({ message: config.promptMessage, initialValue: true });
1055
- if (p.isCancel(want)) return PROMPT_CANCELLED;
1056
- if (!want) return null;
1057
- const selected = await p.multiselect({
1058
- message: "\uC124\uCE58\uD560 \uC124\uC815 \uD56D\uBAA9\uC744 \uC120\uD0DD\uD558\uC138\uC694 (\uC2A4\uD398\uC774\uC2A4\uB85C \uD1A0\uAE00)",
1059
- options: config.groups.map((g) => ({ value: g.value, label: g.label, hint: g.hint })),
1060
- initialValues: config.groups.map((g) => g.value),
1061
- required: false
1040
+ var computeProjectFileHash = (basePath, relativePath) => computeHash([readFileSync6(resolveProjectLayerFilePath(basePath, relativePath), "utf-8").trimEnd()]);
1041
+ var collectDocumentPathsFromManifest = (manifest) => [
1042
+ ...manifest.managed_files.map((file) => file.path),
1043
+ ...manifest.project_files.map((file) => file.path),
1044
+ ...manifest.packs.flatMap((pack) => pack.documents.map((file) => file.path))
1045
+ ].sort();
1046
+ var buildDocsStatusRowsFromDisk = (params) => params.documentPaths.map((path) => {
1047
+ const document = parseProjectLayerDocument(path, readFileSync6(resolveProjectLayerFilePath(params.basePath, path), "utf-8"));
1048
+ return `| ${document.path} | ${document.status} | ${document.owner} |`;
1049
+ });
1050
+ var replaceDocsStatusRows = (content, rows) => {
1051
+ const lines = content.trimEnd().split("\n");
1052
+ const headerIndex = lines.findIndex((line) => line.trim() === "| path | status | owner |");
1053
+ const dividerIndex = headerIndex + 1;
1054
+ if (headerIndex < 0 || !lines[dividerIndex]?.trim().startsWith("| ---")) {
1055
+ throw new Error("docs/docs-status.md table header not found");
1056
+ }
1057
+ let tableEndIndex = dividerIndex + 1;
1058
+ while (tableEndIndex < lines.length && lines[tableEndIndex]?.trim().startsWith("|")) {
1059
+ tableEndIndex += 1;
1060
+ }
1061
+ return [...lines.slice(0, dividerIndex + 1), ...rows, ...lines.slice(tableEndIndex)].join("\n") + "\n";
1062
+ };
1063
+ var updateDocsStatusTable = (basePath, documentPaths) => {
1064
+ const docsStatusPath = "docs/docs-status.md";
1065
+ const absolutePath = resolveProjectLayerFilePath(basePath, docsStatusPath);
1066
+ const beforeHash = computeProjectFileHash(basePath, docsStatusPath);
1067
+ const rows = buildDocsStatusRowsFromDisk({ basePath, documentPaths });
1068
+ const nextContent = replaceDocsStatusRows(readFileSync6(absolutePath, "utf-8"), rows);
1069
+ writeFileSync4(absolutePath, nextContent, "utf-8");
1070
+ return {
1071
+ beforeHash,
1072
+ afterHash: computeProjectFileHash(basePath, docsStatusPath)
1073
+ };
1074
+ };
1075
+ var updateDocsStatusProjectFileRecord = (params) => ProjectLayerManifestSchema.parse({
1076
+ ...params.manifest,
1077
+ project_files: params.manifest.project_files.map((file) => {
1078
+ if (file.path !== "docs/docs-status.md" || !file.created || file.templateHash !== params.beforeHash) {
1079
+ return file;
1080
+ }
1081
+ return {
1082
+ ...file,
1083
+ templateHash: params.afterHash
1084
+ };
1085
+ })
1086
+ });
1087
+ var refreshProjectLayerDerivedState = (params) => {
1088
+ const documentPaths = collectDocumentPathsFromManifest(params.manifest);
1089
+ const docsStatusHashes = updateDocsStatusTable(params.basePath, documentPaths);
1090
+ const manifest = updateDocsStatusProjectFileRecord({
1091
+ manifest: params.manifest,
1092
+ beforeHash: docsStatusHashes.beforeHash,
1093
+ afterHash: docsStatusHashes.afterHash
1062
1094
  });
1063
- if (p.isCancel(selected)) return PROMPT_CANCELLED;
1064
- return selected;
1065
- };
1066
- var installToolSettings = (basePath, selectedValues, config) => {
1067
- if (selectedValues.length === 0) return;
1068
- const settingsDir = join11(basePath, config.dirName);
1069
- const settingsPath = join11(settingsDir, config.fileName);
1070
- let existing = {};
1071
- if (existsSync5(settingsPath)) {
1072
- try {
1073
- existing = JSON.parse(readFileSync6(settingsPath, "utf-8"));
1074
- } catch {
1095
+ const contextIndex = buildContextIndexFromDisk({
1096
+ basePath: params.basePath,
1097
+ documentPaths,
1098
+ generatedAt: params.generatedAt
1099
+ });
1100
+ writeProjectLayerContextIndex(params.basePath, contextIndex);
1101
+ return {
1102
+ manifest,
1103
+ contextIndex
1104
+ };
1105
+ };
1106
+ var buildProjectLayerManifest = (params) => ProjectLayerManifestSchema.parse({
1107
+ schemaVersion: 1,
1108
+ kind: "project-operating-layer",
1109
+ tools: [...params.tools],
1110
+ managed_files: params.managedFiles.map((path) => ({
1111
+ path,
1112
+ sourceHash: params.sourceHash
1113
+ })),
1114
+ project_files: [...params.projectFiles],
1115
+ packs: [...params.packs],
1116
+ settings: params.settings ?? {},
1117
+ sourceHash: params.sourceHash,
1118
+ cliVersion: params.cliVersion,
1119
+ generatedAt: params.generatedAt
1120
+ });
1121
+ var retireUnselectedManagedFiles = (params) => {
1122
+ if (!params.previousManifest) return;
1123
+ const nextManagedPathSet = new Set(params.nextManagedPaths);
1124
+ for (const file of params.previousManifest.managed_files) {
1125
+ if (!nextManagedPathSet.has(file.path)) {
1126
+ removeManagedProjectFile(params.basePath, file.path);
1075
1127
  }
1076
1128
  }
1077
- let merged = existing;
1078
- for (const val of selectedValues) {
1079
- const group = config.groups.find((g) => g.value === val);
1080
- if (!group) continue;
1081
- merged = deepMerge(merged, group.patch);
1082
- }
1083
- mkdirSync5(settingsDir, { recursive: true });
1084
- writeFileSync5(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
1085
1129
  };
1086
- var uninstallToolSettings = (basePath, selectedValues, config) => {
1087
- const settingsPath = join11(basePath, config.dirName, config.fileName);
1088
- if (!existsSync5(settingsPath)) return "notFound";
1089
- let existing = {};
1130
+ var installProjectLayer = (params) => {
1131
+ const previousManifest = params.previousManifest === void 0 ? readProjectLayerManifest(params.basePath) : params.previousManifest;
1132
+ const specs = loadProjectLayerTemplateSpecs(params.tools);
1133
+ const sourceHash = computeProjectLayerSourceHash(specs);
1134
+ const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
1135
+ const managedSpecs = specs.filter((spec) => spec.ownership === "managed");
1136
+ const projectSpecs = specs.filter((spec) => spec.ownership === "project");
1137
+ const managedPaths = managedSpecs.map((spec) => spec.path);
1138
+ retireUnselectedManagedFiles({
1139
+ basePath: params.basePath,
1140
+ previousManifest,
1141
+ nextManagedPaths: managedPaths
1142
+ });
1143
+ const managedResult = installManagedFiles(params.basePath, managedSpecs, { sourceHash, generatedAt });
1144
+ const projectResult = installProjectFiles({
1145
+ basePath: params.basePath,
1146
+ specs: projectSpecs,
1147
+ previousProjectFiles: previousManifest?.project_files
1148
+ });
1149
+ const provisionalManifest = buildProjectLayerManifest({
1150
+ tools: params.tools,
1151
+ managedFiles: managedPaths,
1152
+ projectFiles: projectResult.records,
1153
+ packs: previousManifest?.packs ?? [],
1154
+ sourceHash,
1155
+ cliVersion: getCliVersion(),
1156
+ generatedAt,
1157
+ settings: previousManifest?.settings
1158
+ });
1159
+ const { manifest, contextIndex } = refreshProjectLayerDerivedState({
1160
+ basePath: params.basePath,
1161
+ manifest: provisionalManifest,
1162
+ generatedAt
1163
+ });
1164
+ writeProjectLayerManifest(params.basePath, manifest);
1165
+ return {
1166
+ manifest,
1167
+ contextIndex,
1168
+ written: managedResult.written,
1169
+ appended: managedResult.appended,
1170
+ createdProjectFiles: projectResult.created,
1171
+ refreshedProjectFiles: projectResult.refreshed,
1172
+ preservedProjectFiles: projectResult.preserved
1173
+ };
1174
+ };
1175
+ var updateProjectLayer = (params) => {
1176
+ const specs = loadProjectLayerTemplateSpecs(params.manifest.tools);
1177
+ const sourceHash = computeProjectLayerSourceHash(specs);
1178
+ const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
1179
+ const managedSpecs = specs.filter((spec) => spec.ownership === "managed");
1180
+ const projectSpecs = specs.filter((spec) => spec.ownership === "project");
1181
+ const managedResult = installManagedFiles(params.basePath, managedSpecs, { sourceHash, generatedAt });
1182
+ const projectResult = installProjectFiles({
1183
+ basePath: params.basePath,
1184
+ specs: projectSpecs,
1185
+ previousProjectFiles: params.manifest.project_files
1186
+ });
1187
+ const provisionalManifest = buildProjectLayerManifest({
1188
+ tools: params.manifest.tools,
1189
+ managedFiles: managedSpecs.map((spec) => spec.path),
1190
+ projectFiles: projectResult.records,
1191
+ packs: params.manifest.packs,
1192
+ sourceHash,
1193
+ cliVersion: getCliVersion(),
1194
+ generatedAt,
1195
+ settings: params.manifest.settings
1196
+ });
1197
+ const { manifest, contextIndex } = refreshProjectLayerDerivedState({
1198
+ basePath: params.basePath,
1199
+ manifest: provisionalManifest,
1200
+ generatedAt
1201
+ });
1202
+ writeProjectLayerManifest(params.basePath, manifest);
1203
+ return {
1204
+ manifest,
1205
+ contextIndex,
1206
+ written: managedResult.written,
1207
+ appended: managedResult.appended,
1208
+ createdProjectFiles: projectResult.created,
1209
+ refreshedProjectFiles: projectResult.refreshed,
1210
+ preservedProjectFiles: projectResult.preserved
1211
+ };
1212
+ };
1213
+ var issue = (level, code, message) => ({
1214
+ level,
1215
+ code,
1216
+ message
1217
+ });
1218
+ var readDocumentSafely = (basePath, path) => {
1090
1219
  try {
1091
- existing = JSON.parse(readFileSync6(settingsPath, "utf-8"));
1092
- } catch {
1093
- rmSync2(settingsPath, { force: true });
1094
- return "deleted";
1220
+ const absolutePath = resolveProjectLayerFilePath(basePath, path);
1221
+ if (!existsSync2(absolutePath)) {
1222
+ return issue("error", "missing-file", `\uD30C\uC77C \uC5C6\uC74C: ${path}`);
1223
+ }
1224
+ return parseProjectLayerDocument(path, readFileSync6(absolutePath, "utf-8"));
1225
+ } catch (error) {
1226
+ const reason = error instanceof Error ? error.message : "unknown error";
1227
+ return issue("error", "invalid-frontmatter", `${path} frontmatter \uD30C\uC2F1 \uC2E4\uD328: ${reason}`);
1228
+ }
1229
+ };
1230
+ var buildContextIndexMap = (contextIndex) => new Map((contextIndex?.documents ?? []).map((document) => [document.path, document]));
1231
+ var compareArray = (left, right) => left.length === right.length && left.every((value, index) => value === right[index]);
1232
+ var compareContextDocument = (params) => {
1233
+ const indexed = params.indexed;
1234
+ if (indexed === void 0) {
1235
+ return [issue("error", "context-missing-document", `context-layer \uB204\uB77D: ${params.expected.path}`)];
1236
+ }
1237
+ const issues = [];
1238
+ const scalarKeys = ["status", "layer", "owner", "contentHash"];
1239
+ for (const key of scalarKeys) {
1240
+ if (params.expected[key] !== indexed[key]) {
1241
+ issues.push(
1242
+ issue("error", "context-document-mismatch", `${params.expected.path} context ${key} \uBD88\uC77C\uCE58`)
1243
+ );
1244
+ }
1095
1245
  }
1096
- let result = existing;
1097
- for (const val of selectedValues) {
1098
- const group = config.groups.find((g) => g.value === val);
1099
- if (!group) continue;
1100
- result = deepRemoveKeys(result, group.patch);
1246
+ if (!compareArray(params.expected.read_when, indexed.read_when)) {
1247
+ issues.push(issue("error", "context-document-mismatch", `${params.expected.path} context read_when \uBD88\uC77C\uCE58`));
1101
1248
  }
1102
- if (Object.keys(result).length === 0) {
1103
- rmSync2(settingsPath, { force: true });
1104
- return "deleted";
1249
+ if (!compareArray(params.expected.update_when, indexed.update_when)) {
1250
+ issues.push(issue("error", "context-document-mismatch", `${params.expected.path} context update_when \uBD88\uC77C\uCE58`));
1105
1251
  }
1106
- writeFileSync5(settingsPath, JSON.stringify(result, null, 2) + "\n", "utf-8");
1107
- return "cleaned";
1252
+ return issues;
1108
1253
  };
1109
-
1110
- // src/lib/gemini-settings.ts
1111
- var SETTING_GROUPS = [
1112
- {
1113
- value: "ui",
1114
- label: "UI \u2014 \uC904 \uBC88\uD638 \uC228\uAE30\uAE30",
1115
- hint: "ui.showLineNumbers: false \u2014 \uCF54\uB4DC \uBCF5\uC0AC \uC2DC \uC904 \uBC88\uD638\uAC00 \uD3EC\uD568\uB418\uC9C0 \uC54A\uB3C4\uB85D \uBE44\uD65C\uC131\uD654",
1116
- patch: { ui: { showLineNumbers: false } }
1117
- },
1118
- {
1119
- value: "plan",
1120
- label: "Plan \u2014 \uACC4\uD68D \uD30C\uC77C \uC800\uC7A5 \uBC0F \uBAA8\uB378 \uB77C\uC6B0\uD305",
1121
- hint: "general.plan.directory: .gemini/plans, modelRouting: true \u2014 AI \uACC4\uD68D\uC744 \uD30C\uC77C\uB85C \uC800\uC7A5\uD558\uACE0 \uD0DC\uC2A4\uD06C\uBCC4 \uCD5C\uC801 \uBAA8\uB378 \uC790\uB3D9 \uC120\uD0DD",
1122
- patch: { general: { plan: { directory: ".gemini/plans", modelRouting: true } } }
1123
- },
1124
- {
1125
- value: "sessionRetention",
1126
- label: "Session Retention \u2014 \uC138\uC158 30\uC77C \uBCF4\uC874",
1127
- hint: "general.sessionRetention.maxAge: 30d \u2014 \uC774\uC804 \uB300\uD654 \uCEE8\uD14D\uC2A4\uD2B8\uB97C 30\uC77C\uAC04 \uC720\uC9C0",
1128
- patch: { general: { sessionRetention: { maxAge: "30d" } } }
1129
- },
1130
- {
1131
- value: "experimental",
1132
- label: "Experimental \u2014 JIT \uCEE8\uD14D\uC2A4\uD2B8 + Plan \uAE30\uB2A5",
1133
- hint: "experimental.jitContext: true, plan: true \u2014 \uC11C\uBE0C\uB514\uB809\uD1A0\uB9AC \uCEE8\uD14D\uC2A4\uD2B8 \uC9C0\uC5F0 \uB85C\uB529 \uBC0F \uACC4\uD68D \uAE30\uB2A5 \uC2E4\uD5D8\uC801 \uD65C\uC131\uD654",
1134
- patch: { experimental: { jitContext: true, plan: true } }
1254
+ var compareDocsStatusEntry = (params) => {
1255
+ const entry = params.entry;
1256
+ if (entry === void 0) {
1257
+ return [issue("error", "docs-status-missing-document", `docs-status \uB204\uB77D: ${params.expected.path}`)];
1135
1258
  }
1136
- ];
1137
- var CONFIG = {
1138
- dirName: ".gemini",
1139
- fileName: "settings.json",
1140
- promptMessage: "Gemini CLI \uC124\uC815 \uD30C\uC77C(.gemini/settings.json)\uC744 \uC124\uCE58\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
1141
- groups: SETTING_GROUPS
1142
- };
1143
- var promptGeminiSettings = () => promptToolSettings(CONFIG);
1144
- var installGeminiSettings = (basePath, selectedValues) => installToolSettings(basePath, selectedValues, CONFIG);
1145
- var uninstallGeminiSettings = (basePath, selectedValues) => uninstallToolSettings(basePath, selectedValues, CONFIG);
1146
-
1147
- // src/lib/claude-settings.ts
1148
- var SETTING_GROUPS2 = [
1149
- {
1150
- value: "model",
1151
- label: "Model \u2014 Plan \uBAA8\uB4DC \uBAA8\uB378",
1152
- hint: "model: opusplan \u2014 Plan \uBAA8\uB4DC\uC5D0\uC11C Opus \uBAA8\uB378 \uC0AC\uC6A9",
1153
- patch: { model: "opusplan" }
1154
- },
1155
- {
1156
- value: "plansDirectory",
1157
- label: "Plans Directory \u2014 \uACC4\uD68D \uD30C\uC77C \uC800\uC7A5 \uACBD\uB85C",
1158
- hint: "plansDirectory: ./.claude/plans \u2014 \uACC4\uD68D \uD30C\uC77C\uC744 .claude/plans\uC5D0 \uC800\uC7A5",
1159
- patch: { plansDirectory: "./.claude/plans" }
1259
+ const issues = [];
1260
+ if (entry.status !== params.expected.status) {
1261
+ issues.push(issue("error", "docs-status-mismatch", `${params.expected.path} docs-status status \uBD88\uC77C\uCE58`));
1160
1262
  }
1161
- ];
1162
- var CONFIG2 = {
1163
- dirName: ".claude",
1164
- fileName: "settings.local.json",
1165
- promptMessage: "Claude Code \uC124\uC815 \uD30C\uC77C(.claude/settings.local.json)\uC744 \uC124\uCE58\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
1166
- groups: SETTING_GROUPS2
1167
- };
1168
- var promptClaudeSettings = () => promptToolSettings(CONFIG2);
1169
- var installClaudeSettings = (basePath, selectedValues) => installToolSettings(basePath, selectedValues, CONFIG2);
1170
- var uninstallClaudeSettings = (basePath, selectedValues) => uninstallToolSettings(basePath, selectedValues, CONFIG2);
1171
-
1172
- // src/lib/prettier-ignore.ts
1173
- import * as p2 from "@clack/prompts";
1174
- import { existsSync as existsSync6, readFileSync as readFileSync7, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "fs";
1175
- import { join as join12 } from "path";
1176
- var PRETTIER_IGNORE_CONTENT = `# CLAUDE
1177
- .claude/rules/
1178
- **/CLAUDE.md
1179
-
1180
- # GEMINI
1181
- **/GEMINI.md
1182
-
1183
- # CODEX
1184
- **/AGENTS.md
1185
- **/AGENTS.override.md
1186
-
1187
- .ai-ops-manifest.json`;
1188
- var SECTION_START2 = "# ai-ops:start";
1189
- var SECTION_END2 = "# ai-ops:end";
1190
- var wrapSection = (content) => `${SECTION_START2}
1191
- ${content}
1192
- ${SECTION_END2}`;
1193
- var hasAiOpsSection2 = (content) => content.includes(SECTION_START2) && content.includes(SECTION_END2);
1194
- var replaceSection = (content, newContent) => {
1195
- const lines = content.split("\n");
1196
- const result = [];
1197
- let inside = false;
1198
- let replaced = false;
1199
- for (const line of lines) {
1200
- if (line.trim() === SECTION_START2) {
1201
- inside = true;
1202
- result.push(wrapSection(newContent));
1203
- replaced = true;
1263
+ if (entry.owner !== params.expected.owner) {
1264
+ issues.push(issue("error", "docs-status-mismatch", `${params.expected.path} docs-status owner \uBD88\uC77C\uCE58`));
1265
+ }
1266
+ return issues;
1267
+ };
1268
+ var diffProjectLayer = (basePath) => {
1269
+ let manifest;
1270
+ try {
1271
+ manifest = readProjectLayerManifest(basePath);
1272
+ } catch (error) {
1273
+ const reason = error instanceof Error ? error.message : "unknown error";
1274
+ return {
1275
+ currentSourceHash: null,
1276
+ issues: [issue("error", "invalid-manifest", `${PROJECT_LAYER_MANIFEST_RELATIVE_PATH} \uD30C\uC2F1 \uC2E4\uD328: ${reason}`)]
1277
+ };
1278
+ }
1279
+ if (!manifest) {
1280
+ return {
1281
+ currentSourceHash: null,
1282
+ issues: [issue("error", "missing-manifest", `${PROJECT_LAYER_MANIFEST_RELATIVE_PATH}\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.`)]
1283
+ };
1284
+ }
1285
+ const specs = loadProjectLayerTemplateSpecs(manifest.tools);
1286
+ const currentSourceHash = computeProjectLayerSourceHash(specs);
1287
+ let contextIndex = null;
1288
+ const issues = [];
1289
+ try {
1290
+ contextIndex = readProjectLayerContextIndex(basePath);
1291
+ } catch (error) {
1292
+ const reason = error instanceof Error ? error.message : "unknown error";
1293
+ issues.push(
1294
+ issue("error", "invalid-context-index", `${PROJECT_LAYER_CONTEXT_INDEX_RELATIVE_PATH} \uD30C\uC2F1 \uC2E4\uD328: ${reason}`)
1295
+ );
1296
+ }
1297
+ const contextMap = buildContextIndexMap(contextIndex);
1298
+ const expectedManagedPaths = new Set(specs.filter((spec) => spec.ownership === "managed").map((spec) => spec.path));
1299
+ const manifestManagedPaths = new Set(manifest.managed_files.map((file) => file.path));
1300
+ if (manifest.sourceHash !== currentSourceHash) {
1301
+ issues.push(
1302
+ issue("warning", "source-hash-drift", `template sourceHash \uBCC0\uACBD: ${manifest.sourceHash} -> ${currentSourceHash}`)
1303
+ );
1304
+ }
1305
+ if (contextIndex === null) {
1306
+ issues.push(issue("error", "missing-context-index", `${PROJECT_LAYER_CONTEXT_INDEX_RELATIVE_PATH}\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.`));
1307
+ }
1308
+ for (const expectedPath of expectedManagedPaths) {
1309
+ if (!manifestManagedPaths.has(expectedPath)) {
1310
+ issues.push(issue("error", "manifest-missing-managed-file", `manifest managed_files \uB204\uB77D: ${expectedPath}`));
1311
+ }
1312
+ }
1313
+ for (const file of manifest.managed_files) {
1314
+ const absolutePath = resolveProjectLayerFilePath(basePath, file.path);
1315
+ if (!existsSync2(absolutePath)) {
1316
+ issues.push(issue("error", "missing-file", `\uD30C\uC77C \uC5C6\uC74C: ${file.path}`));
1204
1317
  continue;
1205
1318
  }
1206
- if (line.trim() === SECTION_END2) {
1207
- inside = false;
1319
+ const content = readFileSync6(absolutePath, "utf-8");
1320
+ const meta = parseAiOpsMeta(content);
1321
+ if (!meta) {
1322
+ issues.push(issue("error", "missing-managed-section", `managed section \uBA54\uD0C0 \uC5C6\uC74C: ${file.path}`));
1208
1323
  continue;
1209
1324
  }
1210
- if (!inside) result.push(line);
1325
+ if (meta.sourceHash !== currentSourceHash) {
1326
+ issues.push(
1327
+ issue("warning", "managed-source-hash-drift", `${file.path} sourceHash \uBCC0\uACBD: ${meta.sourceHash} -> ${currentSourceHash}`)
1328
+ );
1329
+ }
1211
1330
  }
1212
- if (!replaced) result.push(wrapSection(newContent));
1213
- return result.join("\n");
1214
- };
1215
- var stripAiOpsSection2 = (content) => {
1216
- const lines = content.split("\n");
1217
- const result = [];
1218
- let inside = false;
1219
- for (const line of lines) {
1220
- if (line.trim() === SECTION_START2) {
1221
- inside = true;
1222
- continue;
1331
+ for (const file of manifest.project_files) {
1332
+ if (!existsSync2(resolveProjectLayerFilePath(basePath, file.path))) {
1333
+ issues.push(issue("error", "missing-file", `\uD30C\uC77C \uC5C6\uC74C: ${file.path}`));
1334
+ }
1335
+ }
1336
+ for (const pack of manifest.packs) {
1337
+ for (const file of [...pack.documents, ...pack.files]) {
1338
+ if (!existsSync2(resolveProjectLayerFilePath(basePath, file.path))) {
1339
+ issues.push(issue("error", "missing-file", `\uD30C\uC77C \uC5C6\uC74C: ${file.path}`));
1340
+ }
1223
1341
  }
1224
- if (line.trim() === SECTION_END2) {
1225
- inside = false;
1342
+ }
1343
+ for (const path of collectDocumentPathsFromManifest(manifest)) {
1344
+ const document = readDocumentSafely(basePath, path);
1345
+ if ("code" in document) {
1346
+ issues.push(document);
1226
1347
  continue;
1227
1348
  }
1228
- if (!inside) result.push(line);
1349
+ issues.push(...compareContextDocument({ expected: document, indexed: contextMap.get(path) }));
1229
1350
  }
1230
- return result.join("\n");
1351
+ return { currentSourceHash, issues };
1231
1352
  };
1232
- var promptPrettierIgnore = async () => {
1233
- const want = await p2.confirm({
1234
- 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)",
1235
- initialValue: false
1236
- });
1237
- if (p2.isCancel(want)) return PROMPT_CANCELLED;
1238
- return want;
1239
- };
1240
- var installPrettierIgnore = (basePath) => {
1241
- const filePath = join12(basePath, ".prettierignore");
1242
- const section = wrapSection(PRETTIER_IGNORE_CONTENT);
1243
- if (!existsSync6(filePath)) {
1244
- writeFileSync6(filePath, section + "\n", "utf-8");
1245
- return;
1353
+ var auditProjectLayer = (basePath) => {
1354
+ const diffReport = diffProjectLayer(basePath);
1355
+ let manifest;
1356
+ try {
1357
+ manifest = readProjectLayerManifest(basePath);
1358
+ } catch {
1359
+ return diffReport;
1246
1360
  }
1247
- const existing = readFileSync7(filePath, "utf-8");
1248
- if (hasAiOpsSection2(existing)) {
1249
- writeFileSync6(filePath, replaceSection(existing, PRETTIER_IGNORE_CONTENT), "utf-8");
1250
- return;
1361
+ if (!manifest) {
1362
+ return diffReport;
1251
1363
  }
1252
- const separator = existing.endsWith("\n") ? "\n" : "\n\n";
1253
- writeFileSync6(filePath, existing + separator + section + "\n", "utf-8");
1254
- };
1255
- var uninstallPrettierIgnore = (basePath) => {
1256
- const filePath = join12(basePath, ".prettierignore");
1257
- if (!existsSync6(filePath)) return "notFound";
1258
- const existing = readFileSync7(filePath, "utf-8");
1259
- if (!hasAiOpsSection2(existing)) return "notFound";
1260
- const stripped = stripAiOpsSection2(existing).trim();
1261
- if (stripped.length === 0) {
1262
- rmSync3(filePath, { force: true });
1263
- return "deleted";
1364
+ let contextIndex = null;
1365
+ try {
1366
+ contextIndex = readProjectLayerContextIndex(basePath);
1367
+ } catch {
1368
+ contextIndex = null;
1369
+ }
1370
+ const documentPaths = collectDocumentPathsFromManifest(manifest);
1371
+ const documentPathSet = new Set(documentPaths);
1372
+ const contextPathSet = new Set(contextIndex?.documents.map((document) => document.path) ?? []);
1373
+ const issues = [...diffReport.issues];
1374
+ const docsStatusPath = resolveProjectLayerFilePath(basePath, "docs/docs-status.md");
1375
+ if (!existsSync2(docsStatusPath)) {
1376
+ issues.push(issue("error", "missing-docs-status", "docs/docs-status.md\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
1377
+ return { currentSourceHash: diffReport.currentSourceHash, issues };
1378
+ }
1379
+ let docsStatusEntries = [];
1380
+ try {
1381
+ docsStatusEntries = parseDocsStatusEntries(readFileSync6(docsStatusPath, "utf-8"));
1382
+ } catch (error) {
1383
+ const reason = error instanceof Error ? error.message : "unknown error";
1384
+ issues.push(issue("error", "invalid-docs-status", `docs/docs-status.md \uD30C\uC2F1 \uC2E4\uD328: ${reason}`));
1385
+ }
1386
+ const docsStatusMap = new Map(docsStatusEntries.map((entry) => [entry.path, entry]));
1387
+ for (const path of documentPaths) {
1388
+ const document = readDocumentSafely(basePath, path);
1389
+ if ("code" in document) {
1390
+ continue;
1391
+ }
1392
+ issues.push(...compareDocsStatusEntry({ expected: document, entry: docsStatusMap.get(path) }));
1264
1393
  }
1265
- writeFileSync6(filePath, stripped + "\n", "utf-8");
1266
- return "cleaned";
1267
- };
1268
-
1269
- // src/lib/skill-state.ts
1270
- var resolveSkillScope = (params) => {
1271
- if (params.scope !== void 0) {
1272
- if (params.scope === "user") return "user";
1273
- if (params.scope === "project") return "project";
1274
- throw new Error(`Unsupported scope: ${params.scope}`);
1394
+ for (const entry of docsStatusEntries) {
1395
+ if (!documentPathSet.has(entry.path)) {
1396
+ issues.push(issue("warning", "docs-status-extra-document", `docs-status\uC5D0 manifest \uC678 \uBB38\uC11C\uAC00 \uC788\uC2B5\uB2C8\uB2E4: ${entry.path}`));
1397
+ }
1275
1398
  }
1276
- if (params.project) return "project";
1277
- return "user";
1278
- };
1279
- var resolveRequestedTools = (params) => {
1280
- if (params.requested === void 0 || params.requested.length === 0) {
1281
- return [...params.supported];
1399
+ for (const contextPath of contextPathSet) {
1400
+ if (!documentPathSet.has(contextPath)) {
1401
+ issues.push(issue("warning", "context-extra-document", `context-layer\uC5D0 manifest \uC678 \uBB38\uC11C\uAC00 \uC788\uC2B5\uB2C8\uB2E4: ${contextPath}`));
1402
+ }
1282
1403
  }
1283
- const supportedSet = new Set(params.supported);
1284
- const invalid = params.requested.filter((tool) => !supportedSet.has(tool));
1285
- if (invalid.length > 0) {
1286
- throw new Error(`Unsupported tools requested: ${invalid.join(", ")}`);
1404
+ return { currentSourceHash: diffReport.currentSourceHash, issues };
1405
+ };
1406
+ function removeManagedProjectFile(basePath, relativePath) {
1407
+ const absolutePath = resolveProjectLayerFilePath(basePath, relativePath);
1408
+ if (!existsSync2(absolutePath)) {
1409
+ return { deleted: [], cleaned: [], preserved: [], notFound: [relativePath] };
1410
+ }
1411
+ const content = readFileSync6(absolutePath, "utf-8");
1412
+ if (!hasAiOpsSection(content)) {
1413
+ return { deleted: [], cleaned: [], preserved: [relativePath], notFound: [] };
1414
+ }
1415
+ const stripped = stripAiOpsSection(content);
1416
+ if (stripped.trim().length === 0) {
1417
+ rmSync(absolutePath);
1418
+ return { deleted: [relativePath], cleaned: [], preserved: [], notFound: [] };
1419
+ }
1420
+ writeFileSync4(absolutePath, stripped, "utf-8");
1421
+ return { deleted: [], cleaned: [relativePath], preserved: [], notFound: [] };
1422
+ }
1423
+ var removeCreateOnlyProjectFile = (basePath, file) => {
1424
+ const absolutePath = resolveProjectLayerFilePath(basePath, file.path);
1425
+ if (!existsSync2(absolutePath)) {
1426
+ return { deleted: [], cleaned: [], preserved: [], notFound: [file.path] };
1427
+ }
1428
+ const content = readFileSync6(absolutePath, "utf-8").trimEnd();
1429
+ const currentHash = computeHash([content]);
1430
+ if (file.created && currentHash === file.templateHash) {
1431
+ rmSync(absolutePath);
1432
+ return { deleted: [file.path], cleaned: [], preserved: [], notFound: [] };
1433
+ }
1434
+ return { deleted: [], cleaned: [], preserved: [file.path], notFound: [] };
1435
+ };
1436
+ var removePackOwnedFile = (basePath, file) => {
1437
+ const absolutePath = resolveProjectLayerFilePath(basePath, file.path);
1438
+ if (!existsSync2(absolutePath)) {
1439
+ return { deleted: [], cleaned: [], preserved: [], notFound: [file.path] };
1440
+ }
1441
+ const currentHash = computeHash([readFileSync6(absolutePath, "utf-8").trimEnd()]);
1442
+ if (currentHash === file.sourceHash) {
1443
+ rmSync(absolutePath);
1444
+ return { deleted: [file.path], cleaned: [], preserved: [], notFound: [] };
1445
+ }
1446
+ return { deleted: [], cleaned: [], preserved: [file.path], notFound: [] };
1447
+ };
1448
+ var mergeRemoveResults = (results) => ({
1449
+ deleted: results.flatMap((result) => result.deleted),
1450
+ cleaned: results.flatMap((result) => result.cleaned),
1451
+ preserved: results.flatMap((result) => result.preserved),
1452
+ notFound: results.flatMap((result) => result.notFound)
1453
+ });
1454
+ var removeEmptyDirs = (basePath, relativePaths) => {
1455
+ const dirs = [...new Set(relativePaths.map(toRelativeDir).filter((dir) => dir !== "."))].sort(
1456
+ (a, b) => b.length - a.length
1457
+ );
1458
+ for (const dir of [...dirs, ".ai-ops"]) {
1459
+ const absoluteDir = resolveProjectLayerFilePath(basePath, dir);
1460
+ if (!existsSync2(absoluteDir)) continue;
1461
+ try {
1462
+ if (readdirSync3(absoluteDir).length === 0) {
1463
+ rmSync(absoluteDir, { recursive: true });
1464
+ }
1465
+ } catch {
1466
+ }
1287
1467
  }
1288
- return [...params.requested];
1289
- };
1290
- var TOOL_ORDER = [SKILL_TOOL.CLAUDE_CODE, SKILL_TOOL.CODEX, SKILL_TOOL.GEMINI];
1291
- var mergeSkillTools = (params) => {
1292
- const merged = /* @__PURE__ */ new Set([...params.existing ?? [], ...params.requested]);
1293
- return TOOL_ORDER.filter((tool) => merged.has(tool));
1294
- };
1295
- var subtractSkillTools = (params) => {
1296
- const installed = new Set(params.installed ?? []);
1297
- return params.requested.filter((tool) => !installed.has(tool));
1298
- };
1299
- var upsertInstalledSkill = (installedSkills, nextSkill) => {
1300
- const nextSkillId = resolveCanonicalSkillId(nextSkill.id);
1301
- const remaining = installedSkills.filter((skill) => resolveCanonicalSkillId(skill.id) !== nextSkillId);
1302
- return [...remaining, nextSkill];
1303
1468
  };
1304
- var removeInstalledSkill = (installedSkills, skillId) => {
1305
- const targetSkillId = resolveCanonicalSkillId(skillId);
1306
- return installedSkills.filter((skill) => resolveCanonicalSkillId(skill.id) !== targetSkillId);
1307
- };
1308
- var findInstalledSkill = (installedSkills, skillId) => {
1309
- const targetSkillId = resolveCanonicalSkillId(skillId);
1310
- return installedSkills.find((skill) => resolveCanonicalSkillId(skill.id) === targetSkillId);
1469
+ var uninstallProjectLayer = (basePath, manifest) => {
1470
+ const managedResults = manifest.managed_files.map((file) => removeManagedProjectFile(basePath, file.path));
1471
+ const projectResults = manifest.project_files.map((file) => removeCreateOnlyProjectFile(basePath, file));
1472
+ const packResults = manifest.packs.flatMap(
1473
+ (pack) => [...pack.documents, ...pack.files].map((file) => removePackOwnedFile(basePath, file))
1474
+ );
1475
+ const stateFiles = [PROJECT_LAYER_CONTEXT_INDEX_RELATIVE_PATH, PROJECT_LAYER_MANIFEST_RELATIVE_PATH];
1476
+ for (const stateFile of stateFiles) {
1477
+ rmSync(resolveProjectLayerFilePath(basePath, stateFile), { force: true });
1478
+ }
1479
+ const result = mergeRemoveResults([...managedResults, ...projectResults, ...packResults]);
1480
+ removeEmptyDirs(basePath, [...result.deleted, ...stateFiles]);
1481
+ return result;
1311
1482
  };
1312
1483
 
1313
- // src/commands/init.ts
1314
- var TOOL_OPTIONS = [
1315
- { value: "claude-code", label: "Claude Code" },
1316
- { value: "codex", label: "Codex" },
1317
- { value: "gemini", label: "Gemini CLI" }
1484
+ // src/core/pack.ts
1485
+ import { existsSync as existsSync3, mkdirSync as mkdirSync5, readFileSync as readFileSync7, readdirSync as readdirSync4, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
1486
+ import { dirname as dirname7, isAbsolute as isAbsolute2, join as join11, relative as relative2, resolve as resolve6 } from "path";
1487
+ var PACK_REGISTRY_FILENAME = "pack-registry.json";
1488
+ var SPEC_LIFECYCLE_PACK_ID = "spec-lifecycle";
1489
+ var PACK_INSTALL_ROOT = "docs/specs/";
1490
+ var RESERVED_DOCUMENT_WARNINGS2 = [
1491
+ "\uD310\uB2E8 \uADFC\uAC70\uB85C \uC0AC\uC6A9\uD558\uC9C0 \uB9C8\uC138\uC694",
1492
+ "Do not use this document as current decision-making evidence"
1318
1493
  ];
1319
- var deduplicateRules = (rules) => {
1320
- const seen = /* @__PURE__ */ new Set();
1321
- return rules.filter((rule) => {
1322
- if (seen.has(rule.id)) return false;
1323
- seen.add(rule.id);
1324
- return true;
1325
- });
1494
+ var DEFAULT_PACKS_DIR = join11(COMPILER_DATA_DIR, "packs");
1495
+ var includesReservedDocumentWarning2 = (content) => RESERVED_DOCUMENT_WARNINGS2.some((warning) => content.includes(warning));
1496
+ var readPackCatalog = (packsDir) => PackCatalogSchema.parse(JSON.parse(readFileSync7(join11(packsDir, PACK_REGISTRY_FILENAME), "utf-8")));
1497
+ var assertPackInstallPath = (path) => {
1498
+ if (!isSafeProjectLayerPath(path) || !path.startsWith(PACK_INSTALL_ROOT)) {
1499
+ throw new Error(`Unsafe pack path: ${path}`);
1500
+ }
1326
1501
  };
1327
- var formatToolList = (toolIds) => toolIds.join(", ");
1328
- var deduplicateSkillTargets = (targets) => {
1329
- const merged = /* @__PURE__ */ new Map();
1330
- for (const target of targets) {
1331
- const previous = merged.get(target.skill.id);
1332
- if (!previous) {
1333
- merged.set(target.skill.id, {
1334
- skill: target.skill,
1335
- requestedTools: [...target.requestedTools]
1502
+ var readPackSourceFiles = (packDir) => {
1503
+ const files = [];
1504
+ const walk = (relativeDir = "") => {
1505
+ const absoluteDir = relativeDir.length > 0 ? join11(packDir, relativeDir) : packDir;
1506
+ const entries = readdirSync4(absoluteDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
1507
+ for (const entry of entries) {
1508
+ const nextRelativePath = relativeDir.length > 0 ? join11(relativeDir, entry.name) : entry.name;
1509
+ if (entry.isDirectory()) {
1510
+ walk(nextRelativePath);
1511
+ continue;
1512
+ }
1513
+ assertPackInstallPath(nextRelativePath);
1514
+ const content = readFileSync7(join11(packDir, nextRelativePath), "utf-8");
1515
+ files.push({
1516
+ path: nextRelativePath,
1517
+ content,
1518
+ sourceHash: computeHash([content.trimEnd()])
1336
1519
  });
1337
- continue;
1338
1520
  }
1339
- merged.set(target.skill.id, {
1340
- skill: target.skill,
1341
- requestedTools: mergeSkillTools({
1342
- existing: previous.requestedTools,
1343
- requested: target.requestedTools
1344
- })
1345
- });
1346
- }
1347
- return [...merged.values()].sort((a, b) => a.skill.id.localeCompare(b.skill.id));
1521
+ };
1522
+ walk();
1523
+ return files;
1348
1524
  };
1349
- var resolveSupportedRequestedTools = (skill, selectedTools) => selectedTools.filter((toolId) => skill.supported_tools.includes(toolId));
1350
- var partitionPresetSkills = (params) => {
1351
- const globalSkills = [];
1352
- const installableSkills = [];
1353
- for (const skill of resolvePresetSkills(params.preset, params.allSkills)) {
1354
- if (skill.kind !== "reference") {
1355
- continue;
1356
- }
1357
- const supportedRequestedTools = resolveSupportedRequestedTools(skill, params.selectedTools);
1358
- if (supportedRequestedTools.length === 0) {
1525
+ var splitPackSourceFiles = (files) => {
1526
+ const documents = [];
1527
+ const regularFiles = [];
1528
+ for (const file of files) {
1529
+ if (!file.path.endsWith(".md")) {
1530
+ regularFiles.push(file);
1359
1531
  continue;
1360
1532
  }
1361
- const installedGlobalSkill = findInstalledSkill(params.globalInstalledSkills, skill.id);
1362
- const availableTools = installedGlobalSkill ? supportedRequestedTools.filter((toolId) => installedGlobalSkill.tools.includes(toolId)) : [];
1363
- const requestedTools = subtractSkillTools({
1364
- requested: supportedRequestedTools,
1365
- installed: availableTools
1366
- });
1367
- if (requestedTools.length === 0) {
1368
- globalSkills.push({
1369
- skill,
1370
- availableTools
1371
- });
1372
- continue;
1533
+ const { frontmatter } = parseMarkdownFrontmatter(file.content);
1534
+ const parsed = ProjectLayerFrontmatterSchema.parse(frontmatter);
1535
+ if (parsed.status === "Reserved" && !includesReservedDocumentWarning2(file.content)) {
1536
+ throw new Error(`Reserved pack document must include warning text: ${file.path}`);
1373
1537
  }
1374
- installableSkills.push({
1375
- skill,
1376
- requestedTools,
1377
- globalTools: availableTools
1378
- });
1538
+ documents.push(file);
1379
1539
  }
1380
- return {
1381
- globalSkills,
1382
- installableSkills
1383
- };
1540
+ return { documents, files: regularFiles };
1384
1541
  };
1385
- var selectPresetAndFineTune = async (workspaceName, presets, allRules, allSkills, selectedTools, globalInstalledSkills) => {
1386
- const preset = await p3.select({
1387
- message: `[${workspaceName}] \uD504\uB9AC\uC14B\uC744 \uC120\uD0DD\uD558\uC138\uC694`,
1388
- options: presets.map((candidate) => ({
1389
- value: candidate,
1390
- label: candidate.id,
1391
- hint: candidate.description
1392
- }))
1393
- });
1394
- if (p3.isCancel(preset)) return null;
1395
- const finalRules = resolvePresetRules(preset, allRules);
1396
- if (finalRules.length > 0) {
1397
- p3.note(finalRules.map((rule) => ` \u2713 ${rule.id}`).join("\n"), `[${workspaceName}] core rules (\uC7A0\uAE08)`);
1398
- }
1399
- const { globalSkills, installableSkills } = partitionPresetSkills({
1400
- preset,
1401
- allSkills,
1402
- selectedTools,
1403
- globalInstalledSkills
1404
- });
1405
- if (globalSkills.length > 0) {
1406
- const globalLines = globalSkills.map(
1407
- ({ skill, availableTools }) => ` \u2713 ${skill.id} (${formatToolList(availableTools)})`
1408
- );
1409
- p3.note(globalLines.join("\n"), `[${workspaceName}] already available globally`);
1410
- }
1411
- if (installableSkills.length === 0) {
1412
- p3.note(" \uC0C8\uB85C \uC124\uCE58\uD560 reference skill\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.", `[${workspaceName}] installable reference skills`);
1542
+ var loadAllPacks = (packsDir) => {
1543
+ const catalog = readPackCatalog(packsDir);
1544
+ const entries = [...catalog.packs].sort((a, b) => a.id.localeCompare(b.id));
1545
+ return entries.map((entry) => {
1546
+ if (entry.id !== SPEC_LIFECYCLE_PACK_ID) {
1547
+ throw new Error(`Unsupported pack id: ${entry.id}`);
1548
+ }
1549
+ const packDir = resolve6(packsDir, entry.source_path);
1550
+ const relativeFromPacks = relative2(resolve6(packsDir), packDir);
1551
+ if (relativeFromPacks.length === 0 || relativeFromPacks.startsWith("..") || isAbsolute2(relativeFromPacks)) {
1552
+ throw new Error(`Pack source path escapes packs dir: ${entry.source_path}`);
1553
+ }
1554
+ const files = readPackSourceFiles(packDir);
1555
+ const split = splitPackSourceFiles(files);
1413
1556
  return {
1414
- workspace: workspaceName,
1415
- preset,
1416
- finalRules,
1417
- finalSkillTargets: []
1557
+ id: entry.id,
1558
+ sourceHash: computeHash(files.map((file) => `${file.path}:${file.content}`).sort()),
1559
+ documents: split.documents,
1560
+ files: split.files
1418
1561
  };
1419
- }
1420
- const selectedSkillIds = await p3.multiselect({
1421
- message: `[${workspaceName}] installable reference skills \uC120\uD0DD`,
1422
- options: installableSkills.map(({ skill, requestedTools, globalTools }) => ({
1423
- value: skill.id,
1424
- label: skill.id,
1425
- hint: globalTools.length > 0 ? `global: ${formatToolList(globalTools)} / install: ${formatToolList(requestedTools)}` : `${skill.description} / install: ${formatToolList(requestedTools)}`
1426
- })),
1427
- initialValues: installableSkills.map(({ skill }) => skill.id),
1428
- required: false
1429
1562
  });
1430
- if (p3.isCancel(selectedSkillIds)) return null;
1431
- const selectedSkillSet = new Set(selectedSkillIds);
1432
- return {
1433
- workspace: workspaceName,
1434
- preset,
1435
- finalRules,
1436
- finalSkillTargets: installableSkills.filter(({ skill }) => selectedSkillSet.has(skill.id)).map(({ skill, requestedTools }) => ({
1437
- skill,
1438
- requestedTools
1439
- }))
1440
- };
1441
1563
  };
1442
- var selectInitSkillScope = async () => {
1443
- const scope = await p3.select({
1444
- message: "\uC120\uD0DD\uB41C skills\uB97C \uC5B4\uB514\uC5D0 \uC124\uCE58\uD560\uAE4C\uC694?",
1445
- options: [
1446
- { value: "user", label: "user (global)", hint: "\uAE30\uBCF8\uAC12. \uC5EC\uB7EC \uD504\uB85C\uC81D\uD2B8\uC5D0\uC11C \uC7AC\uC0AC\uC6A9" },
1447
- { value: "project", label: "project", hint: "\uD604\uC7AC \uD504\uB85C\uC81D\uD2B8\uC5D0\uB9CC \uC124\uCE58" }
1448
- ]
1449
- });
1450
- return p3.isCancel(scope) ? null : scope;
1451
- };
1452
- var initCommand = async () => {
1453
- const basePath = resolveBasePath();
1454
- const userBasePath = resolveUserBasePath();
1455
- const rulesDir = resolveRulesDir();
1456
- const skillsDir = resolveSkillsDir();
1457
- const spinner3 = p3.spinner();
1458
- let spinnerStarted = false;
1459
- const cancelInit = (params) => {
1460
- if (spinnerStarted) {
1461
- spinner3.stop("\uC124\uCE58 \uC911\uB2E8\uB428");
1462
- spinnerStarted = false;
1463
- }
1464
- p3.cancel(params?.message ?? "\uCDE8\uC18C\uB428");
1465
- process.exit(params?.exitCode ?? 0);
1466
- };
1467
- const handleSigint = () => cancelInit({
1468
- message: "\uC0AC\uC6A9\uC790 \uC694\uCCAD\uC73C\uB85C init\uC774 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4.",
1469
- exitCode: 130
1470
- });
1471
- process.once("SIGINT", handleSigint);
1472
- try {
1473
- p3.intro("ai-ops init");
1474
- const selectedTools = await p3.multiselect({
1475
- message: "AI \uB3C4\uAD6C\uB97C \uC120\uD0DD\uD558\uC138\uC694",
1476
- options: TOOL_OPTIONS,
1477
- required: true
1478
- });
1479
- if (p3.isCancel(selectedTools)) {
1480
- cancelInit();
1564
+ var resolvePackById = (packsDir, packId) => {
1565
+ const pack = loadAllPacks(packsDir).find((candidate) => candidate.id === packId);
1566
+ if (!pack) {
1567
+ throw new Error(`Unknown pack: ${packId}`);
1568
+ }
1569
+ return pack;
1570
+ };
1571
+ var serializePackFileContent = (content) => content.length === 0 ? "" : content.trimEnd() + "\n";
1572
+ var readProjectFileHash = (basePath, relativePath) => computeHash([readFileSync7(resolveProjectLayerFilePath(basePath, relativePath), "utf-8").trimEnd()]);
1573
+ var writePackFile = (basePath, file) => {
1574
+ const absolutePath = resolveProjectLayerFilePath(basePath, file.path);
1575
+ mkdirSync5(dirname7(absolutePath), { recursive: true });
1576
+ writeFileSync5(absolutePath, serializePackFileContent(file.content), "utf-8");
1577
+ };
1578
+ var buildPackFileRecords = (files) => files.map((file) => ({
1579
+ path: file.path,
1580
+ sourceHash: file.sourceHash
1581
+ }));
1582
+ var buildPackRecord = (params) => ({
1583
+ id: params.pack.id,
1584
+ sourceHash: params.pack.sourceHash,
1585
+ documents: buildPackFileRecords(params.pack.documents),
1586
+ files: buildPackFileRecords(params.pack.files),
1587
+ installedAt: params.installedAt
1588
+ });
1589
+ var applyPackSourceFiles = (params) => {
1590
+ const written = [];
1591
+ const refreshed = [];
1592
+ const preserved = [];
1593
+ const deleted = [];
1594
+ const notFound = [];
1595
+ const sourceFiles = [...params.pack.documents, ...params.pack.files];
1596
+ const sourceByPath = new Map(sourceFiles.map((file) => [file.path, file]));
1597
+ const previousByPath = new Map(
1598
+ [...params.previousRecord?.documents ?? [], ...params.previousRecord?.files ?? []].map((file) => [
1599
+ file.path,
1600
+ file
1601
+ ])
1602
+ );
1603
+ for (const file of sourceFiles) {
1604
+ const absolutePath = resolveProjectLayerFilePath(params.basePath, file.path);
1605
+ const previous = previousByPath.get(file.path);
1606
+ if (!existsSync3(absolutePath)) {
1607
+ writePackFile(params.basePath, file);
1608
+ written.push(file.path);
1609
+ continue;
1481
1610
  }
1482
- const isMonorepo = await p3.confirm({
1483
- message: "\uBAA8\uB178\uB808\uD3EC \uD504\uB85C\uC81D\uD2B8\uC785\uB2C8\uAE4C?",
1484
- initialValue: false
1485
- });
1486
- if (p3.isCancel(isMonorepo)) {
1487
- cancelInit();
1488
- }
1489
- const allRules = loadAllRules(rulesDir);
1490
- const allSkills = loadAllSkills(skillsDir);
1491
- const presets = loadPresets(resolvePresetsPath());
1492
- const sourceHash = computeSourceHash(resolveCompilerDataDir());
1493
- const globalInstalledSkills = readSkillRegistry(resolveSkillRegistryPath(userBasePath))?.skills ?? [];
1494
- const mappings = [];
1495
- if (!isMonorepo) {
1496
- const mapping = await selectPresetAndFineTune(
1497
- ".",
1498
- presets,
1499
- allRules,
1500
- allSkills,
1501
- selectedTools,
1502
- globalInstalledSkills
1503
- ) ?? cancelInit();
1504
- mappings.push(mapping);
1505
- } else {
1506
- const candidates = listWorkspaceCandidates(basePath);
1507
- const selectedWorkspaces = await p3.multiselect({
1508
- message: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uB97C \uC120\uD0DD\uD558\uC138\uC694",
1509
- options: candidates.map((candidate) => ({ value: candidate, label: candidate })),
1510
- required: true
1511
- });
1512
- if (p3.isCancel(selectedWorkspaces)) {
1513
- cancelInit();
1514
- }
1515
- for (const workspace of selectedWorkspaces) {
1516
- const mapping = await selectPresetAndFineTune(
1517
- workspace,
1518
- presets,
1519
- allRules,
1520
- allSkills,
1521
- selectedTools,
1522
- globalInstalledSkills
1523
- ) ?? cancelInit();
1524
- mappings.push(mapping);
1525
- }
1611
+ if (previous === void 0) {
1612
+ preserved.push(file.path);
1613
+ continue;
1526
1614
  }
1527
- const selectedSkillTargets = deduplicateSkillTargets(mappings.flatMap((mapping) => mapping.finalSkillTargets));
1528
- const skillScope = selectedSkillTargets.length > 0 ? await selectInitSkillScope() : null;
1529
- if (selectedSkillTargets.length > 0 && skillScope === null) {
1530
- cancelInit();
1531
- }
1532
- const geminiSettingValues = selectedTools.includes("gemini") ? await promptGeminiSettings() : null;
1533
- const resolvedGeminiSettingValues = isPromptCancelled(geminiSettingValues) ? cancelInit() : geminiSettingValues;
1534
- const claudeSettingValues = selectedTools.includes("claude-code") ? await promptClaudeSettings() : null;
1535
- const resolvedClaudeSettingValues = isPromptCancelled(claudeSettingValues) ? cancelInit() : claudeSettingValues;
1536
- const wantPrettierIgnore = await promptPrettierIgnore();
1537
- const resolvedWantPrettierIgnore = isPromptCancelled(wantPrettierIgnore) ? cancelInit() : wantPrettierIgnore;
1538
- spinner3.start("\uADDC\uCE59 \uC124\uCE58 \uC911...");
1539
- spinnerStarted = true;
1540
- const meta = { sourceHash, generatedAt: (/* @__PURE__ */ new Date()).toISOString() };
1541
- const allInstalledFiles = [];
1542
- const allAppended = [];
1543
- const selectedRuleIds = deduplicateRules(mappings.flatMap((mapping) => mapping.finalRules)).map((rule) => rule.id);
1544
- let projectInstalledSkills = [];
1545
- if (selectedSkillTargets.length > 0 && skillScope !== null) {
1546
- const skillBasePath = skillScope === "project" ? basePath : userBasePath;
1547
- const installedSkills = selectedSkillTargets.map(({ skill, requestedTools }) => {
1548
- const existingUserSkill = skillScope === "user" ? findInstalledSkill(globalInstalledSkills, skill.id) : void 0;
1549
- const nextRequestedTools = skillScope === "user" ? mergeSkillTools({
1550
- existing: existingUserSkill?.tools,
1551
- requested: requestedTools
1552
- }) : requestedTools;
1553
- const { packages, installedSkill } = buildSkillInstallPlan({
1554
- skill,
1555
- requestedTools: nextRequestedTools,
1556
- scope: skillScope
1557
- });
1558
- installSkillPackages(skillBasePath, packages);
1559
- return installedSkill;
1560
- });
1561
- if (skillScope === "project") {
1562
- projectInstalledSkills = installedSkills;
1563
- } else {
1564
- const registryPath = resolveSkillRegistryPath(skillBasePath);
1565
- const previous = readSkillRegistry(registryPath);
1566
- const nextSkills = installedSkills.reduce(
1567
- (acc, installedSkill) => upsertInstalledSkill(acc, installedSkill),
1568
- previous?.skills ?? []
1569
- );
1570
- writeSkillRegistry(registryPath, {
1571
- skills: nextSkills,
1572
- cliVersion: getCliVersion(),
1573
- generatedAt: (/* @__PURE__ */ new Date()).toISOString()
1574
- });
1575
- }
1615
+ const currentHash = readProjectFileHash(params.basePath, file.path);
1616
+ if (currentHash !== previous.sourceHash) {
1617
+ preserved.push(file.path);
1618
+ continue;
1576
1619
  }
1577
- for (const toolId of selectedTools) {
1578
- if (isMonorepo) {
1579
- const allWorkspaceRules = deduplicateRules(mappings.flatMap((mapping) => mapping.finalRules));
1580
- const workspaceMappings = mappings.map((mapping) => ({
1581
- path: mapping.workspace,
1582
- ruleIds: mapping.finalRules.map((rule) => rule.id)
1583
- }));
1584
- const renderResult = renderForTool(toolId, allWorkspaceRules, workspaceMappings);
1585
- const actions = buildInstallPlan({ toolId, renderResult, meta });
1586
- const result = installFiles(basePath, actions, meta);
1587
- allInstalledFiles.push(...result.written);
1588
- allAppended.push(...result.appended);
1589
- } else {
1590
- const renderResult = renderForTool(toolId, mappings[0].finalRules);
1591
- const actions = buildInstallPlan({ toolId, renderResult, meta });
1592
- const result = installFiles(basePath, actions, meta);
1593
- allInstalledFiles.push(...result.written);
1594
- allAppended.push(...result.appended);
1595
- }
1620
+ if (currentHash !== file.sourceHash) {
1621
+ writePackFile(params.basePath, file);
1622
+ refreshed.push(file.path);
1596
1623
  }
1597
- if (resolvedGeminiSettingValues && resolvedGeminiSettingValues.length > 0) {
1598
- installGeminiSettings(basePath, resolvedGeminiSettingValues);
1624
+ }
1625
+ for (const previous of previousByPath.values()) {
1626
+ if (sourceByPath.has(previous.path)) {
1627
+ continue;
1599
1628
  }
1600
- if (resolvedClaudeSettingValues && resolvedClaudeSettingValues.length > 0) {
1601
- installClaudeSettings(basePath, resolvedClaudeSettingValues);
1629
+ const absolutePath = resolveProjectLayerFilePath(params.basePath, previous.path);
1630
+ if (!existsSync3(absolutePath)) {
1631
+ notFound.push(previous.path);
1632
+ continue;
1602
1633
  }
1603
- if (resolvedWantPrettierIgnore) {
1604
- installPrettierIgnore(basePath);
1634
+ if (readProjectFileHash(params.basePath, previous.path) === previous.sourceHash) {
1635
+ rmSync2(absolutePath);
1636
+ deleted.push(previous.path);
1637
+ } else {
1638
+ preserved.push(previous.path);
1605
1639
  }
1606
- spinner3.stop("\uADDC\uCE59 \uC124\uCE58 \uC644\uB8CC");
1607
- spinnerStarted = false;
1608
- const workspacesRecord = isMonorepo ? Object.fromEntries(
1609
- mappings.map((mapping) => [
1610
- mapping.workspace,
1611
- {
1612
- preset: mapping.preset.id,
1613
- rules: mapping.finalRules.map((rule) => rule.id)
1614
- }
1615
- ])
1616
- ) : void 0;
1617
- const manifest = buildManifest({
1618
- tools: selectedTools,
1619
- scope: "project",
1620
- preset: !isMonorepo ? mappings[0].preset.id : void 0,
1621
- workspaces: workspacesRecord,
1622
- installedRules: selectedRuleIds,
1623
- installedFiles: allInstalledFiles,
1624
- installedSkills: projectInstalledSkills,
1625
- appendedFiles: allAppended,
1626
- settings: resolvedClaudeSettingValues || resolvedGeminiSettingValues || resolvedWantPrettierIgnore ? {
1627
- claude: resolvedClaudeSettingValues ? [...resolvedClaudeSettingValues] : void 0,
1628
- gemini: resolvedGeminiSettingValues ? [...resolvedGeminiSettingValues] : void 0,
1629
- prettierignore: resolvedWantPrettierIgnore || void 0
1630
- } : void 0,
1631
- cliVersion: getCliVersion(),
1632
- sourceHash
1633
- });
1634
- writeManifest(resolveManifestPath(basePath), manifest);
1635
- if (allAppended.length > 0) {
1636
- p3.log.info(`\uAE30\uC874 \uD30C\uC77C\uC5D0 \uC139\uC158 \uCD94\uAC00\uB428 (\uB0B4\uC6A9 \uBCF4\uC874):
1637
- ${allAppended.map((file) => ` ${file}`).join("\n")}`);
1640
+ }
1641
+ return { written, refreshed, preserved, deleted, notFound };
1642
+ };
1643
+ var removePackFiles = (basePath, record) => {
1644
+ const deleted = [];
1645
+ const preserved = [];
1646
+ const notFound = [];
1647
+ for (const file of [...record.documents, ...record.files]) {
1648
+ const absolutePath = resolveProjectLayerFilePath(basePath, file.path);
1649
+ if (!existsSync3(absolutePath)) {
1650
+ notFound.push(file.path);
1651
+ continue;
1638
1652
  }
1639
- p3.log.success(`\uC124\uCE58\uB41C core rules: ${selectedRuleIds.length}\uAC1C`);
1640
- p3.log.success(`\uC124\uCE58\uB41C skills: ${selectedSkillTargets.length}\uAC1C${skillScope ? ` (${skillScope})` : ""}`);
1641
- if (selectedSkillTargets.length > 0 && skillScope === "user") {
1642
- p3.log.info("global skill\uC740 ai-ops uninstall \uB300\uC0C1\uC774 \uC544\uB2D9\uB2C8\uB2E4. ai-ops skill uninstall\uC73C\uB85C \uC81C\uAC70\uD558\uC138\uC694.");
1653
+ if (readProjectFileHash(basePath, file.path) === file.sourceHash) {
1654
+ rmSync2(absolutePath);
1655
+ deleted.push(file.path);
1656
+ } else {
1657
+ preserved.push(file.path);
1643
1658
  }
1644
- p3.outro("ai-ops init \uC644\uB8CC");
1645
- } finally {
1646
- process.off("SIGINT", handleSigint);
1647
1659
  }
1660
+ return { written: [], refreshed: [], preserved, deleted, notFound };
1648
1661
  };
1649
-
1650
- // src/commands/update.ts
1651
- import * as p4 from "@clack/prompts";
1652
- var updateCommand = async (opts) => {
1653
- const basePath = resolveBasePath();
1654
- const manifestPath = resolveManifestPath(basePath);
1655
- p4.intro("ai-ops update");
1656
- const manifest = readManifest(manifestPath);
1662
+ var removeEmptyDirs2 = (basePath, relativePaths) => {
1663
+ const dirs = [...new Set(relativePaths.map((path) => dirname7(path)).filter((dir) => dir !== "."))].sort(
1664
+ (a, b) => b.length - a.length
1665
+ );
1666
+ for (const dir of dirs) {
1667
+ const absoluteDir = resolveProjectLayerFilePath(basePath, dir);
1668
+ if (!existsSync3(absoluteDir)) {
1669
+ continue;
1670
+ }
1671
+ try {
1672
+ if (readdirSync4(absoluteDir).length === 0) {
1673
+ rmSync2(absoluteDir, { recursive: true });
1674
+ }
1675
+ } catch {
1676
+ }
1677
+ }
1678
+ };
1679
+ var requireProjectLayerManifest = (basePath) => {
1680
+ const manifest = readProjectLayerManifest(basePath);
1657
1681
  if (!manifest) {
1658
- p4.log.error("manifest\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 ai-ops init\uC744 \uC2E4\uD589\uD558\uC138\uC694.");
1659
- process.exit(1);
1682
+ throw new Error(".ai-ops/manifest.json\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 ai-ops init\uC744 \uC2E4\uD589\uD558\uC138\uC694.");
1660
1683
  }
1661
- const rulesDir = resolveRulesDir();
1662
- const skillsDir = resolveSkillsDir();
1663
- const presetsPath = resolvePresetsPath();
1664
- const sourceHash = computeSourceHash(resolveCompilerDataDir());
1665
- const cliVersion = getCliVersion();
1666
- const allRules = loadAllRules(rulesDir);
1667
- const allSkills = loadAllSkills(skillsDir);
1668
- const presets = loadPresets(presetsPath);
1669
- const resolvedRules = resolveManifestRules({
1670
- manifest,
1671
- allRules,
1672
- presets
1684
+ return manifest;
1685
+ };
1686
+ var upsertPackRecord = (manifest, record, generatedAt) => ProjectLayerManifestSchema.parse({
1687
+ ...manifest,
1688
+ packs: [...manifest.packs.filter((pack) => pack.id !== record.id), record],
1689
+ cliVersion: getCliVersion(),
1690
+ generatedAt
1691
+ });
1692
+ var removePackRecord = (manifest, packId, generatedAt) => ProjectLayerManifestSchema.parse({
1693
+ ...manifest,
1694
+ packs: manifest.packs.filter((pack) => pack.id !== packId),
1695
+ cliVersion: getCliVersion(),
1696
+ generatedAt
1697
+ });
1698
+ var writeManifestWithDerivedState = (params) => {
1699
+ const derived = refreshProjectLayerDerivedState({
1700
+ basePath: params.basePath,
1701
+ manifest: params.manifest,
1702
+ generatedAt: params.generatedAt
1703
+ });
1704
+ writeProjectLayerManifest(params.basePath, derived.manifest);
1705
+ return derived;
1706
+ };
1707
+ var installProjectLayerPack = (params) => {
1708
+ const manifest = requireProjectLayerManifest(params.basePath);
1709
+ const previousRecord = manifest.packs.find((pack2) => pack2.id === params.packId);
1710
+ if (previousRecord) {
1711
+ return updateProjectLayerPack(params);
1712
+ }
1713
+ const pack = resolvePackById(params.packsDir ?? DEFAULT_PACKS_DIR, params.packId);
1714
+ const installedAt = (/* @__PURE__ */ new Date()).toISOString();
1715
+ const applyResult = applyPackSourceFiles({ basePath: params.basePath, pack, previousRecord: null });
1716
+ const nextManifest = upsertPackRecord(manifest, buildPackRecord({ pack, installedAt }), installedAt);
1717
+ const derived = writeManifestWithDerivedState({
1718
+ basePath: params.basePath,
1719
+ manifest: nextManifest,
1720
+ generatedAt: installedAt
1673
1721
  });
1674
- const resolvedSkills = resolveManifestProjectSkills({
1722
+ return { ...applyResult, manifest: derived.manifest, contextIndex: derived.contextIndex };
1723
+ };
1724
+ var updateProjectLayerPack = (params) => {
1725
+ const manifest = requireProjectLayerManifest(params.basePath);
1726
+ const previousRecord = manifest.packs.find((pack2) => pack2.id === params.packId);
1727
+ if (!previousRecord) {
1728
+ throw new Error(`\uC124\uCE58\uB41C pack\uC744 \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4: ${params.packId}`);
1729
+ }
1730
+ const pack = resolvePackById(params.packsDir ?? DEFAULT_PACKS_DIR, params.packId);
1731
+ const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
1732
+ const applyResult = applyPackSourceFiles({ basePath: params.basePath, pack, previousRecord });
1733
+ const nextManifest = upsertPackRecord(
1675
1734
  manifest,
1676
- allSkills
1735
+ buildPackRecord({ pack, installedAt: previousRecord.installedAt }),
1736
+ generatedAt
1737
+ );
1738
+ const derived = writeManifestWithDerivedState({
1739
+ basePath: params.basePath,
1740
+ manifest: nextManifest,
1741
+ generatedAt
1677
1742
  });
1678
- const diffResult = computeDiff({
1679
- previous: manifest,
1680
- currentRules: resolvedRules.installedRules.map((rule) => rule.id),
1681
- currentSourceHash: sourceHash,
1682
- currentCliVersion: cliVersion
1743
+ removeEmptyDirs2(params.basePath, applyResult.deleted);
1744
+ return { ...applyResult, manifest: derived.manifest, contextIndex: derived.contextIndex };
1745
+ };
1746
+ var uninstallProjectLayerPack = (params) => {
1747
+ const manifest = requireProjectLayerManifest(params.basePath);
1748
+ const previousRecord = manifest.packs.find((pack) => pack.id === params.packId);
1749
+ if (!previousRecord) {
1750
+ throw new Error(`\uC124\uCE58\uB41C pack\uC744 \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4: ${params.packId}`);
1751
+ }
1752
+ const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
1753
+ const applyResult = removePackFiles(params.basePath, previousRecord);
1754
+ const nextManifest = removePackRecord(manifest, params.packId, generatedAt);
1755
+ const derived = writeManifestWithDerivedState({
1756
+ basePath: params.basePath,
1757
+ manifest: nextManifest,
1758
+ generatedAt
1759
+ });
1760
+ removeEmptyDirs2(params.basePath, applyResult.deleted);
1761
+ return { ...applyResult, manifest: derived.manifest, contextIndex: derived.contextIndex };
1762
+ };
1763
+ var packIssue = (level, code, message) => ({
1764
+ level,
1765
+ code,
1766
+ message
1767
+ });
1768
+ var diffProjectLayerPack = (params) => {
1769
+ const manifest = requireProjectLayerManifest(params.basePath);
1770
+ const targets = params.packId ? manifest.packs.filter((pack) => pack.id === params.packId) : manifest.packs;
1771
+ const issues = [];
1772
+ if (targets.length === 0) {
1773
+ return { issues: [packIssue("warning", "missing-pack", "\uBE44\uAD50\uD560 \uC124\uCE58\uB41C pack\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.")] };
1774
+ }
1775
+ for (const record of targets) {
1776
+ const pack = resolvePackById(params.packsDir ?? DEFAULT_PACKS_DIR, record.id);
1777
+ if (record.sourceHash !== pack.sourceHash) {
1778
+ issues.push(
1779
+ packIssue("warning", "pack-source-hash-drift", `${record.id} sourceHash \uBCC0\uACBD: ${record.sourceHash} -> ${pack.sourceHash}`)
1780
+ );
1781
+ }
1782
+ for (const file of [...record.documents, ...record.files]) {
1783
+ const absolutePath = resolveProjectLayerFilePath(params.basePath, file.path);
1784
+ if (!existsSync3(absolutePath)) {
1785
+ issues.push(packIssue("error", "missing-file", `\uD30C\uC77C \uC5C6\uC74C: ${file.path}`));
1786
+ }
1787
+ }
1788
+ }
1789
+ return { issues };
1790
+ };
1791
+
1792
+ // src/core/uninstall-plan.ts
1793
+ import { join as join12 } from "path";
1794
+
1795
+ // src/lib/paths.ts
1796
+ import { join as join13 } from "path";
1797
+ var resolveSkillsDir = () => join13(COMPILER_DATA_DIR, "skills");
1798
+ var resolveSubagentsDir = () => join13(COMPILER_DATA_DIR, "subagents");
1799
+ var resolvePacksDir = () => join13(COMPILER_DATA_DIR, "packs");
1800
+ var resolveBasePath = () => process.cwd();
1801
+ var resolveUserBasePath = () => {
1802
+ const userBasePath = process.env.AI_OPS_HOME ?? process.env.HOME;
1803
+ if (!userBasePath) {
1804
+ throw new Error("AI_OPS_HOME or HOME is required for global asset commands");
1805
+ }
1806
+ return userBasePath;
1807
+ };
1808
+
1809
+ // src/commands/project-layer-errors.ts
1810
+ import * as p from "@clack/prompts";
1811
+ import { ZodError } from "zod";
1812
+ var formatProjectLayerCommandError = (error) => {
1813
+ if (error instanceof ZodError) {
1814
+ return error.issues.map((issue2) => {
1815
+ const path = issue2.path.length > 0 ? issue2.path.join(".") : "manifest";
1816
+ return `${path}: ${issue2.message}`;
1817
+ }).join("; ");
1818
+ }
1819
+ return error instanceof Error ? error.message : "unknown error";
1820
+ };
1821
+ var reportInvalidProjectLayerManifest = (params) => {
1822
+ const reason = formatProjectLayerCommandError(params.error);
1823
+ p.log.error(`[invalid-manifest] .ai-ops/manifest.json \uD30C\uC2F1 \uC2E4\uD328: ${reason}`);
1824
+ process.exitCode = 1;
1825
+ p.outro(params.outro);
1826
+ };
1827
+ var reportProjectLayerApplyError = (params) => {
1828
+ const reason = formatProjectLayerCommandError(params.error);
1829
+ p.log.error(`[project-layer-apply] project operating layer \uC801\uC6A9 \uC2E4\uD328: ${reason}`);
1830
+ process.exitCode = 1;
1831
+ p.outro(params.outro);
1832
+ };
1833
+
1834
+ // src/commands/init.ts
1835
+ var TOOL_OPTIONS = [
1836
+ { value: "codex", label: "Codex" },
1837
+ { value: "gemini", label: "Gemini CLI" },
1838
+ { value: "claude-code", label: "Claude Code" }
1839
+ ];
1840
+ var promptTools = async () => {
1841
+ const selectedTools = await p2.multiselect({
1842
+ message: "AI \uB3C4\uAD6C adapter\uB97C \uC120\uD0DD\uD558\uC138\uC694",
1843
+ options: TOOL_OPTIONS,
1844
+ initialValues: TOOL_OPTIONS.map((option) => option.value),
1845
+ required: true
1683
1846
  });
1684
- if (diffResult.status === "up-to-date" && !opts.force) {
1685
- p4.log.info("\uBCC0\uACBD \uC0AC\uD56D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
1686
- p4.outro("ai-ops update \uC644\uB8CC");
1847
+ return p2.isCancel(selectedTools) ? null : resolveProjectLayerTools(selectedTools);
1848
+ };
1849
+ var initCommand = async (opts = {}) => {
1850
+ p2.intro("ai-ops init");
1851
+ const basePath = resolveBasePath();
1852
+ const tools = opts.tool && opts.tool.length > 0 ? resolveProjectLayerTools(opts.tool) : await promptTools();
1853
+ if (tools === null) {
1854
+ p2.cancel("\uCDE8\uC18C\uB428");
1855
+ process.exit(0);
1856
+ }
1857
+ let previousManifest;
1858
+ try {
1859
+ previousManifest = readProjectLayerManifest(basePath);
1860
+ } catch (error) {
1861
+ reportInvalidProjectLayerManifest({ error, outro: "ai-ops init \uC2E4\uD328" });
1687
1862
  return;
1688
1863
  }
1689
- const s = p4.spinner();
1690
- s.start("\uADDC\uCE59 \uAC31\uC2E0 \uC911...");
1691
- const meta = { sourceHash, generatedAt: (/* @__PURE__ */ new Date()).toISOString() };
1692
- const allInstalledFiles = [];
1693
- const allAppended = [];
1694
- const installedSkills = resolvedSkills.map(({ skill, requestedTools }) => {
1695
- const { packages, installedSkill } = buildSkillInstallPlan({
1696
- skill,
1697
- requestedTools,
1698
- scope: "project"
1864
+ let result;
1865
+ try {
1866
+ result = installProjectLayer({
1867
+ basePath,
1868
+ tools,
1869
+ previousManifest
1699
1870
  });
1700
- installSkillPackages(basePath, packages);
1701
- return installedSkill;
1702
- });
1703
- if (manifest.workspaces) {
1704
- const workspaceEntries = Object.entries(resolvedRules.workspaces ?? {});
1705
- for (const toolIdStr of manifest.tools) {
1706
- const toolId = toolIdStr;
1707
- const rulesToInstall = resolvedRules.installedRules;
1708
- const workspaceMappings = workspaceEntries.map(([path, entry]) => ({
1709
- path,
1710
- ruleIds: entry.rules
1711
- }));
1712
- const renderResult = renderForTool(toolId, rulesToInstall, workspaceMappings);
1713
- const actions = buildInstallPlan({ toolId, renderResult, meta });
1714
- const r = installFiles(basePath, actions, meta);
1715
- allInstalledFiles.push(...r.written);
1716
- allAppended.push(...r.appended);
1717
- }
1718
- } else {
1719
- const rulesToInstall = resolvedRules.installedRules;
1720
- for (const toolIdStr of manifest.tools) {
1721
- const toolId = toolIdStr;
1722
- const renderResult = renderForTool(toolId, rulesToInstall);
1723
- const actions = buildInstallPlan({ toolId, renderResult, meta });
1724
- const r = installFiles(basePath, actions, meta);
1725
- allInstalledFiles.push(...r.written);
1726
- allAppended.push(...r.appended);
1727
- }
1728
- }
1729
- if (manifest.settings?.claude) {
1730
- installClaudeSettings(basePath, manifest.settings.claude);
1731
- }
1732
- if (manifest.settings?.gemini) {
1733
- installGeminiSettings(basePath, manifest.settings.gemini);
1734
- }
1735
- if (manifest.settings?.prettierignore) {
1736
- installPrettierIgnore(basePath);
1737
- }
1738
- const newManifest = buildManifest({
1739
- tools: manifest.tools,
1740
- scope: manifest.scope,
1741
- preset: manifest.preset,
1742
- workspaces: resolvedRules.workspaces,
1743
- installedRules: resolvedRules.installedRules.map((rule) => rule.id),
1744
- installedFiles: allInstalledFiles.length > 0 ? allInstalledFiles : manifest.installed_files,
1745
- installedSkills,
1746
- appendedFiles: allAppended.length > 0 ? allAppended : manifest.appended_files,
1747
- settings: manifest.settings ? {
1748
- claude: manifest.settings.claude,
1749
- gemini: manifest.settings.gemini,
1750
- prettierignore: manifest.settings.prettierignore
1751
- } : void 0,
1752
- cliVersion,
1753
- sourceHash
1754
- });
1755
- writeManifest(manifestPath, newManifest);
1756
- s.stop("\uADDC\uCE59 \uAC31\uC2E0 \uC644\uB8CC");
1757
- p4.outro("ai-ops update \uC644\uB8CC");
1871
+ } catch (error) {
1872
+ reportProjectLayerApplyError({ error, outro: "ai-ops init \uC2E4\uD328" });
1873
+ return;
1874
+ }
1875
+ p2.log.success(`project operating layer \uC124\uCE58 \uC644\uB8CC: ${result.manifest.managed_files.length + result.manifest.project_files.length}\uAC1C \uD30C\uC77C`);
1876
+ p2.log.info(`\uB3C4\uAD6C adapter: ${result.manifest.tools.join(", ")}`);
1877
+ if (result.appended.length > 0) {
1878
+ p2.log.info(`\uAE30\uC874 \uD30C\uC77C\uC5D0 managed section \uCD94\uAC00:
1879
+ ${result.appended.map((file) => ` ${file}`).join("\n")}`);
1880
+ }
1881
+ if (result.refreshedProjectFiles.length > 0) {
1882
+ p2.log.info(`unmodified project-owned \uD30C\uC77C \uAC31\uC2E0:
1883
+ ${result.refreshedProjectFiles.map((file) => ` ${file}`).join("\n")}`);
1884
+ }
1885
+ if (result.preservedProjectFiles.length > 0) {
1886
+ p2.log.info(`\uAE30\uC874 project-owned \uD30C\uC77C \uBCF4\uC874:
1887
+ ${result.preservedProjectFiles.map((file) => ` ${file}`).join("\n")}`);
1888
+ }
1889
+ p2.outro("ai-ops init \uC644\uB8CC");
1758
1890
  };
1759
1891
 
1760
- // src/commands/diff.ts
1761
- import * as p5 from "@clack/prompts";
1762
- var diffCommand = async () => {
1892
+ // src/commands/update.ts
1893
+ import * as p3 from "@clack/prompts";
1894
+ var updateCommand = async (opts) => {
1763
1895
  const basePath = resolveBasePath();
1764
- p5.intro("ai-ops diff");
1765
- const manifest = readManifest(resolveManifestPath(basePath));
1896
+ p3.intro("ai-ops update");
1897
+ let manifest;
1898
+ try {
1899
+ manifest = readProjectLayerManifest(basePath);
1900
+ } catch (error) {
1901
+ reportInvalidProjectLayerManifest({ error, outro: "ai-ops update \uC2E4\uD328" });
1902
+ return;
1903
+ }
1766
1904
  if (!manifest) {
1767
- p5.log.error("manifest\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 ai-ops init\uC744 \uC2E4\uD589\uD558\uC138\uC694.");
1905
+ p3.log.error(".ai-ops/manifest.json\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 ai-ops init\uC744 \uC2E4\uD589\uD558\uC138\uC694.");
1768
1906
  process.exit(1);
1769
1907
  }
1770
- const sourceHash = computeSourceHash(resolveCompilerDataDir());
1771
- const allRules = loadAllRules(resolveRulesDir());
1772
- const allSkills = loadAllSkills(resolveSkillsDir());
1773
- const presets = loadPresets(resolvePresetsPath());
1774
- const resolvedRules = resolveManifestRules({
1775
- manifest,
1776
- allRules,
1777
- presets
1778
- });
1779
- const resolvedSkills = resolveManifestProjectSkills({
1780
- manifest,
1781
- allSkills
1782
- });
1783
- const result = computeDiff({
1784
- previous: manifest,
1785
- currentRules: resolvedRules.installedRules.map((rule) => rule.id),
1786
- currentSourceHash: sourceHash
1787
- });
1788
- const skillLines = resolvedSkills.map(({ skill, requestedTools }) => {
1789
- const { installedSkill: next } = buildSkillInstallPlan({
1790
- skill,
1791
- requestedTools,
1792
- scope: "project"
1793
- });
1794
- const previous = (manifest.installed_skills ?? []).find((installedSkill) => installedSkill.id === skill.id);
1795
- const previousHash = previous?.sourceHash ?? "legacy";
1796
- const changed = previousHash !== next.sourceHash;
1797
- return `- ${skill.id}: ${changed ? "changed" : "up-to-date"} (${previousHash} -> ${next.sourceHash})`;
1798
- });
1799
- if (result.status === "up-to-date") {
1800
- p5.log.success("\uBCC0\uACBD \uC0AC\uD56D \uC5C6\uC74C. \uCD5C\uC2E0 \uC0C1\uD0DC\uC785\uB2C8\uB2E4.");
1801
- } else {
1802
- if (result.sourceChanged) {
1803
- p5.log.warn(`\uC18C\uC2A4 \uBCC0\uACBD \uAC10\uC9C0: ${manifest.sourceHash} \u2192 ${sourceHash}`);
1804
- }
1805
- if (result.added.length > 0) {
1806
- p5.log.info(`\uCD94\uAC00\uB41C \uADDC\uCE59: ${result.added.join(", ")}`);
1807
- }
1808
- if (result.removed.length > 0) {
1809
- p5.log.info(`\uC81C\uAC70\uB41C \uADDC\uCE59: ${result.removed.join(", ")}`);
1810
- }
1908
+ const diffReport = diffProjectLayer(basePath);
1909
+ if (diffReport.issues.length === 0 && !opts.force) {
1910
+ p3.log.info("\uBCC0\uACBD \uC0AC\uD56D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
1911
+ p3.outro("ai-ops update \uC644\uB8CC");
1912
+ return;
1811
1913
  }
1812
- if (skillLines.length > 0) {
1813
- p5.log.info(`project skills:
1814
- ${skillLines.map((line) => ` ${line}`).join("\n")}`);
1914
+ let result;
1915
+ try {
1916
+ result = updateProjectLayer({ basePath, manifest });
1917
+ } catch (error) {
1918
+ reportProjectLayerApplyError({ error, outro: "ai-ops update \uC2E4\uD328" });
1919
+ return;
1920
+ }
1921
+ p3.log.success(`managed \uD30C\uC77C \uAC31\uC2E0: ${result.manifest.managed_files.length}\uAC1C`);
1922
+ if (result.createdProjectFiles.length > 0) {
1923
+ p3.log.info(`\uB204\uB77D\uB41C project-owned \uD30C\uC77C \uBCF5\uAD6C:
1924
+ ${result.createdProjectFiles.map((file) => ` ${file}`).join("\n")}`);
1925
+ }
1926
+ if (result.refreshedProjectFiles.length > 0) {
1927
+ p3.log.info(`unmodified project-owned \uD30C\uC77C \uAC31\uC2E0:
1928
+ ${result.refreshedProjectFiles.map((file) => ` ${file}`).join("\n")}`);
1815
1929
  }
1816
- p5.outro("ai-ops diff \uC644\uB8CC");
1930
+ if (result.preservedProjectFiles.length > 0) {
1931
+ p3.log.info(`project-owned \uD30C\uC77C \uBCF4\uC874:
1932
+ ${result.preservedProjectFiles.map((file) => ` ${file}`).join("\n")}`);
1933
+ }
1934
+ p3.outro("ai-ops update \uC644\uB8CC");
1817
1935
  };
1818
1936
 
1819
- // src/commands/uninstall.ts
1820
- import * as p6 from "@clack/prompts";
1821
- import { rmSync as rmSync5 } from "fs";
1822
-
1823
- // src/lib/uninstall.ts
1824
- import { existsSync as existsSync7, readFileSync as readFileSync8, rmSync as rmSync4, readdirSync as readdirSync4, writeFileSync as writeFileSync7 } from "fs";
1825
- import { resolve as resolve7, dirname as dirname7 } from "path";
1826
- var removeFiles = (basePath, relativePaths) => {
1827
- const deleted = [];
1828
- const cleaned = [];
1829
- const skipped = [];
1830
- const notFound = [];
1831
- for (const rel of relativePaths) {
1832
- const absPath = resolve7(basePath, rel);
1833
- if (!existsSync7(absPath)) {
1834
- notFound.push(rel);
1835
- continue;
1836
- }
1837
- const content = readFileSync8(absPath, "utf-8");
1838
- if (hasAiOpsSection(content)) {
1839
- const stripped = stripAiOpsSection(content);
1840
- if (stripped.trim().length === 0) {
1841
- rmSync4(absPath);
1842
- deleted.push(rel);
1843
- } else {
1844
- writeFileSync7(absPath, stripped, "utf-8");
1845
- cleaned.push(rel);
1846
- }
1847
- } else if (hasLegacyHeader(content)) {
1848
- rmSync4(absPath);
1849
- deleted.push(rel);
1937
+ // src/commands/diff.ts
1938
+ import * as p4 from "@clack/prompts";
1939
+ var diffCommand = async () => {
1940
+ p4.intro("ai-ops diff");
1941
+ const report = diffProjectLayer(resolveBasePath());
1942
+ if (report.issues.length === 0) {
1943
+ p4.log.success("\uBCC0\uACBD \uC0AC\uD56D \uC5C6\uC74C. \uCD5C\uC2E0 \uC0C1\uD0DC\uC785\uB2C8\uB2E4.");
1944
+ p4.outro("ai-ops diff \uC644\uB8CC");
1945
+ return;
1946
+ }
1947
+ for (const item of report.issues) {
1948
+ const line = `[${item.code}] ${item.message}`;
1949
+ if (item.level === "error") {
1950
+ p4.log.error(line);
1850
1951
  } else {
1851
- skipped.push(rel);
1952
+ p4.log.warn(line);
1852
1953
  }
1853
1954
  }
1854
- return { deleted, cleaned, skipped, notFound };
1855
- };
1856
- var cleanEmptyDirs = (basePath, dirs) => {
1857
- const removed = [];
1858
- for (const dir of dirs) {
1859
- const absDir = resolve7(basePath, dir);
1860
- if (!existsSync7(absDir)) continue;
1861
- try {
1862
- const entries = readdirSync4(absDir);
1863
- if (entries.length === 0) {
1864
- rmSync4(absDir, { recursive: true });
1865
- removed.push(dir);
1866
- }
1867
- } catch {
1868
- }
1955
+ if (report.issues.some((item) => item.level === "error")) {
1956
+ process.exitCode = 1;
1869
1957
  }
1870
- return removed;
1958
+ p4.outro("ai-ops diff \uC644\uB8CC");
1871
1959
  };
1872
- var collectManagedDirs = (relativePaths) => {
1873
- const dirs = /* @__PURE__ */ new Set();
1874
- for (const rel of relativePaths) {
1875
- const dir = dirname7(rel);
1876
- if (dir !== ".") {
1877
- dirs.add(dir);
1960
+
1961
+ // src/commands/audit.ts
1962
+ import * as p5 from "@clack/prompts";
1963
+ var auditCommand = async () => {
1964
+ p5.intro("ai-ops audit");
1965
+ const report = auditProjectLayer(resolveBasePath());
1966
+ if (report.issues.length === 0) {
1967
+ p5.log.success("audit \uD1B5\uACFC. manifest, context-layer, frontmatter, docs-status\uAC00 \uC77C\uCE58\uD569\uB2C8\uB2E4.");
1968
+ p5.outro("ai-ops audit \uC644\uB8CC");
1969
+ return;
1970
+ }
1971
+ for (const item of report.issues) {
1972
+ const line = `[${item.code}] ${item.message}`;
1973
+ if (item.level === "error") {
1974
+ p5.log.error(line);
1975
+ } else {
1976
+ p5.log.warn(line);
1878
1977
  }
1879
1978
  }
1880
- return [...dirs];
1979
+ if (report.issues.some((item) => item.level === "error")) {
1980
+ process.exitCode = 1;
1981
+ }
1982
+ p5.outro("ai-ops audit \uC644\uB8CC");
1881
1983
  };
1882
1984
 
1883
1985
  // src/commands/uninstall.ts
1884
- var SETTINGS_PATHS = /* @__PURE__ */ new Set([".claude/settings.local.json", ".gemini/settings.json"]);
1885
- var uninstallCommand = async () => {
1986
+ import * as p6 from "@clack/prompts";
1987
+ var uninstallCommand = async (opts = {}) => {
1886
1988
  const basePath = resolveBasePath();
1887
- const manifestPath = resolveManifestPath(basePath);
1888
1989
  p6.intro("ai-ops uninstall");
1889
- const manifest = readManifest(manifestPath);
1890
- if (!manifest) {
1891
- p6.log.error("manifest\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 ai-ops init\uC744 \uC2E4\uD589\uD558\uC138\uC694.");
1892
- process.exit(1);
1893
- }
1894
- const targetFiles = [
1895
- ...manifest.installed_files ?? inferInstalledFiles(manifest),
1896
- ...manifest.appended_files ?? []
1897
- ].filter((f) => !SETTINGS_PATHS.has(f));
1898
- const targetSkillDirs = (manifest.installed_skills ?? []).flatMap((skill) => skill.installed_paths);
1899
- if (targetFiles.length === 0 && targetSkillDirs.length === 0) {
1900
- p6.log.warn("\uC0AD\uC81C\uD560 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
1901
- p6.outro("ai-ops uninstall \uC644\uB8CC");
1990
+ let manifest;
1991
+ try {
1992
+ manifest = readProjectLayerManifest(basePath);
1993
+ } catch (error) {
1994
+ reportInvalidProjectLayerManifest({ error, outro: "ai-ops uninstall \uC2E4\uD328" });
1902
1995
  return;
1903
1996
  }
1904
- if (targetFiles.length > 0) {
1905
- p6.log.info(`\uC0AD\uC81C \uB300\uC0C1 \uD30C\uC77C (${targetFiles.length}\uAC1C):
1906
- ${targetFiles.map((f) => ` ${f}`).join("\n")}`);
1997
+ if (!manifest) {
1998
+ p6.log.error(".ai-ops/manifest.json\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. project operating layer\uAC00 \uC124\uCE58\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
1999
+ process.exitCode = 1;
2000
+ p6.outro("ai-ops uninstall \uC2E4\uD328");
2001
+ return;
1907
2002
  }
1908
- if (targetSkillDirs.length > 0) {
1909
- p6.log.info(
1910
- `\uC0AD\uC81C \uB300\uC0C1 skill \uB514\uB809\uD1A0\uB9AC (${targetSkillDirs.length}\uAC1C):
1911
- ${targetSkillDirs.map((f) => ` ${f}`).join("\n")}`
1912
- );
2003
+ const targetFiles = [
2004
+ ...manifest.managed_files.map((file) => file.path),
2005
+ ...manifest.project_files.map((file) => file.path),
2006
+ ".ai-ops/context-layer.json",
2007
+ ".ai-ops/manifest.json"
2008
+ ];
2009
+ p6.log.info(`\uCC98\uB9AC \uB300\uC0C1 (${targetFiles.length}\uAC1C):
2010
+ ${targetFiles.map((file) => ` ${file}`).join("\n")}`);
2011
+ if (!opts.yes) {
2012
+ const confirmed = await p6.confirm({
2013
+ message: "project operating layer\uB97C \uC81C\uAC70\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
2014
+ initialValue: false
2015
+ });
2016
+ if (p6.isCancel(confirmed) || !confirmed) {
2017
+ p6.cancel("\uCDE8\uC18C\uB428");
2018
+ process.exit(0);
2019
+ }
1913
2020
  }
1914
- const confirmed = await p6.confirm({
1915
- message: "\uC704 \uD30C\uC77C\uACFC manifest\uB97C \uBAA8\uB450 \uC0AD\uC81C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
1916
- initialValue: false
1917
- });
1918
- if (p6.isCancel(confirmed) || !confirmed) {
1919
- p6.cancel("\uCDE8\uC18C\uB428");
1920
- process.exit(0);
2021
+ let result;
2022
+ try {
2023
+ result = uninstallProjectLayer(basePath, manifest);
2024
+ } catch (error) {
2025
+ reportProjectLayerApplyError({ error, outro: "ai-ops uninstall \uC2E4\uD328" });
2026
+ return;
1921
2027
  }
1922
- const settingsMessages = [];
1923
- if (manifest.settings?.claude) {
1924
- const status = uninstallClaudeSettings(basePath, manifest.settings.claude);
1925
- if (status === "deleted") settingsMessages.push("\uC0AD\uC81C: .claude/settings.local.json");
1926
- else if (status === "cleaned")
1927
- settingsMessages.push("ai-ops \uD0A4 \uC81C\uAC70 (\uC0AC\uC6A9\uC790 \uC124\uC815 \uBCF4\uC874): .claude/settings.local.json");
1928
- }
1929
- if (manifest.settings?.gemini) {
1930
- const status = uninstallGeminiSettings(basePath, manifest.settings.gemini);
1931
- if (status === "deleted") settingsMessages.push("\uC0AD\uC81C: .gemini/settings.json");
1932
- else if (status === "cleaned") settingsMessages.push("ai-ops \uD0A4 \uC81C\uAC70 (\uC0AC\uC6A9\uC790 \uC124\uC815 \uBCF4\uC874): .gemini/settings.json");
1933
- }
1934
- const prettierStatus = uninstallPrettierIgnore(basePath);
1935
- if (prettierStatus === "deleted") settingsMessages.push("\uC0AD\uC81C: .prettierignore");
1936
- else if (prettierStatus === "cleaned") settingsMessages.push("ai-ops \uC139\uC158 \uC81C\uAC70 (\uC0AC\uC6A9\uC790 \uB0B4\uC6A9 \uBCF4\uC874): .prettierignore");
1937
- const result = removeFiles(basePath, targetFiles);
1938
- const removedSkillDirs = removeDirectories(basePath, targetSkillDirs);
1939
- const dirs = collectManagedDirs(targetFiles);
1940
- const removedDirs = cleanEmptyDirs(basePath, dirs);
1941
- rmSync5(manifestPath, { force: true });
1942
2028
  if (result.deleted.length > 0) {
1943
- p6.log.success(`\uC0AD\uC81C \uC644\uB8CC (${result.deleted.length}\uAC1C):
1944
- ${result.deleted.map((f) => ` ${f}`).join("\n")}`);
2029
+ p6.log.success(`\uC0AD\uC81C \uC644\uB8CC:
2030
+ ${result.deleted.map((file) => ` ${file}`).join("\n")}`);
1945
2031
  }
1946
2032
  if (result.cleaned.length > 0) {
1947
- p6.log.success(
1948
- `\uC139\uC158 \uC81C\uAC70 \uC644\uB8CC (\uC0AC\uC6A9\uC790 \uB0B4\uC6A9 \uBCF4\uC874, ${result.cleaned.length}\uAC1C):
1949
- ${result.cleaned.map((f) => ` ${f}`).join("\n")}`
1950
- );
2033
+ p6.log.success(`managed section \uC81C\uAC70, \uC0AC\uC6A9\uC790 \uB0B4\uC6A9 \uBCF4\uC874:
2034
+ ${result.cleaned.map((file) => ` ${file}`).join("\n")}`);
1951
2035
  }
1952
- if (result.skipped.length > 0) {
1953
- p6.log.warn(
1954
- `\uAC74\uB108\uB700 (non-managed \uD30C\uC77C \uBCF4\uD638, ${result.skipped.length}\uAC1C):
1955
- ${result.skipped.map((f) => ` ${f}`).join("\n")}`
1956
- );
2036
+ if (result.preserved.length > 0) {
2037
+ p6.log.info(`\uC218\uC815\uB418\uC5C8\uAC70\uB098 \uAE30\uC874\uC5D0 \uC788\uB358 project-owned \uD30C\uC77C \uBCF4\uC874:
2038
+ ${result.preserved.map((file) => ` ${file}`).join("\n")}`);
1957
2039
  }
1958
2040
  if (result.notFound.length > 0) {
1959
- p6.log.info(`\uC774\uBBF8 \uC5C6\uC74C (${result.notFound.length}\uAC1C):
1960
- ${result.notFound.map((f) => ` ${f}`).join("\n")}`);
1961
- }
1962
- if (removedDirs.length > 0) {
1963
- p6.log.info(`\uBE48 \uB514\uB809\uD1A0\uB9AC \uC815\uB9AC (${removedDirs.length}\uAC1C):
1964
- ${removedDirs.map((d) => ` ${d}`).join("\n")}`);
1965
- }
1966
- if (removedSkillDirs.length > 0) {
1967
- p6.log.success(
1968
- `skill \uB514\uB809\uD1A0\uB9AC \uC0AD\uC81C (${removedSkillDirs.length}\uAC1C):
1969
- ${removedSkillDirs.map((d) => ` ${d}`).join("\n")}`
1970
- );
1971
- }
1972
- if (settingsMessages.length > 0) {
1973
- p6.log.success(`\uC124\uC815 \uD30C\uC77C \uCC98\uB9AC:
1974
- ${settingsMessages.map((m) => ` ${m}`).join("\n")}`);
2041
+ p6.log.info(`\uC774\uBBF8 \uC5C6\uC74C:
2042
+ ${result.notFound.map((file) => ` ${file}`).join("\n")}`);
1975
2043
  }
1976
- p6.log.success(`manifest \uC0AD\uC81C: ${MANIFEST_FILENAME}`);
1977
2044
  p6.outro("ai-ops uninstall \uC644\uB8CC");
1978
2045
  };
1979
2046
 
1980
2047
  // src/commands/skill.ts
1981
2048
  import * as p7 from "@clack/prompts";
1982
- import { rmSync as rmSync6 } from "fs";
1983
- var resolveScopeContext = (opts) => {
1984
- const scope = resolveSkillScope(opts);
1985
- return {
1986
- scope,
1987
- basePath: scope === "project" ? resolveBasePath() : resolveUserBasePath()
1988
- };
2049
+ import { rmSync as rmSync4 } from "fs";
2050
+
2051
+ // src/lib/skill-state.ts
2052
+ var resolveRequestedTools = (params) => {
2053
+ if (params.requested === void 0 || params.requested.length === 0) {
2054
+ return [...params.supported];
2055
+ }
2056
+ const supportedSet = new Set(params.supported);
2057
+ const invalid = params.requested.filter((tool) => !supportedSet.has(tool));
2058
+ if (invalid.length > 0) {
2059
+ throw new Error(`Unsupported tools requested: ${invalid.join(", ")}`);
2060
+ }
2061
+ return [...params.requested];
2062
+ };
2063
+ var TOOL_ORDER2 = [SKILL_TOOL.CLAUDE_CODE, SKILL_TOOL.CODEX, SKILL_TOOL.GEMINI];
2064
+ var mergeSkillTools = (params) => {
2065
+ const merged = /* @__PURE__ */ new Set([...params.existing ?? [], ...params.requested]);
2066
+ return TOOL_ORDER2.filter((tool) => merged.has(tool));
2067
+ };
2068
+ var upsertInstalledSkill = (installedSkills, nextSkill) => {
2069
+ const nextSkillId = resolveCanonicalSkillId(nextSkill.id);
2070
+ const remaining = installedSkills.filter((skill) => resolveCanonicalSkillId(skill.id) !== nextSkillId);
2071
+ return [...remaining, nextSkill];
2072
+ };
2073
+ var removeInstalledSkill = (installedSkills, skillId) => {
2074
+ const targetSkillId = resolveCanonicalSkillId(skillId);
2075
+ return installedSkills.filter((skill) => resolveCanonicalSkillId(skill.id) !== targetSkillId);
2076
+ };
2077
+ var findInstalledSkill = (installedSkills, skillId) => {
2078
+ const targetSkillId = resolveCanonicalSkillId(skillId);
2079
+ return installedSkills.find((skill) => resolveCanonicalSkillId(skill.id) === targetSkillId);
2080
+ };
2081
+
2082
+ // src/lib/skill-install.ts
2083
+ import { existsSync as existsSync4, mkdirSync as mkdirSync6, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "fs";
2084
+ import { dirname as dirname8, resolve as resolve7 } from "path";
2085
+ var installSkillPackages = (basePath, packages) => {
2086
+ const writtenRoots = [];
2087
+ for (const skillPackage of packages) {
2088
+ const absRoot = resolve7(basePath, skillPackage.rootDir);
2089
+ if (existsSync4(absRoot)) {
2090
+ rmSync3(absRoot, { recursive: true, force: true });
2091
+ }
2092
+ for (const file of skillPackage.files) {
2093
+ const absPath = resolve7(basePath, file.relativePath);
2094
+ mkdirSync6(dirname8(absPath), { recursive: true });
2095
+ writeFileSync6(absPath, file.content + "\n", "utf-8");
2096
+ }
2097
+ writtenRoots.push(skillPackage.rootDir);
2098
+ }
2099
+ return writtenRoots;
2100
+ };
2101
+ var removeDirectories = (basePath, relativeDirs) => {
2102
+ const removed = [];
2103
+ for (const relativeDir of relativeDirs) {
2104
+ const absPath = resolve7(basePath, relativeDir);
2105
+ if (!existsSync4(absPath)) continue;
2106
+ rmSync3(absPath, { recursive: true, force: true });
2107
+ removed.push(relativeDir);
2108
+ }
2109
+ return removed;
1989
2110
  };
2111
+
2112
+ // src/commands/skill.ts
1990
2113
  var loadCompilerInputs = () => {
1991
- const compilerDataDir = resolveCompilerDataDir();
1992
2114
  return {
1993
2115
  allSkills: loadAllSkills(resolveSkillsDir()),
1994
- sourceHash: computeSourceHash(compilerDataDir),
1995
2116
  cliVersion: getCliVersion()
1996
2117
  };
1997
2118
  };
@@ -2003,42 +2124,12 @@ var resolveSkillById = (skills, skillId) => {
2003
2124
  }
2004
2125
  return skill;
2005
2126
  };
2006
- var assertScopeAllowed = (skill, scope) => {
2007
- if (!skill.install_scopes.includes(scope)) {
2008
- throw new Error(`Skill ${skill.id} does not support ${scope} scope`);
2009
- }
2010
- };
2011
- var writeProjectSkillState = (params) => {
2012
- const manifestPath = resolveManifestPath(params.basePath);
2013
- const previous = readManifest(manifestPath);
2014
- const installedSkills = params.removeSkillId ? removeInstalledSkill(previous?.installed_skills ?? [], params.removeSkillId) : params.nextSkill ? upsertInstalledSkill(previous?.installed_skills ?? [], params.nextSkill) : previous?.installed_skills ?? [];
2015
- const nextTools = params.nextSkill !== void 0 ? [.../* @__PURE__ */ new Set([...previous?.tools ?? [], ...params.nextSkill.tools])] : previous?.tools ?? [];
2016
- 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;
2017
- if (!hasProjectState) {
2018
- rmSync6(manifestPath, { force: true });
2019
- return;
2020
- }
2021
- const manifest = buildManifest({
2022
- tools: nextTools.length > 0 ? nextTools : params.nextSkill?.tools ?? ["codex"],
2023
- scope: "project",
2024
- preset: previous?.preset,
2025
- workspaces: previous?.workspaces,
2026
- installedRules: previous?.installed_rules ?? [],
2027
- installedFiles: previous?.installed_files,
2028
- installedSkills,
2029
- appendedFiles: previous?.appended_files,
2030
- settings: previous?.settings,
2031
- cliVersion: params.cliVersion,
2032
- sourceHash: params.sourceHash
2033
- });
2034
- writeManifest(manifestPath, manifest);
2035
- };
2036
2127
  var writeUserSkillState = (params) => {
2037
2128
  const registryPath = resolveSkillRegistryPath(params.basePath);
2038
2129
  const previous = readSkillRegistry(registryPath);
2039
2130
  const skills = params.removeSkillId ? removeInstalledSkill(previous?.skills ?? [], params.removeSkillId) : params.nextSkill ? upsertInstalledSkill(previous?.skills ?? [], params.nextSkill) : previous?.skills ?? [];
2040
2131
  if (skills.length === 0) {
2041
- rmSync6(registryPath, { force: true });
2132
+ rmSync4(registryPath, { force: true });
2042
2133
  return;
2043
2134
  }
2044
2135
  writeSkillRegistry(registryPath, {
@@ -2047,20 +2138,14 @@ var writeUserSkillState = (params) => {
2047
2138
  generatedAt: (/* @__PURE__ */ new Date()).toISOString()
2048
2139
  });
2049
2140
  };
2050
- var readInstalledSkills = (scope, basePath) => {
2051
- if (scope === "project") {
2052
- return (readManifest(resolveManifestPath(basePath))?.installed_skills ?? []).map((installedSkill) => ({
2053
- ...installedSkill,
2054
- id: resolveCanonicalSkillId(installedSkill.id)
2055
- }));
2056
- }
2141
+ var readInstalledSkills = (basePath) => {
2057
2142
  return (readSkillRegistry(resolveSkillRegistryPath(basePath))?.skills ?? []).map((installedSkill) => ({
2058
2143
  ...installedSkill,
2059
2144
  id: resolveCanonicalSkillId(installedSkill.id)
2060
2145
  }));
2061
2146
  };
2062
2147
  var installSkill = (params) => {
2063
- const installedSkills = readInstalledSkills(params.scope, params.basePath);
2148
+ const installedSkills = readInstalledSkills(params.basePath);
2064
2149
  const existingInstalledSkill = findInstalledSkill(installedSkills, params.skill.id);
2065
2150
  const nextRequestedTools = mergeSkillTools({
2066
2151
  existing: existingInstalledSkill?.tools,
@@ -2068,31 +2153,21 @@ var installSkill = (params) => {
2068
2153
  });
2069
2154
  const { packages, installedSkill } = buildSkillInstallPlan({
2070
2155
  skill: params.skill,
2071
- requestedTools: nextRequestedTools,
2072
- scope: params.scope
2156
+ requestedTools: nextRequestedTools
2073
2157
  });
2074
2158
  installSkillPackages(params.basePath, packages);
2075
- if (params.scope === "project") {
2076
- writeProjectSkillState({
2077
- basePath: params.basePath,
2078
- sourceHash: params.sourceHash,
2079
- cliVersion: params.cliVersion,
2080
- nextSkill: installedSkill
2081
- });
2082
- } else {
2083
- writeUserSkillState({
2084
- basePath: params.basePath,
2085
- cliVersion: params.cliVersion,
2086
- nextSkill: installedSkill
2087
- });
2088
- }
2159
+ writeUserSkillState({
2160
+ basePath: params.basePath,
2161
+ cliVersion: params.cliVersion,
2162
+ nextSkill: installedSkill
2163
+ });
2089
2164
  return installedSkill;
2090
2165
  };
2091
- var skillListCommand = async (opts) => {
2092
- const { scope, basePath } = resolveScopeContext(opts);
2166
+ var skillListCommand = async () => {
2167
+ const basePath = resolveUserBasePath();
2093
2168
  const { allSkills } = loadCompilerInputs();
2094
- const installedSkills = readInstalledSkills(scope, basePath);
2095
- p7.intro(`ai-ops skill list (${scope})`);
2169
+ const installedSkills = readInstalledSkills(basePath);
2170
+ p7.intro("ai-ops skill list");
2096
2171
  const sections = [
2097
2172
  { kind: "reference", title: "reference skills" },
2098
2173
  { kind: "task", title: "task skills" }
@@ -2100,7 +2175,7 @@ var skillListCommand = async (opts) => {
2100
2175
  const lines = allSkills.filter((skill) => skill.kind === kind).map((skill) => {
2101
2176
  const installed = findInstalledSkill(installedSkills, skill.id);
2102
2177
  const suffix = installed ? `installed for ${installed.tools.join(", ")}` : "not installed";
2103
- return `- ${skill.id} (${skill.install_scopes.join(", ")}) - ${suffix}`;
2178
+ return `- ${skill.id} - ${suffix}`;
2104
2179
  });
2105
2180
  if (lines.length === 0) {
2106
2181
  return null;
@@ -2112,29 +2187,26 @@ ${lines.join("\n")}`;
2112
2187
  p7.outro("ai-ops skill list \uC644\uB8CC");
2113
2188
  };
2114
2189
  var skillInstallCommand = async (skillId, opts) => {
2115
- const { scope, basePath } = resolveScopeContext(opts);
2116
- const { allSkills, sourceHash, cliVersion } = loadCompilerInputs();
2190
+ const basePath = resolveUserBasePath();
2191
+ const { allSkills, cliVersion } = loadCompilerInputs();
2117
2192
  const skill = resolveSkillById(allSkills, skillId);
2118
- assertScopeAllowed(skill, scope);
2119
2193
  const requestedTools = resolveRequestedTools({ requested: opts.tool, supported: skill.supported_tools });
2120
2194
  p7.intro(`ai-ops skill install ${skillId}`);
2121
2195
  const installedSkill = installSkill({
2122
2196
  skill,
2123
2197
  requestedTools,
2124
- scope,
2125
2198
  basePath,
2126
- cliVersion,
2127
- sourceHash
2199
+ cliVersion
2128
2200
  });
2129
2201
  p7.log.success(`\uC124\uCE58 \uC644\uB8CC: ${installedSkill.id} (${installedSkill.installed_paths.join(", ")})`);
2130
2202
  p7.outro("ai-ops skill install \uC644\uB8CC");
2131
2203
  };
2132
- var skillDiffCommand = async (skillId, opts) => {
2133
- const { scope, basePath } = resolveScopeContext(opts);
2204
+ var skillDiffCommand = async (skillId) => {
2205
+ const basePath = resolveUserBasePath();
2134
2206
  const { allSkills } = loadCompilerInputs();
2135
- const installedSkills = readInstalledSkills(scope, basePath);
2207
+ const installedSkills = readInstalledSkills(basePath);
2136
2208
  const targets = skillId ? installedSkills.filter((skill) => skill.id === skillId) : installedSkills;
2137
- p7.intro(`ai-ops skill diff (${scope})`);
2209
+ p7.intro("ai-ops skill diff");
2138
2210
  if (targets.length === 0) {
2139
2211
  p7.log.warn("\uBE44\uAD50\uD560 \uC124\uCE58\uB41C skill\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
2140
2212
  p7.outro("ai-ops skill diff \uC644\uB8CC");
@@ -2144,8 +2216,7 @@ var skillDiffCommand = async (skillId, opts) => {
2144
2216
  const skill = resolveSkillById(allSkills, installedSkill.id);
2145
2217
  const { installedSkill: next } = buildSkillInstallPlan({
2146
2218
  skill,
2147
- requestedTools: installedSkill.tools,
2148
- scope
2219
+ requestedTools: installedSkill.tools
2149
2220
  });
2150
2221
  const changed = next.sourceHash !== installedSkill.sourceHash;
2151
2222
  return `- ${installedSkill.id}: ${changed ? "changed" : "up-to-date"} (${installedSkill.sourceHash} -> ${next.sourceHash})`;
@@ -2153,12 +2224,12 @@ var skillDiffCommand = async (skillId, opts) => {
2153
2224
  p7.log.info(lines.join("\n"));
2154
2225
  p7.outro("ai-ops skill diff \uC644\uB8CC");
2155
2226
  };
2156
- var skillUpdateCommand = async (skillId, opts) => {
2157
- const { scope, basePath } = resolveScopeContext(opts);
2158
- const { allSkills, sourceHash, cliVersion } = loadCompilerInputs();
2159
- const installedSkills = readInstalledSkills(scope, basePath);
2227
+ var skillUpdateCommand = async (skillId) => {
2228
+ const basePath = resolveUserBasePath();
2229
+ const { allSkills, cliVersion } = loadCompilerInputs();
2230
+ const installedSkills = readInstalledSkills(basePath);
2160
2231
  const targets = skillId ? installedSkills.filter((skill) => skill.id === skillId) : installedSkills;
2161
- p7.intro(`ai-ops skill update (${scope})`);
2232
+ p7.intro("ai-ops skill update");
2162
2233
  if (targets.length === 0) {
2163
2234
  p7.log.warn("\uAC31\uC2E0\uD560 \uC124\uCE58\uB41C skill\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
2164
2235
  p7.outro("ai-ops skill update \uC644\uB8CC");
@@ -2168,57 +2239,29 @@ var skillUpdateCommand = async (skillId, opts) => {
2168
2239
  const skill = resolveSkillById(allSkills, installedSkill.id);
2169
2240
  const { packages, installedSkill: next } = buildSkillInstallPlan({
2170
2241
  skill,
2171
- requestedTools: installedSkill.tools,
2172
- scope
2242
+ requestedTools: installedSkill.tools
2173
2243
  });
2174
2244
  installSkillPackages(basePath, packages);
2175
2245
  return next;
2176
2246
  });
2177
- if (scope === "project") {
2178
- const manifestPath = resolveManifestPath(basePath);
2179
- const previous = readManifest(manifestPath);
2180
- if (!previous) {
2181
- p7.log.error("project manifest\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
2182
- process.exit(1);
2183
- }
2184
- const untouched = (previous.installed_skills ?? []).filter(
2185
- (installedSkill) => !nextInstalledSkills.some((next) => next.id === installedSkill.id)
2186
- );
2187
- writeManifest(
2188
- manifestPath,
2189
- buildManifest({
2190
- tools: previous.tools,
2191
- scope: previous.scope,
2192
- preset: previous.preset,
2193
- workspaces: previous.workspaces,
2194
- installedRules: previous.installed_rules,
2195
- installedFiles: previous.installed_files,
2196
- installedSkills: [...untouched, ...nextInstalledSkills],
2197
- appendedFiles: previous.appended_files,
2198
- settings: previous.settings,
2199
- cliVersion,
2200
- sourceHash
2201
- })
2202
- );
2203
- } else {
2204
- const registryPath = resolveSkillRegistryPath(basePath);
2205
- const previous = readSkillRegistry(registryPath);
2206
- const untouched = (previous?.skills ?? []).filter(
2207
- (installedSkill) => !nextInstalledSkills.some((next) => next.id === installedSkill.id)
2208
- );
2209
- writeSkillRegistry(registryPath, {
2210
- skills: [...untouched, ...nextInstalledSkills],
2211
- cliVersion,
2212
- generatedAt: (/* @__PURE__ */ new Date()).toISOString()
2213
- });
2214
- }
2247
+ const registryPath = resolveSkillRegistryPath(basePath);
2248
+ const previous = readSkillRegistry(registryPath);
2249
+ const nextSkillIds = new Set(nextInstalledSkills.map((skill) => skill.id));
2250
+ const untouched = (previous?.skills ?? []).filter(
2251
+ (installedSkill) => !nextSkillIds.has(resolveCanonicalSkillId(installedSkill.id))
2252
+ );
2253
+ writeSkillRegistry(registryPath, {
2254
+ skills: [...untouched, ...nextInstalledSkills],
2255
+ cliVersion,
2256
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
2257
+ });
2215
2258
  p7.log.success(`\uAC31\uC2E0 \uC644\uB8CC: ${nextInstalledSkills.map((skill) => skill.id).join(", ")}`);
2216
2259
  p7.outro("ai-ops skill update \uC644\uB8CC");
2217
2260
  };
2218
- var skillUninstallCommand = async (skillId, opts) => {
2219
- const { scope, basePath } = resolveScopeContext(opts);
2220
- const { sourceHash, cliVersion } = loadCompilerInputs();
2221
- const installedSkills = readInstalledSkills(scope, basePath);
2261
+ var skillUninstallCommand = async (skillId) => {
2262
+ const basePath = resolveUserBasePath();
2263
+ const { cliVersion } = loadCompilerInputs();
2264
+ const installedSkills = readInstalledSkills(basePath);
2222
2265
  const installedSkill = findInstalledSkill(installedSkills, skillId);
2223
2266
  p7.intro(`ai-ops skill uninstall ${skillId}`);
2224
2267
  if (!installedSkill) {
@@ -2227,70 +2270,416 @@ var skillUninstallCommand = async (skillId, opts) => {
2227
2270
  return;
2228
2271
  }
2229
2272
  const removed = removeDirectories(basePath, installedSkill.installed_paths);
2230
- if (scope === "project") {
2231
- writeProjectSkillState({
2232
- basePath,
2233
- sourceHash,
2234
- cliVersion,
2235
- removeSkillId: skillId
2236
- });
2237
- } else {
2238
- writeUserSkillState({
2239
- basePath,
2240
- cliVersion,
2241
- removeSkillId: skillId
2242
- });
2243
- }
2273
+ writeUserSkillState({
2274
+ basePath,
2275
+ cliVersion,
2276
+ removeSkillId: skillId
2277
+ });
2244
2278
  p7.log.success(`\uC81C\uAC70 \uC644\uB8CC: ${removed.join(", ")}`);
2245
2279
  p7.outro("ai-ops skill uninstall \uC644\uB8CC");
2246
2280
  };
2247
2281
 
2248
- // src/commands/spec.ts
2249
- import { existsSync as existsSync8, mkdirSync as mkdirSync6, writeFileSync as writeFileSync8 } from "fs";
2250
- import { join as join13, dirname as dirname8 } from "path";
2282
+ // src/commands/subagent.ts
2251
2283
  import * as p8 from "@clack/prompts";
2252
- var specInitCommand = async (opts) => {
2253
- p8.intro("ai-ops spec init");
2254
- const specsDir = join13(process.cwd(), "specs");
2255
- if (existsSync8(specsDir) && !opts.force) {
2256
- p8.log.error("specs/ \uB514\uB809\uD1A0\uB9AC\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4. \uB36E\uC5B4\uC4F0\uB824\uBA74 --force \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.");
2257
- process.exit(1);
2284
+ import { existsSync as existsSync6, rmSync as rmSync6 } from "fs";
2285
+
2286
+ // src/lib/subagent-install.ts
2287
+ import { existsSync as existsSync5, mkdirSync as mkdirSync7, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "fs";
2288
+ import { dirname as dirname9, isAbsolute as isAbsolute3, relative as relative3, resolve as resolve8 } from "path";
2289
+ var resolveInsideBasePath = (basePath, relativePath) => {
2290
+ const absBasePath = resolve8(basePath);
2291
+ const absPath = resolve8(absBasePath, relativePath);
2292
+ const fromBase = relative3(absBasePath, absPath);
2293
+ if (fromBase.length === 0 || fromBase.startsWith("..") || isAbsolute3(fromBase)) {
2294
+ throw new Error(`Subagent path escapes AI_OPS_HOME: ${relativePath}`);
2295
+ }
2296
+ return absPath;
2297
+ };
2298
+ var installSubagentPackages = (basePath, packages) => {
2299
+ const written = [];
2300
+ for (const subagentPackage of packages) {
2301
+ for (const file of subagentPackage.files) {
2302
+ const absPath = resolveInsideBasePath(basePath, file.relativePath);
2303
+ if (existsSync5(absPath)) {
2304
+ rmSync5(absPath, { recursive: true, force: true });
2305
+ }
2306
+ mkdirSync7(dirname9(absPath), { recursive: true });
2307
+ writeFileSync7(absPath, file.content.trimEnd() + "\n", "utf-8");
2308
+ written.push(file.relativePath);
2309
+ }
2310
+ }
2311
+ return written;
2312
+ };
2313
+ var removeSubagentFiles = (basePath, relativePaths) => {
2314
+ const removed = [];
2315
+ for (const relativePath of relativePaths) {
2316
+ const absPath = resolveInsideBasePath(basePath, relativePath);
2317
+ if (!existsSync5(absPath)) continue;
2318
+ rmSync5(absPath, { recursive: true, force: true });
2319
+ removed.push(relativePath);
2320
+ }
2321
+ return removed;
2322
+ };
2323
+
2324
+ // src/lib/subagent-state.ts
2325
+ var resolveRequestedSubagentTools = (params) => {
2326
+ if (params.requested === void 0 || params.requested.length === 0) {
2327
+ return [...params.supported];
2328
+ }
2329
+ const supportedSet = new Set(params.supported);
2330
+ const invalid = params.requested.filter((tool) => !supportedSet.has(tool));
2331
+ if (invalid.length > 0) {
2332
+ throw new Error(`Unsupported tools requested: ${invalid.join(", ")}`);
2333
+ }
2334
+ return [...params.requested];
2335
+ };
2336
+ var TOOL_ORDER3 = [SKILL_TOOL.CLAUDE_CODE, SKILL_TOOL.CODEX, SKILL_TOOL.GEMINI];
2337
+ var mergeSubagentTools = (params) => {
2338
+ const merged = /* @__PURE__ */ new Set([...params.existing ?? [], ...params.requested]);
2339
+ return TOOL_ORDER3.filter((tool) => merged.has(tool));
2340
+ };
2341
+ var upsertInstalledSubagent = (installedSubagents, nextSubagent) => {
2342
+ const remaining = installedSubagents.filter((subagent) => subagent.id !== nextSubagent.id);
2343
+ return [...remaining, nextSubagent];
2344
+ };
2345
+ var removeInstalledSubagent = (installedSubagents, subagentId) => installedSubagents.filter((subagent) => subagent.id !== subagentId);
2346
+ var findInstalledSubagent = (installedSubagents, subagentId) => installedSubagents.find((subagent) => subagent.id === subagentId);
2347
+ var resolveInstalledSubagentPaths = (installedSubagent) => installedSubagent.tools.map((tool) => buildSubagentRelativePath(installedSubagent.id, tool));
2348
+
2349
+ // src/commands/subagent.ts
2350
+ var loadCompilerInputs2 = () => {
2351
+ return {
2352
+ allSubagents: loadAllSubagents(resolveSubagentsDir()),
2353
+ cliVersion: getCliVersion()
2354
+ };
2355
+ };
2356
+ var resolveSubagentById = (subagents, subagentId) => {
2357
+ const subagent = subagents.find((candidate) => candidate.id === subagentId);
2358
+ if (!subagent) {
2359
+ throw new Error(`Unknown subagent: ${subagentId}`);
2360
+ }
2361
+ return subagent;
2362
+ };
2363
+ var writeUserSubagentState = (params) => {
2364
+ const manifestPath = resolveSubagentManifestPath(params.basePath);
2365
+ const previous = readSubagentManifest(manifestPath);
2366
+ const subagents = params.removeSubagentId ? removeInstalledSubagent(previous?.subagents ?? [], params.removeSubagentId) : params.nextSubagent ? upsertInstalledSubagent(previous?.subagents ?? [], params.nextSubagent) : previous?.subagents ?? [];
2367
+ if (subagents.length === 0) {
2368
+ rmSync6(manifestPath, { force: true });
2369
+ return;
2370
+ }
2371
+ writeSubagentManifest(manifestPath, {
2372
+ subagents,
2373
+ cliVersion: params.cliVersion,
2374
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
2375
+ });
2376
+ };
2377
+ var readInstalledSubagents = (basePath) => readSubagentManifest(resolveSubagentManifestPath(basePath))?.subagents ?? [];
2378
+ var warnMissingSkills = (requiredSkills) => {
2379
+ const missing = requiredSkills.filter((skill) => !existsSync6(skill.path));
2380
+ if (missing.length === 0) {
2381
+ return;
2382
+ }
2383
+ p8.log.warn(
2384
+ [
2385
+ "\uD544\uC694\uD55C skill\uC774 \uC544\uC9C1 \uC124\uCE58\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. subagent \uC124\uCE58\uB294 \uACC4\uC18D \uC9C4\uD589\uD569\uB2C8\uB2E4.",
2386
+ ...missing.map((skill) => `- ${skill.tool}:${skill.skillName} (${skill.path})`)
2387
+ ].join("\n")
2388
+ );
2389
+ };
2390
+ var installSubagent = (params) => {
2391
+ const installedSubagents = readInstalledSubagents(params.basePath);
2392
+ const existingInstalledSubagent = findInstalledSubagent(installedSubagents, params.subagent.id);
2393
+ const nextRequestedTools = mergeSubagentTools({
2394
+ existing: existingInstalledSubagent?.tools,
2395
+ requested: params.requestedTools
2396
+ });
2397
+ const { packages, installedSubagent, requiredSkills } = buildSubagentInstallPlan({
2398
+ subagent: params.subagent,
2399
+ requestedTools: nextRequestedTools,
2400
+ userBasePath: params.basePath
2401
+ });
2402
+ installSubagentPackages(params.basePath, packages);
2403
+ warnMissingSkills(requiredSkills);
2404
+ writeUserSubagentState({
2405
+ basePath: params.basePath,
2406
+ cliVersion: params.cliVersion,
2407
+ nextSubagent: installedSubagent
2408
+ });
2409
+ return installedSubagent;
2410
+ };
2411
+ var subagentListCommand = async () => {
2412
+ const basePath = resolveUserBasePath();
2413
+ const { allSubagents } = loadCompilerInputs2();
2414
+ const installedSubagents = readInstalledSubagents(basePath);
2415
+ p8.intro("ai-ops subagent list");
2416
+ const lines = allSubagents.map((subagent) => {
2417
+ const installed = findInstalledSubagent(installedSubagents, subagent.id);
2418
+ const suffix = installed ? `installed for ${installed.tools.join(", ")}` : "not installed";
2419
+ return `- ${subagent.id} - ${suffix}`;
2420
+ });
2421
+ p8.log.info(lines.join("\n"));
2422
+ p8.outro("ai-ops subagent list \uC644\uB8CC");
2423
+ };
2424
+ var subagentInstallCommand = async (subagentId, opts) => {
2425
+ const basePath = resolveUserBasePath();
2426
+ const { allSubagents, cliVersion } = loadCompilerInputs2();
2427
+ const subagent = resolveSubagentById(allSubagents, subagentId);
2428
+ const requestedTools = resolveRequestedSubagentTools({ requested: opts.tool, supported: subagent.supported_tools });
2429
+ p8.intro(`ai-ops subagent install ${subagentId}`);
2430
+ const installedSubagent = installSubagent({
2431
+ subagent,
2432
+ requestedTools,
2433
+ basePath,
2434
+ cliVersion
2435
+ });
2436
+ p8.log.success(`\uC124\uCE58 \uC644\uB8CC: ${installedSubagent.id} (${installedSubagent.installed_paths.join(", ")})`);
2437
+ p8.outro("ai-ops subagent install \uC644\uB8CC");
2438
+ };
2439
+ var subagentDiffCommand = async (subagentId) => {
2440
+ const basePath = resolveUserBasePath();
2441
+ const { allSubagents } = loadCompilerInputs2();
2442
+ const installedSubagents = readInstalledSubagents(basePath);
2443
+ const targets = subagentId ? installedSubagents.filter((subagent) => subagent.id === subagentId) : installedSubagents;
2444
+ p8.intro("ai-ops subagent diff");
2445
+ if (targets.length === 0) {
2446
+ p8.log.warn("\uBE44\uAD50\uD560 \uC124\uCE58\uB41C subagent\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
2447
+ p8.outro("ai-ops subagent diff \uC644\uB8CC");
2448
+ return;
2449
+ }
2450
+ const lines = targets.map((installedSubagent) => {
2451
+ const subagent = resolveSubagentById(allSubagents, installedSubagent.id);
2452
+ const { installedSubagent: next } = buildSubagentInstallPlan({
2453
+ subagent,
2454
+ requestedTools: installedSubagent.tools,
2455
+ userBasePath: basePath
2456
+ });
2457
+ const changed = next.sourceHash !== installedSubagent.sourceHash;
2458
+ return `- ${installedSubagent.id}: ${changed ? "changed" : "up-to-date"} (${installedSubagent.sourceHash} -> ${next.sourceHash})`;
2459
+ });
2460
+ p8.log.info(lines.join("\n"));
2461
+ p8.outro("ai-ops subagent diff \uC644\uB8CC");
2462
+ };
2463
+ var subagentUpdateCommand = async (subagentId) => {
2464
+ const basePath = resolveUserBasePath();
2465
+ const { allSubagents, cliVersion } = loadCompilerInputs2();
2466
+ const installedSubagents = readInstalledSubagents(basePath);
2467
+ const targets = subagentId ? installedSubagents.filter((subagent) => subagent.id === subagentId) : installedSubagents;
2468
+ p8.intro("ai-ops subagent update");
2469
+ if (targets.length === 0) {
2470
+ p8.log.warn("\uAC31\uC2E0\uD560 \uC124\uCE58\uB41C subagent\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
2471
+ p8.outro("ai-ops subagent update \uC644\uB8CC");
2472
+ return;
2473
+ }
2474
+ const nextInstalledSubagents = targets.map((installedSubagent) => {
2475
+ const subagent = resolveSubagentById(allSubagents, installedSubagent.id);
2476
+ const { packages, installedSubagent: next, requiredSkills } = buildSubagentInstallPlan({
2477
+ subagent,
2478
+ requestedTools: installedSubagent.tools,
2479
+ userBasePath: basePath
2480
+ });
2481
+ installSubagentPackages(basePath, packages);
2482
+ warnMissingSkills(requiredSkills);
2483
+ return next;
2484
+ });
2485
+ const manifestPath = resolveSubagentManifestPath(basePath);
2486
+ const previous = readSubagentManifest(manifestPath);
2487
+ const nextSubagentIds = new Set(nextInstalledSubagents.map((subagent) => subagent.id));
2488
+ const untouched = (previous?.subagents ?? []).filter((subagent) => !nextSubagentIds.has(subagent.id));
2489
+ writeSubagentManifest(manifestPath, {
2490
+ subagents: [...untouched, ...nextInstalledSubagents],
2491
+ cliVersion,
2492
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
2493
+ });
2494
+ p8.log.success(`\uAC31\uC2E0 \uC644\uB8CC: ${nextInstalledSubagents.map((subagent) => subagent.id).join(", ")}`);
2495
+ p8.outro("ai-ops subagent update \uC644\uB8CC");
2496
+ };
2497
+ var subagentUninstallCommand = async (subagentId) => {
2498
+ const basePath = resolveUserBasePath();
2499
+ const cliVersion = getCliVersion();
2500
+ const installedSubagents = readInstalledSubagents(basePath);
2501
+ const installedSubagent = findInstalledSubagent(installedSubagents, subagentId);
2502
+ p8.intro(`ai-ops subagent uninstall ${subagentId}`);
2503
+ if (!installedSubagent) {
2504
+ p8.log.warn("\uC124\uCE58\uB41C subagent\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
2505
+ p8.outro("ai-ops subagent uninstall \uC644\uB8CC");
2506
+ return;
2507
+ }
2508
+ const removed = removeSubagentFiles(basePath, resolveInstalledSubagentPaths(installedSubagent));
2509
+ writeUserSubagentState({
2510
+ basePath,
2511
+ cliVersion,
2512
+ removeSubagentId: subagentId
2513
+ });
2514
+ p8.log.success(`\uC81C\uAC70 \uC644\uB8CC: ${removed.join(", ")}`);
2515
+ p8.outro("ai-ops subagent uninstall \uC644\uB8CC");
2516
+ };
2517
+
2518
+ // src/commands/pack.ts
2519
+ import * as p9 from "@clack/prompts";
2520
+ var readManifestForPackCommand = (basePath) => {
2521
+ try {
2522
+ return readProjectLayerManifest(basePath);
2523
+ } catch (error) {
2524
+ const message = error instanceof Error ? error.message : "unknown error";
2525
+ p9.log.error(`.ai-ops/manifest.json \uD30C\uC2F1 \uC2E4\uD328: ${message}`);
2526
+ process.exitCode = 1;
2527
+ return null;
2528
+ }
2529
+ };
2530
+ var reportPackError = (error) => {
2531
+ const message = error instanceof Error ? error.message : "unknown error";
2532
+ p9.log.error(message);
2533
+ process.exitCode = 1;
2534
+ };
2535
+ var packListCommand = async () => {
2536
+ const basePath = resolveBasePath();
2537
+ const packs = loadAllPacks(resolvePacksDir());
2538
+ const manifest = readManifestForPackCommand(basePath);
2539
+ const installedPackIds = new Set(manifest?.packs.map((pack) => pack.id) ?? []);
2540
+ p9.intro("ai-ops pack list");
2541
+ p9.log.info(
2542
+ packs.map((pack) => {
2543
+ const suffix = installedPackIds.has(pack.id) ? "installed" : "not installed";
2544
+ return `- ${pack.id} - ${suffix}`;
2545
+ }).join("\n")
2546
+ );
2547
+ p9.outro("ai-ops pack list \uC644\uB8CC");
2548
+ };
2549
+ var packInstallCommand = async (packId) => {
2550
+ const basePath = resolveBasePath();
2551
+ p9.intro(`ai-ops pack install ${packId}`);
2552
+ try {
2553
+ const result = installProjectLayerPack({ basePath, packId, packsDir: resolvePacksDir() });
2554
+ p9.log.success(`pack \uC124\uCE58 \uC644\uB8CC: ${packId}`);
2555
+ if (result.written.length > 0) {
2556
+ p9.log.info(`\uC0DD\uC131:
2557
+ ${result.written.map((file) => ` ${file}`).join("\n")}`);
2558
+ }
2559
+ if (result.refreshed.length > 0) {
2560
+ p9.log.info(`\uAC31\uC2E0:
2561
+ ${result.refreshed.map((file) => ` ${file}`).join("\n")}`);
2562
+ }
2563
+ if (result.preserved.length > 0) {
2564
+ p9.log.info(`\uBCF4\uC874:
2565
+ ${result.preserved.map((file) => ` ${file}`).join("\n")}`);
2566
+ }
2567
+ } catch (error) {
2568
+ reportPackError(error);
2569
+ }
2570
+ p9.outro("ai-ops pack install \uC644\uB8CC");
2571
+ };
2572
+ var packDiffCommand = async (packId) => {
2573
+ const basePath = resolveBasePath();
2574
+ p9.intro("ai-ops pack diff");
2575
+ try {
2576
+ const report = diffProjectLayerPack({ basePath, packId, packsDir: resolvePacksDir() });
2577
+ if (report.issues.length === 0) {
2578
+ p9.log.success("\uBCC0\uACBD \uC0AC\uD56D \uC5C6\uC74C. pack\uC774 \uCD5C\uC2E0 \uC0C1\uD0DC\uC785\uB2C8\uB2E4.");
2579
+ p9.outro("ai-ops pack diff \uC644\uB8CC");
2580
+ return;
2581
+ }
2582
+ for (const item of report.issues) {
2583
+ const line = `[${item.code}] ${item.message}`;
2584
+ if (item.level === "error") {
2585
+ p9.log.error(line);
2586
+ } else {
2587
+ p9.log.warn(line);
2588
+ }
2589
+ }
2590
+ if (report.issues.some((item) => item.level === "error")) {
2591
+ process.exitCode = 1;
2592
+ }
2593
+ } catch (error) {
2594
+ reportPackError(error);
2595
+ }
2596
+ p9.outro("ai-ops pack diff \uC644\uB8CC");
2597
+ };
2598
+ var packUpdateCommand = async (packId) => {
2599
+ const basePath = resolveBasePath();
2600
+ p9.intro("ai-ops pack update");
2601
+ try {
2602
+ const manifest = readManifestForPackCommand(basePath);
2603
+ if (!manifest) {
2604
+ p9.log.error(".ai-ops/manifest.json\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 ai-ops init\uC744 \uC2E4\uD589\uD558\uC138\uC694.");
2605
+ process.exitCode = 1;
2606
+ p9.outro("ai-ops pack update \uC644\uB8CC");
2607
+ return;
2608
+ }
2609
+ const targetPackIds = packId ? [packId] : manifest.packs.map((pack) => pack.id);
2610
+ if (targetPackIds.length === 0) {
2611
+ p9.log.warn("\uAC31\uC2E0\uD560 \uC124\uCE58\uB41C pack\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
2612
+ p9.outro("ai-ops pack update \uC644\uB8CC");
2613
+ return;
2614
+ }
2615
+ for (const targetPackId of targetPackIds) {
2616
+ const result = updateProjectLayerPack({ basePath, packId: targetPackId, packsDir: resolvePacksDir() });
2617
+ p9.log.success(`pack \uAC31\uC2E0 \uC644\uB8CC: ${targetPackId}`);
2618
+ if (result.refreshed.length > 0) {
2619
+ p9.log.info(`\uAC31\uC2E0:
2620
+ ${result.refreshed.map((file) => ` ${file}`).join("\n")}`);
2621
+ }
2622
+ if (result.preserved.length > 0) {
2623
+ p9.log.info(`\uBCF4\uC874:
2624
+ ${result.preserved.map((file) => ` ${file}`).join("\n")}`);
2625
+ }
2626
+ }
2627
+ } catch (error) {
2628
+ reportPackError(error);
2258
2629
  }
2259
- const actions = buildSpecInitPlan();
2260
- for (const action of actions) {
2261
- const dest = join13(process.cwd(), action.relativePath);
2262
- mkdirSync6(dirname8(dest), { recursive: true });
2263
- writeFileSync8(dest, action.content, "utf-8");
2264
- p8.log.success(`\uC0DD\uC131: ${action.relativePath}`);
2630
+ p9.outro("ai-ops pack update \uC644\uB8CC");
2631
+ };
2632
+ var packUninstallCommand = async (packId) => {
2633
+ const basePath = resolveBasePath();
2634
+ p9.intro(`ai-ops pack uninstall ${packId}`);
2635
+ try {
2636
+ const result = uninstallProjectLayerPack({ basePath, packId });
2637
+ p9.log.success(`pack \uC81C\uAC70 \uC644\uB8CC: ${packId}`);
2638
+ if (result.deleted.length > 0) {
2639
+ p9.log.info(`\uC0AD\uC81C:
2640
+ ${result.deleted.map((file) => ` ${file}`).join("\n")}`);
2641
+ }
2642
+ if (result.preserved.length > 0) {
2643
+ p9.log.info(`\uBCF4\uC874:
2644
+ ${result.preserved.map((file) => ` ${file}`).join("\n")}`);
2645
+ }
2646
+ } catch (error) {
2647
+ reportPackError(error);
2265
2648
  }
2266
- p8.outro("ai-ops spec init \uC644\uB8CC");
2649
+ p9.outro("ai-ops pack uninstall \uC644\uB8CC");
2267
2650
  };
2268
2651
 
2269
2652
  // src/bin/index.ts
2270
2653
  var program = new Command();
2271
- program.name("ai-ops").description("AI \uC5D0\uC774\uC804\uD2B8 \uADDC\uCE59 \uC2A4\uCE90\uD3F4\uB354").version("0.1.0");
2272
- program.command("init").description("AI \uADDC\uCE59 \uCD08\uAE30 \uC124\uCE58").action(() => initCommand());
2273
- 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));
2274
- program.command("diff").description("\uC124\uCE58\uB41C \uADDC\uCE59\uACFC \uCD5C\uC2E0 \uC18C\uC2A4 \uBE44\uAD50").action(() => diffCommand());
2275
- program.command("uninstall").description("\uC124\uCE58\uB41C \uADDC\uCE59 \uD30C\uC77C \uBC0F manifest \uC81C\uAC70").action(() => uninstallCommand());
2654
+ program.name("ai-ops").description("AI agent operating layer manager").version("0.1.0");
2655
+ program.command("init").description("project operating layer \uCD08\uAE30 \uC124\uCE58").option("--tool <tool...>", "\uB300\uC0C1 \uB3C4\uAD6C adapter \uC9C0\uC815 (codex|gemini|claude-code)").action((opts) => initCommand(opts));
2656
+ program.command("update").description("project operating layer \uAC31\uC2E0").option("--force", "\uBCC0\uACBD \uC5C6\uC5B4\uB3C4 \uAC15\uC81C \uC7AC\uC124\uCE58", false).action((opts) => updateCommand(opts));
2657
+ program.command("diff").description("project operating layer drift \uBE44\uAD50").action(() => diffCommand());
2658
+ program.command("audit").description("project operating layer \uC0C1\uD0DC \uAC80\uC0AC").action(() => auditCommand());
2659
+ program.command("uninstall").description("project operating layer \uC81C\uAC70").option("--yes", "\uD655\uC778 \uD504\uB86C\uD504\uD2B8 \uC5C6\uC774 \uC81C\uAC70", false).action((opts) => uninstallCommand(opts));
2276
2660
  var skillCommand = program.command("skill").description("\uC5D0\uC774\uC804\uD2B8 skill \uC124\uCE58/\uC870\uD68C/\uAC31\uC2E0");
2277
- 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");
2278
- applySkillScopeOptions(skillCommand.command("list").description("\uC0AC\uC6A9 \uAC00\uB2A5\uD55C skill \uBAA9\uB85D")).action(
2279
- (opts) => skillListCommand(opts)
2280
- );
2281
- applySkillScopeOptions(skillCommand.command("install <skillId>").description("skill \uC124\uCE58")).action(
2661
+ var applySkillInstallOptions = (command) => command.option("--tool <tool...>", "\uB300\uC0C1 \uB3C4\uAD6C \uC9C0\uC815");
2662
+ skillCommand.command("list").description("\uC0AC\uC6A9 \uAC00\uB2A5\uD55C skill \uBAA9\uB85D").action(() => skillListCommand());
2663
+ applySkillInstallOptions(skillCommand.command("install <skillId>").description("skill \uC124\uCE58")).action(
2282
2664
  (skillId, opts) => skillInstallCommand(skillId, opts)
2283
2665
  );
2284
- applySkillScopeOptions(skillCommand.command("diff [skillId]").description("skill \uBCC0\uACBD \uBE44\uAD50")).action(
2285
- (skillId, opts) => skillDiffCommand(skillId, opts)
2286
- );
2287
- applySkillScopeOptions(skillCommand.command("update [skillId]").description("skill \uAC31\uC2E0")).action(
2288
- (skillId, opts) => skillUpdateCommand(skillId, opts)
2289
- );
2290
- applySkillScopeOptions(skillCommand.command("uninstall <skillId>").description("skill \uC81C\uAC70")).action(
2291
- (skillId, opts) => skillUninstallCommand(skillId, opts)
2666
+ skillCommand.command("diff [skillId]").description("skill \uBCC0\uACBD \uBE44\uAD50").action((skillId) => skillDiffCommand(skillId));
2667
+ skillCommand.command("update [skillId]").description("skill \uAC31\uC2E0").action((skillId) => skillUpdateCommand(skillId));
2668
+ skillCommand.command("uninstall <skillId>").description("skill \uC81C\uAC70").action((skillId) => skillUninstallCommand(skillId));
2669
+ var subagentCommand = program.command("subagent").description("\uC5D0\uC774\uC804\uD2B8 subagent \uC124\uCE58/\uC870\uD68C/\uAC31\uC2E0");
2670
+ var applySubagentInstallOptions = (command) => command.option("--tool <tool...>", "\uB300\uC0C1 \uB3C4\uAD6C \uC9C0\uC815");
2671
+ subagentCommand.command("list").description("\uC0AC\uC6A9 \uAC00\uB2A5\uD55C subagent \uBAA9\uB85D").action(() => subagentListCommand());
2672
+ applySubagentInstallOptions(subagentCommand.command("install <subagentId>").description("subagent \uC124\uCE58")).action(
2673
+ (subagentId, opts) => subagentInstallCommand(subagentId, opts)
2292
2674
  );
2293
- var specCommand = program.command("spec").description("spec \uD30C\uC774\uD504\uB77C\uC778 \uAD00\uB9AC");
2294
- specCommand.command("init").description("specs/ \uB514\uB809\uD1A0\uB9AC \uAD6C\uC870 \uCD08\uAE30\uD654").option("--force", "\uC774\uBBF8 \uC874\uC7AC\uD574\uB3C4 \uAC15\uC81C \uC7AC\uC0DD\uC131", false).action((opts) => specInitCommand(opts));
2675
+ subagentCommand.command("diff [subagentId]").description("subagent \uBCC0\uACBD \uBE44\uAD50").action((subagentId) => subagentDiffCommand(subagentId));
2676
+ subagentCommand.command("update [subagentId]").description("subagent \uAC31\uC2E0").action((subagentId) => subagentUpdateCommand(subagentId));
2677
+ subagentCommand.command("uninstall <subagentId>").description("subagent \uC81C\uAC70").action((subagentId) => subagentUninstallCommand(subagentId));
2678
+ var packCommand = program.command("pack").description("optional project operating layer pack \uC124\uCE58/\uC870\uD68C/\uAC31\uC2E0");
2679
+ packCommand.command("list").description("\uC0AC\uC6A9 \uAC00\uB2A5\uD55C pack \uBAA9\uB85D").action(() => packListCommand());
2680
+ packCommand.command("install <packId>").description("pack \uC124\uCE58").action((packId) => packInstallCommand(packId));
2681
+ packCommand.command("diff [packId]").description("pack \uBCC0\uACBD \uBE44\uAD50").action((packId) => packDiffCommand(packId));
2682
+ packCommand.command("update [packId]").description("pack \uAC31\uC2E0").action((packId) => packUpdateCommand(packId));
2683
+ packCommand.command("uninstall <packId>").description("pack \uC81C\uAC70").action((packId) => packUninstallCommand(packId));
2295
2684
  program.parse();
2296
2685
  //# sourceMappingURL=index.js.map