ai-ops-cli 1.1.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/index.js CHANGED
@@ -6,42 +6,8 @@ import { Command } from "commander";
6
6
  // src/commands/init.ts
7
7
  import * as p2 from "@clack/prompts";
8
8
 
9
- // src/core/schemas/rule.schema.ts
10
- import { z } from "zod";
11
- var DecisionTableEntrySchema = z.object({
12
- when: z.string().min(1),
13
- then: z.string().min(1),
14
- /** 조건부 규칙에서 회피해야 할 패턴 */
15
- avoid: z.string().min(1).optional()
16
- }).strict();
17
- var RuleContentSchema = z.object({
18
- /** Anti-pattern 규칙 ('하지 마라'). guidelines보다 항상 상단 렌더링 */
19
- constraints: z.array(z.string().min(1)).default([]),
20
- /** Positive 규칙 ('해라') */
21
- guidelines: z.array(z.string().min(1)),
22
- /** 조건부 규칙. when→then→avoid 구조 */
23
- decision_table: z.array(DecisionTableEntrySchema).optional()
24
- }).strict();
25
- var RuleSchema = z.object({
26
- id: z.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "id must be kebab-case"),
27
- category: z.string().min(1),
28
- tags: z.array(z.string().min(1)),
29
- /** 0-100. 높을수록 생성 파일 상단 배치 (U-shaped attention 최적화) */
30
- priority: z.number().int().min(0).max(100),
31
- supported_tools: z.array(z.string().min(1)).min(1).default(["claude-code", "codex", "gemini"]),
32
- content: RuleContentSchema
33
- }).strict();
34
-
35
- // src/core/schemas/preset.schema.ts
36
- import { z as z2 } from "zod";
37
- var PresetSchema = z2.object({
38
- id: z2.string().regex(/^[a-z][a-z0-9-]*$/).min(1),
39
- description: z2.string().min(1),
40
- rules: z2.array(z2.string().min(1)).min(1)
41
- }).strict();
42
-
43
9
  // src/core/schemas/skill.schema.ts
44
- import { z as z3 } from "zod";
10
+ import { z } from "zod";
45
11
  var SKILL_KIND = {
46
12
  REFERENCE: "reference",
47
13
  TASK: "task"
@@ -51,69 +17,68 @@ var SKILL_TOOL = {
51
17
  CODEX: "codex",
52
18
  GEMINI: "gemini"
53
19
  };
54
- var SkillKindSchema = z3.union([z3.literal(SKILL_KIND.REFERENCE), z3.literal(SKILL_KIND.TASK)]);
55
- var SkillToolSchema = z3.union([
56
- z3.literal(SKILL_TOOL.CLAUDE_CODE),
57
- z3.literal(SKILL_TOOL.CODEX),
58
- z3.literal(SKILL_TOOL.GEMINI)
20
+ var SkillKindSchema = z.union([z.literal(SKILL_KIND.REFERENCE), z.literal(SKILL_KIND.TASK)]);
21
+ var SkillToolSchema = z.union([
22
+ z.literal(SKILL_TOOL.CLAUDE_CODE),
23
+ z.literal(SKILL_TOOL.CODEX),
24
+ z.literal(SKILL_TOOL.GEMINI)
59
25
  ]);
60
- var SkillFileSchema = z3.object({
61
- path: z3.string().min(1),
62
- content: z3.string()
26
+ var SkillFileSchema = z.object({
27
+ path: z.string().min(1),
28
+ content: z.string()
63
29
  }).strict();
64
- var SkillFrontmatterSchema = z3.object({
65
- name: z3.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "name must be kebab-case"),
66
- description: z3.string().min(1)
30
+ var SkillFrontmatterSchema = z.object({
31
+ name: z.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "name must be kebab-case"),
32
+ description: z.string().min(1)
67
33
  }).passthrough();
68
- var InstalledSkillSchema = z3.object({
69
- id: z3.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "id must be kebab-case"),
34
+ var InstalledSkillSchema = z.object({
35
+ id: z.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "id must be kebab-case"),
70
36
  kind: SkillKindSchema,
71
- tools: z3.array(SkillToolSchema).min(1),
72
- installed_paths: z3.array(z3.string().min(1)).min(1),
73
- sourceHash: z3.string().regex(/^[a-f0-9]{6}$/, "sourceHash must be 6 lowercase hex chars")
37
+ tools: z.array(SkillToolSchema).min(1),
38
+ installed_paths: z.array(z.string().min(1)).min(1),
39
+ sourceHash: z.string().regex(/^[a-f0-9]{6}$/, "sourceHash must be 6 lowercase hex chars")
74
40
  }).strip();
75
41
 
76
42
  // src/core/schemas/skill-catalog.schema.ts
77
- import { z as z4 } from "zod";
78
- var SkillIdSchema = z4.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "id must be kebab-case");
79
- var SkillCatalogPathSchema = z4.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*(?:\/[a-z0-9]+(?:-[a-z0-9]+)*)+$/, "source_path must be relative kebab-case path");
80
- var SkillCatalogEntrySchema = z4.object({
43
+ import { z as z2 } from "zod";
44
+ var SkillIdSchema = z2.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "id must be kebab-case");
45
+ var SkillCatalogPathSchema = z2.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*(?:\/[a-z0-9]+(?:-[a-z0-9]+)*)+$/, "source_path must be relative kebab-case path");
46
+ var SkillCatalogEntrySchema = z2.object({
81
47
  id: SkillIdSchema,
82
48
  kind: SkillKindSchema,
83
- supported_tools: z4.array(SkillToolSchema).min(1),
84
- groups: z4.array(z4.string().min(1)),
85
- included_in_presets: z4.array(z4.string().min(1)),
49
+ supported_tools: z2.array(SkillToolSchema).min(1),
50
+ groups: z2.array(z2.string().min(1)),
86
51
  source_path: SkillCatalogPathSchema
87
52
  }).strict().superRefine((entry, ctx) => {
88
53
  const expectedPrefix = entry.kind === "reference" ? "reference-skills/" : "task-skills/";
89
54
  if (!entry.source_path.startsWith(expectedPrefix)) {
90
55
  ctx.addIssue({
91
- code: z4.ZodIssueCode.custom,
56
+ code: z2.ZodIssueCode.custom,
92
57
  path: ["source_path"],
93
58
  message: `source_path must start with ${expectedPrefix}`
94
59
  });
95
60
  }
96
61
  });
97
- var SkillCatalogSchema = z4.object({
98
- skills: z4.array(SkillCatalogEntrySchema)
62
+ var SkillCatalogSchema = z2.object({
63
+ skills: z2.array(SkillCatalogEntrySchema)
99
64
  }).strict();
100
65
 
101
66
  // src/core/schemas/skill-registry.schema.ts
102
- import { z as z5 } from "zod";
103
- var SkillRegistrySchema = z5.object({
104
- skills: z5.array(InstalledSkillSchema),
105
- cliVersion: z5.string().optional(),
106
- generatedAt: z5.string().datetime({ offset: true })
67
+ import { z as z3 } from "zod";
68
+ var SkillRegistrySchema = z3.object({
69
+ skills: z3.array(InstalledSkillSchema),
70
+ cliVersion: z3.string().optional(),
71
+ generatedAt: z3.string().datetime({ offset: true })
107
72
  }).strict();
108
73
 
109
74
  // src/core/schemas/subagent.schema.ts
110
- import { z as z7 } from "zod";
75
+ import { z as z5 } from "zod";
111
76
 
112
77
  // src/core/schemas/project-layer.schema.ts
113
- import { z as z6 } from "zod";
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");
78
+ import { z as z4 } from "zod";
79
+ var ProjectLayerToolSchema = z4.enum(["claude-code", "codex", "gemini"]);
80
+ var ProjectLayerDocumentStatusSchema = z4.enum(["Active", "Reserved", "Draft", "Archived"]);
81
+ var ShortHashSchema = z4.string().regex(/^[a-f0-9]{6}$/, "hash must be 6 lowercase hex chars");
117
82
  var isSafeProjectLayerPath = (value) => {
118
83
  if (value.length === 0) return false;
119
84
  if (value.includes("\0")) return false;
@@ -123,55 +88,55 @@ var isSafeProjectLayerPath = (value) => {
123
88
  const segments = value.split("/");
124
89
  return segments.every((segment) => segment.length > 0 && segment !== "." && segment !== "..");
125
90
  };
126
- var ProjectLayerPathSchema = z6.string().min(1).refine(isSafeProjectLayerPath, "path must be a safe project-relative path");
127
- var ProjectLayerFrontmatterSchema = z6.object({
91
+ var ProjectLayerPathSchema = z4.string().min(1).refine(isSafeProjectLayerPath, "path must be a safe project-relative path");
92
+ var ProjectLayerFrontmatterSchema = z4.object({
128
93
  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)
94
+ layer: z4.string().min(1),
95
+ owner: z4.string().min(1),
96
+ read_when: z4.array(z4.string().min(1)).min(1),
97
+ update_when: z4.array(z4.string().min(1)).min(1)
133
98
  }).strict();
134
- var ProjectLayerManagedFileSchema = z6.object({
99
+ var ProjectLayerManagedFileSchema = z4.object({
135
100
  path: ProjectLayerPathSchema,
136
101
  sourceHash: ShortHashSchema
137
102
  }).strict();
138
- var ProjectLayerProjectFileSchema = z6.object({
103
+ var ProjectLayerProjectFileSchema = z4.object({
139
104
  path: ProjectLayerPathSchema,
140
105
  templateHash: ShortHashSchema,
141
- created: z6.boolean()
106
+ created: z4.boolean()
142
107
  }).strict();
143
- var ProjectLayerPackFileRecordSchema = z6.object({
108
+ var ProjectLayerPackFileRecordSchema = z4.object({
144
109
  path: ProjectLayerPathSchema,
145
110
  sourceHash: ShortHashSchema
146
111
  }).strict();
147
- var ProjectLayerPackRecordSchema = z6.object({
148
- id: z6.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "id must be kebab-case"),
112
+ var ProjectLayerPackRecordSchema = z4.object({
113
+ id: z4.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "id must be kebab-case"),
149
114
  sourceHash: ShortHashSchema,
150
- documents: z6.array(ProjectLayerPackFileRecordSchema),
151
- files: z6.array(ProjectLayerPackFileRecordSchema),
152
- installedAt: z6.string().datetime({ offset: true })
115
+ documents: z4.array(ProjectLayerPackFileRecordSchema),
116
+ files: z4.array(ProjectLayerPackFileRecordSchema),
117
+ installedAt: z4.string().datetime({ offset: true })
153
118
  }).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()),
119
+ var ProjectLayerManifestSchema = z4.object({
120
+ schemaVersion: z4.literal(1),
121
+ kind: z4.literal("project-operating-layer"),
122
+ tools: z4.array(ProjectLayerToolSchema).min(1),
123
+ managed_files: z4.array(ProjectLayerManagedFileSchema),
124
+ project_files: z4.array(ProjectLayerProjectFileSchema),
125
+ packs: z4.array(ProjectLayerPackRecordSchema).default([]),
126
+ settings: z4.record(z4.unknown()),
162
127
  sourceHash: ShortHashSchema,
163
- cliVersion: z6.string().min(1),
164
- generatedAt: z6.string().datetime({ offset: true })
128
+ cliVersion: z4.string().min(1),
129
+ generatedAt: z4.string().datetime({ offset: true })
165
130
  }).strict();
166
131
  var ProjectLayerContextDocumentSchema = ProjectLayerFrontmatterSchema.extend({
167
132
  path: ProjectLayerPathSchema,
168
133
  contentHash: ShortHashSchema
169
134
  }).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 })
135
+ var ProjectLayerContextIndexSchema = z4.object({
136
+ schemaVersion: z4.literal(1),
137
+ kind: z4.literal("context-layer-index"),
138
+ documents: z4.array(ProjectLayerContextDocumentSchema),
139
+ generatedAt: z4.string().datetime({ offset: true })
175
140
  }).strict();
176
141
 
177
142
  // src/core/subagent-paths.ts
@@ -196,29 +161,29 @@ var buildSubagentRelativePath = (subagentId, toolId) => {
196
161
  };
197
162
 
198
163
  // 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({
164
+ var SubagentIdSchema = z5.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "id must be kebab-case");
165
+ var SubagentMarkdownFrontmatterSchema = z5.object({
201
166
  name: SubagentIdSchema,
202
- description: z7.string().min(1)
167
+ description: z5.string().min(1)
203
168
  }).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({
169
+ var TomlValueSchema = z5.union([z5.string(), z5.number(), z5.boolean(), z5.array(z5.string())]);
170
+ var SubagentInstalledPathSchema = z5.string().min(1).refine(isSafeProjectLayerPath, "installed path must be safe relative path");
171
+ var CodexSubagentFrontmatterSchema = z5.object({
207
172
  name: SubagentIdSchema,
208
- description: z7.string().min(1),
209
- skill_names: z7.array(SubagentIdSchema).optional()
173
+ description: z5.string().min(1),
174
+ skill_names: z5.array(SubagentIdSchema).optional()
210
175
  }).catchall(TomlValueSchema);
211
- var InstalledSubagentSchema = z7.object({
176
+ var InstalledSubagentSchema = z5.object({
212
177
  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")
178
+ tools: z5.array(SkillToolSchema).min(1),
179
+ installed_paths: z5.array(SubagentInstalledPathSchema).min(1),
180
+ sourceHash: z5.string().regex(/^[a-f0-9]{6}$/, "sourceHash must be 6 lowercase hex chars")
216
181
  }).strip().superRefine((subagent, ctx) => {
217
182
  const expectedPaths = new Set(subagent.tools.map((tool) => buildSubagentRelativePath(subagent.id, tool)));
218
183
  const installedPaths = new Set(subagent.installed_paths);
219
184
  if (installedPaths.size !== subagent.installed_paths.length) {
220
185
  ctx.addIssue({
221
- code: z7.ZodIssueCode.custom,
186
+ code: z5.ZodIssueCode.custom,
222
187
  path: ["installed_paths"],
223
188
  message: "installed_paths must not contain duplicates"
224
189
  });
@@ -226,7 +191,7 @@ var InstalledSubagentSchema = z7.object({
226
191
  }
227
192
  if (installedPaths.size !== expectedPaths.size) {
228
193
  ctx.addIssue({
229
- code: z7.ZodIssueCode.custom,
194
+ code: z5.ZodIssueCode.custom,
230
195
  path: ["installed_paths"],
231
196
  message: "installed_paths must match id and tools"
232
197
  });
@@ -235,7 +200,7 @@ var InstalledSubagentSchema = z7.object({
235
200
  for (const installedPath of installedPaths) {
236
201
  if (!expectedPaths.has(installedPath)) {
237
202
  ctx.addIssue({
238
- code: z7.ZodIssueCode.custom,
203
+ code: z5.ZodIssueCode.custom,
239
204
  path: ["installed_paths"],
240
205
  message: "installed_paths must match id and tools"
241
206
  });
@@ -245,27 +210,146 @@ var InstalledSubagentSchema = z7.object({
245
210
  });
246
211
 
247
212
  // src/core/schemas/subagent-catalog.schema.ts
248
- import { z as z8 } from "zod";
249
- var SubagentCatalogPathSchema = z8.string().regex(
213
+ import { z as z6 } from "zod";
214
+ var SubagentCatalogPathSchema = z6.string().regex(
250
215
  /^[a-z0-9]+(?:-[a-z0-9]+)*(?:\/[a-z0-9]+(?:-[a-z0-9]+)*)*$/,
251
216
  "source_path must be relative kebab-case path"
252
217
  );
253
- var SubagentCatalogEntrySchema = z8.object({
218
+ var SubagentCatalogEntrySchema = z6.object({
254
219
  id: SubagentIdSchema,
255
- supported_tools: z8.array(SkillToolSchema).min(1),
220
+ supported_tools: z6.array(SkillToolSchema).min(1),
256
221
  source_path: SubagentCatalogPathSchema
257
222
  }).strict();
258
- var SubagentCatalogSchema = z8.object({
259
- subagents: z8.array(SubagentCatalogEntrySchema)
223
+ var SubagentCatalogSchema = z6.object({
224
+ subagents: z6.array(SubagentCatalogEntrySchema)
260
225
  }).strict();
261
226
 
262
227
  // src/core/schemas/subagent-manifest.schema.ts
228
+ import { z as z7 } from "zod";
229
+ var SubagentManifestSchema = z7.object({
230
+ subagents: z7.array(InstalledSubagentSchema),
231
+ cliVersion: z7.string().optional(),
232
+ generatedAt: z7.string().datetime({ offset: true })
233
+ }).strict();
234
+
235
+ // src/core/schemas/integration.schema.ts
236
+ import { z as z8 } from "zod";
237
+ var INTEGRATION_ID = {
238
+ CONTEXT_PROMOTION: "context-promotion",
239
+ PC: "pc"
240
+ };
241
+ var INTEGRATION_COMPONENT_TYPE = {
242
+ SKILL: "skill",
243
+ CODEX_HOOK: "codex-hook",
244
+ RECEIPT_CONFIG: "receipt-config"
245
+ };
246
+ var IntegrationIdSchema = z8.union([z8.literal(INTEGRATION_ID.CONTEXT_PROMOTION), z8.literal(INTEGRATION_ID.PC)]);
247
+ var IntegrationSkillComponentSchema = z8.object({
248
+ type: z8.literal(INTEGRATION_COMPONENT_TYPE.SKILL),
249
+ id: z8.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/),
250
+ tools: z8.array(SkillToolSchema).min(1),
251
+ owned: z8.boolean()
252
+ }).strict();
253
+ var IntegrationCodexHookComponentSchema = z8.object({
254
+ type: z8.literal(INTEGRATION_COMPONENT_TYPE.CODEX_HOOK),
255
+ id: IntegrationIdSchema,
256
+ command: z8.string().min(1),
257
+ owned: z8.boolean()
258
+ }).strict();
259
+ var IntegrationReceiptConfigComponentSchema = z8.object({
260
+ type: z8.literal(INTEGRATION_COMPONENT_TYPE.RECEIPT_CONFIG),
261
+ id: z8.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/),
262
+ storagePath: z8.string().min(1),
263
+ owned: z8.boolean()
264
+ }).strict();
265
+ var IntegrationComponentSchema = z8.union([
266
+ IntegrationSkillComponentSchema,
267
+ IntegrationCodexHookComponentSchema,
268
+ IntegrationReceiptConfigComponentSchema
269
+ ]);
270
+ var InstalledIntegrationSchema = z8.object({
271
+ id: IntegrationIdSchema,
272
+ components: z8.array(IntegrationComponentSchema),
273
+ installedAt: z8.string().min(1),
274
+ updatedAt: z8.string().min(1)
275
+ }).strict();
276
+ var IntegrationManifestSchema = z8.object({
277
+ schemaVersion: z8.literal(1),
278
+ kind: z8.literal("ai-ops-integrations-manifest"),
279
+ integrations: z8.array(InstalledIntegrationSchema),
280
+ cliVersion: z8.string().min(1),
281
+ generatedAt: z8.string().min(1)
282
+ }).strict();
283
+
284
+ // src/core/schemas/integration-catalog.schema.ts
263
285
  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 })
286
+ var ComponentIdSchema = z9.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "component id must be kebab-case");
287
+ var IntegrationCatalogSkillComponentSchema = z9.object({
288
+ type: z9.literal(INTEGRATION_COMPONENT_TYPE.SKILL),
289
+ id: ComponentIdSchema,
290
+ tools: z9.array(z9.literal("codex")).min(1)
291
+ }).strict();
292
+ var IntegrationCatalogCodexHookComponentSchema = z9.object({
293
+ type: z9.literal(INTEGRATION_COMPONENT_TYPE.CODEX_HOOK),
294
+ id: IntegrationIdSchema
268
295
  }).strict();
296
+ var IntegrationCatalogReceiptConfigComponentSchema = z9.object({
297
+ type: z9.literal(INTEGRATION_COMPONENT_TYPE.RECEIPT_CONFIG),
298
+ id: ComponentIdSchema,
299
+ storage_path: z9.string().min(1)
300
+ }).strict();
301
+ var IntegrationCatalogComponentSchema = z9.union([
302
+ IntegrationCatalogSkillComponentSchema,
303
+ IntegrationCatalogCodexHookComponentSchema,
304
+ IntegrationCatalogReceiptConfigComponentSchema
305
+ ]);
306
+ var IntegrationCatalogEntrySchema = z9.object({
307
+ id: IntegrationIdSchema,
308
+ description: z9.string().min(1),
309
+ components: z9.array(IntegrationCatalogComponentSchema).min(1)
310
+ }).strict().superRefine((entry, ctx) => {
311
+ const hasSkill = entry.components.some((component) => component.type === INTEGRATION_COMPONENT_TYPE.SKILL);
312
+ const hasCodexHook = entry.components.some((component) => component.type === INTEGRATION_COMPONENT_TYPE.CODEX_HOOK);
313
+ const hasReceiptConfig = entry.components.some(
314
+ (component) => component.type === INTEGRATION_COMPONENT_TYPE.RECEIPT_CONFIG
315
+ );
316
+ if (!hasSkill) {
317
+ ctx.addIssue({
318
+ code: z9.ZodIssueCode.custom,
319
+ path: ["components"],
320
+ message: `integration must declare a skill component: ${entry.id}`
321
+ });
322
+ }
323
+ if (!hasCodexHook) {
324
+ ctx.addIssue({
325
+ code: z9.ZodIssueCode.custom,
326
+ path: ["components"],
327
+ message: `integration must declare a codex-hook component: ${entry.id}`
328
+ });
329
+ }
330
+ if (!hasReceiptConfig) {
331
+ ctx.addIssue({
332
+ code: z9.ZodIssueCode.custom,
333
+ path: ["components"],
334
+ message: `integration must declare a receipt-config component: ${entry.id}`
335
+ });
336
+ }
337
+ });
338
+ var IntegrationCatalogSchema = z9.object({
339
+ integrations: z9.array(IntegrationCatalogEntrySchema)
340
+ }).strict().superRefine((catalog, ctx) => {
341
+ const seen = /* @__PURE__ */ new Set();
342
+ for (const [index, entry] of catalog.integrations.entries()) {
343
+ if (seen.has(entry.id)) {
344
+ ctx.addIssue({
345
+ code: z9.ZodIssueCode.custom,
346
+ path: ["integrations", index, "id"],
347
+ message: `duplicate integration id: ${entry.id}`
348
+ });
349
+ }
350
+ seen.add(entry.id);
351
+ }
352
+ });
269
353
 
270
354
  // src/core/schemas/pack.schema.ts
271
355
  import { z as z10 } from "zod";
@@ -279,40 +363,6 @@ var PackCatalogSchema = z10.object({
279
363
  packs: z10.array(PackCatalogEntrySchema)
280
364
  }).strict();
281
365
 
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()
288
- }).strict();
289
- var WorkspaceEntrySchema = z11.object({
290
- preset: z11.string().min(1),
291
- rules: z11.array(z11.string().min(1))
292
- }).strict();
293
- var ManifestSchema = z11.object({
294
- tools: z11.array(z11.string().min(1)).min(1),
295
- scope: z11.literal("project"),
296
- /** 비모노레포 단일 preset */
297
- preset: z11.string().min(1).optional(),
298
- /** 모노레포: workspace path → { preset, rules } */
299
- workspaces: z11.record(z11.string(), WorkspaceEntrySchema).optional(),
300
- installed_rules: z11.array(z11.string().min(1)),
301
- /** 실제 디스크에 쓰여진 파일 상대 경로 목록 (uninstall용). 기존 manifest 호환성 위해 optional */
302
- installed_files: z11.array(z11.string().min(1)).optional(),
303
- /** skill 설치 루트 디렉토리 목록 */
304
- installed_skills: z11.array(InstalledSkillSchema).optional(),
305
- /** non-managed 파일에 섹션을 append한 경우 추적 (uninstall 시 섹션만 제거) */
306
- appended_files: z11.array(z11.string().min(1)).optional(),
307
- /** init 시 선택된 settings 항목 — update 시 재생성에 사용 */
308
- settings: SettingsConfigSchema.optional(),
309
- /** init/update 실행 시점의 CLI 패키지 버전 — 버전 변경 감지에 사용 */
310
- cliVersion: z11.string().optional(),
311
- /** SSOT 데이터 파일들의 deterministic SHA-256 해시 (6자리 hex). diff/update 판단 기준 */
312
- sourceHash: z11.string().regex(/^[a-f0-9]{6}$/, "sourceHash must be 6 lowercase hex chars"),
313
- generatedAt: z11.string().datetime({ offset: true })
314
- }).strict();
315
-
316
366
  // src/core/loader.ts
317
367
  import { readFileSync, readdirSync } from "fs";
318
368
  import { join as join2, resolve } from "path";
@@ -424,7 +474,6 @@ var loadAllSkills = (skillsDir) => {
424
474
  description: parsed.description,
425
475
  supported_tools: [...entry.supported_tools],
426
476
  groups: [...entry.groups],
427
- included_in_presets: [...entry.included_in_presets],
428
477
  directory,
429
478
  files
430
479
  };
@@ -444,6 +493,10 @@ var assertSubagentFrontmatterName = (params) => {
444
493
  }
445
494
  };
446
495
  var loadSubagentCatalog = (subagentsDir) => SubagentCatalogSchema.parse(JSON.parse(readFileSync(resolve(subagentsDir, "subagent-registry.json"), "utf-8")));
496
+ var loadIntegrationCatalog = (integrationsDir) => IntegrationCatalogSchema.parse(
497
+ JSON.parse(readFileSync(resolve(integrationsDir, "integration-registry.json"), "utf-8"))
498
+ );
499
+ var loadAllIntegrations = (integrationsDir) => [...loadIntegrationCatalog(integrationsDir).integrations].sort((a, b) => a.id.localeCompare(b.id));
447
500
  var loadAllSubagents = (subagentsDir) => {
448
501
  const catalog = loadSubagentCatalog(subagentsDir);
449
502
  const entries = [...catalog.subagents].sort((a, b) => a.id.localeCompare(b.id));
@@ -483,16 +536,13 @@ var loadAllSubagents = (subagentsDir) => {
483
536
  });
484
537
  };
485
538
 
486
- // src/core/renderer.ts
487
- import { join as join3 } from "path";
488
-
489
539
  // src/core/skill-renderer.ts
490
- import { join as join5 } from "path";
540
+ import { join as join4 } from "path";
491
541
 
492
542
  // src/core/source-hash.ts
493
543
  import { createHash } from "crypto";
494
544
  import { readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
495
- import { dirname, join as join4, resolve as resolve2 } from "path";
545
+ import { dirname, join as join3, resolve as resolve2 } from "path";
496
546
  import { fileURLToPath } from "url";
497
547
  var __dirname = dirname(fileURLToPath(import.meta.url));
498
548
  var getCliVersion = () => {
@@ -514,10 +564,10 @@ var CLAUDE_SKILLS_DIR = ".claude/skills";
514
564
  var buildRootDirs = (skillId, toolIds) => {
515
565
  const dirs = [];
516
566
  if (toolIds.some((toolId) => toolId === "codex" || toolId === "gemini")) {
517
- dirs.push(join5(AGENT_SKILLS_DIR, skillId));
567
+ dirs.push(join4(AGENT_SKILLS_DIR, skillId));
518
568
  }
519
569
  if (toolIds.includes("claude-code")) {
520
- dirs.push(join5(CLAUDE_SKILLS_DIR, skillId));
570
+ dirs.push(join4(CLAUDE_SKILLS_DIR, skillId));
521
571
  }
522
572
  return dirs;
523
573
  };
@@ -539,7 +589,7 @@ var buildSkillInstallPlan = (params) => {
539
589
  });
540
590
  const packages = rootDirs.map((rootDir) => {
541
591
  const files = params.skill.files.map((file) => ({
542
- relativePath: join5(rootDir, file.path),
592
+ relativePath: join4(rootDir, file.path),
543
593
  content: file.content
544
594
  }));
545
595
  return {
@@ -583,11 +633,9 @@ var renderCodexSubagent = (params) => {
583
633
  path = ${JSON.stringify(skillPath)}
584
634
  enabled = true`;
585
635
  });
586
- const sections = [
587
- metadata,
588
- `developer_instructions = ${JSON.stringify(params.prompt.trimEnd())}`,
589
- ...skills
590
- ].filter((section) => section.length > 0);
636
+ const sections = [metadata, `developer_instructions = ${JSON.stringify(params.prompt.trimEnd())}`, ...skills].filter(
637
+ (section) => section.length > 0
638
+ );
591
639
  return sections.join("\n\n") + "\n";
592
640
  };
593
641
  var renderSubagentForTool = (params) => {
@@ -732,64 +780,97 @@ var parseAiOpsMeta = (content) => {
732
780
  return { sourceHash: match[1], generatedAt: match[2] };
733
781
  };
734
782
 
735
- // src/core/manifest-io.ts
783
+ // src/core/skill-registry-io.ts
736
784
  import { mkdirSync, readFileSync as readFileSync3, writeFileSync } from "fs";
737
- import { dirname as dirname2, join as join6 } from "path";
738
-
739
- // src/core/manifest-resolution.ts
785
+ import { dirname as dirname2, join as join5 } from "path";
786
+ var SKILL_REGISTRY_FILENAME = "skills-manifest.json";
740
787
  var LEGACY_SKILL_ID_MAP = {
741
788
  "engineering-standards-pack": "backend-service-standards"
742
789
  };
743
790
  var resolveCanonicalSkillId = (skillId) => LEGACY_SKILL_ID_MAP[skillId] ?? skillId;
744
-
745
- // src/core/skill-registry-io.ts
746
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
747
- import { dirname as dirname3, join as join7 } from "path";
748
- var SKILL_REGISTRY_FILENAME = "skills-manifest.json";
749
791
  var parseSkillRegistry = (json) => SkillRegistrySchema.parse(JSON.parse(json));
750
792
  var serializeSkillRegistry = (registry) => JSON.stringify(registry, null, 2) + "\n";
751
- var resolveSkillRegistryPath = (userBasePath) => join7(userBasePath, ".ai-ops", SKILL_REGISTRY_FILENAME);
793
+ var resolveSkillRegistryPath = (userBasePath) => join5(userBasePath, ".ai-ops", SKILL_REGISTRY_FILENAME);
752
794
  var readSkillRegistry = (registryPath) => {
753
795
  let raw;
754
796
  try {
755
- raw = readFileSync4(registryPath, "utf-8");
797
+ raw = readFileSync3(registryPath, "utf-8");
756
798
  } catch {
757
799
  return null;
758
800
  }
759
801
  return parseSkillRegistry(raw);
760
802
  };
761
803
  var writeSkillRegistry = (registryPath, registry) => {
762
- mkdirSync2(dirname3(registryPath), { recursive: true });
763
- writeFileSync2(registryPath, serializeSkillRegistry(registry), "utf-8");
804
+ mkdirSync(dirname2(registryPath), { recursive: true });
805
+ writeFileSync(registryPath, serializeSkillRegistry(registry), "utf-8");
764
806
  };
765
807
 
766
808
  // 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";
809
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
810
+ import { dirname as dirname3, join as join6 } from "path";
769
811
  var SUBAGENT_MANIFEST_FILENAME = "subagents-manifest.json";
770
812
  var parseSubagentManifest = (json) => SubagentManifestSchema.parse(JSON.parse(json));
771
813
  var serializeSubagentManifest = (manifest) => JSON.stringify(manifest, null, 2) + "\n";
772
- var resolveSubagentManifestPath = (userBasePath) => join8(userBasePath, ".ai-ops", SUBAGENT_MANIFEST_FILENAME);
814
+ var resolveSubagentManifestPath = (userBasePath) => join6(userBasePath, ".ai-ops", SUBAGENT_MANIFEST_FILENAME);
773
815
  var readSubagentManifest = (manifestPath) => {
774
816
  let raw;
775
817
  try {
776
- raw = readFileSync5(manifestPath, "utf-8");
818
+ raw = readFileSync4(manifestPath, "utf-8");
777
819
  } catch {
778
820
  return null;
779
821
  }
780
822
  return parseSubagentManifest(raw);
781
823
  };
782
824
  var writeSubagentManifest = (manifestPath, manifest) => {
783
- mkdirSync3(dirname4(manifestPath), { recursive: true });
784
- writeFileSync3(manifestPath, serializeSubagentManifest(manifest), "utf-8");
825
+ mkdirSync2(dirname3(manifestPath), { recursive: true });
826
+ writeFileSync2(manifestPath, serializeSubagentManifest(manifest), "utf-8");
785
827
  };
786
828
 
787
- // src/core/install-plan.ts
788
- import { join as join9 } from "path";
829
+ // src/core/integration-manifest-io.ts
830
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync3 } from "fs";
831
+ import { dirname as dirname4, join as join7 } from "path";
832
+ var INTEGRATION_MANIFEST_FILENAME = "integrations-manifest.json";
833
+ var parseIntegrationManifest = (json) => IntegrationManifestSchema.parse(JSON.parse(json));
834
+ var serializeIntegrationManifest = (manifest) => JSON.stringify(manifest, null, 2) + "\n";
835
+ var resolveIntegrationManifestPath = (userBasePath) => join7(userBasePath, ".ai-ops", INTEGRATION_MANIFEST_FILENAME);
836
+ var readIntegrationManifest = (manifestPath) => {
837
+ let raw;
838
+ try {
839
+ raw = readFileSync5(manifestPath, "utf-8");
840
+ } catch {
841
+ return null;
842
+ }
843
+ return parseIntegrationManifest(raw);
844
+ };
845
+ var writeIntegrationManifest = (manifestPath, manifest) => {
846
+ mkdirSync3(dirname4(manifestPath), { recursive: true });
847
+ writeFileSync3(manifestPath, serializeIntegrationManifest(manifest), "utf-8");
848
+ };
849
+ var findInstalledIntegration = (integrations, integrationId) => integrations.find((integration) => integration.id === integrationId);
850
+ var upsertInstalledIntegration = (integrations, nextIntegration) => [
851
+ ...integrations.filter((integration) => integration.id !== nextIntegration.id),
852
+ nextIntegration
853
+ ];
854
+ var removeInstalledIntegration = (integrations, integrationId) => integrations.filter((integration) => integration.id !== integrationId);
855
+ var writeUserIntegrationState = (params) => {
856
+ const previous = readIntegrationManifest(params.manifestPath);
857
+ const integrations = params.removeIntegrationId ? removeInstalledIntegration(previous?.integrations ?? [], params.removeIntegrationId) : params.nextIntegration ? upsertInstalledIntegration(previous?.integrations ?? [], params.nextIntegration) : previous?.integrations ?? [];
858
+ if (integrations.length === 0) {
859
+ rmSync(params.manifestPath, { force: true });
860
+ return;
861
+ }
862
+ writeIntegrationManifest(params.manifestPath, {
863
+ schemaVersion: 1,
864
+ kind: "ai-ops-integrations-manifest",
865
+ integrations,
866
+ cliVersion: params.cliVersion,
867
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
868
+ });
869
+ };
789
870
 
790
871
  // src/core/project-layer.ts
791
- import { existsSync, 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";
872
+ import { existsSync, mkdirSync as mkdirSync4, readFileSync as readFileSync6, readdirSync as readdirSync3, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
873
+ import { dirname as dirname6, isAbsolute, join as join8, relative, resolve as resolve5 } from "path";
793
874
 
794
875
  // src/core/paths.ts
795
876
  import { dirname as dirname5, resolve as resolve4 } from "path";
@@ -800,7 +881,7 @@ var COMPILER_DATA_DIR = resolve4(__dirname2, "..", "..", "data");
800
881
  // src/core/project-layer.ts
801
882
  var PROJECT_LAYER_MANIFEST_RELATIVE_PATH = ".ai-ops/manifest.json";
802
883
  var PROJECT_LAYER_CONTEXT_INDEX_RELATIVE_PATH = ".ai-ops/context-layer.json";
803
- var CONTEXT_LAYER_DATA_DIR = join10(COMPILER_DATA_DIR, "context-layer");
884
+ var CONTEXT_LAYER_DATA_DIR = join8(COMPILER_DATA_DIR, "context-layer");
804
885
  var TOOL_ORDER = ["codex", "gemini", "claude-code"];
805
886
  var DEFAULT_TOOLS = TOOL_ORDER;
806
887
  var TEMPLATE_PATHS = [
@@ -813,7 +894,6 @@ var TEMPLATE_PATHS = [
813
894
  "docs/agent/rules/doc-update-rules.md",
814
895
  "docs/agent/rules/stop-rules.md",
815
896
  "docs/agent/checks/impact-checklist.md",
816
- "docs/agent/checks/review-checklist.md",
817
897
  "docs/agent/maps/codebase-map.md",
818
898
  "docs/business/business-rules.md",
819
899
  "docs/docs-status.md"
@@ -827,9 +907,9 @@ var RESERVED_DOCUMENT_WARNINGS = [
827
907
  "\uD310\uB2E8 \uADFC\uAC70\uB85C \uC0AC\uC6A9\uD558\uC9C0 \uB9C8\uC138\uC694",
828
908
  "Do not use this document as current decision-making evidence"
829
909
  ];
830
- var resolveProjectLayerManifestPath = (basePath) => join10(basePath, PROJECT_LAYER_MANIFEST_RELATIVE_PATH);
831
- var resolveProjectLayerContextIndexPath = (basePath) => join10(basePath, PROJECT_LAYER_CONTEXT_INDEX_RELATIVE_PATH);
832
- var resolveTemplatePath = (relativePath) => join10(CONTEXT_LAYER_DATA_DIR, relativePath);
910
+ var resolveProjectLayerManifestPath = (basePath) => join8(basePath, PROJECT_LAYER_MANIFEST_RELATIVE_PATH);
911
+ var resolveProjectLayerContextIndexPath = (basePath) => join8(basePath, PROJECT_LAYER_CONTEXT_INDEX_RELATIVE_PATH);
912
+ var resolveTemplatePath = (relativePath) => join8(CONTEXT_LAYER_DATA_DIR, relativePath);
833
913
  var toRelativeDir = (relativePath) => dirname6(relativePath);
834
914
  var resolveProjectLayerFilePath = (basePath, relativePath) => {
835
915
  if (!isSafeProjectLayerPath(relativePath)) {
@@ -1441,7 +1521,7 @@ function removeManagedProjectFile(basePath, relativePath) {
1441
1521
  }
1442
1522
  const stripped = stripAiOpsSection(content);
1443
1523
  if (stripped.trim().length === 0) {
1444
- rmSync(absolutePath);
1524
+ rmSync2(absolutePath);
1445
1525
  return { deleted: [relativePath], cleaned: [], preserved: [], notFound: [] };
1446
1526
  }
1447
1527
  writeFileSync4(absolutePath, stripped, "utf-8");
@@ -1455,7 +1535,7 @@ var removeCreateOnlyProjectFile = (basePath, file) => {
1455
1535
  const content = readFileSync6(absolutePath, "utf-8").trimEnd();
1456
1536
  const currentHash = computeHash([content]);
1457
1537
  if (file.created && currentHash === file.templateHash) {
1458
- rmSync(absolutePath);
1538
+ rmSync2(absolutePath);
1459
1539
  return { deleted: [file.path], cleaned: [], preserved: [], notFound: [] };
1460
1540
  }
1461
1541
  return { deleted: [], cleaned: [], preserved: [file.path], notFound: [] };
@@ -1467,7 +1547,7 @@ var removePackOwnedFile = (basePath, file) => {
1467
1547
  }
1468
1548
  const currentHash = computeHash([readFileSync6(absolutePath, "utf-8").trimEnd()]);
1469
1549
  if (currentHash === file.sourceHash) {
1470
- rmSync(absolutePath);
1550
+ rmSync2(absolutePath);
1471
1551
  return { deleted: [file.path], cleaned: [], preserved: [], notFound: [] };
1472
1552
  }
1473
1553
  return { deleted: [], cleaned: [], preserved: [file.path], notFound: [] };
@@ -1487,7 +1567,7 @@ var removeEmptyDirs = (basePath, relativePaths) => {
1487
1567
  if (!existsSync(absoluteDir)) continue;
1488
1568
  try {
1489
1569
  if (readdirSync3(absoluteDir).length === 0) {
1490
- rmSync(absoluteDir, { recursive: true });
1570
+ rmSync2(absoluteDir, { recursive: true });
1491
1571
  }
1492
1572
  } catch {
1493
1573
  }
@@ -1501,7 +1581,7 @@ var uninstallProjectLayer = (basePath, manifest) => {
1501
1581
  );
1502
1582
  const stateFiles = [PROJECT_LAYER_CONTEXT_INDEX_RELATIVE_PATH, PROJECT_LAYER_MANIFEST_RELATIVE_PATH];
1503
1583
  for (const stateFile of stateFiles) {
1504
- rmSync(resolveProjectLayerFilePath(basePath, stateFile), { force: true });
1584
+ rmSync2(resolveProjectLayerFilePath(basePath, stateFile), { force: true });
1505
1585
  }
1506
1586
  const result = mergeRemoveResults([...managedResults, ...projectResults, ...packResults]);
1507
1587
  removeEmptyDirs(basePath, [...result.deleted, ...stateFiles]);
@@ -1509,8 +1589,8 @@ var uninstallProjectLayer = (basePath, manifest) => {
1509
1589
  };
1510
1590
 
1511
1591
  // src/core/pack.ts
1512
- import { existsSync as existsSync2, mkdirSync as mkdirSync5, readFileSync as readFileSync7, readdirSync as readdirSync4, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
1513
- import { dirname as dirname7, isAbsolute as isAbsolute2, join as join11, relative as relative2, resolve as resolve6 } from "path";
1592
+ import { existsSync as existsSync2, mkdirSync as mkdirSync5, readFileSync as readFileSync7, readdirSync as readdirSync4, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "fs";
1593
+ import { dirname as dirname7, isAbsolute as isAbsolute2, join as join9, relative as relative2, resolve as resolve6 } from "path";
1514
1594
  var PACK_REGISTRY_FILENAME = "pack-registry.json";
1515
1595
  var SPEC_LIFECYCLE_PACK_ID = "spec-lifecycle";
1516
1596
  var PACK_INSTALL_ROOT = "docs/specs/";
@@ -1518,9 +1598,9 @@ var RESERVED_DOCUMENT_WARNINGS2 = [
1518
1598
  "\uD310\uB2E8 \uADFC\uAC70\uB85C \uC0AC\uC6A9\uD558\uC9C0 \uB9C8\uC138\uC694",
1519
1599
  "Do not use this document as current decision-making evidence"
1520
1600
  ];
1521
- var DEFAULT_PACKS_DIR = join11(COMPILER_DATA_DIR, "packs");
1601
+ var DEFAULT_PACKS_DIR = join9(COMPILER_DATA_DIR, "packs");
1522
1602
  var includesReservedDocumentWarning2 = (content) => RESERVED_DOCUMENT_WARNINGS2.some((warning) => content.includes(warning));
1523
- var readPackCatalog = (packsDir) => PackCatalogSchema.parse(JSON.parse(readFileSync7(join11(packsDir, PACK_REGISTRY_FILENAME), "utf-8")));
1603
+ var readPackCatalog = (packsDir) => PackCatalogSchema.parse(JSON.parse(readFileSync7(join9(packsDir, PACK_REGISTRY_FILENAME), "utf-8")));
1524
1604
  var assertPackInstallPath = (path) => {
1525
1605
  if (!isSafeProjectLayerPath(path) || !path.startsWith(PACK_INSTALL_ROOT)) {
1526
1606
  throw new Error(`Unsafe pack path: ${path}`);
@@ -1529,16 +1609,16 @@ var assertPackInstallPath = (path) => {
1529
1609
  var readPackSourceFiles = (packDir) => {
1530
1610
  const files = [];
1531
1611
  const walk = (relativeDir = "") => {
1532
- const absoluteDir = relativeDir.length > 0 ? join11(packDir, relativeDir) : packDir;
1612
+ const absoluteDir = relativeDir.length > 0 ? join9(packDir, relativeDir) : packDir;
1533
1613
  const entries = readdirSync4(absoluteDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
1534
1614
  for (const entry of entries) {
1535
- const nextRelativePath = relativeDir.length > 0 ? join11(relativeDir, entry.name) : entry.name;
1615
+ const nextRelativePath = relativeDir.length > 0 ? join9(relativeDir, entry.name) : entry.name;
1536
1616
  if (entry.isDirectory()) {
1537
1617
  walk(nextRelativePath);
1538
1618
  continue;
1539
1619
  }
1540
1620
  assertPackInstallPath(nextRelativePath);
1541
- const content = readFileSync7(join11(packDir, nextRelativePath), "utf-8");
1621
+ const content = readFileSync7(join9(packDir, nextRelativePath), "utf-8");
1542
1622
  files.push({
1543
1623
  path: nextRelativePath,
1544
1624
  content,
@@ -1659,7 +1739,7 @@ var applyPackSourceFiles = (params) => {
1659
1739
  continue;
1660
1740
  }
1661
1741
  if (readProjectFileHash(params.basePath, previous.path) === previous.sourceHash) {
1662
- rmSync2(absolutePath);
1742
+ rmSync3(absolutePath);
1663
1743
  deleted.push(previous.path);
1664
1744
  } else {
1665
1745
  preserved.push(previous.path);
@@ -1678,7 +1758,7 @@ var removePackFiles = (basePath, record) => {
1678
1758
  continue;
1679
1759
  }
1680
1760
  if (readProjectFileHash(basePath, file.path) === file.sourceHash) {
1681
- rmSync2(absolutePath);
1761
+ rmSync3(absolutePath);
1682
1762
  deleted.push(file.path);
1683
1763
  } else {
1684
1764
  preserved.push(file.path);
@@ -1697,7 +1777,7 @@ var removeEmptyDirs2 = (basePath, relativePaths) => {
1697
1777
  }
1698
1778
  try {
1699
1779
  if (readdirSync4(absoluteDir).length === 0) {
1700
- rmSync2(absoluteDir, { recursive: true });
1780
+ rmSync3(absoluteDir, { recursive: true });
1701
1781
  }
1702
1782
  } catch {
1703
1783
  }
@@ -1816,99 +1896,315 @@ var diffProjectLayerPack = (params) => {
1816
1896
  return { issues };
1817
1897
  };
1818
1898
 
1819
- // src/core/uninstall-plan.ts
1820
- import { join as join12 } from "path";
1821
-
1822
1899
  // src/core/context-promotion.ts
1823
1900
  import { createHash as createHash2 } from "crypto";
1824
1901
  import { execFileSync } from "child_process";
1825
1902
  import { existsSync as existsSync3, mkdirSync as mkdirSync6, readFileSync as readFileSync8, statSync, writeFileSync as writeFileSync6 } from "fs";
1826
- import { dirname as dirname8, join as join13, resolve as resolve7 } from "path";
1903
+ import { dirname as dirname8, join as join10, resolve as resolve7 } from "path";
1827
1904
  import { z as z12 } from "zod";
1828
- var CONTEXT_PROMOTION_DECISION = {
1829
- PROMOTED: "promoted",
1830
- NO_PROMOTION: "no-promotion"
1831
- };
1832
- var CONTEXT_PROMOTION_SCOPE = {
1833
- CORE: "core",
1834
- PROJECT_LOCAL: "project-local",
1835
- GLOBAL: "global"
1836
- };
1837
- var ContextPromotionDecisionSchema = z12.union([
1838
- z12.literal(CONTEXT_PROMOTION_DECISION.PROMOTED),
1839
- z12.literal(CONTEXT_PROMOTION_DECISION.NO_PROMOTION)
1840
- ]);
1841
- var ContextPromotionScopeSchema = z12.union([
1842
- z12.literal(CONTEXT_PROMOTION_SCOPE.CORE),
1843
- z12.literal(CONTEXT_PROMOTION_SCOPE.PROJECT_LOCAL),
1844
- z12.literal(CONTEXT_PROMOTION_SCOPE.GLOBAL)
1905
+
1906
+ // src/core/tool-use-hook.ts
1907
+ import { z as z11 } from "zod";
1908
+ var HookToolInputSchema = z11.object({
1909
+ command: z11.string().optional()
1910
+ }).passthrough();
1911
+ var ToolUseHookInputSchema = z11.object({
1912
+ hook_event_name: z11.string(),
1913
+ cwd: z11.string(),
1914
+ tool_name: z11.string().optional(),
1915
+ tool_input: z11.unknown().optional(),
1916
+ tool_response: z11.unknown().optional()
1917
+ }).passthrough();
1918
+ var SHELL_CONTROL_TOKENS = /* @__PURE__ */ new Set(["&&", "||", ";", "|", "(", ")"]);
1919
+ var SHELL_SCRIPT_FLAGS = /* @__PURE__ */ new Set(["-c", "-lc"]);
1920
+ var GIT_GLOBAL_OPTIONS_WITH_VALUE = /* @__PURE__ */ new Set([
1921
+ "-C",
1922
+ "-c",
1923
+ "--git-dir",
1924
+ "--work-tree",
1925
+ "--namespace",
1926
+ "--config-env",
1927
+ "--exec-path"
1845
1928
  ]);
1846
- var ContextPromotionReceiptSchema = z12.object({
1847
- fingerprint: z12.string().regex(/^[a-f0-9]{16}$/),
1848
- commitHash: z12.string().regex(/^(NO_HEAD|[a-f0-9]{40})$/).optional(),
1849
- decision: ContextPromotionDecisionSchema,
1850
- scopes: z12.array(ContextPromotionScopeSchema),
1851
- targets: z12.array(z12.string().min(1)),
1852
- summary: z12.string().min(1),
1853
- resolvedAt: z12.string().min(1)
1854
- }).strict();
1855
- var ContextPromotionReceiptIndexSchema = z12.object({
1856
- schemaVersion: z12.literal(1),
1857
- kind: z12.literal("context-promotion-receipts"),
1858
- projectKey: z12.string().regex(/^[a-f0-9]{12}$/),
1859
- projectRoot: z12.string().min(1),
1860
- receipts: z12.array(ContextPromotionReceiptSchema)
1861
- }).strict();
1862
- var RECEIPT_INDEX_FILENAME = "receipts-index.json";
1863
- var DEFAULT_PRUNE_MAX = 50;
1864
- var hashHex = (parts, length) => createHash2("sha256").update(parts.join("\0")).digest("hex").slice(0, length);
1865
- var buildContextPromotionProjectKey = (gitRoot) => hashHex([resolve7(gitRoot)], 12);
1866
- var runGit = (cwd, args) => execFileSync("git", [...args], {
1867
- cwd,
1868
- encoding: "utf-8",
1869
- maxBuffer: 20 * 1024 * 1024,
1870
- stdio: ["ignore", "pipe", "ignore"]
1871
- });
1872
- var resolveContextPromotionGitRoot = (cwd) => {
1873
- try {
1874
- return runGit(cwd, ["rev-parse", "--show-toplevel"]).trim();
1875
- } catch {
1876
- return null;
1929
+ var basename = (token) => token.replace(/\\/g, "/").split("/").at(-1) ?? token;
1930
+ var isAssignmentToken = (token) => /^[A-Za-z_][A-Za-z0-9_]*=/.test(token);
1931
+ var tokenizeShellLike = (command) => {
1932
+ const tokens = [];
1933
+ let current = "";
1934
+ let quote = null;
1935
+ const pushCurrent = () => {
1936
+ if (current.length > 0) {
1937
+ tokens.push(current);
1938
+ current = "";
1939
+ }
1940
+ };
1941
+ for (let index = 0; index < command.length; index += 1) {
1942
+ const char = command[index];
1943
+ const nextChar = command[index + 1];
1944
+ if (quote) {
1945
+ if (char === quote) {
1946
+ quote = null;
1947
+ continue;
1948
+ }
1949
+ current += char;
1950
+ continue;
1951
+ }
1952
+ if (char === '"' || char === "'") {
1953
+ quote = char;
1954
+ continue;
1955
+ }
1956
+ if (/\s/.test(char)) {
1957
+ pushCurrent();
1958
+ continue;
1959
+ }
1960
+ if (char === "&" && nextChar === "&" || char === "|" && nextChar === "|") {
1961
+ pushCurrent();
1962
+ tokens.push(`${char}${nextChar}`);
1963
+ index += 1;
1964
+ continue;
1965
+ }
1966
+ if (char === ";" || char === "|" || char === "(" || char === ")") {
1967
+ pushCurrent();
1968
+ tokens.push(char);
1969
+ continue;
1970
+ }
1971
+ current += char;
1877
1972
  }
1973
+ pushCurrent();
1974
+ return tokens;
1878
1975
  };
1879
- var readGitHead = (gitRoot) => {
1880
- try {
1881
- return runGit(gitRoot, ["rev-parse", "--verify", "HEAD"]).trim();
1882
- } catch {
1883
- return "NO_HEAD";
1976
+ var splitCommandSegments = (tokens) => {
1977
+ const segments = [];
1978
+ let current = [];
1979
+ for (const token of tokens) {
1980
+ if (SHELL_CONTROL_TOKENS.has(token)) {
1981
+ if (current.length > 0) {
1982
+ segments.push(current);
1983
+ current = [];
1984
+ }
1985
+ continue;
1986
+ }
1987
+ current.push(token);
1884
1988
  }
1989
+ if (current.length > 0) {
1990
+ segments.push(current);
1991
+ }
1992
+ return segments;
1885
1993
  };
1886
- var readUntrackedFingerprintParts = (gitRoot) => {
1887
- const raw = runGit(gitRoot, ["ls-files", "--others", "--exclude-standard", "-z"]);
1888
- const paths = raw.split("\0").filter((path) => path.length > 0).sort((a, b) => a.localeCompare(b));
1889
- return paths.map((relativePath) => {
1890
- const absolutePath = join13(gitRoot, relativePath);
1891
- try {
1892
- const stat = statSync(absolutePath);
1893
- if (!stat.isFile()) {
1894
- return `${relativePath}:non-file`;
1895
- }
1896
- const content = readFileSync8(absolutePath);
1897
- return `${relativePath}:${createHash2("sha256").update(content).digest("hex")}`;
1898
- } catch {
1899
- throw new Error(`Unable to read untracked path for context promotion fingerprint: ${relativePath}`);
1994
+ var firstExecutableIndex = (segment) => {
1995
+ let index = 0;
1996
+ while (index < segment.length && isAssignmentToken(segment[index])) {
1997
+ index += 1;
1998
+ }
1999
+ if (segment[index] === "env") {
2000
+ index += 1;
2001
+ while (index < segment.length && isAssignmentToken(segment[index])) {
2002
+ index += 1;
1900
2003
  }
1901
- });
2004
+ }
2005
+ if (segment[index] === "command" || segment[index] === "sudo") {
2006
+ index += 1;
2007
+ }
2008
+ return index;
1902
2009
  };
1903
- var readTrackedWorkingTreeFingerprintParts = (gitRoot) => {
1904
- const rawDiff = runGit(gitRoot, ["diff", "--raw", "-z"]);
1905
- const rawNames = runGit(gitRoot, ["diff", "--name-only", "-z"]);
1906
- const paths = rawNames.split("\0").filter((path) => path.length > 0).sort((a, b) => a.localeCompare(b));
1907
- return [
1908
- `raw:${rawDiff}`,
1909
- ...paths.map((relativePath) => {
1910
- const absolutePath = join13(gitRoot, relativePath);
1911
- if (!existsSync3(absolutePath)) {
2010
+ var segmentInvokesGitCommit = (segment) => {
2011
+ const executableIndex = firstExecutableIndex(segment);
2012
+ if (executableIndex >= segment.length || basename(segment[executableIndex]) !== "git") {
2013
+ return false;
2014
+ }
2015
+ for (let index = executableIndex + 1; index < segment.length; index += 1) {
2016
+ const token = segment[index];
2017
+ if (GIT_GLOBAL_OPTIONS_WITH_VALUE.has(token)) {
2018
+ index += 1;
2019
+ continue;
2020
+ }
2021
+ if (token.startsWith("-")) {
2022
+ continue;
2023
+ }
2024
+ return token === "commit";
2025
+ }
2026
+ return false;
2027
+ };
2028
+ var segmentInvokesShellScriptWithGitCommit = (segment) => {
2029
+ const executableIndex = firstExecutableIndex(segment);
2030
+ const executable = segment[executableIndex];
2031
+ if (!executable || !["bash", "sh", "zsh"].includes(basename(executable))) {
2032
+ return false;
2033
+ }
2034
+ for (let index = executableIndex + 1; index < segment.length - 1; index += 1) {
2035
+ if (SHELL_SCRIPT_FLAGS.has(segment[index]) && isGitCommitCommand(segment[index + 1])) {
2036
+ return true;
2037
+ }
2038
+ }
2039
+ return false;
2040
+ };
2041
+ var isGitCommitCommand = (command) => {
2042
+ const segments = splitCommandSegments(tokenizeShellLike(command));
2043
+ return segments.some(
2044
+ (segment) => segmentInvokesGitCommit(segment) || segmentInvokesShellScriptWithGitCommit(segment)
2045
+ );
2046
+ };
2047
+ var isJsonRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2048
+ var numberField = (record, keys) => {
2049
+ for (const key of keys) {
2050
+ const value = record[key];
2051
+ if (typeof value === "number") {
2052
+ return value;
2053
+ }
2054
+ }
2055
+ return null;
2056
+ };
2057
+ var booleanField = (record, keys) => {
2058
+ for (const key of keys) {
2059
+ const value = record[key];
2060
+ if (typeof value === "boolean") {
2061
+ return value;
2062
+ }
2063
+ }
2064
+ return null;
2065
+ };
2066
+ var GIT_COMMIT_FAILURE_OUTPUT_PATTERNS = [
2067
+ /(^|\n)\s*fatal:/i,
2068
+ /(^|\n)\s*error:/i,
2069
+ /(^|\n)\s*nothing to commit\b/i,
2070
+ /(^|\n)\s*no changes added to commit\b/i,
2071
+ /(^|\n).*aborting commit\b/i,
2072
+ /(^|\n).*commit failed\b/i,
2073
+ /(^|\n).*failed to .*commit\b/i,
2074
+ /(^|\n).*command failed\b/i,
2075
+ /(^|\n).*non-zero exit\b/i,
2076
+ /(^|\n).*exit (code|status)\s+[1-9]\d*\b/i,
2077
+ /(^|\n).*exited with code\s+[1-9]\d*\b/i,
2078
+ /(^|\n).*hook.*(failed|declined|error|exit(?:ed)? with code|non-zero)/i
2079
+ ];
2080
+ var GIT_COMMIT_SUCCESS_OUTPUT_PATTERN = /(^|\n)\[[^\]\n]+ [a-f0-9]{7,40}\]/i;
2081
+ var stringIndicatesGitCommitSuccess = (output) => GIT_COMMIT_SUCCESS_OUTPUT_PATTERN.test(output);
2082
+ var stringIndicatesGitCommitFailureOrSuccess = (output) => stringIndicatesGitCommitSuccess(output) ? false : GIT_COMMIT_FAILURE_OUTPUT_PATTERNS.some((pattern) => pattern.test(output)) ? true : null;
2083
+ var recordStringFieldsIndicateGitCommitFailure = (record) => ["message", "output", "stdout", "stderr", "error", "combinedOutput"].some((key) => {
2084
+ const value = record[key];
2085
+ return typeof value === "string" && stringIndicatesGitCommitFailureOrSuccess(value) === true;
2086
+ });
2087
+ var toolResponseIndicatesFailure = (toolResponse) => {
2088
+ if (typeof toolResponse === "string") {
2089
+ return stringIndicatesGitCommitFailureOrSuccess(toolResponse) === true;
2090
+ }
2091
+ if (!isJsonRecord(toolResponse)) {
2092
+ return false;
2093
+ }
2094
+ const success = booleanField(toolResponse, ["success", "ok"]);
2095
+ if (success === false) {
2096
+ return true;
2097
+ }
2098
+ const exitCode = numberField(toolResponse, ["exit_code", "exitCode", "status", "code"]);
2099
+ if (exitCode !== null && exitCode !== 0) {
2100
+ return true;
2101
+ }
2102
+ return recordStringFieldsIndicateGitCommitFailure(toolResponse);
2103
+ };
2104
+ var parseSuccessfulGitCommitPostToolUseHook = (hookInput) => {
2105
+ const parsed = ToolUseHookInputSchema.safeParse(hookInput);
2106
+ if (!parsed.success) {
2107
+ return null;
2108
+ }
2109
+ if (parsed.data.hook_event_name !== "PostToolUse" || parsed.data.tool_name !== "Bash") {
2110
+ return null;
2111
+ }
2112
+ const toolInput = HookToolInputSchema.safeParse(parsed.data.tool_input);
2113
+ const command = toolInput.success ? toolInput.data.command ?? "" : "";
2114
+ if (!isGitCommitCommand(command) || toolResponseIndicatesFailure(parsed.data.tool_response)) {
2115
+ return null;
2116
+ }
2117
+ return {
2118
+ cwd: parsed.data.cwd,
2119
+ command
2120
+ };
2121
+ };
2122
+
2123
+ // src/core/context-promotion.ts
2124
+ var CONTEXT_PROMOTION_DECISION = {
2125
+ PROMOTED: "promoted",
2126
+ NO_PROMOTION: "no-promotion"
2127
+ };
2128
+ var CONTEXT_PROMOTION_SCOPE = {
2129
+ CORE: "core",
2130
+ PROJECT_LOCAL: "project-local",
2131
+ GLOBAL: "global"
2132
+ };
2133
+ var ContextPromotionDecisionSchema = z12.union([
2134
+ z12.literal(CONTEXT_PROMOTION_DECISION.PROMOTED),
2135
+ z12.literal(CONTEXT_PROMOTION_DECISION.NO_PROMOTION)
2136
+ ]);
2137
+ var ContextPromotionScopeSchema = z12.union([
2138
+ z12.literal(CONTEXT_PROMOTION_SCOPE.CORE),
2139
+ z12.literal(CONTEXT_PROMOTION_SCOPE.PROJECT_LOCAL),
2140
+ z12.literal(CONTEXT_PROMOTION_SCOPE.GLOBAL)
2141
+ ]);
2142
+ var ContextPromotionReceiptSchema = z12.object({
2143
+ fingerprint: z12.string().regex(/^[a-f0-9]{16}$/),
2144
+ commitHash: z12.string().regex(/^(NO_HEAD|[a-f0-9]{40})$/).optional(),
2145
+ decision: ContextPromotionDecisionSchema,
2146
+ scopes: z12.array(ContextPromotionScopeSchema),
2147
+ targets: z12.array(z12.string().min(1)),
2148
+ summary: z12.string().min(1),
2149
+ resolvedAt: z12.string().min(1)
2150
+ }).strict();
2151
+ var ContextPromotionReceiptIndexSchema = z12.object({
2152
+ schemaVersion: z12.literal(1),
2153
+ kind: z12.literal("context-promotion-receipts"),
2154
+ projectKey: z12.string().regex(/^[a-f0-9]{12}$/),
2155
+ projectRoot: z12.string().min(1),
2156
+ receipts: z12.array(ContextPromotionReceiptSchema)
2157
+ }).strict();
2158
+ var RECEIPT_INDEX_FILENAME = "receipts-index.json";
2159
+ var DEFAULT_PRUNE_MAX = 50;
2160
+ var hashHex = (parts, length) => createHash2("sha256").update(parts.join("\0")).digest("hex").slice(0, length);
2161
+ var buildContextPromotionProjectKey = (gitRoot) => hashHex([resolve7(gitRoot)], 12);
2162
+ var runGit = (cwd, args) => execFileSync("git", [...args], {
2163
+ cwd,
2164
+ encoding: "utf-8",
2165
+ maxBuffer: 20 * 1024 * 1024,
2166
+ stdio: ["ignore", "pipe", "ignore"]
2167
+ });
2168
+ var resolveContextPromotionGitRoot = (cwd) => {
2169
+ try {
2170
+ return runGit(cwd, ["rev-parse", "--show-toplevel"]).trim();
2171
+ } catch {
2172
+ return null;
2173
+ }
2174
+ };
2175
+ var readGitHead = (gitRoot) => {
2176
+ try {
2177
+ return runGit(gitRoot, ["rev-parse", "--verify", "HEAD"]).trim();
2178
+ } catch {
2179
+ return "NO_HEAD";
2180
+ }
2181
+ };
2182
+ var readUntrackedFingerprintParts = (gitRoot) => {
2183
+ const raw = runGit(gitRoot, ["ls-files", "--others", "--exclude-standard", "-z"]);
2184
+ const paths = raw.split("\0").filter((path) => path.length > 0).sort((a, b) => a.localeCompare(b));
2185
+ return paths.map((relativePath) => {
2186
+ const absolutePath = join10(gitRoot, relativePath);
2187
+ try {
2188
+ const stat = statSync(absolutePath);
2189
+ if (!stat.isFile()) {
2190
+ return `${relativePath}:non-file`;
2191
+ }
2192
+ const content = readFileSync8(absolutePath);
2193
+ return `${relativePath}:${createHash2("sha256").update(content).digest("hex")}`;
2194
+ } catch {
2195
+ throw new Error(`Unable to read untracked path for context promotion fingerprint: ${relativePath}`);
2196
+ }
2197
+ });
2198
+ };
2199
+ var readTrackedWorkingTreeFingerprintParts = (gitRoot) => {
2200
+ const rawDiff = runGit(gitRoot, ["diff", "--raw", "-z"]);
2201
+ const rawNames = runGit(gitRoot, ["diff", "--name-only", "-z"]);
2202
+ const paths = rawNames.split("\0").filter((path) => path.length > 0).sort((a, b) => a.localeCompare(b));
2203
+ return [
2204
+ `raw:${rawDiff}`,
2205
+ ...paths.map((relativePath) => {
2206
+ const absolutePath = join10(gitRoot, relativePath);
2207
+ if (!existsSync3(absolutePath)) {
1912
2208
  return `${relativePath}:deleted`;
1913
2209
  }
1914
2210
  const stat = statSync(absolutePath);
@@ -1933,14 +2229,7 @@ var computeContextPromotionFingerprint = (gitRoot) => hashHex(
1933
2229
  ],
1934
2230
  16
1935
2231
  );
1936
- var resolveContextPromotionReceiptIndexPath = (params) => join13(
1937
- params.userBasePath,
1938
- ".ai-ops",
1939
- "context-promotion",
1940
- "projects",
1941
- params.projectKey,
1942
- RECEIPT_INDEX_FILENAME
1943
- );
2232
+ var resolveContextPromotionReceiptIndexPath = (params) => join10(params.userBasePath, ".ai-ops", "context-promotion", "projects", params.projectKey, RECEIPT_INDEX_FILENAME);
1944
2233
  var parseContextPromotionReceiptIndex = (json) => ContextPromotionReceiptIndexSchema.parse(JSON.parse(json));
1945
2234
  var serializeContextPromotionReceiptIndex = (index) => JSON.stringify(index, null, 2) + "\n";
1946
2235
  var readContextPromotionReceiptIndex = (indexPath) => {
@@ -2000,7 +2289,7 @@ var pruneContextPromotionReceipts = (params) => {
2000
2289
  writeContextPromotionReceiptIndex(params.indexPath, nextIndex);
2001
2290
  return nextIndex;
2002
2291
  };
2003
- var hasContextPromotionLayer = (gitRoot) => existsSync3(join13(gitRoot, PROJECT_LAYER_CONTEXT_INDEX_RELATIVE_PATH));
2292
+ var hasContextPromotionLayer = (gitRoot) => existsSync3(join10(gitRoot, PROJECT_LAYER_CONTEXT_INDEX_RELATIVE_PATH));
2004
2293
  var getContextPromotionStatus = (params) => {
2005
2294
  const cwd = resolve7(params.cwd);
2006
2295
  const gitRoot = resolveContextPromotionGitRoot(cwd);
@@ -2087,143 +2376,6 @@ var resolveContextPromotion = (params) => {
2087
2376
  });
2088
2377
  return getContextPromotionStatus({ cwd: params.cwd, userBasePath: params.userBasePath });
2089
2378
  };
2090
- var HookToolInputSchema = z12.object({
2091
- command: z12.string().optional()
2092
- }).passthrough();
2093
- var ToolUseHookInputSchema = z12.object({
2094
- hook_event_name: z12.string(),
2095
- cwd: z12.string(),
2096
- tool_name: z12.string().optional(),
2097
- tool_input: z12.unknown().optional(),
2098
- tool_response: z12.unknown().optional()
2099
- }).passthrough();
2100
- var SHELL_CONTROL_TOKENS = /* @__PURE__ */ new Set(["&&", "||", ";", "|", "(", ")"]);
2101
- var SHELL_SCRIPT_FLAGS = /* @__PURE__ */ new Set(["-c", "-lc"]);
2102
- var GIT_GLOBAL_OPTIONS_WITH_VALUE = /* @__PURE__ */ new Set([
2103
- "-C",
2104
- "-c",
2105
- "--git-dir",
2106
- "--work-tree",
2107
- "--namespace",
2108
- "--config-env",
2109
- "--exec-path"
2110
- ]);
2111
- var basename = (token) => token.replace(/\\/g, "/").split("/").at(-1) ?? token;
2112
- var isAssignmentToken = (token) => /^[A-Za-z_][A-Za-z0-9_]*=/.test(token);
2113
- var tokenizeShellLike = (command) => {
2114
- const tokens = [];
2115
- let current = "";
2116
- let quote = null;
2117
- const pushCurrent = () => {
2118
- if (current.length > 0) {
2119
- tokens.push(current);
2120
- current = "";
2121
- }
2122
- };
2123
- for (let index = 0; index < command.length; index += 1) {
2124
- const char = command[index];
2125
- const nextChar = command[index + 1];
2126
- if (quote) {
2127
- if (char === quote) {
2128
- quote = null;
2129
- continue;
2130
- }
2131
- current += char;
2132
- continue;
2133
- }
2134
- if (char === '"' || char === "'") {
2135
- quote = char;
2136
- continue;
2137
- }
2138
- if (/\s/.test(char)) {
2139
- pushCurrent();
2140
- continue;
2141
- }
2142
- if (char === "&" && nextChar === "&" || char === "|" && nextChar === "|") {
2143
- pushCurrent();
2144
- tokens.push(`${char}${nextChar}`);
2145
- index += 1;
2146
- continue;
2147
- }
2148
- if (char === ";" || char === "|" || char === "(" || char === ")") {
2149
- pushCurrent();
2150
- tokens.push(char);
2151
- continue;
2152
- }
2153
- current += char;
2154
- }
2155
- pushCurrent();
2156
- return tokens;
2157
- };
2158
- var splitCommandSegments = (tokens) => {
2159
- const segments = [];
2160
- let current = [];
2161
- for (const token of tokens) {
2162
- if (SHELL_CONTROL_TOKENS.has(token)) {
2163
- if (current.length > 0) {
2164
- segments.push(current);
2165
- current = [];
2166
- }
2167
- continue;
2168
- }
2169
- current.push(token);
2170
- }
2171
- if (current.length > 0) {
2172
- segments.push(current);
2173
- }
2174
- return segments;
2175
- };
2176
- var firstExecutableIndex = (segment) => {
2177
- let index = 0;
2178
- while (index < segment.length && isAssignmentToken(segment[index])) {
2179
- index += 1;
2180
- }
2181
- if (segment[index] === "env") {
2182
- index += 1;
2183
- while (index < segment.length && isAssignmentToken(segment[index])) {
2184
- index += 1;
2185
- }
2186
- }
2187
- if (segment[index] === "command" || segment[index] === "sudo") {
2188
- index += 1;
2189
- }
2190
- return index;
2191
- };
2192
- var segmentInvokesGitCommit = (segment) => {
2193
- const executableIndex = firstExecutableIndex(segment);
2194
- if (executableIndex >= segment.length || basename(segment[executableIndex]) !== "git") {
2195
- return false;
2196
- }
2197
- for (let index = executableIndex + 1; index < segment.length; index += 1) {
2198
- const token = segment[index];
2199
- if (GIT_GLOBAL_OPTIONS_WITH_VALUE.has(token)) {
2200
- index += 1;
2201
- continue;
2202
- }
2203
- if (token.startsWith("-")) {
2204
- continue;
2205
- }
2206
- return token === "commit";
2207
- }
2208
- return false;
2209
- };
2210
- var segmentInvokesShellScriptWithGitCommit = (segment) => {
2211
- const executableIndex = firstExecutableIndex(segment);
2212
- const executable = segment[executableIndex];
2213
- if (!executable || !["bash", "sh", "zsh"].includes(basename(executable))) {
2214
- return false;
2215
- }
2216
- for (let index = executableIndex + 1; index < segment.length - 1; index += 1) {
2217
- if (SHELL_SCRIPT_FLAGS.has(segment[index]) && isGitCommitCommand(segment[index + 1])) {
2218
- return true;
2219
- }
2220
- }
2221
- return false;
2222
- };
2223
- var isGitCommitCommand = (command) => {
2224
- const segments = splitCommandSegments(tokenizeShellLike(command));
2225
- return segments.some((segment) => segmentInvokesGitCommit(segment) || segmentInvokesShellScriptWithGitCommit(segment));
2226
- };
2227
2379
  var buildContextPromotionReviewPrompt = (status) => {
2228
2380
  const projectRoot = status.gitRoot ?? status.cwd;
2229
2381
  const cdCommand = `cd ${JSON.stringify(projectRoot)}`;
@@ -2279,107 +2431,388 @@ var buildPostToolUseOutput = (prompt) => ({
2279
2431
  additionalContext: prompt
2280
2432
  }
2281
2433
  });
2282
- var isJsonRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2283
- var numberField = (record, keys) => {
2284
- for (const key of keys) {
2285
- const value = record[key];
2286
- if (typeof value === "number") {
2434
+ var evaluateContextPromotionPostToolUseHook = (params) => {
2435
+ const hookInput = parseSuccessfulGitCommitPostToolUseHook(params.hookInput);
2436
+ if (!hookInput) {
2437
+ return null;
2438
+ }
2439
+ let status;
2440
+ try {
2441
+ status = getContextPromotionStatus({
2442
+ cwd: hookInput.cwd,
2443
+ userBasePath: params.userBasePath
2444
+ });
2445
+ } catch (error) {
2446
+ return buildPostToolUseOutput(buildContextPromotionStatusFailurePrompt(hookInput.cwd, error));
2447
+ }
2448
+ if (!status.hasContextLayer || status.receipt) {
2449
+ return null;
2450
+ }
2451
+ return buildPostToolUseOutput(buildContextPromotionReviewPrompt(status));
2452
+ };
2453
+
2454
+ // src/core/pc-integration.ts
2455
+ import { execFileSync as execFileSync2 } from "child_process";
2456
+ import { existsSync as existsSync4, readFileSync as readFileSync9, readdirSync as readdirSync5 } from "fs";
2457
+ import { join as join11, resolve as resolve8, sep } from "path";
2458
+ var normalizePath = (path) => resolve8(path.replace(/^~(?=$|\/)/, process.env.HOME ?? "~"));
2459
+ var pathContains = (parentPath, childPath) => {
2460
+ const parent = normalizePath(parentPath);
2461
+ const child = normalizePath(childPath);
2462
+ return child === parent || child.startsWith(`${parent}${sep}`);
2463
+ };
2464
+ var normalizeFieldValue = (value) => {
2465
+ const trimmed = value?.trim() ?? "";
2466
+ if (trimmed.length === 0 || ["none", "null", "-", "<empty>"].includes(trimmed.toLowerCase())) {
2467
+ return null;
2468
+ }
2469
+ return trimmed.replace(/^`|`$/g, "");
2470
+ };
2471
+ var extractSection = (content, headings) => {
2472
+ const headingPattern = headings.map((heading) => heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
2473
+ const match = new RegExp(`^##\\s+(?:${headingPattern})\\s*$`, "mu").exec(content);
2474
+ if (!match) {
2475
+ return "";
2476
+ }
2477
+ const start = match.index + match[0].length;
2478
+ const rest = content.slice(start);
2479
+ const nextHeading = /^##\s+/mu.exec(rest);
2480
+ return nextHeading ? rest.slice(0, nextHeading.index) : rest;
2481
+ };
2482
+ var parseListField = (content, labels) => {
2483
+ for (const label of labels) {
2484
+ const escapedLabel = label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2485
+ const match = new RegExp(`^-\\s+${escapedLabel}:\\s*(.+)$`, "mu").exec(content);
2486
+ const value = normalizeFieldValue(match?.[1] ?? null);
2487
+ if (value) {
2287
2488
  return value;
2288
2489
  }
2289
2490
  }
2290
- return null;
2491
+ return null;
2492
+ };
2493
+ var readTextFileOrNull = (filePath) => {
2494
+ try {
2495
+ return readFileSync9(filePath, "utf-8");
2496
+ } catch {
2497
+ return null;
2498
+ }
2499
+ };
2500
+ var runGit2 = (cwd, args) => execFileSync2("git", [...args], {
2501
+ cwd,
2502
+ encoding: "utf-8",
2503
+ stdio: ["ignore", "pipe", "ignore"]
2504
+ }).trim();
2505
+ var resolveGitRoot = (cwd) => {
2506
+ try {
2507
+ return runGit2(cwd, ["rev-parse", "--show-toplevel"]);
2508
+ } catch {
2509
+ return null;
2510
+ }
2511
+ };
2512
+ var readGitHead2 = (cwd) => {
2513
+ try {
2514
+ return runGit2(cwd, ["rev-parse", "--verify", "HEAD"]);
2515
+ } catch {
2516
+ return null;
2517
+ }
2518
+ };
2519
+ var listWorkspaceStatePaths = (contextRoot) => {
2520
+ const workspacesDir = join11(contextRoot, "workspaces");
2521
+ if (!existsSync4(workspacesDir)) {
2522
+ return [];
2523
+ }
2524
+ return readdirSync5(workspacesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join11(workspacesDir, entry.name, "workspace-state.md")).filter((statePath) => existsSync4(statePath)).sort((a, b) => a.localeCompare(b));
2525
+ };
2526
+ var parseWorkspaceCandidate = (statePath) => {
2527
+ const content = readTextFileOrNull(statePath);
2528
+ if (!content) {
2529
+ return null;
2530
+ }
2531
+ const workspaceRoot = parseListField(content, ["\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uB8E8\uD2B8", "Workspace Root"]);
2532
+ if (!workspaceRoot) {
2533
+ return null;
2534
+ }
2535
+ const activeSection = extractSection(content, ["\uD65C\uC131 Workstream", "Active Workstream"]);
2536
+ const activeWorkstreamId = parseListField(activeSection, ["ID", "Workstream ID", "Active Workstream"]);
2537
+ const workspaceDir = resolve8(statePath, "..");
2538
+ const id = parseListField(content, ["\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 ID", "Workspace ID"]) ?? workspaceDir.split(sep).at(-1) ?? "unknown";
2539
+ return {
2540
+ id,
2541
+ statePath,
2542
+ workspaceDir,
2543
+ workspaceRoot,
2544
+ activeWorkstreamId
2545
+ };
2546
+ };
2547
+ var findMatchingWorkspace = (params) => {
2548
+ const candidates = listWorkspaceStatePaths(params.contextRoot).map(parseWorkspaceCandidate).filter((candidate) => candidate !== null).filter((candidate) => pathContains(candidate.workspaceRoot, params.cwd)).sort((a, b) => normalizePath(b.workspaceRoot).length - normalizePath(a.workspaceRoot).length);
2549
+ return candidates[0] ?? null;
2550
+ };
2551
+ var parseRepoEntry = (entryPath) => {
2552
+ const content = readTextFileOrNull(entryPath);
2553
+ if (!content) {
2554
+ return null;
2555
+ }
2556
+ const id = parseListField(content, ["\uC5D4\uD2B8\uB9AC ID", "Entry ID"]);
2557
+ if (!id) {
2558
+ return null;
2559
+ }
2560
+ return {
2561
+ id,
2562
+ path: parseListField(content, ["\uACBD\uB85C", "Path"]),
2563
+ gitRoot: parseListField(content, ["Git \uB8E8\uD2B8", "Git Root"])
2564
+ };
2565
+ };
2566
+ var findCurrentEntry = (params) => {
2567
+ const reposDir = join11(params.workspaceDir, "repos");
2568
+ if (!existsSync4(reposDir)) {
2569
+ return null;
2570
+ }
2571
+ const entries = readdirSync5(reposDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => parseRepoEntry(join11(reposDir, entry.name))).filter((entry) => entry !== null).filter((entry) => {
2572
+ const paths = [entry.path, entry.gitRoot].filter((path) => path !== null);
2573
+ return paths.some((path) => pathContains(path, params.cwd));
2574
+ }).sort((a, b) => {
2575
+ const aLength = Math.max(0, ...[a.path, a.gitRoot].map((path) => path ? normalizePath(path).length : 0));
2576
+ const bLength = Math.max(0, ...[b.path, b.gitRoot].map((path) => path ? normalizePath(path).length : 0));
2577
+ return bLength - aLength;
2578
+ });
2579
+ return entries[0] ?? null;
2580
+ };
2581
+ var parseWorkstreamScopeEntryIds = (content) => {
2582
+ const scopeSection = extractSection(content, ["\uBC94\uC704", "Scope"]);
2583
+ if (scopeSection.length === 0) {
2584
+ return [];
2585
+ }
2586
+ const lines = scopeSection.split("\n");
2587
+ const ids = [];
2588
+ let inEntryBlock = false;
2589
+ for (const line of lines) {
2590
+ if (/^-\s*(엔트리|Entries|Entry):/.test(line)) {
2591
+ inEntryBlock = true;
2592
+ const inlineValue = normalizeFieldValue(line.split(":").slice(1).join(":"));
2593
+ if (inlineValue && !inlineValue.includes("<")) {
2594
+ ids.push(...inlineValue.split(",").map((value) => value.trim().replace(/^`|`$/g, "")));
2595
+ }
2596
+ continue;
2597
+ }
2598
+ if (inEntryBlock && /^-\s+\S/.test(line)) {
2599
+ inEntryBlock = false;
2600
+ }
2601
+ if (!inEntryBlock) {
2602
+ continue;
2603
+ }
2604
+ const nestedMatch = /^\s+-\s+`?([a-z0-9]+(?:-[a-z0-9]+)*)`?/.exec(line);
2605
+ if (nestedMatch) {
2606
+ ids.push(nestedMatch[1]);
2607
+ }
2608
+ }
2609
+ return [...new Set(ids.filter((id) => /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(id)))];
2291
2610
  };
2292
- var booleanField = (record, keys) => {
2293
- for (const key of keys) {
2294
- const value = record[key];
2295
- if (typeof value === "boolean") {
2296
- return value;
2297
- }
2611
+ var parseLastConfirmedCommitHash = (params) => {
2612
+ const section = extractSection(params.content, ["\uB9C8\uC9C0\uB9C9 \uD655\uC778 Commit", "Last Confirmed Commit"]);
2613
+ if (section.length === 0) {
2614
+ return null;
2298
2615
  }
2299
- return null;
2616
+ const escapedEntryId = params.entryId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2617
+ const match = new RegExp(`^-\\s+\`?${escapedEntryId}\`?:\\s*([a-f0-9]{40})\\b`, "imu").exec(section);
2618
+ return match?.[1] ?? null;
2300
2619
  };
2301
- var GIT_COMMIT_FAILURE_OUTPUT_PATTERNS = [
2302
- /(^|\n)\s*fatal:/i,
2303
- /(^|\n)\s*error:/i,
2304
- /(^|\n)\s*nothing to commit\b/i,
2305
- /(^|\n)\s*no changes added to commit\b/i,
2306
- /(^|\n).*aborting commit\b/i,
2307
- /(^|\n).*commit failed\b/i,
2308
- /(^|\n).*failed to .*commit\b/i,
2309
- /(^|\n).*command failed\b/i,
2310
- /(^|\n).*non-zero exit\b/i,
2311
- /(^|\n).*exit (code|status)\s+[1-9]\d*\b/i,
2312
- /(^|\n).*exited with code\s+[1-9]\d*\b/i,
2313
- /(^|\n).*hook.*(failed|declined|error|exit(?:ed)? with code|non-zero)/i
2314
- ];
2315
- var GIT_COMMIT_SUCCESS_OUTPUT_PATTERN = /(^|\n)\[[^\]\n]+ [a-f0-9]{7,40}\]/i;
2316
- var stringIndicatesGitCommitSuccess = (output) => GIT_COMMIT_SUCCESS_OUTPUT_PATTERN.test(output);
2317
- var stringIndicatesGitCommitFailureOrSuccess = (output) => stringIndicatesGitCommitSuccess(output) ? false : GIT_COMMIT_FAILURE_OUTPUT_PATTERNS.some((pattern) => pattern.test(output)) ? true : null;
2318
- var recordStringFieldsIndicateGitCommitFailure = (record) => ["message", "output", "stdout", "stderr", "error", "combinedOutput"].some((key) => {
2319
- const value = record[key];
2320
- return typeof value === "string" && stringIndicatesGitCommitFailureOrSuccess(value) === true;
2321
- });
2322
- var toolResponseIndicatesFailure = (toolResponse) => {
2323
- if (typeof toolResponse === "string") {
2324
- return stringIndicatesGitCommitFailureOrSuccess(toolResponse) === true;
2620
+ var getPcHandoffStatus = (params) => {
2621
+ const cwd = normalizePath(params.cwd);
2622
+ const contextRoot = normalizePath(params.contextRoot);
2623
+ if (!existsSync4(contextRoot)) {
2624
+ return {
2625
+ cwd,
2626
+ contextRoot,
2627
+ workspaceId: null,
2628
+ workspaceRoot: null,
2629
+ activeWorkstreamId: null,
2630
+ activeWorkstreamPath: null,
2631
+ currentEntryId: null,
2632
+ lastConfirmedCommitHash: null,
2633
+ ready: false,
2634
+ skipReason: "pc context root not found"
2635
+ };
2325
2636
  }
2326
- if (!isJsonRecord(toolResponse)) {
2327
- return false;
2637
+ const workspace = findMatchingWorkspace({ cwd, contextRoot });
2638
+ if (!workspace) {
2639
+ return {
2640
+ cwd,
2641
+ contextRoot,
2642
+ workspaceId: null,
2643
+ workspaceRoot: null,
2644
+ activeWorkstreamId: null,
2645
+ activeWorkstreamPath: null,
2646
+ currentEntryId: null,
2647
+ lastConfirmedCommitHash: null,
2648
+ ready: false,
2649
+ skipReason: "matching pc workspace not found"
2650
+ };
2328
2651
  }
2329
- const success = booleanField(toolResponse, ["success", "ok"]);
2330
- if (success === false) {
2331
- return true;
2652
+ if (!workspace.activeWorkstreamId) {
2653
+ return {
2654
+ cwd,
2655
+ contextRoot,
2656
+ workspaceId: workspace.id,
2657
+ workspaceRoot: workspace.workspaceRoot,
2658
+ activeWorkstreamId: null,
2659
+ activeWorkstreamPath: null,
2660
+ currentEntryId: null,
2661
+ lastConfirmedCommitHash: null,
2662
+ ready: false,
2663
+ skipReason: "active pc workstream not selected"
2664
+ };
2332
2665
  }
2333
- const exitCode = numberField(toolResponse, ["exit_code", "exitCode", "status", "code"]);
2334
- if (exitCode !== null && exitCode !== 0) {
2335
- return true;
2666
+ const activeWorkstreamPath = join11(workspace.workspaceDir, "workstreams", `${workspace.activeWorkstreamId}.md`);
2667
+ const activeWorkstreamContent = readTextFileOrNull(activeWorkstreamPath);
2668
+ if (!activeWorkstreamContent) {
2669
+ return {
2670
+ cwd,
2671
+ contextRoot,
2672
+ workspaceId: workspace.id,
2673
+ workspaceRoot: workspace.workspaceRoot,
2674
+ activeWorkstreamId: workspace.activeWorkstreamId,
2675
+ activeWorkstreamPath,
2676
+ currentEntryId: null,
2677
+ lastConfirmedCommitHash: null,
2678
+ ready: false,
2679
+ skipReason: "active pc workstream file not found"
2680
+ };
2336
2681
  }
2337
- return recordStringFieldsIndicateGitCommitFailure(toolResponse);
2682
+ const currentEntry = findCurrentEntry({ cwd, workspaceDir: workspace.workspaceDir });
2683
+ if (!currentEntry) {
2684
+ return {
2685
+ cwd,
2686
+ contextRoot,
2687
+ workspaceId: workspace.id,
2688
+ workspaceRoot: workspace.workspaceRoot,
2689
+ activeWorkstreamId: workspace.activeWorkstreamId,
2690
+ activeWorkstreamPath,
2691
+ currentEntryId: null,
2692
+ lastConfirmedCommitHash: null,
2693
+ ready: false,
2694
+ skipReason: "current repo is not registered in pc workspace"
2695
+ };
2696
+ }
2697
+ const lastConfirmedCommitHash = parseLastConfirmedCommitHash({
2698
+ content: activeWorkstreamContent,
2699
+ entryId: currentEntry.id
2700
+ });
2701
+ const scopeEntryIds = parseWorkstreamScopeEntryIds(activeWorkstreamContent);
2702
+ if (scopeEntryIds.length > 0 && !scopeEntryIds.includes(currentEntry.id)) {
2703
+ return {
2704
+ cwd,
2705
+ contextRoot,
2706
+ workspaceId: workspace.id,
2707
+ workspaceRoot: workspace.workspaceRoot,
2708
+ activeWorkstreamId: workspace.activeWorkstreamId,
2709
+ activeWorkstreamPath,
2710
+ currentEntryId: currentEntry.id,
2711
+ lastConfirmedCommitHash,
2712
+ ready: false,
2713
+ skipReason: "current repo is outside the active pc workstream scope"
2714
+ };
2715
+ }
2716
+ return {
2717
+ cwd,
2718
+ contextRoot,
2719
+ workspaceId: workspace.id,
2720
+ workspaceRoot: workspace.workspaceRoot,
2721
+ activeWorkstreamId: workspace.activeWorkstreamId,
2722
+ activeWorkstreamPath,
2723
+ currentEntryId: currentEntry.id,
2724
+ lastConfirmedCommitHash,
2725
+ ready: true,
2726
+ skipReason: null
2727
+ };
2338
2728
  };
2339
- var evaluateContextPromotionPostToolUseHook = (params) => {
2340
- const hookInput = ToolUseHookInputSchema.safeParse(params.hookInput);
2341
- if (!hookInput.success) {
2729
+ var buildPcDonePrompt = (params) => [
2730
+ "A successful git commit just created a new HEAD commit.",
2731
+ "",
2732
+ "Run `$pc:done` now to record the handoff for the active personal project context.",
2733
+ "",
2734
+ "Important guardrails:",
2735
+ "- Do not create or initialize a new pc context from this hook.",
2736
+ "- If `$pc:done` cannot match the prepared workspace, active workstream, or current repo scope, skip and briefly say why.",
2737
+ "- If the active workstream already records this HEAD as the last confirmed commit, skip without writing another handoff.",
2738
+ "- Do not modify the product repo for this hook; `$pc:done` may only update `~/.personal-project-contexts/` and commit that context repo.",
2739
+ "- Use the just-created HEAD commit as the newest evidence for completed work and the next first action.",
2740
+ "",
2741
+ `Project git root: ${params.gitRoot}`,
2742
+ `HEAD: ${params.head}`,
2743
+ `pc context root: ${params.status.contextRoot}`,
2744
+ `pc workspace: ${params.status.workspaceId ?? "unknown"} (${params.status.workspaceRoot ?? "unknown"})`,
2745
+ `active workstream: ${params.status.activeWorkstreamId ?? "unknown"}`,
2746
+ `current entry: ${params.status.currentEntryId ?? "unknown"}`,
2747
+ `last confirmed commit: ${params.status.lastConfirmedCommitHash ?? "none"}`
2748
+ ].join("\n");
2749
+ var buildPostToolUseOutput2 = (prompt) => ({
2750
+ decision: "block",
2751
+ reason: prompt,
2752
+ hookSpecificOutput: {
2753
+ hookEventName: "PostToolUse",
2754
+ additionalContext: prompt
2755
+ }
2756
+ });
2757
+ var evaluatePcPostToolUseHook = (params) => {
2758
+ const gitCommitHook = parseSuccessfulGitCommitPostToolUseHook(params.hookInput);
2759
+ if (!gitCommitHook) {
2342
2760
  return null;
2343
2761
  }
2344
- if (hookInput.data.hook_event_name !== "PostToolUse" || hookInput.data.tool_name !== "Bash") {
2762
+ const gitRoot = resolveGitRoot(gitCommitHook.cwd);
2763
+ if (!gitRoot) {
2345
2764
  return null;
2346
2765
  }
2347
- const toolInput = HookToolInputSchema.safeParse(hookInput.data.tool_input);
2348
- const command = toolInput.success ? toolInput.data.command ?? "" : "";
2349
- if (!isGitCommitCommand(command) || toolResponseIndicatesFailure(hookInput.data.tool_response)) {
2766
+ const head = readGitHead2(gitRoot);
2767
+ if (!head) {
2350
2768
  return null;
2351
2769
  }
2352
- let status;
2353
- try {
2354
- status = getContextPromotionStatus({
2355
- cwd: hookInput.data.cwd,
2356
- userBasePath: params.userBasePath
2357
- });
2358
- } catch (error) {
2359
- return buildPostToolUseOutput(buildContextPromotionStatusFailurePrompt(hookInput.data.cwd, error));
2770
+ const status = getPcHandoffStatus({
2771
+ cwd: gitCommitHook.cwd,
2772
+ contextRoot: params.contextRoot
2773
+ });
2774
+ if (!status.ready) {
2775
+ return null;
2360
2776
  }
2361
- if (!status.hasContextLayer || status.receipt) {
2777
+ if (status.lastConfirmedCommitHash === head) {
2362
2778
  return null;
2363
2779
  }
2364
- return buildPostToolUseOutput(buildContextPromotionReviewPrompt(status));
2780
+ return buildPostToolUseOutput2(buildPcDonePrompt({ status, head, gitRoot }));
2365
2781
  };
2366
2782
 
2367
2783
  // src/core/codex-hook.ts
2368
- import { existsSync as existsSync4, mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
2369
- import { dirname as dirname9, join as join14 } from "path";
2784
+ import { existsSync as existsSync5, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "fs";
2785
+ import { dirname as dirname9, join as join12 } from "path";
2370
2786
  var CONTEXT_PROMOTION_HOOK_ID = "context-promotion";
2371
2787
  var CONTEXT_PROMOTION_HOOK_COMMAND_MARKER = "context-promotion hook post-tool-use";
2372
2788
  var CONTEXT_PROMOTION_LEGACY_HOOK_COMMAND_MARKER = "context-promotion hook pre-tool-use";
2373
2789
  var CONTEXT_PROMOTION_DEFAULT_HOOK_COMMAND = `ai-ops ${CONTEXT_PROMOTION_HOOK_COMMAND_MARKER}`;
2790
+ var PC_HOOK_ID = "pc";
2791
+ var PC_HOOK_COMMAND_MARKER = "integration hook post-tool-use pc";
2792
+ var PC_DEFAULT_HOOK_COMMAND = `ai-ops ${PC_HOOK_COMMAND_MARKER}`;
2374
2793
  var PRE_TOOL_USE_EVENT = "PreToolUse";
2375
2794
  var POST_TOOL_USE_EVENT = "PostToolUse";
2376
2795
  var BASH_MATCHER = "^Bash$";
2796
+ var CONTEXT_PROMOTION_CODEX_HOOK = {
2797
+ id: CONTEXT_PROMOTION_HOOK_ID,
2798
+ commandMarker: CONTEXT_PROMOTION_HOOK_COMMAND_MARKER,
2799
+ legacyCommandMarkers: [CONTEXT_PROMOTION_LEGACY_HOOK_COMMAND_MARKER],
2800
+ defaultCommand: CONTEXT_PROMOTION_DEFAULT_HOOK_COMMAND,
2801
+ statusMessage: "Checking context promotion review"
2802
+ };
2803
+ var PC_CODEX_HOOK = {
2804
+ id: PC_HOOK_ID,
2805
+ commandMarker: PC_HOOK_COMMAND_MARKER,
2806
+ legacyCommandMarkers: [],
2807
+ defaultCommand: PC_DEFAULT_HOOK_COMMAND,
2808
+ statusMessage: "Checking pc handoff"
2809
+ };
2377
2810
  var isJsonRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2378
2811
  var readJsonRecord = (filePath) => {
2379
- if (!existsSync4(filePath)) {
2812
+ if (!existsSync5(filePath)) {
2380
2813
  return {};
2381
2814
  }
2382
- const parsed = JSON.parse(readFileSync9(filePath, "utf-8"));
2815
+ const parsed = JSON.parse(readFileSync10(filePath, "utf-8"));
2383
2816
  if (!isJsonRecord2(parsed)) {
2384
2817
  throw new Error("hooks.json must contain a JSON object");
2385
2818
  }
@@ -2402,34 +2835,34 @@ var getArray = (record, key) => {
2402
2835
  const existing = record[key];
2403
2836
  return Array.isArray(existing) ? existing : [];
2404
2837
  };
2405
- var handlerMatchesContextPromotion = (handler) => isJsonRecord2(handler) && typeof handler.command === "string" && (handler.command.includes(CONTEXT_PROMOTION_HOOK_COMMAND_MARKER) || handler.command.includes(CONTEXT_PROMOTION_LEGACY_HOOK_COMMAND_MARKER));
2838
+ var handlerMatchesDefinition = (definition) => (handler) => isJsonRecord2(handler) && typeof handler.command === "string" && [definition.commandMarker, ...definition.legacyCommandMarkers].some((marker) => handler.command.includes(marker));
2406
2839
  var handlerMatchesCommand = (handler, command) => isJsonRecord2(handler) && handler.command === command;
2407
- var groupHasContextPromotionHook = (group) => isJsonRecord2(group) && getArray(group, "hooks").some(handlerMatchesContextPromotion);
2408
- var groupHasCurrentContextPromotionHook = (group, command) => isJsonRecord2(group) && getArray(group, "hooks").some((handler) => handlerMatchesCommand(handler, command));
2409
- var countContextPromotionHandlers = (groups) => groups.reduce((count, group) => {
2840
+ var groupHasDefinitionHook = (definition) => (group) => isJsonRecord2(group) && getArray(group, "hooks").some(handlerMatchesDefinition(definition));
2841
+ var groupHasCurrentDefinitionHook = (group, command) => isJsonRecord2(group) && getArray(group, "hooks").some((handler) => handlerMatchesCommand(handler, command));
2842
+ var countDefinitionHandlers = (groups, definition) => groups.reduce((count, group) => {
2410
2843
  if (!isJsonRecord2(group)) {
2411
2844
  return count;
2412
2845
  }
2413
- return count + getArray(group, "hooks").filter(handlerMatchesContextPromotion).length;
2846
+ return count + getArray(group, "hooks").filter(handlerMatchesDefinition(definition)).length;
2414
2847
  }, 0);
2415
- var configHasContextPromotionHook = (config) => {
2848
+ var configHasDefinitionHook = (config, definition) => {
2416
2849
  const hooks = config.hooks;
2417
2850
  if (!isJsonRecord2(hooks)) {
2418
2851
  return false;
2419
2852
  }
2420
- return getArray(hooks, POST_TOOL_USE_EVENT).some(groupHasContextPromotionHook);
2853
+ return getArray(hooks, POST_TOOL_USE_EVENT).some(groupHasDefinitionHook(definition));
2421
2854
  };
2422
- var configHasOnlyCurrentContextPromotionHook = (config, command) => {
2855
+ var configHasOnlyCurrentDefinitionHook = (config, definition, command) => {
2423
2856
  const hooks = config.hooks;
2424
2857
  if (!isJsonRecord2(hooks)) {
2425
2858
  return false;
2426
2859
  }
2427
- const hasLegacy = getArray(hooks, PRE_TOOL_USE_EVENT).some(groupHasContextPromotionHook);
2860
+ const hasLegacy = getArray(hooks, PRE_TOOL_USE_EVENT).some(groupHasDefinitionHook(definition));
2428
2861
  const postGroups = getArray(hooks, POST_TOOL_USE_EVENT);
2429
- const hasCurrent = postGroups.some((group) => groupHasCurrentContextPromotionHook(group, command));
2430
- return hasCurrent && !hasLegacy && countContextPromotionHandlers(postGroups) === 1;
2862
+ const hasCurrent = postGroups.some((group) => groupHasCurrentDefinitionHook(group, command));
2863
+ return hasCurrent && !hasLegacy && countDefinitionHandlers(postGroups, definition) === 1;
2431
2864
  };
2432
- var removeContextPromotionHooksFromEvent = (hooks, eventName) => {
2865
+ var removeDefinitionHooksFromEvent = (hooks, eventName, definition) => {
2433
2866
  const previousGroups = getArray(hooks, eventName);
2434
2867
  let removed = false;
2435
2868
  const nextGroups = previousGroups.map((group) => {
@@ -2438,7 +2871,7 @@ var removeContextPromotionHooksFromEvent = (hooks, eventName) => {
2438
2871
  }
2439
2872
  const previousHandlers = getArray(group, "hooks");
2440
2873
  const nextHandlers = previousHandlers.filter((handler) => {
2441
- const matches = handlerMatchesContextPromotion(handler);
2874
+ const matches = handlerMatchesDefinition(definition)(handler);
2442
2875
  if (matches) {
2443
2876
  removed = true;
2444
2877
  }
@@ -2462,21 +2895,29 @@ var removeContextPromotionHooksFromEvent = (hooks, eventName) => {
2462
2895
  }
2463
2896
  return true;
2464
2897
  };
2465
- var resolveCodexHooksPath = (codexHomePath) => join14(codexHomePath, "hooks.json");
2466
- var buildContextPromotionHookCommand = (overrideCommand) => {
2467
- const command = overrideCommand?.trim() ?? CONTEXT_PROMOTION_DEFAULT_HOOK_COMMAND;
2468
- if (!command.includes(CONTEXT_PROMOTION_HOOK_COMMAND_MARKER)) {
2469
- throw new Error(`context promotion hook command must include: ${CONTEXT_PROMOTION_HOOK_COMMAND_MARKER}`);
2898
+ var resolveCodexHooksPath = (codexHomePath) => join12(codexHomePath, "hooks.json");
2899
+ var buildCodexHookCommand = (params) => {
2900
+ const command = params.overrideCommand?.trim() ?? params.definition.defaultCommand;
2901
+ if (!command.includes(params.definition.commandMarker)) {
2902
+ throw new Error(`${params.definition.id} hook command must include: ${params.definition.commandMarker}`);
2470
2903
  }
2471
2904
  return command;
2472
2905
  };
2906
+ var buildContextPromotionHookCommand = (overrideCommand) => buildCodexHookCommand({
2907
+ definition: CONTEXT_PROMOTION_CODEX_HOOK,
2908
+ overrideCommand
2909
+ });
2910
+ var inspectCodexHook = (params) => ({
2911
+ hooksPath: params.hooksPath,
2912
+ installed: configHasDefinitionHook(readJsonRecord(params.hooksPath), params.definition)
2913
+ });
2473
2914
  var inspectContextPromotionHook = (hooksPath) => ({
2474
2915
  hooksPath,
2475
- installed: configHasContextPromotionHook(readJsonRecord(hooksPath))
2916
+ installed: inspectCodexHook({ hooksPath, definition: CONTEXT_PROMOTION_CODEX_HOOK }).installed
2476
2917
  });
2477
- var installContextPromotionHook = (params) => {
2918
+ var installCodexHook = (params) => {
2478
2919
  const config = readJsonRecord(params.hooksPath);
2479
- if (configHasOnlyCurrentContextPromotionHook(config, params.command)) {
2920
+ if (configHasOnlyCurrentDefinitionHook(config, params.definition, params.command)) {
2480
2921
  return {
2481
2922
  hooksPath: params.hooksPath,
2482
2923
  installed: true,
@@ -2484,8 +2925,8 @@ var installContextPromotionHook = (params) => {
2484
2925
  };
2485
2926
  }
2486
2927
  const hooks = getOrCreateRecord(config, "hooks");
2487
- removeContextPromotionHooksFromEvent(hooks, PRE_TOOL_USE_EVENT);
2488
- removeContextPromotionHooksFromEvent(hooks, POST_TOOL_USE_EVENT);
2928
+ removeDefinitionHooksFromEvent(hooks, PRE_TOOL_USE_EVENT, params.definition);
2929
+ removeDefinitionHooksFromEvent(hooks, POST_TOOL_USE_EVENT, params.definition);
2489
2930
  const existingGroups = getArray(hooks, POST_TOOL_USE_EVENT);
2490
2931
  const nextGroup = {
2491
2932
  matcher: BASH_MATCHER,
@@ -2494,7 +2935,7 @@ var installContextPromotionHook = (params) => {
2494
2935
  type: "command",
2495
2936
  command: params.command,
2496
2937
  timeout: 30,
2497
- statusMessage: "Checking context promotion review"
2938
+ statusMessage: params.definition.statusMessage
2498
2939
  }
2499
2940
  ]
2500
2941
  };
@@ -2506,32 +2947,42 @@ var installContextPromotionHook = (params) => {
2506
2947
  changed: true
2507
2948
  };
2508
2949
  };
2509
- var uninstallContextPromotionHook = (hooksPath) => {
2510
- const config = readJsonRecord(hooksPath);
2950
+ var installContextPromotionHook = (params) => installCodexHook({
2951
+ hooksPath: params.hooksPath,
2952
+ definition: CONTEXT_PROMOTION_CODEX_HOOK,
2953
+ command: params.command
2954
+ });
2955
+ var uninstallCodexHook = (params) => {
2956
+ const config = readJsonRecord(params.hooksPath);
2511
2957
  const hooks = config.hooks;
2512
2958
  if (!isJsonRecord2(hooks)) {
2513
- return { hooksPath, removed: false, changed: false };
2959
+ return { hooksPath: params.hooksPath, removed: false, changed: false };
2514
2960
  }
2515
- const removedLegacy = removeContextPromotionHooksFromEvent(hooks, PRE_TOOL_USE_EVENT);
2516
- const removedCurrent = removeContextPromotionHooksFromEvent(hooks, POST_TOOL_USE_EVENT);
2961
+ const removedLegacy = removeDefinitionHooksFromEvent(hooks, PRE_TOOL_USE_EVENT, params.definition);
2962
+ const removedCurrent = removeDefinitionHooksFromEvent(hooks, POST_TOOL_USE_EVENT, params.definition);
2517
2963
  const removed = removedLegacy || removedCurrent;
2518
2964
  if (!removed) {
2519
- return { hooksPath, removed: false, changed: false };
2965
+ return { hooksPath: params.hooksPath, removed: false, changed: false };
2520
2966
  }
2521
- writeJsonRecord(hooksPath, config);
2522
- return { hooksPath, removed: true, changed: true };
2967
+ writeJsonRecord(params.hooksPath, config);
2968
+ return { hooksPath: params.hooksPath, removed: true, changed: true };
2523
2969
  };
2970
+ var uninstallContextPromotionHook = (hooksPath) => uninstallCodexHook({
2971
+ hooksPath,
2972
+ definition: CONTEXT_PROMOTION_CODEX_HOOK
2973
+ });
2524
2974
 
2525
2975
  // src/lib/paths.ts
2526
- import { join as join15 } from "path";
2527
- var resolveSkillsDir = () => join15(COMPILER_DATA_DIR, "skills");
2528
- var resolveSubagentsDir = () => join15(COMPILER_DATA_DIR, "subagents");
2529
- var resolvePacksDir = () => join15(COMPILER_DATA_DIR, "packs");
2976
+ import { join as join13 } from "path";
2977
+ var resolveSkillsDir = () => join13(COMPILER_DATA_DIR, "skills");
2978
+ var resolveSubagentsDir = () => join13(COMPILER_DATA_DIR, "subagents");
2979
+ var resolvePacksDir = () => join13(COMPILER_DATA_DIR, "packs");
2980
+ var resolveIntegrationsDir = () => join13(COMPILER_DATA_DIR, "integrations");
2530
2981
  var resolveBasePath = () => process.cwd();
2531
2982
  var resolveUserBasePath = () => {
2532
2983
  const userBasePath = process.env.AI_OPS_HOME ?? process.env.HOME;
2533
2984
  if (!userBasePath) {
2534
- throw new Error("AI_OPS_HOME or HOME is required for global asset commands");
2985
+ throw new Error("AI_OPS_HOME or HOME is required for user/global component commands");
2535
2986
  }
2536
2987
  return userBasePath;
2537
2988
  };
@@ -2776,7 +3227,7 @@ ${result.notFound.map((file) => ` ${file}`).join("\n")}`);
2776
3227
 
2777
3228
  // src/commands/skill.ts
2778
3229
  import * as p7 from "@clack/prompts";
2779
- import { rmSync as rmSync4 } from "fs";
3230
+ import { rmSync as rmSync5 } from "fs";
2780
3231
 
2781
3232
  // src/lib/skill-state.ts
2782
3233
  var resolveRequestedTools = (params) => {
@@ -2810,17 +3261,17 @@ var findInstalledSkill = (installedSkills, skillId) => {
2810
3261
  };
2811
3262
 
2812
3263
  // src/lib/skill-install.ts
2813
- import { existsSync as existsSync5, mkdirSync as mkdirSync8, rmSync as rmSync3, writeFileSync as writeFileSync8 } from "fs";
2814
- import { dirname as dirname10, resolve as resolve8 } from "path";
3264
+ import { existsSync as existsSync6, mkdirSync as mkdirSync8, rmSync as rmSync4, writeFileSync as writeFileSync8 } from "fs";
3265
+ import { dirname as dirname10, resolve as resolve9 } from "path";
2815
3266
  var installSkillPackages = (basePath, packages) => {
2816
3267
  const writtenRoots = [];
2817
3268
  for (const skillPackage of packages) {
2818
- const absRoot = resolve8(basePath, skillPackage.rootDir);
2819
- if (existsSync5(absRoot)) {
2820
- rmSync3(absRoot, { recursive: true, force: true });
3269
+ const absRoot = resolve9(basePath, skillPackage.rootDir);
3270
+ if (existsSync6(absRoot)) {
3271
+ rmSync4(absRoot, { recursive: true, force: true });
2821
3272
  }
2822
3273
  for (const file of skillPackage.files) {
2823
- const absPath = resolve8(basePath, file.relativePath);
3274
+ const absPath = resolve9(basePath, file.relativePath);
2824
3275
  mkdirSync8(dirname10(absPath), { recursive: true });
2825
3276
  writeFileSync8(absPath, file.content + "\n", "utf-8");
2826
3277
  }
@@ -2831,9 +3282,9 @@ var installSkillPackages = (basePath, packages) => {
2831
3282
  var removeDirectories = (basePath, relativeDirs) => {
2832
3283
  const removed = [];
2833
3284
  for (const relativeDir of relativeDirs) {
2834
- const absPath = resolve8(basePath, relativeDir);
2835
- if (!existsSync5(absPath)) continue;
2836
- rmSync3(absPath, { recursive: true, force: true });
3285
+ const absPath = resolve9(basePath, relativeDir);
3286
+ if (!existsSync6(absPath)) continue;
3287
+ rmSync4(absPath, { recursive: true, force: true });
2837
3288
  removed.push(relativeDir);
2838
3289
  }
2839
3290
  return removed;
@@ -2859,7 +3310,7 @@ var writeUserSkillState = (params) => {
2859
3310
  const previous = readSkillRegistry(registryPath);
2860
3311
  const skills = params.removeSkillId ? removeInstalledSkill(previous?.skills ?? [], params.removeSkillId) : params.nextSkill ? upsertInstalledSkill(previous?.skills ?? [], params.nextSkill) : previous?.skills ?? [];
2861
3312
  if (skills.length === 0) {
2862
- rmSync4(registryPath, { force: true });
3313
+ rmSync5(registryPath, { force: true });
2863
3314
  return;
2864
3315
  }
2865
3316
  writeSkillRegistry(registryPath, {
@@ -3011,14 +3462,14 @@ var skillUninstallCommand = async (skillId) => {
3011
3462
 
3012
3463
  // src/commands/subagent.ts
3013
3464
  import * as p8 from "@clack/prompts";
3014
- import { existsSync as existsSync7, rmSync as rmSync6 } from "fs";
3465
+ import { existsSync as existsSync8, rmSync as rmSync7 } from "fs";
3015
3466
 
3016
3467
  // src/lib/subagent-install.ts
3017
- import { existsSync as existsSync6, mkdirSync as mkdirSync9, rmSync as rmSync5, writeFileSync as writeFileSync9 } from "fs";
3018
- import { dirname as dirname11, isAbsolute as isAbsolute3, relative as relative3, resolve as resolve9 } from "path";
3468
+ import { existsSync as existsSync7, mkdirSync as mkdirSync9, rmSync as rmSync6, writeFileSync as writeFileSync9 } from "fs";
3469
+ import { dirname as dirname11, isAbsolute as isAbsolute3, relative as relative3, resolve as resolve10 } from "path";
3019
3470
  var resolveInsideBasePath = (basePath, relativePath) => {
3020
- const absBasePath = resolve9(basePath);
3021
- const absPath = resolve9(absBasePath, relativePath);
3471
+ const absBasePath = resolve10(basePath);
3472
+ const absPath = resolve10(absBasePath, relativePath);
3022
3473
  const fromBase = relative3(absBasePath, absPath);
3023
3474
  if (fromBase.length === 0 || fromBase.startsWith("..") || isAbsolute3(fromBase)) {
3024
3475
  throw new Error(`Subagent path escapes AI_OPS_HOME: ${relativePath}`);
@@ -3030,8 +3481,8 @@ var installSubagentPackages = (basePath, packages) => {
3030
3481
  for (const subagentPackage of packages) {
3031
3482
  for (const file of subagentPackage.files) {
3032
3483
  const absPath = resolveInsideBasePath(basePath, file.relativePath);
3033
- if (existsSync6(absPath)) {
3034
- rmSync5(absPath, { recursive: true, force: true });
3484
+ if (existsSync7(absPath)) {
3485
+ rmSync6(absPath, { recursive: true, force: true });
3035
3486
  }
3036
3487
  mkdirSync9(dirname11(absPath), { recursive: true });
3037
3488
  writeFileSync9(absPath, file.content.trimEnd() + "\n", "utf-8");
@@ -3044,8 +3495,8 @@ var removeSubagentFiles = (basePath, relativePaths) => {
3044
3495
  const removed = [];
3045
3496
  for (const relativePath of relativePaths) {
3046
3497
  const absPath = resolveInsideBasePath(basePath, relativePath);
3047
- if (!existsSync6(absPath)) continue;
3048
- rmSync5(absPath, { recursive: true, force: true });
3498
+ if (!existsSync7(absPath)) continue;
3499
+ rmSync6(absPath, { recursive: true, force: true });
3049
3500
  removed.push(relativePath);
3050
3501
  }
3051
3502
  return removed;
@@ -3095,7 +3546,7 @@ var writeUserSubagentState = (params) => {
3095
3546
  const previous = readSubagentManifest(manifestPath);
3096
3547
  const subagents = params.removeSubagentId ? removeInstalledSubagent(previous?.subagents ?? [], params.removeSubagentId) : params.nextSubagent ? upsertInstalledSubagent(previous?.subagents ?? [], params.nextSubagent) : previous?.subagents ?? [];
3097
3548
  if (subagents.length === 0) {
3098
- rmSync6(manifestPath, { force: true });
3549
+ rmSync7(manifestPath, { force: true });
3099
3550
  return;
3100
3551
  }
3101
3552
  writeSubagentManifest(manifestPath, {
@@ -3106,7 +3557,7 @@ var writeUserSubagentState = (params) => {
3106
3557
  };
3107
3558
  var readInstalledSubagents = (basePath) => readSubagentManifest(resolveSubagentManifestPath(basePath))?.subagents ?? [];
3108
3559
  var warnMissingSkills = (requiredSkills) => {
3109
- const missing = requiredSkills.filter((skill) => !existsSync7(skill.path));
3560
+ const missing = requiredSkills.filter((skill) => !existsSync8(skill.path));
3110
3561
  if (missing.length === 0) {
3111
3562
  return;
3112
3563
  }
@@ -3417,13 +3868,13 @@ var parseMax = (max) => {
3417
3868
  }
3418
3869
  return parsed;
3419
3870
  };
3420
- var readStdin = async () => new Promise((resolve10, reject) => {
3871
+ var readStdin = async () => new Promise((resolve11, reject) => {
3421
3872
  let raw = "";
3422
3873
  process.stdin.setEncoding("utf-8");
3423
3874
  process.stdin.on("data", (chunk) => {
3424
3875
  raw += chunk;
3425
3876
  });
3426
- process.stdin.on("end", () => resolve10(raw));
3877
+ process.stdin.on("end", () => resolve11(raw));
3427
3878
  process.stdin.on("error", reject);
3428
3879
  });
3429
3880
  var reportContextPromotionError = (error) => {
@@ -3524,8 +3975,8 @@ var contextPromotionPostToolUseHookCommand = async () => {
3524
3975
 
3525
3976
  // src/commands/codex-hook.ts
3526
3977
  import * as p11 from "@clack/prompts";
3527
- import { existsSync as existsSync8 } from "fs";
3528
- import { join as join16 } from "path";
3978
+ import { existsSync as existsSync9 } from "fs";
3979
+ import { join as join14 } from "path";
3529
3980
  var CONTEXT_PROMOTION_REVIEW_SKILL_ID = "context-promotion-review";
3530
3981
  var resolveCodexHomePath = () => {
3531
3982
  const codexHome = process.env.CODEX_HOME;
@@ -3563,7 +4014,7 @@ var resolveContextPromotionReviewSkill = () => {
3563
4014
  };
3564
4015
  var hasInstalledContextPromotionReviewSkill = (basePath) => {
3565
4016
  const installedSkill = findInstalledSkill(readInstalledSkills2(basePath), CONTEXT_PROMOTION_REVIEW_SKILL_ID);
3566
- return installedSkill?.tools.includes(SKILL_TOOL.CODEX) === true && existsSync8(join16(basePath, ".agents/skills/context-promotion-review/SKILL.md"));
4017
+ return installedSkill?.tools.includes(SKILL_TOOL.CODEX) === true && existsSync9(join14(basePath, ".agents/skills/context-promotion-review/SKILL.md"));
3567
4018
  };
3568
4019
  var ensureContextPromotionReviewSkill = (basePath) => {
3569
4020
  const skill = resolveContextPromotionReviewSkill();
@@ -3577,7 +4028,7 @@ var ensureContextPromotionReviewSkill = (basePath) => {
3577
4028
  skill,
3578
4029
  requestedTools
3579
4030
  });
3580
- const alreadyInstalled = existingInstalledSkill?.sourceHash === installedSkill.sourceHash && existingInstalledSkill.tools.includes(SKILL_TOOL.CODEX) && existsSync8(join16(basePath, ".agents/skills/context-promotion-review/SKILL.md"));
4031
+ const alreadyInstalled = existingInstalledSkill?.sourceHash === installedSkill.sourceHash && existingInstalledSkill.tools.includes(SKILL_TOOL.CODEX) && existsSync9(join14(basePath, ".agents/skills/context-promotion-review/SKILL.md"));
3581
4032
  if (alreadyInstalled) {
3582
4033
  return { changed: false, installedSkill };
3583
4034
  }
@@ -3638,6 +4089,386 @@ var codexHookUninstallCommand = async (hookId) => {
3638
4089
  p11.outro("ai-ops codex-hook uninstall \uC644\uB8CC");
3639
4090
  };
3640
4091
 
4092
+ // src/commands/integration.ts
4093
+ import * as p12 from "@clack/prompts";
4094
+ import { existsSync as existsSync10, rmSync as rmSync8 } from "fs";
4095
+ import { join as join15 } from "path";
4096
+ var CODEX_HOOK_DEFINITIONS = [CONTEXT_PROMOTION_CODEX_HOOK, PC_CODEX_HOOK];
4097
+ var resolveCodexHomePath2 = () => {
4098
+ const codexHome = process.env.CODEX_HOME;
4099
+ if (codexHome && codexHome.length > 0) {
4100
+ return codexHome;
4101
+ }
4102
+ const home = process.env.HOME;
4103
+ if (!home) {
4104
+ throw new Error("CODEX_HOME or HOME is required for Codex hook commands");
4105
+ }
4106
+ return `${home}/.codex`;
4107
+ };
4108
+ var resolvePersonalContextRoot = () => {
4109
+ const home = process.env.HOME;
4110
+ if (!home) {
4111
+ throw new Error("HOME is required for pc integration commands");
4112
+ }
4113
+ return `${home}/.personal-project-contexts`;
4114
+ };
4115
+ var resolveCodexHookDefinition = (hookId) => {
4116
+ const hookDefinition = CODEX_HOOK_DEFINITIONS.find((definition) => definition.id === hookId);
4117
+ if (!hookDefinition) {
4118
+ throw new Error(`Unknown Codex hook for integration: ${hookId}`);
4119
+ }
4120
+ return hookDefinition;
4121
+ };
4122
+ var resolveCatalogSkillComponent = (entry) => {
4123
+ const component = entry.components.find((candidate) => candidate.type === INTEGRATION_COMPONENT_TYPE.SKILL);
4124
+ if (!component || component.type !== INTEGRATION_COMPONENT_TYPE.SKILL) {
4125
+ throw new Error(`Integration catalog entry must declare a skill component: ${entry.id}`);
4126
+ }
4127
+ return component;
4128
+ };
4129
+ var resolveCatalogHookComponent = (entry) => {
4130
+ const component = entry.components.find((candidate) => candidate.type === INTEGRATION_COMPONENT_TYPE.CODEX_HOOK);
4131
+ if (!component || component.type !== INTEGRATION_COMPONENT_TYPE.CODEX_HOOK) {
4132
+ throw new Error(`Integration catalog entry must declare a codex-hook component: ${entry.id}`);
4133
+ }
4134
+ return component;
4135
+ };
4136
+ var resolveCatalogReceiptConfigComponents = (entry) => entry.components.filter(
4137
+ (component) => component.type === INTEGRATION_COMPONENT_TYPE.RECEIPT_CONFIG
4138
+ );
4139
+ var loadIntegrationDefinitions = () => loadAllIntegrations(resolveIntegrationsDir()).map((entry) => {
4140
+ const hookComponent = resolveCatalogHookComponent(entry);
4141
+ return {
4142
+ ...entry,
4143
+ skillComponent: resolveCatalogSkillComponent(entry),
4144
+ hookComponent,
4145
+ receiptConfigComponents: resolveCatalogReceiptConfigComponents(entry),
4146
+ hookDefinition: resolveCodexHookDefinition(hookComponent.id)
4147
+ };
4148
+ });
4149
+ var parseIntegrationId = (integrationId) => {
4150
+ const definition = loadIntegrationDefinitions().find((candidate) => candidate.id === integrationId);
4151
+ if (!definition) {
4152
+ throw new Error(`Unknown integration: ${integrationId}`);
4153
+ }
4154
+ return definition.id;
4155
+ };
4156
+ var resolveIntegrationDefinition = (integrationId) => {
4157
+ const definition = loadIntegrationDefinitions().find((candidate) => candidate.id === integrationId);
4158
+ if (!definition) {
4159
+ throw new Error(`Unknown integration: ${integrationId}`);
4160
+ }
4161
+ return definition;
4162
+ };
4163
+ var reportIntegrationError = (error) => {
4164
+ const message = error instanceof Error ? error.message : "unknown error";
4165
+ p12.log.error(message);
4166
+ process.exitCode = 1;
4167
+ };
4168
+ var readStdin2 = async () => new Promise((resolve11, reject) => {
4169
+ let raw = "";
4170
+ process.stdin.setEncoding("utf-8");
4171
+ process.stdin.on("data", (chunk) => {
4172
+ raw += chunk;
4173
+ });
4174
+ process.stdin.on("end", () => resolve11(raw));
4175
+ process.stdin.on("error", reject);
4176
+ });
4177
+ var readInstalledSkills3 = (basePath) => (readSkillRegistry(resolveSkillRegistryPath(basePath))?.skills ?? []).map((installedSkill) => ({
4178
+ ...installedSkill,
4179
+ id: resolveCanonicalSkillId(installedSkill.id)
4180
+ }));
4181
+ var resolveSkillById2 = (skillId) => {
4182
+ const skill = loadAllSkills(resolveSkillsDir()).find((candidate) => candidate.id === skillId);
4183
+ if (!skill) {
4184
+ throw new Error(`Unknown skill: ${skillId}`);
4185
+ }
4186
+ return skill;
4187
+ };
4188
+ var hasInstalledCodexSkill = (params) => {
4189
+ const installedSkill = findInstalledSkill(readInstalledSkills3(params.basePath), params.skillId);
4190
+ return installedSkill?.tools.includes(SKILL_TOOL.CODEX) === true && existsSync10(join15(params.basePath, ".agents/skills", params.skillId, "SKILL.md"));
4191
+ };
4192
+ var writeUserSkillState2 = (params) => {
4193
+ const registryPath = resolveSkillRegistryPath(params.basePath);
4194
+ const previous = readSkillRegistry(registryPath);
4195
+ const skills = params.removeSkillId ? removeInstalledSkill(previous?.skills ?? [], params.removeSkillId) : params.nextSkill ? upsertInstalledSkill(previous?.skills ?? [], params.nextSkill) : previous?.skills ?? [];
4196
+ if (skills.length === 0) {
4197
+ rmSync8(registryPath, { force: true });
4198
+ return;
4199
+ }
4200
+ writeSkillRegistry(registryPath, {
4201
+ skills,
4202
+ cliVersion: params.cliVersion,
4203
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
4204
+ });
4205
+ };
4206
+ var ensureSkillComponent = (params) => {
4207
+ const skill = resolveSkillById2(params.skillId);
4208
+ const installedSkills = readInstalledSkills3(params.basePath);
4209
+ const existingInstalledSkill = findInstalledSkill(installedSkills, skill.id);
4210
+ const requestedTools = mergeSkillTools({
4211
+ existing: existingInstalledSkill?.tools,
4212
+ requested: [SKILL_TOOL.CODEX]
4213
+ });
4214
+ const { packages, installedSkill } = buildSkillInstallPlan({
4215
+ skill,
4216
+ requestedTools
4217
+ });
4218
+ const alreadyCurrent = existingInstalledSkill?.sourceHash === installedSkill.sourceHash && existingInstalledSkill.tools.includes(SKILL_TOOL.CODEX) && existsSync10(join15(params.basePath, ".agents/skills", params.skillId, "SKILL.md"));
4219
+ if (alreadyCurrent) {
4220
+ return {
4221
+ type: INTEGRATION_COMPONENT_TYPE.SKILL,
4222
+ id: params.skillId,
4223
+ tools: [SKILL_TOOL.CODEX],
4224
+ owned: params.previouslyOwned
4225
+ };
4226
+ }
4227
+ installSkillPackages(params.basePath, packages);
4228
+ writeUserSkillState2({
4229
+ basePath: params.basePath,
4230
+ cliVersion: params.cliVersion,
4231
+ nextSkill: installedSkill
4232
+ });
4233
+ return {
4234
+ type: INTEGRATION_COMPONENT_TYPE.SKILL,
4235
+ id: params.skillId,
4236
+ tools: [SKILL_TOOL.CODEX],
4237
+ owned: true
4238
+ };
4239
+ };
4240
+ var ensureHookComponent = (params) => {
4241
+ const command = buildCodexHookCommand({
4242
+ definition: params.definition,
4243
+ overrideCommand: params.command
4244
+ });
4245
+ const installedBefore = inspectCodexHook({
4246
+ hooksPath: params.hooksPath,
4247
+ definition: params.definition
4248
+ }).installed;
4249
+ const result = installCodexHook({
4250
+ hooksPath: params.hooksPath,
4251
+ definition: params.definition,
4252
+ command
4253
+ });
4254
+ return {
4255
+ type: INTEGRATION_COMPONENT_TYPE.CODEX_HOOK,
4256
+ id: params.hookId,
4257
+ command,
4258
+ owned: params.previouslyOwned || result.changed || !installedBefore
4259
+ };
4260
+ };
4261
+ var buildReceiptConfigComponents = (components) => components.map((component) => ({
4262
+ type: INTEGRATION_COMPONENT_TYPE.RECEIPT_CONFIG,
4263
+ id: component.id,
4264
+ storagePath: component.storage_path,
4265
+ owned: false
4266
+ }));
4267
+ var componentWasOwned = (params) => params.previous?.components.some(
4268
+ (component) => component.type === params.type && component.id === params.id && component.owned
4269
+ ) ?? false;
4270
+ var buildInstalledIntegration = (params) => {
4271
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4272
+ return {
4273
+ id: params.definition.id,
4274
+ components: [...params.components],
4275
+ installedAt: params.previous?.installedAt ?? now,
4276
+ updatedAt: now
4277
+ };
4278
+ };
4279
+ var removeOwnedSkill = (params) => {
4280
+ const installedSkill = findInstalledSkill(readInstalledSkills3(params.basePath), params.skillId);
4281
+ if (!installedSkill) {
4282
+ return [];
4283
+ }
4284
+ const removed = removeDirectories(params.basePath, installedSkill.installed_paths);
4285
+ writeUserSkillState2({
4286
+ basePath: params.basePath,
4287
+ cliVersion: params.cliVersion,
4288
+ removeSkillId: params.skillId
4289
+ });
4290
+ return removed;
4291
+ };
4292
+ var formatComponentStatus = (component) => {
4293
+ const owner = component.owned ? "owned" : "pre-existing";
4294
+ if (component.type === INTEGRATION_COMPONENT_TYPE.SKILL) {
4295
+ return `skill:${component.id} (${owner})`;
4296
+ }
4297
+ if (component.type === INTEGRATION_COMPONENT_TYPE.CODEX_HOOK) {
4298
+ return `codex-hook:${component.id} (${owner})`;
4299
+ }
4300
+ return `receipt-config:${component.id} (${owner})`;
4301
+ };
4302
+ var integrationListCommand = async () => {
4303
+ p12.intro("ai-ops integration list");
4304
+ try {
4305
+ const manifest = readIntegrationManifest(resolveIntegrationManifestPath(resolveUserBasePath()));
4306
+ const installed = new Set((manifest?.integrations ?? []).map((integration) => integration.id));
4307
+ const lines = loadIntegrationDefinitions().map((definition) => {
4308
+ const suffix = installed.has(definition.id) ? "installed" : "not installed";
4309
+ return `- ${definition.id} - ${suffix} - ${definition.description}`;
4310
+ });
4311
+ p12.log.info(lines.join("\n"));
4312
+ } catch (error) {
4313
+ reportIntegrationError(error);
4314
+ }
4315
+ p12.outro("ai-ops integration list \uC644\uB8CC");
4316
+ };
4317
+ var integrationInstallCommand = async (integrationId, opts = {}) => {
4318
+ p12.intro(`ai-ops integration install ${integrationId}`);
4319
+ try {
4320
+ const definition = resolveIntegrationDefinition(integrationId);
4321
+ const basePath = resolveUserBasePath();
4322
+ const cliVersion = getCliVersion();
4323
+ const manifestPath = resolveIntegrationManifestPath(basePath);
4324
+ const previous = findInstalledIntegration(readIntegrationManifest(manifestPath)?.integrations ?? [], definition.id);
4325
+ const skillComponent = ensureSkillComponent({
4326
+ basePath,
4327
+ cliVersion,
4328
+ skillId: definition.skillComponent.id,
4329
+ previouslyOwned: componentWasOwned({
4330
+ previous,
4331
+ type: INTEGRATION_COMPONENT_TYPE.SKILL,
4332
+ id: definition.skillComponent.id
4333
+ })
4334
+ });
4335
+ const hookComponent = ensureHookComponent({
4336
+ hooksPath: resolveCodexHooksPath(resolveCodexHomePath2()),
4337
+ hookId: definition.hookComponent.id,
4338
+ definition: definition.hookDefinition,
4339
+ command: opts.command,
4340
+ previouslyOwned: componentWasOwned({
4341
+ previous,
4342
+ type: INTEGRATION_COMPONENT_TYPE.CODEX_HOOK,
4343
+ id: definition.hookComponent.id
4344
+ })
4345
+ });
4346
+ const installedIntegration = buildInstalledIntegration({
4347
+ definition,
4348
+ previous,
4349
+ components: [skillComponent, hookComponent, ...buildReceiptConfigComponents(definition.receiptConfigComponents)]
4350
+ });
4351
+ writeUserIntegrationState({
4352
+ manifestPath,
4353
+ cliVersion,
4354
+ nextIntegration: installedIntegration
4355
+ });
4356
+ p12.log.success(`integration \uC124\uCE58 \uC644\uB8CC: ${definition.id}`);
4357
+ p12.log.info(installedIntegration.components.map(formatComponentStatus).join("\n"));
4358
+ } catch (error) {
4359
+ reportIntegrationError(error);
4360
+ }
4361
+ p12.outro("ai-ops integration install \uC644\uB8CC");
4362
+ };
4363
+ var integrationStatusCommand = async (integrationId) => {
4364
+ p12.intro(`ai-ops integration status ${integrationId}`);
4365
+ try {
4366
+ const definition = resolveIntegrationDefinition(integrationId);
4367
+ const basePath = resolveUserBasePath();
4368
+ const manifest = readIntegrationManifest(resolveIntegrationManifestPath(basePath));
4369
+ const installedIntegration = findInstalledIntegration(manifest?.integrations ?? [], definition.id);
4370
+ const hookStatus = inspectCodexHook({
4371
+ hooksPath: resolveCodexHooksPath(resolveCodexHomePath2()),
4372
+ definition: definition.hookDefinition
4373
+ });
4374
+ const lines = [
4375
+ `integration installed: ${installedIntegration ? "yes" : "no"}`,
4376
+ `skill installed: ${hasInstalledCodexSkill({ basePath, skillId: definition.skillComponent.id }) ? "yes" : "no"}`,
4377
+ `hook installed: ${hookStatus.installed ? "yes" : "no"}`,
4378
+ `hooks file: ${hookStatus.hooksPath}`
4379
+ ];
4380
+ if (definition.id === INTEGRATION_ID.PC) {
4381
+ const pcStatus = getPcHandoffStatus({
4382
+ cwd: resolveBasePath(),
4383
+ contextRoot: resolvePersonalContextRoot()
4384
+ });
4385
+ lines.push(
4386
+ `pc context ready: ${pcStatus.ready ? "yes" : "no"}`,
4387
+ `pc skip reason: ${pcStatus.skipReason ?? "none"}`,
4388
+ `pc workspace: ${pcStatus.workspaceId ?? "not found"}`,
4389
+ `pc active workstream: ${pcStatus.activeWorkstreamId ?? "not found"}`,
4390
+ `pc last confirmed commit: ${pcStatus.lastConfirmedCommitHash ?? "not found"}`
4391
+ );
4392
+ }
4393
+ if (installedIntegration) {
4394
+ lines.push(`owned components: ${installedIntegration.components.map(formatComponentStatus).join(", ")}`);
4395
+ }
4396
+ p12.log.info(lines.join("\n"));
4397
+ } catch (error) {
4398
+ reportIntegrationError(error);
4399
+ }
4400
+ p12.outro("ai-ops integration status \uC644\uB8CC");
4401
+ };
4402
+ var integrationUninstallCommand = async (integrationId) => {
4403
+ p12.intro(`ai-ops integration uninstall ${integrationId}`);
4404
+ try {
4405
+ const definition = resolveIntegrationDefinition(integrationId);
4406
+ const basePath = resolveUserBasePath();
4407
+ const cliVersion = getCliVersion();
4408
+ const manifestPath = resolveIntegrationManifestPath(basePath);
4409
+ const installedIntegration = findInstalledIntegration(
4410
+ readIntegrationManifest(manifestPath)?.integrations ?? [],
4411
+ definition.id
4412
+ );
4413
+ if (!installedIntegration) {
4414
+ p12.log.warn("\uC124\uCE58\uB41C integration manifest entry\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
4415
+ p12.outro("ai-ops integration uninstall \uC644\uB8CC");
4416
+ return;
4417
+ }
4418
+ const removed = [];
4419
+ for (const component of installedIntegration.components) {
4420
+ if (!component.owned) {
4421
+ continue;
4422
+ }
4423
+ if (component.type === INTEGRATION_COMPONENT_TYPE.SKILL) {
4424
+ removed.push(...removeOwnedSkill({ basePath, cliVersion, skillId: component.id }));
4425
+ }
4426
+ if (component.type === INTEGRATION_COMPONENT_TYPE.CODEX_HOOK) {
4427
+ const result = uninstallCodexHook({
4428
+ hooksPath: resolveCodexHooksPath(resolveCodexHomePath2()),
4429
+ definition: definition.hookDefinition
4430
+ });
4431
+ if (result.removed) {
4432
+ removed.push(result.hooksPath);
4433
+ }
4434
+ }
4435
+ }
4436
+ writeUserIntegrationState({
4437
+ manifestPath,
4438
+ cliVersion,
4439
+ removeIntegrationId: definition.id
4440
+ });
4441
+ p12.log.success(removed.length > 0 ? `\uC81C\uAC70 \uC644\uB8CC: ${removed.join(", ")}` : "\uC81C\uAC70\uD560 owned component \uC5C6\uC74C");
4442
+ } catch (error) {
4443
+ reportIntegrationError(error);
4444
+ }
4445
+ p12.outro("ai-ops integration uninstall \uC644\uB8CC");
4446
+ };
4447
+ var integrationPostToolUseHookCommand = async (integrationId) => {
4448
+ try {
4449
+ const id = parseIntegrationId(integrationId);
4450
+ const raw = await readStdin2();
4451
+ const hookInput = raw.trim().length > 0 ? JSON.parse(raw) : {};
4452
+ if (id !== INTEGRATION_ID.PC) {
4453
+ return;
4454
+ }
4455
+ const output = evaluatePcPostToolUseHook({
4456
+ hookInput,
4457
+ contextRoot: resolvePersonalContextRoot()
4458
+ });
4459
+ if (output) {
4460
+ process.stdout.write(JSON.stringify(output) + "\n");
4461
+ }
4462
+ } catch (error) {
4463
+ const message = error instanceof Error ? error.message : "unknown error";
4464
+ process.stdout.write(
4465
+ JSON.stringify({
4466
+ systemMessage: `ai-ops integration hook skipped: ${message}`
4467
+ }) + "\n"
4468
+ );
4469
+ }
4470
+ };
4471
+
3641
4472
  // src/bin/index.ts
3642
4473
  var program = new Command();
3643
4474
  program.name("ai-ops").description("AI agent operating layer manager").version(getCliVersion());
@@ -3683,5 +4514,12 @@ var codexHookCommand = program.command("codex-hook").description("Codex hooks \u
3683
4514
  codexHookCommand.command("install <hookId>").description("Codex hook \uC124\uCE58").option("--command <command>", "hook\uC5D0 \uC800\uC7A5\uD560 context-promotion \uC2E4\uD589 \uBA85\uB839").action((hookId, opts) => codexHookInstallCommand(hookId, opts));
3684
4515
  codexHookCommand.command("status <hookId>").description("Codex hook \uC124\uCE58 \uC0C1\uD0DC \uD655\uC778").action((hookId) => codexHookStatusCommand(hookId));
3685
4516
  codexHookCommand.command("uninstall <hookId>").description("Codex hook \uC81C\uAC70").action((hookId) => codexHookUninstallCommand(hookId));
4517
+ var integrationCommand = program.command("integration").description("user/global runtime integration \uC124\uCE58/\uC870\uD68C/\uC81C\uAC70");
4518
+ integrationCommand.command("list").description("\uC0AC\uC6A9 \uAC00\uB2A5\uD55C integration \uBAA9\uB85D").action(() => integrationListCommand());
4519
+ integrationCommand.command("install <integrationId>").description("integration \uC124\uCE58").option("--command <command>", "Codex hook\uC5D0 \uC800\uC7A5\uD560 \uC2E4\uD589 \uBA85\uB839").action((integrationId, opts) => integrationInstallCommand(integrationId, opts));
4520
+ integrationCommand.command("status <integrationId>").description("integration \uC124\uCE58 \uC0C1\uD0DC \uD655\uC778").action((integrationId) => integrationStatusCommand(integrationId));
4521
+ integrationCommand.command("uninstall <integrationId>").description("integration \uC81C\uAC70").action((integrationId) => integrationUninstallCommand(integrationId));
4522
+ var integrationHookCommand = integrationCommand.command("hook").description("integration hook \uB0B4\uBD80 \uBA85\uB839");
4523
+ integrationHookCommand.command("post-tool-use <integrationId>").description("Codex PostToolUse integration hook entrypoint").action((integrationId) => integrationPostToolUseHookCommand(integrationId));
3686
4524
  program.parse();
3687
4525
  //# sourceMappingURL=index.js.map