ai-ops-cli 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/index.js CHANGED
@@ -267,50 +267,169 @@ var SubagentManifestSchema = z9.object({
267
267
  generatedAt: z9.string().datetime({ offset: true })
268
268
  }).strict();
269
269
 
270
- // src/core/schemas/pack.schema.ts
270
+ // src/core/schemas/integration.schema.ts
271
271
  import { z as z10 } from "zod";
272
- var PackIdSchema = z10.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "id must be kebab-case");
273
- var PackSourcePathSchema = z10.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*(?:\/[a-z0-9]+(?:-[a-z0-9]+)*)*$/, "source_path must be relative kebab-case path");
274
- var PackCatalogEntrySchema = z10.object({
272
+ var INTEGRATION_ID = {
273
+ CONTEXT_PROMOTION: "context-promotion",
274
+ PC: "pc"
275
+ };
276
+ var INTEGRATION_COMPONENT_TYPE = {
277
+ SKILL: "skill",
278
+ CODEX_HOOK: "codex-hook",
279
+ RECEIPT_CONFIG: "receipt-config"
280
+ };
281
+ var IntegrationIdSchema = z10.union([z10.literal(INTEGRATION_ID.CONTEXT_PROMOTION), z10.literal(INTEGRATION_ID.PC)]);
282
+ var IntegrationSkillComponentSchema = z10.object({
283
+ type: z10.literal(INTEGRATION_COMPONENT_TYPE.SKILL),
284
+ id: z10.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/),
285
+ tools: z10.array(SkillToolSchema).min(1),
286
+ owned: z10.boolean()
287
+ }).strict();
288
+ var IntegrationCodexHookComponentSchema = z10.object({
289
+ type: z10.literal(INTEGRATION_COMPONENT_TYPE.CODEX_HOOK),
290
+ id: IntegrationIdSchema,
291
+ command: z10.string().min(1),
292
+ owned: z10.boolean()
293
+ }).strict();
294
+ var IntegrationReceiptConfigComponentSchema = z10.object({
295
+ type: z10.literal(INTEGRATION_COMPONENT_TYPE.RECEIPT_CONFIG),
296
+ id: z10.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/),
297
+ storagePath: z10.string().min(1),
298
+ owned: z10.boolean()
299
+ }).strict();
300
+ var IntegrationComponentSchema = z10.union([
301
+ IntegrationSkillComponentSchema,
302
+ IntegrationCodexHookComponentSchema,
303
+ IntegrationReceiptConfigComponentSchema
304
+ ]);
305
+ var InstalledIntegrationSchema = z10.object({
306
+ id: IntegrationIdSchema,
307
+ components: z10.array(IntegrationComponentSchema),
308
+ installedAt: z10.string().min(1),
309
+ updatedAt: z10.string().min(1)
310
+ }).strict();
311
+ var IntegrationManifestSchema = z10.object({
312
+ schemaVersion: z10.literal(1),
313
+ kind: z10.literal("ai-ops-integrations-manifest"),
314
+ integrations: z10.array(InstalledIntegrationSchema),
315
+ cliVersion: z10.string().min(1),
316
+ generatedAt: z10.string().min(1)
317
+ }).strict();
318
+
319
+ // src/core/schemas/integration-catalog.schema.ts
320
+ import { z as z11 } from "zod";
321
+ var ComponentIdSchema = z11.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "component id must be kebab-case");
322
+ var IntegrationCatalogSkillComponentSchema = z11.object({
323
+ type: z11.literal(INTEGRATION_COMPONENT_TYPE.SKILL),
324
+ id: ComponentIdSchema,
325
+ tools: z11.array(z11.literal("codex")).min(1)
326
+ }).strict();
327
+ var IntegrationCatalogCodexHookComponentSchema = z11.object({
328
+ type: z11.literal(INTEGRATION_COMPONENT_TYPE.CODEX_HOOK),
329
+ id: IntegrationIdSchema
330
+ }).strict();
331
+ var IntegrationCatalogReceiptConfigComponentSchema = z11.object({
332
+ type: z11.literal(INTEGRATION_COMPONENT_TYPE.RECEIPT_CONFIG),
333
+ id: ComponentIdSchema,
334
+ storage_path: z11.string().min(1)
335
+ }).strict();
336
+ var IntegrationCatalogComponentSchema = z11.union([
337
+ IntegrationCatalogSkillComponentSchema,
338
+ IntegrationCatalogCodexHookComponentSchema,
339
+ IntegrationCatalogReceiptConfigComponentSchema
340
+ ]);
341
+ var IntegrationCatalogEntrySchema = z11.object({
342
+ id: IntegrationIdSchema,
343
+ description: z11.string().min(1),
344
+ components: z11.array(IntegrationCatalogComponentSchema).min(1)
345
+ }).strict().superRefine((entry, ctx) => {
346
+ const hasSkill = entry.components.some((component) => component.type === INTEGRATION_COMPONENT_TYPE.SKILL);
347
+ const hasCodexHook = entry.components.some((component) => component.type === INTEGRATION_COMPONENT_TYPE.CODEX_HOOK);
348
+ const hasReceiptConfig = entry.components.some(
349
+ (component) => component.type === INTEGRATION_COMPONENT_TYPE.RECEIPT_CONFIG
350
+ );
351
+ if (!hasSkill) {
352
+ ctx.addIssue({
353
+ code: z11.ZodIssueCode.custom,
354
+ path: ["components"],
355
+ message: `integration must declare a skill component: ${entry.id}`
356
+ });
357
+ }
358
+ if (!hasCodexHook) {
359
+ ctx.addIssue({
360
+ code: z11.ZodIssueCode.custom,
361
+ path: ["components"],
362
+ message: `integration must declare a codex-hook component: ${entry.id}`
363
+ });
364
+ }
365
+ if (!hasReceiptConfig) {
366
+ ctx.addIssue({
367
+ code: z11.ZodIssueCode.custom,
368
+ path: ["components"],
369
+ message: `integration must declare a receipt-config component: ${entry.id}`
370
+ });
371
+ }
372
+ });
373
+ var IntegrationCatalogSchema = z11.object({
374
+ integrations: z11.array(IntegrationCatalogEntrySchema)
375
+ }).strict().superRefine((catalog, ctx) => {
376
+ const seen = /* @__PURE__ */ new Set();
377
+ for (const [index, entry] of catalog.integrations.entries()) {
378
+ if (seen.has(entry.id)) {
379
+ ctx.addIssue({
380
+ code: z11.ZodIssueCode.custom,
381
+ path: ["integrations", index, "id"],
382
+ message: `duplicate integration id: ${entry.id}`
383
+ });
384
+ }
385
+ seen.add(entry.id);
386
+ }
387
+ });
388
+
389
+ // src/core/schemas/pack.schema.ts
390
+ import { z as z12 } from "zod";
391
+ var PackIdSchema = z12.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "id must be kebab-case");
392
+ var PackSourcePathSchema = z12.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*(?:\/[a-z0-9]+(?:-[a-z0-9]+)*)*$/, "source_path must be relative kebab-case path");
393
+ var PackCatalogEntrySchema = z12.object({
275
394
  id: PackIdSchema,
276
395
  source_path: PackSourcePathSchema
277
396
  }).strict();
278
- var PackCatalogSchema = z10.object({
279
- packs: z10.array(PackCatalogEntrySchema)
397
+ var PackCatalogSchema = z12.object({
398
+ packs: z12.array(PackCatalogEntrySchema)
280
399
  }).strict();
281
400
 
282
401
  // 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()
402
+ import { z as z13 } from "zod";
403
+ var SettingsConfigSchema = z13.object({
404
+ claude: z13.array(z13.string().min(1)).optional(),
405
+ gemini: z13.array(z13.string().min(1)).optional(),
406
+ prettierignore: z13.boolean().optional()
288
407
  }).strict();
289
- var WorkspaceEntrySchema = z11.object({
290
- preset: z11.string().min(1),
291
- rules: z11.array(z11.string().min(1))
408
+ var WorkspaceEntrySchema = z13.object({
409
+ preset: z13.string().min(1),
410
+ rules: z13.array(z13.string().min(1))
292
411
  }).strict();
293
- var ManifestSchema = z11.object({
294
- tools: z11.array(z11.string().min(1)).min(1),
295
- scope: z11.literal("project"),
412
+ var ManifestSchema = z13.object({
413
+ tools: z13.array(z13.string().min(1)).min(1),
414
+ scope: z13.literal("project"),
296
415
  /** 비모노레포 단일 preset */
297
- preset: z11.string().min(1).optional(),
416
+ preset: z13.string().min(1).optional(),
298
417
  /** 모노레포: workspace path → { preset, rules } */
299
- workspaces: z11.record(z11.string(), WorkspaceEntrySchema).optional(),
300
- installed_rules: z11.array(z11.string().min(1)),
418
+ workspaces: z13.record(z13.string(), WorkspaceEntrySchema).optional(),
419
+ installed_rules: z13.array(z13.string().min(1)),
301
420
  /** 실제 디스크에 쓰여진 파일 상대 경로 목록 (uninstall용). 기존 manifest 호환성 위해 optional */
302
- installed_files: z11.array(z11.string().min(1)).optional(),
421
+ installed_files: z13.array(z13.string().min(1)).optional(),
303
422
  /** skill 설치 루트 디렉토리 목록 */
304
- installed_skills: z11.array(InstalledSkillSchema).optional(),
423
+ installed_skills: z13.array(InstalledSkillSchema).optional(),
305
424
  /** non-managed 파일에 섹션을 append한 경우 추적 (uninstall 시 섹션만 제거) */
306
- appended_files: z11.array(z11.string().min(1)).optional(),
425
+ appended_files: z13.array(z13.string().min(1)).optional(),
307
426
  /** init 시 선택된 settings 항목 — update 시 재생성에 사용 */
308
427
  settings: SettingsConfigSchema.optional(),
309
428
  /** init/update 실행 시점의 CLI 패키지 버전 — 버전 변경 감지에 사용 */
310
- cliVersion: z11.string().optional(),
429
+ cliVersion: z13.string().optional(),
311
430
  /** 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 })
431
+ sourceHash: z13.string().regex(/^[a-f0-9]{6}$/, "sourceHash must be 6 lowercase hex chars"),
432
+ generatedAt: z13.string().datetime({ offset: true })
314
433
  }).strict();
315
434
 
316
435
  // src/core/loader.ts
@@ -444,6 +563,10 @@ var assertSubagentFrontmatterName = (params) => {
444
563
  }
445
564
  };
446
565
  var loadSubagentCatalog = (subagentsDir) => SubagentCatalogSchema.parse(JSON.parse(readFileSync(resolve(subagentsDir, "subagent-registry.json"), "utf-8")));
566
+ var loadIntegrationCatalog = (integrationsDir) => IntegrationCatalogSchema.parse(
567
+ JSON.parse(readFileSync(resolve(integrationsDir, "integration-registry.json"), "utf-8"))
568
+ );
569
+ var loadAllIntegrations = (integrationsDir) => [...loadIntegrationCatalog(integrationsDir).integrations].sort((a, b) => a.id.localeCompare(b.id));
447
570
  var loadAllSubagents = (subagentsDir) => {
448
571
  const catalog = loadSubagentCatalog(subagentsDir);
449
572
  const entries = [...catalog.subagents].sort((a, b) => a.id.localeCompare(b.id));
@@ -784,23 +907,65 @@ var writeSubagentManifest = (manifestPath, manifest) => {
784
907
  writeFileSync3(manifestPath, serializeSubagentManifest(manifest), "utf-8");
785
908
  };
786
909
 
910
+ // src/core/integration-manifest-io.ts
911
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync6, rmSync, writeFileSync as writeFileSync4 } from "fs";
912
+ import { dirname as dirname5, join as join9 } from "path";
913
+ var INTEGRATION_MANIFEST_FILENAME = "integrations-manifest.json";
914
+ var parseIntegrationManifest = (json) => IntegrationManifestSchema.parse(JSON.parse(json));
915
+ var serializeIntegrationManifest = (manifest) => JSON.stringify(manifest, null, 2) + "\n";
916
+ var resolveIntegrationManifestPath = (userBasePath) => join9(userBasePath, ".ai-ops", INTEGRATION_MANIFEST_FILENAME);
917
+ var readIntegrationManifest = (manifestPath) => {
918
+ let raw;
919
+ try {
920
+ raw = readFileSync6(manifestPath, "utf-8");
921
+ } catch {
922
+ return null;
923
+ }
924
+ return parseIntegrationManifest(raw);
925
+ };
926
+ var writeIntegrationManifest = (manifestPath, manifest) => {
927
+ mkdirSync4(dirname5(manifestPath), { recursive: true });
928
+ writeFileSync4(manifestPath, serializeIntegrationManifest(manifest), "utf-8");
929
+ };
930
+ var findInstalledIntegration = (integrations, integrationId) => integrations.find((integration) => integration.id === integrationId);
931
+ var upsertInstalledIntegration = (integrations, nextIntegration) => [
932
+ ...integrations.filter((integration) => integration.id !== nextIntegration.id),
933
+ nextIntegration
934
+ ];
935
+ var removeInstalledIntegration = (integrations, integrationId) => integrations.filter((integration) => integration.id !== integrationId);
936
+ var writeUserIntegrationState = (params) => {
937
+ const previous = readIntegrationManifest(params.manifestPath);
938
+ const integrations = params.removeIntegrationId ? removeInstalledIntegration(previous?.integrations ?? [], params.removeIntegrationId) : params.nextIntegration ? upsertInstalledIntegration(previous?.integrations ?? [], params.nextIntegration) : previous?.integrations ?? [];
939
+ if (integrations.length === 0) {
940
+ rmSync(params.manifestPath, { force: true });
941
+ return;
942
+ }
943
+ writeIntegrationManifest(params.manifestPath, {
944
+ schemaVersion: 1,
945
+ kind: "ai-ops-integrations-manifest",
946
+ integrations,
947
+ cliVersion: params.cliVersion,
948
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
949
+ });
950
+ };
951
+
787
952
  // src/core/install-plan.ts
788
- import { join as join9 } from "path";
953
+ import { join as join10 } from "path";
789
954
 
790
955
  // 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";
956
+ import { existsSync, mkdirSync as mkdirSync5, readFileSync as readFileSync7, readdirSync as readdirSync3, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
957
+ import { dirname as dirname7, isAbsolute, join as join11, relative, resolve as resolve5 } from "path";
793
958
 
794
959
  // src/core/paths.ts
795
- import { dirname as dirname5, resolve as resolve4 } from "path";
960
+ import { dirname as dirname6, resolve as resolve4 } from "path";
796
961
  import { fileURLToPath as fileURLToPath2 } from "url";
797
- var __dirname2 = dirname5(fileURLToPath2(import.meta.url));
962
+ var __dirname2 = dirname6(fileURLToPath2(import.meta.url));
798
963
  var COMPILER_DATA_DIR = resolve4(__dirname2, "..", "..", "data");
799
964
 
800
965
  // src/core/project-layer.ts
801
966
  var PROJECT_LAYER_MANIFEST_RELATIVE_PATH = ".ai-ops/manifest.json";
802
967
  var PROJECT_LAYER_CONTEXT_INDEX_RELATIVE_PATH = ".ai-ops/context-layer.json";
803
- var CONTEXT_LAYER_DATA_DIR = join10(COMPILER_DATA_DIR, "context-layer");
968
+ var CONTEXT_LAYER_DATA_DIR = join11(COMPILER_DATA_DIR, "context-layer");
804
969
  var TOOL_ORDER = ["codex", "gemini", "claude-code"];
805
970
  var DEFAULT_TOOLS = TOOL_ORDER;
806
971
  var TEMPLATE_PATHS = [
@@ -827,10 +992,10 @@ var RESERVED_DOCUMENT_WARNINGS = [
827
992
  "\uD310\uB2E8 \uADFC\uAC70\uB85C \uC0AC\uC6A9\uD558\uC9C0 \uB9C8\uC138\uC694",
828
993
  "Do not use this document as current decision-making evidence"
829
994
  ];
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);
833
- var toRelativeDir = (relativePath) => dirname6(relativePath);
995
+ var resolveProjectLayerManifestPath = (basePath) => join11(basePath, PROJECT_LAYER_MANIFEST_RELATIVE_PATH);
996
+ var resolveProjectLayerContextIndexPath = (basePath) => join11(basePath, PROJECT_LAYER_CONTEXT_INDEX_RELATIVE_PATH);
997
+ var resolveTemplatePath = (relativePath) => join11(CONTEXT_LAYER_DATA_DIR, relativePath);
998
+ var toRelativeDir = (relativePath) => dirname7(relativePath);
834
999
  var resolveProjectLayerFilePath = (basePath, relativePath) => {
835
1000
  if (!isSafeProjectLayerPath(relativePath)) {
836
1001
  throw new Error(`Unsafe project layer path: ${relativePath}`);
@@ -945,8 +1110,8 @@ var loadTemplateSpec = (relativePath, content) => {
945
1110
  };
946
1111
  var loadProjectLayerTemplateSpecs = (tools) => {
947
1112
  const selectedPaths = TEMPLATE_PATHS.filter((relativePath) => shouldIncludeTemplate(relativePath, tools));
948
- const nonStatusSpecs = selectedPaths.filter((relativePath) => relativePath !== "docs/docs-status.md").map((relativePath) => loadTemplateSpec(relativePath, readFileSync6(resolveTemplatePath(relativePath), "utf-8")));
949
- const statusTemplate = readFileSync6(resolveTemplatePath("docs/docs-status.md"), "utf-8");
1113
+ const nonStatusSpecs = selectedPaths.filter((relativePath) => relativePath !== "docs/docs-status.md").map((relativePath) => loadTemplateSpec(relativePath, readFileSync7(resolveTemplatePath(relativePath), "utf-8")));
1114
+ const statusTemplate = readFileSync7(resolveTemplatePath("docs/docs-status.md"), "utf-8");
950
1115
  const statusPlaceholderSpec = loadTemplateSpec("docs/docs-status.md", statusTemplate);
951
1116
  const specsForStatus = [...nonStatusSpecs, statusPlaceholderSpec].sort((a, b) => a.path.localeCompare(b.path));
952
1117
  const statusContent = statusTemplate.replace("{{documents_table}}", buildDocsStatusRows(specsForStatus));
@@ -956,7 +1121,7 @@ var loadProjectLayerTemplateSpecs = (tools) => {
956
1121
  var computeProjectLayerSourceHash = (specs) => computeHash(specs.map((spec) => `${spec.path}:${spec.content}`));
957
1122
  var readProjectLayerManifest = (basePath) => {
958
1123
  try {
959
- return parseProjectLayerManifest(readFileSync6(resolveProjectLayerManifestPath(basePath), "utf-8"));
1124
+ return parseProjectLayerManifest(readFileSync7(resolveProjectLayerManifestPath(basePath), "utf-8"));
960
1125
  } catch (error) {
961
1126
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
962
1127
  return null;
@@ -966,12 +1131,12 @@ var readProjectLayerManifest = (basePath) => {
966
1131
  };
967
1132
  var writeProjectLayerManifest = (basePath, manifest) => {
968
1133
  const manifestPath = resolveProjectLayerManifestPath(basePath);
969
- mkdirSync4(dirname6(manifestPath), { recursive: true });
970
- writeFileSync4(manifestPath, serializeProjectLayerManifest(manifest), "utf-8");
1134
+ mkdirSync5(dirname7(manifestPath), { recursive: true });
1135
+ writeFileSync5(manifestPath, serializeProjectLayerManifest(manifest), "utf-8");
971
1136
  };
972
1137
  var readProjectLayerContextIndex = (basePath) => {
973
1138
  try {
974
- return parseProjectLayerContextIndex(readFileSync6(resolveProjectLayerContextIndexPath(basePath), "utf-8"));
1139
+ return parseProjectLayerContextIndex(readFileSync7(resolveProjectLayerContextIndexPath(basePath), "utf-8"));
975
1140
  } catch (error) {
976
1141
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
977
1142
  return null;
@@ -981,8 +1146,8 @@ var readProjectLayerContextIndex = (basePath) => {
981
1146
  };
982
1147
  var writeProjectLayerContextIndex = (basePath, contextIndex) => {
983
1148
  const contextIndexPath = resolveProjectLayerContextIndexPath(basePath);
984
- mkdirSync4(dirname6(contextIndexPath), { recursive: true });
985
- writeFileSync4(contextIndexPath, serializeProjectLayerContextIndex(contextIndex), "utf-8");
1149
+ mkdirSync5(dirname7(contextIndexPath), { recursive: true });
1150
+ writeFileSync5(contextIndexPath, serializeProjectLayerContextIndex(contextIndex), "utf-8");
986
1151
  };
987
1152
  var installManagedFiles = (basePath, specs, meta) => {
988
1153
  const written = [];
@@ -991,24 +1156,24 @@ var installManagedFiles = (basePath, specs, meta) => {
991
1156
  const absolutePath = resolveProjectLayerFilePath(basePath, spec.path);
992
1157
  const wrappedContent = wrapWithSection(spec.content, meta);
993
1158
  if (!existsSync(absolutePath)) {
994
- mkdirSync4(dirname6(absolutePath), { recursive: true });
995
- writeFileSync4(absolutePath, wrappedContent + "\n", "utf-8");
1159
+ mkdirSync5(dirname7(absolutePath), { recursive: true });
1160
+ writeFileSync5(absolutePath, wrappedContent + "\n", "utf-8");
996
1161
  written.push(spec.path);
997
1162
  continue;
998
1163
  }
999
- const existing = readFileSync6(absolutePath, "utf-8");
1164
+ const existing = readFileSync7(absolutePath, "utf-8");
1000
1165
  if (hasAiOpsSection(existing)) {
1001
- writeFileSync4(absolutePath, replaceAiOpsSection(existing, wrappedContent), "utf-8");
1166
+ writeFileSync5(absolutePath, replaceAiOpsSection(existing, wrappedContent), "utf-8");
1002
1167
  const stripped = stripAiOpsSection(existing);
1003
1168
  (stripped.trim().length > 0 ? appended : written).push(spec.path);
1004
1169
  continue;
1005
1170
  }
1006
1171
  if (hasLegacyHeader(existing)) {
1007
- writeFileSync4(absolutePath, wrappedContent + "\n", "utf-8");
1172
+ writeFileSync5(absolutePath, wrappedContent + "\n", "utf-8");
1008
1173
  written.push(spec.path);
1009
1174
  continue;
1010
1175
  }
1011
- writeFileSync4(absolutePath, existing.trimEnd() + "\n\n" + wrappedContent + "\n", "utf-8");
1176
+ writeFileSync5(absolutePath, existing.trimEnd() + "\n\n" + wrappedContent + "\n", "utf-8");
1012
1177
  appended.push(spec.path);
1013
1178
  }
1014
1179
  return { written, appended };
@@ -1023,8 +1188,8 @@ var installProjectFiles = (params) => {
1023
1188
  const absolutePath = resolveProjectLayerFilePath(params.basePath, spec.path);
1024
1189
  const previous = previousByPath.get(spec.path);
1025
1190
  if (!existsSync(absolutePath)) {
1026
- mkdirSync4(dirname6(absolutePath), { recursive: true });
1027
- writeFileSync4(absolutePath, spec.content + "\n", "utf-8");
1191
+ mkdirSync5(dirname7(absolutePath), { recursive: true });
1192
+ writeFileSync5(absolutePath, spec.content + "\n", "utf-8");
1028
1193
  created.push(spec.path);
1029
1194
  records.push({
1030
1195
  path: spec.path,
@@ -1033,11 +1198,11 @@ var installProjectFiles = (params) => {
1033
1198
  });
1034
1199
  continue;
1035
1200
  }
1036
- const existingContent = readFileSync6(absolutePath, "utf-8").trimEnd();
1201
+ const existingContent = readFileSync7(absolutePath, "utf-8").trimEnd();
1037
1202
  const existingHash = computeHash([existingContent]);
1038
1203
  if (previous?.created === true && existingHash === previous.templateHash) {
1039
1204
  if (existingHash !== spec.contentHash) {
1040
- writeFileSync4(absolutePath, spec.content + "\n", "utf-8");
1205
+ writeFileSync5(absolutePath, spec.content + "\n", "utf-8");
1041
1206
  refreshed.push(spec.path);
1042
1207
  } else {
1043
1208
  preserved.push(spec.path);
@@ -1060,7 +1225,7 @@ var installProjectFiles = (params) => {
1060
1225
  };
1061
1226
  var buildContextIndexFromDisk = (params) => {
1062
1227
  const documents = params.documentPaths.map(
1063
- (path) => parseProjectLayerDocument(path, readFileSync6(resolveProjectLayerFilePath(params.basePath, path), "utf-8"))
1228
+ (path) => parseProjectLayerDocument(path, readFileSync7(resolveProjectLayerFilePath(params.basePath, path), "utf-8"))
1064
1229
  );
1065
1230
  return ProjectLayerContextIndexSchema.parse({
1066
1231
  schemaVersion: 1,
@@ -1069,14 +1234,14 @@ var buildContextIndexFromDisk = (params) => {
1069
1234
  generatedAt: params.generatedAt
1070
1235
  });
1071
1236
  };
1072
- var computeProjectFileHash = (basePath, relativePath) => computeHash([readFileSync6(resolveProjectLayerFilePath(basePath, relativePath), "utf-8").trimEnd()]);
1237
+ var computeProjectFileHash = (basePath, relativePath) => computeHash([readFileSync7(resolveProjectLayerFilePath(basePath, relativePath), "utf-8").trimEnd()]);
1073
1238
  var collectDocumentPathsFromManifest = (manifest) => [
1074
1239
  ...manifest.managed_files.map((file) => file.path),
1075
1240
  ...manifest.project_files.map((file) => file.path),
1076
1241
  ...manifest.packs.flatMap((pack) => pack.documents.map((file) => file.path))
1077
1242
  ].sort();
1078
1243
  var buildDocsStatusRowsFromDisk = (params) => params.documentPaths.map((path) => {
1079
- const document = parseProjectLayerDocument(path, readFileSync6(resolveProjectLayerFilePath(params.basePath, path), "utf-8"));
1244
+ const document = parseProjectLayerDocument(path, readFileSync7(resolveProjectLayerFilePath(params.basePath, path), "utf-8"));
1080
1245
  return `| ${document.path} | ${document.status} | ${document.owner} |`;
1081
1246
  });
1082
1247
  var replaceDocsStatusRows = (content, rows) => {
@@ -1092,8 +1257,8 @@ var updateDocsStatusTable = (basePath, documentPaths) => {
1092
1257
  const absolutePath = resolveProjectLayerFilePath(basePath, docsStatusPath);
1093
1258
  const beforeHash = computeProjectFileHash(basePath, docsStatusPath);
1094
1259
  const rows = buildDocsStatusRowsFromDisk({ basePath, documentPaths });
1095
- const nextContent = replaceDocsStatusRows(readFileSync6(absolutePath, "utf-8"), rows);
1096
- writeFileSync4(absolutePath, nextContent, "utf-8");
1260
+ const nextContent = replaceDocsStatusRows(readFileSync7(absolutePath, "utf-8"), rows);
1261
+ writeFileSync5(absolutePath, nextContent, "utf-8");
1097
1262
  return {
1098
1263
  beforeHash,
1099
1264
  afterHash: computeProjectFileHash(basePath, docsStatusPath)
@@ -1248,7 +1413,7 @@ var readDocumentSafely = (basePath, path) => {
1248
1413
  if (!existsSync(absolutePath)) {
1249
1414
  return issue("error", "missing-file", `\uD30C\uC77C \uC5C6\uC74C: ${path}`);
1250
1415
  }
1251
- return parseProjectLayerDocument(path, readFileSync6(absolutePath, "utf-8"));
1416
+ return parseProjectLayerDocument(path, readFileSync7(absolutePath, "utf-8"));
1252
1417
  } catch (error) {
1253
1418
  const reason = error instanceof Error ? error.message : "unknown error";
1254
1419
  return issue("error", "invalid-frontmatter", `${path} frontmatter \uD30C\uC2F1 \uC2E4\uD328: ${reason}`);
@@ -1343,7 +1508,7 @@ var diffProjectLayer = (basePath) => {
1343
1508
  issues.push(issue("error", "missing-file", `\uD30C\uC77C \uC5C6\uC74C: ${file.path}`));
1344
1509
  continue;
1345
1510
  }
1346
- const content = readFileSync6(absolutePath, "utf-8");
1511
+ const content = readFileSync7(absolutePath, "utf-8");
1347
1512
  const meta = parseAiOpsMeta(content);
1348
1513
  if (!meta) {
1349
1514
  issues.push(issue("error", "missing-managed-section", `managed section \uBA54\uD0C0 \uC5C6\uC74C: ${file.path}`));
@@ -1405,7 +1570,7 @@ var auditProjectLayer = (basePath) => {
1405
1570
  }
1406
1571
  let docsStatusEntries = [];
1407
1572
  try {
1408
- docsStatusEntries = parseDocsStatusEntries(readFileSync6(docsStatusPath, "utf-8"));
1573
+ docsStatusEntries = parseDocsStatusEntries(readFileSync7(docsStatusPath, "utf-8"));
1409
1574
  } catch (error) {
1410
1575
  const reason = error instanceof Error ? error.message : "unknown error";
1411
1576
  issues.push(issue("error", "invalid-docs-status", `docs/docs-status.md \uD30C\uC2F1 \uC2E4\uD328: ${reason}`));
@@ -1435,16 +1600,16 @@ function removeManagedProjectFile(basePath, relativePath) {
1435
1600
  if (!existsSync(absolutePath)) {
1436
1601
  return { deleted: [], cleaned: [], preserved: [], notFound: [relativePath] };
1437
1602
  }
1438
- const content = readFileSync6(absolutePath, "utf-8");
1603
+ const content = readFileSync7(absolutePath, "utf-8");
1439
1604
  if (!hasAiOpsSection(content)) {
1440
1605
  return { deleted: [], cleaned: [], preserved: [relativePath], notFound: [] };
1441
1606
  }
1442
1607
  const stripped = stripAiOpsSection(content);
1443
1608
  if (stripped.trim().length === 0) {
1444
- rmSync(absolutePath);
1609
+ rmSync2(absolutePath);
1445
1610
  return { deleted: [relativePath], cleaned: [], preserved: [], notFound: [] };
1446
1611
  }
1447
- writeFileSync4(absolutePath, stripped, "utf-8");
1612
+ writeFileSync5(absolutePath, stripped, "utf-8");
1448
1613
  return { deleted: [], cleaned: [relativePath], preserved: [], notFound: [] };
1449
1614
  }
1450
1615
  var removeCreateOnlyProjectFile = (basePath, file) => {
@@ -1452,10 +1617,10 @@ var removeCreateOnlyProjectFile = (basePath, file) => {
1452
1617
  if (!existsSync(absolutePath)) {
1453
1618
  return { deleted: [], cleaned: [], preserved: [], notFound: [file.path] };
1454
1619
  }
1455
- const content = readFileSync6(absolutePath, "utf-8").trimEnd();
1620
+ const content = readFileSync7(absolutePath, "utf-8").trimEnd();
1456
1621
  const currentHash = computeHash([content]);
1457
1622
  if (file.created && currentHash === file.templateHash) {
1458
- rmSync(absolutePath);
1623
+ rmSync2(absolutePath);
1459
1624
  return { deleted: [file.path], cleaned: [], preserved: [], notFound: [] };
1460
1625
  }
1461
1626
  return { deleted: [], cleaned: [], preserved: [file.path], notFound: [] };
@@ -1465,9 +1630,9 @@ var removePackOwnedFile = (basePath, file) => {
1465
1630
  if (!existsSync(absolutePath)) {
1466
1631
  return { deleted: [], cleaned: [], preserved: [], notFound: [file.path] };
1467
1632
  }
1468
- const currentHash = computeHash([readFileSync6(absolutePath, "utf-8").trimEnd()]);
1633
+ const currentHash = computeHash([readFileSync7(absolutePath, "utf-8").trimEnd()]);
1469
1634
  if (currentHash === file.sourceHash) {
1470
- rmSync(absolutePath);
1635
+ rmSync2(absolutePath);
1471
1636
  return { deleted: [file.path], cleaned: [], preserved: [], notFound: [] };
1472
1637
  }
1473
1638
  return { deleted: [], cleaned: [], preserved: [file.path], notFound: [] };
@@ -1487,7 +1652,7 @@ var removeEmptyDirs = (basePath, relativePaths) => {
1487
1652
  if (!existsSync(absoluteDir)) continue;
1488
1653
  try {
1489
1654
  if (readdirSync3(absoluteDir).length === 0) {
1490
- rmSync(absoluteDir, { recursive: true });
1655
+ rmSync2(absoluteDir, { recursive: true });
1491
1656
  }
1492
1657
  } catch {
1493
1658
  }
@@ -1501,7 +1666,7 @@ var uninstallProjectLayer = (basePath, manifest) => {
1501
1666
  );
1502
1667
  const stateFiles = [PROJECT_LAYER_CONTEXT_INDEX_RELATIVE_PATH, PROJECT_LAYER_MANIFEST_RELATIVE_PATH];
1503
1668
  for (const stateFile of stateFiles) {
1504
- rmSync(resolveProjectLayerFilePath(basePath, stateFile), { force: true });
1669
+ rmSync2(resolveProjectLayerFilePath(basePath, stateFile), { force: true });
1505
1670
  }
1506
1671
  const result = mergeRemoveResults([...managedResults, ...projectResults, ...packResults]);
1507
1672
  removeEmptyDirs(basePath, [...result.deleted, ...stateFiles]);
@@ -1509,8 +1674,8 @@ var uninstallProjectLayer = (basePath, manifest) => {
1509
1674
  };
1510
1675
 
1511
1676
  // 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";
1677
+ import { existsSync as existsSync2, mkdirSync as mkdirSync6, readFileSync as readFileSync8, readdirSync as readdirSync4, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "fs";
1678
+ import { dirname as dirname8, isAbsolute as isAbsolute2, join as join12, relative as relative2, resolve as resolve6 } from "path";
1514
1679
  var PACK_REGISTRY_FILENAME = "pack-registry.json";
1515
1680
  var SPEC_LIFECYCLE_PACK_ID = "spec-lifecycle";
1516
1681
  var PACK_INSTALL_ROOT = "docs/specs/";
@@ -1518,9 +1683,9 @@ var RESERVED_DOCUMENT_WARNINGS2 = [
1518
1683
  "\uD310\uB2E8 \uADFC\uAC70\uB85C \uC0AC\uC6A9\uD558\uC9C0 \uB9C8\uC138\uC694",
1519
1684
  "Do not use this document as current decision-making evidence"
1520
1685
  ];
1521
- var DEFAULT_PACKS_DIR = join11(COMPILER_DATA_DIR, "packs");
1686
+ var DEFAULT_PACKS_DIR = join12(COMPILER_DATA_DIR, "packs");
1522
1687
  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")));
1688
+ var readPackCatalog = (packsDir) => PackCatalogSchema.parse(JSON.parse(readFileSync8(join12(packsDir, PACK_REGISTRY_FILENAME), "utf-8")));
1524
1689
  var assertPackInstallPath = (path) => {
1525
1690
  if (!isSafeProjectLayerPath(path) || !path.startsWith(PACK_INSTALL_ROOT)) {
1526
1691
  throw new Error(`Unsafe pack path: ${path}`);
@@ -1529,16 +1694,16 @@ var assertPackInstallPath = (path) => {
1529
1694
  var readPackSourceFiles = (packDir) => {
1530
1695
  const files = [];
1531
1696
  const walk = (relativeDir = "") => {
1532
- const absoluteDir = relativeDir.length > 0 ? join11(packDir, relativeDir) : packDir;
1697
+ const absoluteDir = relativeDir.length > 0 ? join12(packDir, relativeDir) : packDir;
1533
1698
  const entries = readdirSync4(absoluteDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
1534
1699
  for (const entry of entries) {
1535
- const nextRelativePath = relativeDir.length > 0 ? join11(relativeDir, entry.name) : entry.name;
1700
+ const nextRelativePath = relativeDir.length > 0 ? join12(relativeDir, entry.name) : entry.name;
1536
1701
  if (entry.isDirectory()) {
1537
1702
  walk(nextRelativePath);
1538
1703
  continue;
1539
1704
  }
1540
1705
  assertPackInstallPath(nextRelativePath);
1541
- const content = readFileSync7(join11(packDir, nextRelativePath), "utf-8");
1706
+ const content = readFileSync8(join12(packDir, nextRelativePath), "utf-8");
1542
1707
  files.push({
1543
1708
  path: nextRelativePath,
1544
1709
  content,
@@ -1596,11 +1761,11 @@ var resolvePackById = (packsDir, packId) => {
1596
1761
  return pack;
1597
1762
  };
1598
1763
  var serializePackFileContent = (content) => content.length === 0 ? "" : content.trimEnd() + "\n";
1599
- var readProjectFileHash = (basePath, relativePath) => computeHash([readFileSync7(resolveProjectLayerFilePath(basePath, relativePath), "utf-8").trimEnd()]);
1764
+ var readProjectFileHash = (basePath, relativePath) => computeHash([readFileSync8(resolveProjectLayerFilePath(basePath, relativePath), "utf-8").trimEnd()]);
1600
1765
  var writePackFile = (basePath, file) => {
1601
1766
  const absolutePath = resolveProjectLayerFilePath(basePath, file.path);
1602
- mkdirSync5(dirname7(absolutePath), { recursive: true });
1603
- writeFileSync5(absolutePath, serializePackFileContent(file.content), "utf-8");
1767
+ mkdirSync6(dirname8(absolutePath), { recursive: true });
1768
+ writeFileSync6(absolutePath, serializePackFileContent(file.content), "utf-8");
1604
1769
  };
1605
1770
  var buildPackFileRecords = (files) => files.map((file) => ({
1606
1771
  path: file.path,
@@ -1659,7 +1824,7 @@ var applyPackSourceFiles = (params) => {
1659
1824
  continue;
1660
1825
  }
1661
1826
  if (readProjectFileHash(params.basePath, previous.path) === previous.sourceHash) {
1662
- rmSync2(absolutePath);
1827
+ rmSync3(absolutePath);
1663
1828
  deleted.push(previous.path);
1664
1829
  } else {
1665
1830
  preserved.push(previous.path);
@@ -1678,7 +1843,7 @@ var removePackFiles = (basePath, record) => {
1678
1843
  continue;
1679
1844
  }
1680
1845
  if (readProjectFileHash(basePath, file.path) === file.sourceHash) {
1681
- rmSync2(absolutePath);
1846
+ rmSync3(absolutePath);
1682
1847
  deleted.push(file.path);
1683
1848
  } else {
1684
1849
  preserved.push(file.path);
@@ -1687,7 +1852,7 @@ var removePackFiles = (basePath, record) => {
1687
1852
  return { written: [], refreshed: [], preserved, deleted, notFound };
1688
1853
  };
1689
1854
  var removeEmptyDirs2 = (basePath, relativePaths) => {
1690
- const dirs = [...new Set(relativePaths.map((path) => dirname7(path)).filter((dir) => dir !== "."))].sort(
1855
+ const dirs = [...new Set(relativePaths.map((path) => dirname8(path)).filter((dir) => dir !== "."))].sort(
1691
1856
  (a, b) => b.length - a.length
1692
1857
  );
1693
1858
  for (const dir of dirs) {
@@ -1697,7 +1862,7 @@ var removeEmptyDirs2 = (basePath, relativePaths) => {
1697
1862
  }
1698
1863
  try {
1699
1864
  if (readdirSync4(absoluteDir).length === 0) {
1700
- rmSync2(absoluteDir, { recursive: true });
1865
+ rmSync3(absoluteDir, { recursive: true });
1701
1866
  }
1702
1867
  } catch {
1703
1868
  }
@@ -1817,14 +1982,233 @@ var diffProjectLayerPack = (params) => {
1817
1982
  };
1818
1983
 
1819
1984
  // src/core/uninstall-plan.ts
1820
- import { join as join12 } from "path";
1985
+ import { join as join13 } from "path";
1821
1986
 
1822
1987
  // src/core/context-promotion.ts
1823
1988
  import { createHash as createHash2 } from "crypto";
1824
1989
  import { execFileSync } from "child_process";
1825
- 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";
1827
- import { z as z12 } from "zod";
1990
+ import { existsSync as existsSync3, mkdirSync as mkdirSync7, readFileSync as readFileSync9, statSync, writeFileSync as writeFileSync7 } from "fs";
1991
+ import { dirname as dirname9, join as join14, resolve as resolve7 } from "path";
1992
+ import { z as z15 } from "zod";
1993
+
1994
+ // src/core/tool-use-hook.ts
1995
+ import { z as z14 } from "zod";
1996
+ var HookToolInputSchema = z14.object({
1997
+ command: z14.string().optional()
1998
+ }).passthrough();
1999
+ var ToolUseHookInputSchema = z14.object({
2000
+ hook_event_name: z14.string(),
2001
+ cwd: z14.string(),
2002
+ tool_name: z14.string().optional(),
2003
+ tool_input: z14.unknown().optional(),
2004
+ tool_response: z14.unknown().optional()
2005
+ }).passthrough();
2006
+ var SHELL_CONTROL_TOKENS = /* @__PURE__ */ new Set(["&&", "||", ";", "|", "(", ")"]);
2007
+ var SHELL_SCRIPT_FLAGS = /* @__PURE__ */ new Set(["-c", "-lc"]);
2008
+ var GIT_GLOBAL_OPTIONS_WITH_VALUE = /* @__PURE__ */ new Set([
2009
+ "-C",
2010
+ "-c",
2011
+ "--git-dir",
2012
+ "--work-tree",
2013
+ "--namespace",
2014
+ "--config-env",
2015
+ "--exec-path"
2016
+ ]);
2017
+ var basename = (token) => token.replace(/\\/g, "/").split("/").at(-1) ?? token;
2018
+ var isAssignmentToken = (token) => /^[A-Za-z_][A-Za-z0-9_]*=/.test(token);
2019
+ var tokenizeShellLike = (command) => {
2020
+ const tokens = [];
2021
+ let current = "";
2022
+ let quote = null;
2023
+ const pushCurrent = () => {
2024
+ if (current.length > 0) {
2025
+ tokens.push(current);
2026
+ current = "";
2027
+ }
2028
+ };
2029
+ for (let index = 0; index < command.length; index += 1) {
2030
+ const char = command[index];
2031
+ const nextChar = command[index + 1];
2032
+ if (quote) {
2033
+ if (char === quote) {
2034
+ quote = null;
2035
+ continue;
2036
+ }
2037
+ current += char;
2038
+ continue;
2039
+ }
2040
+ if (char === '"' || char === "'") {
2041
+ quote = char;
2042
+ continue;
2043
+ }
2044
+ if (/\s/.test(char)) {
2045
+ pushCurrent();
2046
+ continue;
2047
+ }
2048
+ if (char === "&" && nextChar === "&" || char === "|" && nextChar === "|") {
2049
+ pushCurrent();
2050
+ tokens.push(`${char}${nextChar}`);
2051
+ index += 1;
2052
+ continue;
2053
+ }
2054
+ if (char === ";" || char === "|" || char === "(" || char === ")") {
2055
+ pushCurrent();
2056
+ tokens.push(char);
2057
+ continue;
2058
+ }
2059
+ current += char;
2060
+ }
2061
+ pushCurrent();
2062
+ return tokens;
2063
+ };
2064
+ var splitCommandSegments = (tokens) => {
2065
+ const segments = [];
2066
+ let current = [];
2067
+ for (const token of tokens) {
2068
+ if (SHELL_CONTROL_TOKENS.has(token)) {
2069
+ if (current.length > 0) {
2070
+ segments.push(current);
2071
+ current = [];
2072
+ }
2073
+ continue;
2074
+ }
2075
+ current.push(token);
2076
+ }
2077
+ if (current.length > 0) {
2078
+ segments.push(current);
2079
+ }
2080
+ return segments;
2081
+ };
2082
+ var firstExecutableIndex = (segment) => {
2083
+ let index = 0;
2084
+ while (index < segment.length && isAssignmentToken(segment[index])) {
2085
+ index += 1;
2086
+ }
2087
+ if (segment[index] === "env") {
2088
+ index += 1;
2089
+ while (index < segment.length && isAssignmentToken(segment[index])) {
2090
+ index += 1;
2091
+ }
2092
+ }
2093
+ if (segment[index] === "command" || segment[index] === "sudo") {
2094
+ index += 1;
2095
+ }
2096
+ return index;
2097
+ };
2098
+ var segmentInvokesGitCommit = (segment) => {
2099
+ const executableIndex = firstExecutableIndex(segment);
2100
+ if (executableIndex >= segment.length || basename(segment[executableIndex]) !== "git") {
2101
+ return false;
2102
+ }
2103
+ for (let index = executableIndex + 1; index < segment.length; index += 1) {
2104
+ const token = segment[index];
2105
+ if (GIT_GLOBAL_OPTIONS_WITH_VALUE.has(token)) {
2106
+ index += 1;
2107
+ continue;
2108
+ }
2109
+ if (token.startsWith("-")) {
2110
+ continue;
2111
+ }
2112
+ return token === "commit";
2113
+ }
2114
+ return false;
2115
+ };
2116
+ var segmentInvokesShellScriptWithGitCommit = (segment) => {
2117
+ const executableIndex = firstExecutableIndex(segment);
2118
+ const executable = segment[executableIndex];
2119
+ if (!executable || !["bash", "sh", "zsh"].includes(basename(executable))) {
2120
+ return false;
2121
+ }
2122
+ for (let index = executableIndex + 1; index < segment.length - 1; index += 1) {
2123
+ if (SHELL_SCRIPT_FLAGS.has(segment[index]) && isGitCommitCommand(segment[index + 1])) {
2124
+ return true;
2125
+ }
2126
+ }
2127
+ return false;
2128
+ };
2129
+ var isGitCommitCommand = (command) => {
2130
+ const segments = splitCommandSegments(tokenizeShellLike(command));
2131
+ return segments.some(
2132
+ (segment) => segmentInvokesGitCommit(segment) || segmentInvokesShellScriptWithGitCommit(segment)
2133
+ );
2134
+ };
2135
+ var isJsonRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2136
+ var numberField = (record, keys) => {
2137
+ for (const key of keys) {
2138
+ const value = record[key];
2139
+ if (typeof value === "number") {
2140
+ return value;
2141
+ }
2142
+ }
2143
+ return null;
2144
+ };
2145
+ var booleanField = (record, keys) => {
2146
+ for (const key of keys) {
2147
+ const value = record[key];
2148
+ if (typeof value === "boolean") {
2149
+ return value;
2150
+ }
2151
+ }
2152
+ return null;
2153
+ };
2154
+ var GIT_COMMIT_FAILURE_OUTPUT_PATTERNS = [
2155
+ /(^|\n)\s*fatal:/i,
2156
+ /(^|\n)\s*error:/i,
2157
+ /(^|\n)\s*nothing to commit\b/i,
2158
+ /(^|\n)\s*no changes added to commit\b/i,
2159
+ /(^|\n).*aborting commit\b/i,
2160
+ /(^|\n).*commit failed\b/i,
2161
+ /(^|\n).*failed to .*commit\b/i,
2162
+ /(^|\n).*command failed\b/i,
2163
+ /(^|\n).*non-zero exit\b/i,
2164
+ /(^|\n).*exit (code|status)\s+[1-9]\d*\b/i,
2165
+ /(^|\n).*exited with code\s+[1-9]\d*\b/i,
2166
+ /(^|\n).*hook.*(failed|declined|error|exit(?:ed)? with code|non-zero)/i
2167
+ ];
2168
+ var GIT_COMMIT_SUCCESS_OUTPUT_PATTERN = /(^|\n)\[[^\]\n]+ [a-f0-9]{7,40}\]/i;
2169
+ var stringIndicatesGitCommitSuccess = (output) => GIT_COMMIT_SUCCESS_OUTPUT_PATTERN.test(output);
2170
+ var stringIndicatesGitCommitFailureOrSuccess = (output) => stringIndicatesGitCommitSuccess(output) ? false : GIT_COMMIT_FAILURE_OUTPUT_PATTERNS.some((pattern) => pattern.test(output)) ? true : null;
2171
+ var recordStringFieldsIndicateGitCommitFailure = (record) => ["message", "output", "stdout", "stderr", "error", "combinedOutput"].some((key) => {
2172
+ const value = record[key];
2173
+ return typeof value === "string" && stringIndicatesGitCommitFailureOrSuccess(value) === true;
2174
+ });
2175
+ var toolResponseIndicatesFailure = (toolResponse) => {
2176
+ if (typeof toolResponse === "string") {
2177
+ return stringIndicatesGitCommitFailureOrSuccess(toolResponse) === true;
2178
+ }
2179
+ if (!isJsonRecord(toolResponse)) {
2180
+ return false;
2181
+ }
2182
+ const success = booleanField(toolResponse, ["success", "ok"]);
2183
+ if (success === false) {
2184
+ return true;
2185
+ }
2186
+ const exitCode = numberField(toolResponse, ["exit_code", "exitCode", "status", "code"]);
2187
+ if (exitCode !== null && exitCode !== 0) {
2188
+ return true;
2189
+ }
2190
+ return recordStringFieldsIndicateGitCommitFailure(toolResponse);
2191
+ };
2192
+ var parseSuccessfulGitCommitPostToolUseHook = (hookInput) => {
2193
+ const parsed = ToolUseHookInputSchema.safeParse(hookInput);
2194
+ if (!parsed.success) {
2195
+ return null;
2196
+ }
2197
+ if (parsed.data.hook_event_name !== "PostToolUse" || parsed.data.tool_name !== "Bash") {
2198
+ return null;
2199
+ }
2200
+ const toolInput = HookToolInputSchema.safeParse(parsed.data.tool_input);
2201
+ const command = toolInput.success ? toolInput.data.command ?? "" : "";
2202
+ if (!isGitCommitCommand(command) || toolResponseIndicatesFailure(parsed.data.tool_response)) {
2203
+ return null;
2204
+ }
2205
+ return {
2206
+ cwd: parsed.data.cwd,
2207
+ command
2208
+ };
2209
+ };
2210
+
2211
+ // src/core/context-promotion.ts
1828
2212
  var CONTEXT_PROMOTION_DECISION = {
1829
2213
  PROMOTED: "promoted",
1830
2214
  NO_PROMOTION: "no-promotion"
@@ -1834,30 +2218,30 @@ var CONTEXT_PROMOTION_SCOPE = {
1834
2218
  PROJECT_LOCAL: "project-local",
1835
2219
  GLOBAL: "global"
1836
2220
  };
1837
- var ContextPromotionDecisionSchema = z12.union([
1838
- z12.literal(CONTEXT_PROMOTION_DECISION.PROMOTED),
1839
- z12.literal(CONTEXT_PROMOTION_DECISION.NO_PROMOTION)
2221
+ var ContextPromotionDecisionSchema = z15.union([
2222
+ z15.literal(CONTEXT_PROMOTION_DECISION.PROMOTED),
2223
+ z15.literal(CONTEXT_PROMOTION_DECISION.NO_PROMOTION)
1840
2224
  ]);
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)
2225
+ var ContextPromotionScopeSchema = z15.union([
2226
+ z15.literal(CONTEXT_PROMOTION_SCOPE.CORE),
2227
+ z15.literal(CONTEXT_PROMOTION_SCOPE.PROJECT_LOCAL),
2228
+ z15.literal(CONTEXT_PROMOTION_SCOPE.GLOBAL)
1845
2229
  ]);
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(),
2230
+ var ContextPromotionReceiptSchema = z15.object({
2231
+ fingerprint: z15.string().regex(/^[a-f0-9]{16}$/),
2232
+ commitHash: z15.string().regex(/^(NO_HEAD|[a-f0-9]{40})$/).optional(),
1849
2233
  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)
2234
+ scopes: z15.array(ContextPromotionScopeSchema),
2235
+ targets: z15.array(z15.string().min(1)),
2236
+ summary: z15.string().min(1),
2237
+ resolvedAt: z15.string().min(1)
1854
2238
  }).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)
2239
+ var ContextPromotionReceiptIndexSchema = z15.object({
2240
+ schemaVersion: z15.literal(1),
2241
+ kind: z15.literal("context-promotion-receipts"),
2242
+ projectKey: z15.string().regex(/^[a-f0-9]{12}$/),
2243
+ projectRoot: z15.string().min(1),
2244
+ receipts: z15.array(ContextPromotionReceiptSchema)
1861
2245
  }).strict();
1862
2246
  var RECEIPT_INDEX_FILENAME = "receipts-index.json";
1863
2247
  var DEFAULT_PRUNE_MAX = 50;
@@ -1887,13 +2271,13 @@ var readUntrackedFingerprintParts = (gitRoot) => {
1887
2271
  const raw = runGit(gitRoot, ["ls-files", "--others", "--exclude-standard", "-z"]);
1888
2272
  const paths = raw.split("\0").filter((path) => path.length > 0).sort((a, b) => a.localeCompare(b));
1889
2273
  return paths.map((relativePath) => {
1890
- const absolutePath = join13(gitRoot, relativePath);
2274
+ const absolutePath = join14(gitRoot, relativePath);
1891
2275
  try {
1892
2276
  const stat = statSync(absolutePath);
1893
2277
  if (!stat.isFile()) {
1894
2278
  return `${relativePath}:non-file`;
1895
2279
  }
1896
- const content = readFileSync8(absolutePath);
2280
+ const content = readFileSync9(absolutePath);
1897
2281
  return `${relativePath}:${createHash2("sha256").update(content).digest("hex")}`;
1898
2282
  } catch {
1899
2283
  throw new Error(`Unable to read untracked path for context promotion fingerprint: ${relativePath}`);
@@ -1907,7 +2291,7 @@ var readTrackedWorkingTreeFingerprintParts = (gitRoot) => {
1907
2291
  return [
1908
2292
  `raw:${rawDiff}`,
1909
2293
  ...paths.map((relativePath) => {
1910
- const absolutePath = join13(gitRoot, relativePath);
2294
+ const absolutePath = join14(gitRoot, relativePath);
1911
2295
  if (!existsSync3(absolutePath)) {
1912
2296
  return `${relativePath}:deleted`;
1913
2297
  }
@@ -1915,7 +2299,7 @@ var readTrackedWorkingTreeFingerprintParts = (gitRoot) => {
1915
2299
  if (!stat.isFile()) {
1916
2300
  return `${relativePath}:non-file`;
1917
2301
  }
1918
- const content = readFileSync8(absolutePath);
2302
+ const content = readFileSync9(absolutePath);
1919
2303
  return `${relativePath}:${createHash2("sha256").update(content).digest("hex")}`;
1920
2304
  })
1921
2305
  ];
@@ -1933,26 +2317,19 @@ var computeContextPromotionFingerprint = (gitRoot) => hashHex(
1933
2317
  ],
1934
2318
  16
1935
2319
  );
1936
- var resolveContextPromotionReceiptIndexPath = (params) => join13(
1937
- params.userBasePath,
1938
- ".ai-ops",
1939
- "context-promotion",
1940
- "projects",
1941
- params.projectKey,
1942
- RECEIPT_INDEX_FILENAME
1943
- );
2320
+ var resolveContextPromotionReceiptIndexPath = (params) => join14(params.userBasePath, ".ai-ops", "context-promotion", "projects", params.projectKey, RECEIPT_INDEX_FILENAME);
1944
2321
  var parseContextPromotionReceiptIndex = (json) => ContextPromotionReceiptIndexSchema.parse(JSON.parse(json));
1945
2322
  var serializeContextPromotionReceiptIndex = (index) => JSON.stringify(index, null, 2) + "\n";
1946
2323
  var readContextPromotionReceiptIndex = (indexPath) => {
1947
2324
  try {
1948
- return parseContextPromotionReceiptIndex(readFileSync8(indexPath, "utf-8"));
2325
+ return parseContextPromotionReceiptIndex(readFileSync9(indexPath, "utf-8"));
1949
2326
  } catch {
1950
2327
  return null;
1951
2328
  }
1952
2329
  };
1953
2330
  var writeContextPromotionReceiptIndex = (indexPath, index) => {
1954
- mkdirSync6(dirname8(indexPath), { recursive: true });
1955
- writeFileSync6(indexPath, serializeContextPromotionReceiptIndex(index), "utf-8");
2331
+ mkdirSync7(dirname9(indexPath), { recursive: true });
2332
+ writeFileSync7(indexPath, serializeContextPromotionReceiptIndex(index), "utf-8");
1956
2333
  };
1957
2334
  var buildEmptyReceiptIndex = (params) => ({
1958
2335
  schemaVersion: 1,
@@ -2000,7 +2377,7 @@ var pruneContextPromotionReceipts = (params) => {
2000
2377
  writeContextPromotionReceiptIndex(params.indexPath, nextIndex);
2001
2378
  return nextIndex;
2002
2379
  };
2003
- var hasContextPromotionLayer = (gitRoot) => existsSync3(join13(gitRoot, PROJECT_LAYER_CONTEXT_INDEX_RELATIVE_PATH));
2380
+ var hasContextPromotionLayer = (gitRoot) => existsSync3(join14(gitRoot, PROJECT_LAYER_CONTEXT_INDEX_RELATIVE_PATH));
2004
2381
  var getContextPromotionStatus = (params) => {
2005
2382
  const cwd = resolve7(params.cwd);
2006
2383
  const gitRoot = resolveContextPromotionGitRoot(cwd);
@@ -2087,143 +2464,6 @@ var resolveContextPromotion = (params) => {
2087
2464
  });
2088
2465
  return getContextPromotionStatus({ cwd: params.cwd, userBasePath: params.userBasePath });
2089
2466
  };
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
2467
  var buildContextPromotionReviewPrompt = (status) => {
2228
2468
  const projectRoot = status.gitRoot ?? status.cwd;
2229
2469
  const cdCommand = `cd ${JSON.stringify(projectRoot)}`;
@@ -2279,115 +2519,396 @@ var buildPostToolUseOutput = (prompt) => ({
2279
2519
  additionalContext: prompt
2280
2520
  }
2281
2521
  });
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") {
2522
+ var evaluateContextPromotionPostToolUseHook = (params) => {
2523
+ const hookInput = parseSuccessfulGitCommitPostToolUseHook(params.hookInput);
2524
+ if (!hookInput) {
2525
+ return null;
2526
+ }
2527
+ let status;
2528
+ try {
2529
+ status = getContextPromotionStatus({
2530
+ cwd: hookInput.cwd,
2531
+ userBasePath: params.userBasePath
2532
+ });
2533
+ } catch (error) {
2534
+ return buildPostToolUseOutput(buildContextPromotionStatusFailurePrompt(hookInput.cwd, error));
2535
+ }
2536
+ if (!status.hasContextLayer || status.receipt) {
2537
+ return null;
2538
+ }
2539
+ return buildPostToolUseOutput(buildContextPromotionReviewPrompt(status));
2540
+ };
2541
+
2542
+ // src/core/pc-integration.ts
2543
+ import { execFileSync as execFileSync2 } from "child_process";
2544
+ import { existsSync as existsSync4, readFileSync as readFileSync10, readdirSync as readdirSync5 } from "fs";
2545
+ import { join as join15, resolve as resolve8, sep } from "path";
2546
+ var normalizePath = (path) => resolve8(path.replace(/^~(?=$|\/)/, process.env.HOME ?? "~"));
2547
+ var pathContains = (parentPath, childPath) => {
2548
+ const parent = normalizePath(parentPath);
2549
+ const child = normalizePath(childPath);
2550
+ return child === parent || child.startsWith(`${parent}${sep}`);
2551
+ };
2552
+ var normalizeFieldValue = (value) => {
2553
+ const trimmed = value?.trim() ?? "";
2554
+ if (trimmed.length === 0 || ["none", "null", "-", "<empty>"].includes(trimmed.toLowerCase())) {
2555
+ return null;
2556
+ }
2557
+ return trimmed.replace(/^`|`$/g, "");
2558
+ };
2559
+ var extractSection = (content, headings) => {
2560
+ const headingPattern = headings.map((heading) => heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
2561
+ const match = new RegExp(`^##\\s+(?:${headingPattern})\\s*$`, "mu").exec(content);
2562
+ if (!match) {
2563
+ return "";
2564
+ }
2565
+ const start = match.index + match[0].length;
2566
+ const rest = content.slice(start);
2567
+ const nextHeading = /^##\s+/mu.exec(rest);
2568
+ return nextHeading ? rest.slice(0, nextHeading.index) : rest;
2569
+ };
2570
+ var parseListField = (content, labels) => {
2571
+ for (const label of labels) {
2572
+ const escapedLabel = label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2573
+ const match = new RegExp(`^-\\s+${escapedLabel}:\\s*(.+)$`, "mu").exec(content);
2574
+ const value = normalizeFieldValue(match?.[1] ?? null);
2575
+ if (value) {
2287
2576
  return value;
2288
2577
  }
2289
2578
  }
2290
2579
  return null;
2291
2580
  };
2292
- var booleanField = (record, keys) => {
2293
- for (const key of keys) {
2294
- const value = record[key];
2295
- if (typeof value === "boolean") {
2296
- return value;
2581
+ var readTextFileOrNull = (filePath) => {
2582
+ try {
2583
+ return readFileSync10(filePath, "utf-8");
2584
+ } catch {
2585
+ return null;
2586
+ }
2587
+ };
2588
+ var runGit2 = (cwd, args) => execFileSync2("git", [...args], {
2589
+ cwd,
2590
+ encoding: "utf-8",
2591
+ stdio: ["ignore", "pipe", "ignore"]
2592
+ }).trim();
2593
+ var resolveGitRoot = (cwd) => {
2594
+ try {
2595
+ return runGit2(cwd, ["rev-parse", "--show-toplevel"]);
2596
+ } catch {
2597
+ return null;
2598
+ }
2599
+ };
2600
+ var readGitHead2 = (cwd) => {
2601
+ try {
2602
+ return runGit2(cwd, ["rev-parse", "--verify", "HEAD"]);
2603
+ } catch {
2604
+ return null;
2605
+ }
2606
+ };
2607
+ var listWorkspaceStatePaths = (contextRoot) => {
2608
+ const workspacesDir = join15(contextRoot, "workspaces");
2609
+ if (!existsSync4(workspacesDir)) {
2610
+ return [];
2611
+ }
2612
+ return readdirSync5(workspacesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join15(workspacesDir, entry.name, "workspace-state.md")).filter((statePath) => existsSync4(statePath)).sort((a, b) => a.localeCompare(b));
2613
+ };
2614
+ var parseWorkspaceCandidate = (statePath) => {
2615
+ const content = readTextFileOrNull(statePath);
2616
+ if (!content) {
2617
+ return null;
2618
+ }
2619
+ const workspaceRoot = parseListField(content, ["\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uB8E8\uD2B8", "Workspace Root"]);
2620
+ if (!workspaceRoot) {
2621
+ return null;
2622
+ }
2623
+ const activeSection = extractSection(content, ["\uD65C\uC131 Workstream", "Active Workstream"]);
2624
+ const activeWorkstreamId = parseListField(activeSection, ["ID", "Workstream ID", "Active Workstream"]);
2625
+ const workspaceDir = resolve8(statePath, "..");
2626
+ const id = parseListField(content, ["\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 ID", "Workspace ID"]) ?? workspaceDir.split(sep).at(-1) ?? "unknown";
2627
+ return {
2628
+ id,
2629
+ statePath,
2630
+ workspaceDir,
2631
+ workspaceRoot,
2632
+ activeWorkstreamId
2633
+ };
2634
+ };
2635
+ var findMatchingWorkspace = (params) => {
2636
+ 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);
2637
+ return candidates[0] ?? null;
2638
+ };
2639
+ var parseRepoEntry = (entryPath) => {
2640
+ const content = readTextFileOrNull(entryPath);
2641
+ if (!content) {
2642
+ return null;
2643
+ }
2644
+ const id = parseListField(content, ["\uC5D4\uD2B8\uB9AC ID", "Entry ID"]);
2645
+ if (!id) {
2646
+ return null;
2647
+ }
2648
+ return {
2649
+ id,
2650
+ path: parseListField(content, ["\uACBD\uB85C", "Path"]),
2651
+ gitRoot: parseListField(content, ["Git \uB8E8\uD2B8", "Git Root"])
2652
+ };
2653
+ };
2654
+ var findCurrentEntry = (params) => {
2655
+ const reposDir = join15(params.workspaceDir, "repos");
2656
+ if (!existsSync4(reposDir)) {
2657
+ return null;
2658
+ }
2659
+ const entries = readdirSync5(reposDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => parseRepoEntry(join15(reposDir, entry.name))).filter((entry) => entry !== null).filter((entry) => {
2660
+ const paths = [entry.path, entry.gitRoot].filter((path) => path !== null);
2661
+ return paths.some((path) => pathContains(path, params.cwd));
2662
+ }).sort((a, b) => {
2663
+ const aLength = Math.max(0, ...[a.path, a.gitRoot].map((path) => path ? normalizePath(path).length : 0));
2664
+ const bLength = Math.max(0, ...[b.path, b.gitRoot].map((path) => path ? normalizePath(path).length : 0));
2665
+ return bLength - aLength;
2666
+ });
2667
+ return entries[0] ?? null;
2668
+ };
2669
+ var parseWorkstreamScopeEntryIds = (content) => {
2670
+ const scopeSection = extractSection(content, ["\uBC94\uC704", "Scope"]);
2671
+ if (scopeSection.length === 0) {
2672
+ return [];
2673
+ }
2674
+ const lines = scopeSection.split("\n");
2675
+ const ids = [];
2676
+ let inEntryBlock = false;
2677
+ for (const line of lines) {
2678
+ if (/^-\s*(엔트리|Entries|Entry):/.test(line)) {
2679
+ inEntryBlock = true;
2680
+ const inlineValue = normalizeFieldValue(line.split(":").slice(1).join(":"));
2681
+ if (inlineValue && !inlineValue.includes("<")) {
2682
+ ids.push(...inlineValue.split(",").map((value) => value.trim().replace(/^`|`$/g, "")));
2683
+ }
2684
+ continue;
2685
+ }
2686
+ if (inEntryBlock && /^-\s+\S/.test(line)) {
2687
+ inEntryBlock = false;
2688
+ }
2689
+ if (!inEntryBlock) {
2690
+ continue;
2691
+ }
2692
+ const nestedMatch = /^\s+-\s+`?([a-z0-9]+(?:-[a-z0-9]+)*)`?/.exec(line);
2693
+ if (nestedMatch) {
2694
+ ids.push(nestedMatch[1]);
2297
2695
  }
2298
2696
  }
2299
- return null;
2697
+ return [...new Set(ids.filter((id) => /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(id)))];
2300
2698
  };
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;
2699
+ var parseLastConfirmedCommitHash = (params) => {
2700
+ const section = extractSection(params.content, ["\uB9C8\uC9C0\uB9C9 \uD655\uC778 Commit", "Last Confirmed Commit"]);
2701
+ if (section.length === 0) {
2702
+ return null;
2325
2703
  }
2326
- if (!isJsonRecord(toolResponse)) {
2327
- return false;
2704
+ const escapedEntryId = params.entryId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2705
+ const match = new RegExp(`^-\\s+\`?${escapedEntryId}\`?:\\s*([a-f0-9]{40})\\b`, "imu").exec(section);
2706
+ return match?.[1] ?? null;
2707
+ };
2708
+ var getPcHandoffStatus = (params) => {
2709
+ const cwd = normalizePath(params.cwd);
2710
+ const contextRoot = normalizePath(params.contextRoot);
2711
+ if (!existsSync4(contextRoot)) {
2712
+ return {
2713
+ cwd,
2714
+ contextRoot,
2715
+ workspaceId: null,
2716
+ workspaceRoot: null,
2717
+ activeWorkstreamId: null,
2718
+ activeWorkstreamPath: null,
2719
+ currentEntryId: null,
2720
+ lastConfirmedCommitHash: null,
2721
+ ready: false,
2722
+ skipReason: "pc context root not found"
2723
+ };
2328
2724
  }
2329
- const success = booleanField(toolResponse, ["success", "ok"]);
2330
- if (success === false) {
2331
- return true;
2725
+ const workspace = findMatchingWorkspace({ cwd, contextRoot });
2726
+ if (!workspace) {
2727
+ return {
2728
+ cwd,
2729
+ contextRoot,
2730
+ workspaceId: null,
2731
+ workspaceRoot: null,
2732
+ activeWorkstreamId: null,
2733
+ activeWorkstreamPath: null,
2734
+ currentEntryId: null,
2735
+ lastConfirmedCommitHash: null,
2736
+ ready: false,
2737
+ skipReason: "matching pc workspace not found"
2738
+ };
2332
2739
  }
2333
- const exitCode = numberField(toolResponse, ["exit_code", "exitCode", "status", "code"]);
2334
- if (exitCode !== null && exitCode !== 0) {
2335
- return true;
2740
+ if (!workspace.activeWorkstreamId) {
2741
+ return {
2742
+ cwd,
2743
+ contextRoot,
2744
+ workspaceId: workspace.id,
2745
+ workspaceRoot: workspace.workspaceRoot,
2746
+ activeWorkstreamId: null,
2747
+ activeWorkstreamPath: null,
2748
+ currentEntryId: null,
2749
+ lastConfirmedCommitHash: null,
2750
+ ready: false,
2751
+ skipReason: "active pc workstream not selected"
2752
+ };
2336
2753
  }
2337
- return recordStringFieldsIndicateGitCommitFailure(toolResponse);
2754
+ const activeWorkstreamPath = join15(workspace.workspaceDir, "workstreams", `${workspace.activeWorkstreamId}.md`);
2755
+ const activeWorkstreamContent = readTextFileOrNull(activeWorkstreamPath);
2756
+ if (!activeWorkstreamContent) {
2757
+ return {
2758
+ cwd,
2759
+ contextRoot,
2760
+ workspaceId: workspace.id,
2761
+ workspaceRoot: workspace.workspaceRoot,
2762
+ activeWorkstreamId: workspace.activeWorkstreamId,
2763
+ activeWorkstreamPath,
2764
+ currentEntryId: null,
2765
+ lastConfirmedCommitHash: null,
2766
+ ready: false,
2767
+ skipReason: "active pc workstream file not found"
2768
+ };
2769
+ }
2770
+ const currentEntry = findCurrentEntry({ cwd, workspaceDir: workspace.workspaceDir });
2771
+ if (!currentEntry) {
2772
+ return {
2773
+ cwd,
2774
+ contextRoot,
2775
+ workspaceId: workspace.id,
2776
+ workspaceRoot: workspace.workspaceRoot,
2777
+ activeWorkstreamId: workspace.activeWorkstreamId,
2778
+ activeWorkstreamPath,
2779
+ currentEntryId: null,
2780
+ lastConfirmedCommitHash: null,
2781
+ ready: false,
2782
+ skipReason: "current repo is not registered in pc workspace"
2783
+ };
2784
+ }
2785
+ const lastConfirmedCommitHash = parseLastConfirmedCommitHash({
2786
+ content: activeWorkstreamContent,
2787
+ entryId: currentEntry.id
2788
+ });
2789
+ const scopeEntryIds = parseWorkstreamScopeEntryIds(activeWorkstreamContent);
2790
+ if (scopeEntryIds.length > 0 && !scopeEntryIds.includes(currentEntry.id)) {
2791
+ return {
2792
+ cwd,
2793
+ contextRoot,
2794
+ workspaceId: workspace.id,
2795
+ workspaceRoot: workspace.workspaceRoot,
2796
+ activeWorkstreamId: workspace.activeWorkstreamId,
2797
+ activeWorkstreamPath,
2798
+ currentEntryId: currentEntry.id,
2799
+ lastConfirmedCommitHash,
2800
+ ready: false,
2801
+ skipReason: "current repo is outside the active pc workstream scope"
2802
+ };
2803
+ }
2804
+ return {
2805
+ cwd,
2806
+ contextRoot,
2807
+ workspaceId: workspace.id,
2808
+ workspaceRoot: workspace.workspaceRoot,
2809
+ activeWorkstreamId: workspace.activeWorkstreamId,
2810
+ activeWorkstreamPath,
2811
+ currentEntryId: currentEntry.id,
2812
+ lastConfirmedCommitHash,
2813
+ ready: true,
2814
+ skipReason: null
2815
+ };
2338
2816
  };
2339
- var evaluateContextPromotionPostToolUseHook = (params) => {
2340
- const hookInput = ToolUseHookInputSchema.safeParse(params.hookInput);
2341
- if (!hookInput.success) {
2817
+ var buildPcDonePrompt = (params) => [
2818
+ "A successful git commit just created a new HEAD commit.",
2819
+ "",
2820
+ "Run `$pc:done` now to record the handoff for the active personal project context.",
2821
+ "",
2822
+ "Important guardrails:",
2823
+ "- Do not create or initialize a new pc context from this hook.",
2824
+ "- If `$pc:done` cannot match the prepared workspace, active workstream, or current repo scope, skip and briefly say why.",
2825
+ "- If the active workstream already records this HEAD as the last confirmed commit, skip without writing another handoff.",
2826
+ "- Do not modify the product repo for this hook; `$pc:done` may only update `~/.personal-project-contexts/` and commit that context repo.",
2827
+ "- Use the just-created HEAD commit as the newest evidence for completed work and the next first action.",
2828
+ "",
2829
+ `Project git root: ${params.gitRoot}`,
2830
+ `HEAD: ${params.head}`,
2831
+ `pc context root: ${params.status.contextRoot}`,
2832
+ `pc workspace: ${params.status.workspaceId ?? "unknown"} (${params.status.workspaceRoot ?? "unknown"})`,
2833
+ `active workstream: ${params.status.activeWorkstreamId ?? "unknown"}`,
2834
+ `current entry: ${params.status.currentEntryId ?? "unknown"}`,
2835
+ `last confirmed commit: ${params.status.lastConfirmedCommitHash ?? "none"}`
2836
+ ].join("\n");
2837
+ var buildPostToolUseOutput2 = (prompt) => ({
2838
+ decision: "block",
2839
+ reason: prompt,
2840
+ hookSpecificOutput: {
2841
+ hookEventName: "PostToolUse",
2842
+ additionalContext: prompt
2843
+ }
2844
+ });
2845
+ var evaluatePcPostToolUseHook = (params) => {
2846
+ const gitCommitHook = parseSuccessfulGitCommitPostToolUseHook(params.hookInput);
2847
+ if (!gitCommitHook) {
2342
2848
  return null;
2343
2849
  }
2344
- if (hookInput.data.hook_event_name !== "PostToolUse" || hookInput.data.tool_name !== "Bash") {
2850
+ const gitRoot = resolveGitRoot(gitCommitHook.cwd);
2851
+ if (!gitRoot) {
2345
2852
  return null;
2346
2853
  }
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)) {
2854
+ const head = readGitHead2(gitRoot);
2855
+ if (!head) {
2350
2856
  return null;
2351
2857
  }
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));
2858
+ const status = getPcHandoffStatus({
2859
+ cwd: gitCommitHook.cwd,
2860
+ contextRoot: params.contextRoot
2861
+ });
2862
+ if (!status.ready) {
2863
+ return null;
2360
2864
  }
2361
- if (!status.hasContextLayer || status.receipt) {
2865
+ if (status.lastConfirmedCommitHash === head) {
2362
2866
  return null;
2363
2867
  }
2364
- return buildPostToolUseOutput(buildContextPromotionReviewPrompt(status));
2868
+ return buildPostToolUseOutput2(buildPcDonePrompt({ status, head, gitRoot }));
2365
2869
  };
2366
2870
 
2367
2871
  // 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";
2872
+ import { existsSync as existsSync5, mkdirSync as mkdirSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync8 } from "fs";
2873
+ import { dirname as dirname10, join as join16 } from "path";
2370
2874
  var CONTEXT_PROMOTION_HOOK_ID = "context-promotion";
2371
2875
  var CONTEXT_PROMOTION_HOOK_COMMAND_MARKER = "context-promotion hook post-tool-use";
2372
2876
  var CONTEXT_PROMOTION_LEGACY_HOOK_COMMAND_MARKER = "context-promotion hook pre-tool-use";
2373
2877
  var CONTEXT_PROMOTION_DEFAULT_HOOK_COMMAND = `ai-ops ${CONTEXT_PROMOTION_HOOK_COMMAND_MARKER}`;
2878
+ var PC_HOOK_ID = "pc";
2879
+ var PC_HOOK_COMMAND_MARKER = "integration hook post-tool-use pc";
2880
+ var PC_DEFAULT_HOOK_COMMAND = `ai-ops ${PC_HOOK_COMMAND_MARKER}`;
2374
2881
  var PRE_TOOL_USE_EVENT = "PreToolUse";
2375
2882
  var POST_TOOL_USE_EVENT = "PostToolUse";
2376
2883
  var BASH_MATCHER = "^Bash$";
2884
+ var CONTEXT_PROMOTION_CODEX_HOOK = {
2885
+ id: CONTEXT_PROMOTION_HOOK_ID,
2886
+ commandMarker: CONTEXT_PROMOTION_HOOK_COMMAND_MARKER,
2887
+ legacyCommandMarkers: [CONTEXT_PROMOTION_LEGACY_HOOK_COMMAND_MARKER],
2888
+ defaultCommand: CONTEXT_PROMOTION_DEFAULT_HOOK_COMMAND,
2889
+ statusMessage: "Checking context promotion review"
2890
+ };
2891
+ var PC_CODEX_HOOK = {
2892
+ id: PC_HOOK_ID,
2893
+ commandMarker: PC_HOOK_COMMAND_MARKER,
2894
+ legacyCommandMarkers: [],
2895
+ defaultCommand: PC_DEFAULT_HOOK_COMMAND,
2896
+ statusMessage: "Checking pc handoff"
2897
+ };
2377
2898
  var isJsonRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2378
2899
  var readJsonRecord = (filePath) => {
2379
- if (!existsSync4(filePath)) {
2900
+ if (!existsSync5(filePath)) {
2380
2901
  return {};
2381
2902
  }
2382
- const parsed = JSON.parse(readFileSync9(filePath, "utf-8"));
2903
+ const parsed = JSON.parse(readFileSync11(filePath, "utf-8"));
2383
2904
  if (!isJsonRecord2(parsed)) {
2384
2905
  throw new Error("hooks.json must contain a JSON object");
2385
2906
  }
2386
2907
  return parsed;
2387
2908
  };
2388
2909
  var writeJsonRecord = (filePath, value) => {
2389
- mkdirSync7(dirname9(filePath), { recursive: true });
2390
- writeFileSync7(filePath, JSON.stringify(value, null, 2) + "\n", "utf-8");
2910
+ mkdirSync8(dirname10(filePath), { recursive: true });
2911
+ writeFileSync8(filePath, JSON.stringify(value, null, 2) + "\n", "utf-8");
2391
2912
  };
2392
2913
  var getOrCreateRecord = (record, key) => {
2393
2914
  const existing = record[key];
@@ -2402,34 +2923,34 @@ var getArray = (record, key) => {
2402
2923
  const existing = record[key];
2403
2924
  return Array.isArray(existing) ? existing : [];
2404
2925
  };
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));
2926
+ var handlerMatchesDefinition = (definition) => (handler) => isJsonRecord2(handler) && typeof handler.command === "string" && [definition.commandMarker, ...definition.legacyCommandMarkers].some((marker) => handler.command.includes(marker));
2406
2927
  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) => {
2928
+ var groupHasDefinitionHook = (definition) => (group) => isJsonRecord2(group) && getArray(group, "hooks").some(handlerMatchesDefinition(definition));
2929
+ var groupHasCurrentDefinitionHook = (group, command) => isJsonRecord2(group) && getArray(group, "hooks").some((handler) => handlerMatchesCommand(handler, command));
2930
+ var countDefinitionHandlers = (groups, definition) => groups.reduce((count, group) => {
2410
2931
  if (!isJsonRecord2(group)) {
2411
2932
  return count;
2412
2933
  }
2413
- return count + getArray(group, "hooks").filter(handlerMatchesContextPromotion).length;
2934
+ return count + getArray(group, "hooks").filter(handlerMatchesDefinition(definition)).length;
2414
2935
  }, 0);
2415
- var configHasContextPromotionHook = (config) => {
2936
+ var configHasDefinitionHook = (config, definition) => {
2416
2937
  const hooks = config.hooks;
2417
2938
  if (!isJsonRecord2(hooks)) {
2418
2939
  return false;
2419
2940
  }
2420
- return getArray(hooks, POST_TOOL_USE_EVENT).some(groupHasContextPromotionHook);
2941
+ return getArray(hooks, POST_TOOL_USE_EVENT).some(groupHasDefinitionHook(definition));
2421
2942
  };
2422
- var configHasOnlyCurrentContextPromotionHook = (config, command) => {
2943
+ var configHasOnlyCurrentDefinitionHook = (config, definition, command) => {
2423
2944
  const hooks = config.hooks;
2424
2945
  if (!isJsonRecord2(hooks)) {
2425
2946
  return false;
2426
2947
  }
2427
- const hasLegacy = getArray(hooks, PRE_TOOL_USE_EVENT).some(groupHasContextPromotionHook);
2948
+ const hasLegacy = getArray(hooks, PRE_TOOL_USE_EVENT).some(groupHasDefinitionHook(definition));
2428
2949
  const postGroups = getArray(hooks, POST_TOOL_USE_EVENT);
2429
- const hasCurrent = postGroups.some((group) => groupHasCurrentContextPromotionHook(group, command));
2430
- return hasCurrent && !hasLegacy && countContextPromotionHandlers(postGroups) === 1;
2950
+ const hasCurrent = postGroups.some((group) => groupHasCurrentDefinitionHook(group, command));
2951
+ return hasCurrent && !hasLegacy && countDefinitionHandlers(postGroups, definition) === 1;
2431
2952
  };
2432
- var removeContextPromotionHooksFromEvent = (hooks, eventName) => {
2953
+ var removeDefinitionHooksFromEvent = (hooks, eventName, definition) => {
2433
2954
  const previousGroups = getArray(hooks, eventName);
2434
2955
  let removed = false;
2435
2956
  const nextGroups = previousGroups.map((group) => {
@@ -2438,7 +2959,7 @@ var removeContextPromotionHooksFromEvent = (hooks, eventName) => {
2438
2959
  }
2439
2960
  const previousHandlers = getArray(group, "hooks");
2440
2961
  const nextHandlers = previousHandlers.filter((handler) => {
2441
- const matches = handlerMatchesContextPromotion(handler);
2962
+ const matches = handlerMatchesDefinition(definition)(handler);
2442
2963
  if (matches) {
2443
2964
  removed = true;
2444
2965
  }
@@ -2462,21 +2983,29 @@ var removeContextPromotionHooksFromEvent = (hooks, eventName) => {
2462
2983
  }
2463
2984
  return true;
2464
2985
  };
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}`);
2986
+ var resolveCodexHooksPath = (codexHomePath) => join16(codexHomePath, "hooks.json");
2987
+ var buildCodexHookCommand = (params) => {
2988
+ const command = params.overrideCommand?.trim() ?? params.definition.defaultCommand;
2989
+ if (!command.includes(params.definition.commandMarker)) {
2990
+ throw new Error(`${params.definition.id} hook command must include: ${params.definition.commandMarker}`);
2470
2991
  }
2471
2992
  return command;
2472
2993
  };
2994
+ var buildContextPromotionHookCommand = (overrideCommand) => buildCodexHookCommand({
2995
+ definition: CONTEXT_PROMOTION_CODEX_HOOK,
2996
+ overrideCommand
2997
+ });
2998
+ var inspectCodexHook = (params) => ({
2999
+ hooksPath: params.hooksPath,
3000
+ installed: configHasDefinitionHook(readJsonRecord(params.hooksPath), params.definition)
3001
+ });
2473
3002
  var inspectContextPromotionHook = (hooksPath) => ({
2474
3003
  hooksPath,
2475
- installed: configHasContextPromotionHook(readJsonRecord(hooksPath))
3004
+ installed: inspectCodexHook({ hooksPath, definition: CONTEXT_PROMOTION_CODEX_HOOK }).installed
2476
3005
  });
2477
- var installContextPromotionHook = (params) => {
3006
+ var installCodexHook = (params) => {
2478
3007
  const config = readJsonRecord(params.hooksPath);
2479
- if (configHasOnlyCurrentContextPromotionHook(config, params.command)) {
3008
+ if (configHasOnlyCurrentDefinitionHook(config, params.definition, params.command)) {
2480
3009
  return {
2481
3010
  hooksPath: params.hooksPath,
2482
3011
  installed: true,
@@ -2484,8 +3013,8 @@ var installContextPromotionHook = (params) => {
2484
3013
  };
2485
3014
  }
2486
3015
  const hooks = getOrCreateRecord(config, "hooks");
2487
- removeContextPromotionHooksFromEvent(hooks, PRE_TOOL_USE_EVENT);
2488
- removeContextPromotionHooksFromEvent(hooks, POST_TOOL_USE_EVENT);
3016
+ removeDefinitionHooksFromEvent(hooks, PRE_TOOL_USE_EVENT, params.definition);
3017
+ removeDefinitionHooksFromEvent(hooks, POST_TOOL_USE_EVENT, params.definition);
2489
3018
  const existingGroups = getArray(hooks, POST_TOOL_USE_EVENT);
2490
3019
  const nextGroup = {
2491
3020
  matcher: BASH_MATCHER,
@@ -2494,7 +3023,7 @@ var installContextPromotionHook = (params) => {
2494
3023
  type: "command",
2495
3024
  command: params.command,
2496
3025
  timeout: 30,
2497
- statusMessage: "Checking context promotion review"
3026
+ statusMessage: params.definition.statusMessage
2498
3027
  }
2499
3028
  ]
2500
3029
  };
@@ -2506,32 +3035,42 @@ var installContextPromotionHook = (params) => {
2506
3035
  changed: true
2507
3036
  };
2508
3037
  };
2509
- var uninstallContextPromotionHook = (hooksPath) => {
2510
- const config = readJsonRecord(hooksPath);
3038
+ var installContextPromotionHook = (params) => installCodexHook({
3039
+ hooksPath: params.hooksPath,
3040
+ definition: CONTEXT_PROMOTION_CODEX_HOOK,
3041
+ command: params.command
3042
+ });
3043
+ var uninstallCodexHook = (params) => {
3044
+ const config = readJsonRecord(params.hooksPath);
2511
3045
  const hooks = config.hooks;
2512
3046
  if (!isJsonRecord2(hooks)) {
2513
- return { hooksPath, removed: false, changed: false };
3047
+ return { hooksPath: params.hooksPath, removed: false, changed: false };
2514
3048
  }
2515
- const removedLegacy = removeContextPromotionHooksFromEvent(hooks, PRE_TOOL_USE_EVENT);
2516
- const removedCurrent = removeContextPromotionHooksFromEvent(hooks, POST_TOOL_USE_EVENT);
3049
+ const removedLegacy = removeDefinitionHooksFromEvent(hooks, PRE_TOOL_USE_EVENT, params.definition);
3050
+ const removedCurrent = removeDefinitionHooksFromEvent(hooks, POST_TOOL_USE_EVENT, params.definition);
2517
3051
  const removed = removedLegacy || removedCurrent;
2518
3052
  if (!removed) {
2519
- return { hooksPath, removed: false, changed: false };
3053
+ return { hooksPath: params.hooksPath, removed: false, changed: false };
2520
3054
  }
2521
- writeJsonRecord(hooksPath, config);
2522
- return { hooksPath, removed: true, changed: true };
3055
+ writeJsonRecord(params.hooksPath, config);
3056
+ return { hooksPath: params.hooksPath, removed: true, changed: true };
2523
3057
  };
3058
+ var uninstallContextPromotionHook = (hooksPath) => uninstallCodexHook({
3059
+ hooksPath,
3060
+ definition: CONTEXT_PROMOTION_CODEX_HOOK
3061
+ });
2524
3062
 
2525
3063
  // 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");
3064
+ import { join as join17 } from "path";
3065
+ var resolveSkillsDir = () => join17(COMPILER_DATA_DIR, "skills");
3066
+ var resolveSubagentsDir = () => join17(COMPILER_DATA_DIR, "subagents");
3067
+ var resolvePacksDir = () => join17(COMPILER_DATA_DIR, "packs");
3068
+ var resolveIntegrationsDir = () => join17(COMPILER_DATA_DIR, "integrations");
2530
3069
  var resolveBasePath = () => process.cwd();
2531
3070
  var resolveUserBasePath = () => {
2532
3071
  const userBasePath = process.env.AI_OPS_HOME ?? process.env.HOME;
2533
3072
  if (!userBasePath) {
2534
- throw new Error("AI_OPS_HOME or HOME is required for global asset commands");
3073
+ throw new Error("AI_OPS_HOME or HOME is required for user/global component commands");
2535
3074
  }
2536
3075
  return userBasePath;
2537
3076
  };
@@ -2776,7 +3315,7 @@ ${result.notFound.map((file) => ` ${file}`).join("\n")}`);
2776
3315
 
2777
3316
  // src/commands/skill.ts
2778
3317
  import * as p7 from "@clack/prompts";
2779
- import { rmSync as rmSync4 } from "fs";
3318
+ import { rmSync as rmSync5 } from "fs";
2780
3319
 
2781
3320
  // src/lib/skill-state.ts
2782
3321
  var resolveRequestedTools = (params) => {
@@ -2810,19 +3349,19 @@ var findInstalledSkill = (installedSkills, skillId) => {
2810
3349
  };
2811
3350
 
2812
3351
  // 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";
3352
+ import { existsSync as existsSync6, mkdirSync as mkdirSync9, rmSync as rmSync4, writeFileSync as writeFileSync9 } from "fs";
3353
+ import { dirname as dirname11, resolve as resolve9 } from "path";
2815
3354
  var installSkillPackages = (basePath, packages) => {
2816
3355
  const writtenRoots = [];
2817
3356
  for (const skillPackage of packages) {
2818
- const absRoot = resolve8(basePath, skillPackage.rootDir);
2819
- if (existsSync5(absRoot)) {
2820
- rmSync3(absRoot, { recursive: true, force: true });
3357
+ const absRoot = resolve9(basePath, skillPackage.rootDir);
3358
+ if (existsSync6(absRoot)) {
3359
+ rmSync4(absRoot, { recursive: true, force: true });
2821
3360
  }
2822
3361
  for (const file of skillPackage.files) {
2823
- const absPath = resolve8(basePath, file.relativePath);
2824
- mkdirSync8(dirname10(absPath), { recursive: true });
2825
- writeFileSync8(absPath, file.content + "\n", "utf-8");
3362
+ const absPath = resolve9(basePath, file.relativePath);
3363
+ mkdirSync9(dirname11(absPath), { recursive: true });
3364
+ writeFileSync9(absPath, file.content + "\n", "utf-8");
2826
3365
  }
2827
3366
  writtenRoots.push(skillPackage.rootDir);
2828
3367
  }
@@ -2831,9 +3370,9 @@ var installSkillPackages = (basePath, packages) => {
2831
3370
  var removeDirectories = (basePath, relativeDirs) => {
2832
3371
  const removed = [];
2833
3372
  for (const relativeDir of relativeDirs) {
2834
- const absPath = resolve8(basePath, relativeDir);
2835
- if (!existsSync5(absPath)) continue;
2836
- rmSync3(absPath, { recursive: true, force: true });
3373
+ const absPath = resolve9(basePath, relativeDir);
3374
+ if (!existsSync6(absPath)) continue;
3375
+ rmSync4(absPath, { recursive: true, force: true });
2837
3376
  removed.push(relativeDir);
2838
3377
  }
2839
3378
  return removed;
@@ -2859,7 +3398,7 @@ var writeUserSkillState = (params) => {
2859
3398
  const previous = readSkillRegistry(registryPath);
2860
3399
  const skills = params.removeSkillId ? removeInstalledSkill(previous?.skills ?? [], params.removeSkillId) : params.nextSkill ? upsertInstalledSkill(previous?.skills ?? [], params.nextSkill) : previous?.skills ?? [];
2861
3400
  if (skills.length === 0) {
2862
- rmSync4(registryPath, { force: true });
3401
+ rmSync5(registryPath, { force: true });
2863
3402
  return;
2864
3403
  }
2865
3404
  writeSkillRegistry(registryPath, {
@@ -3011,14 +3550,14 @@ var skillUninstallCommand = async (skillId) => {
3011
3550
 
3012
3551
  // src/commands/subagent.ts
3013
3552
  import * as p8 from "@clack/prompts";
3014
- import { existsSync as existsSync7, rmSync as rmSync6 } from "fs";
3553
+ import { existsSync as existsSync8, rmSync as rmSync7 } from "fs";
3015
3554
 
3016
3555
  // 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";
3556
+ import { existsSync as existsSync7, mkdirSync as mkdirSync10, rmSync as rmSync6, writeFileSync as writeFileSync10 } from "fs";
3557
+ import { dirname as dirname12, isAbsolute as isAbsolute3, relative as relative3, resolve as resolve10 } from "path";
3019
3558
  var resolveInsideBasePath = (basePath, relativePath) => {
3020
- const absBasePath = resolve9(basePath);
3021
- const absPath = resolve9(absBasePath, relativePath);
3559
+ const absBasePath = resolve10(basePath);
3560
+ const absPath = resolve10(absBasePath, relativePath);
3022
3561
  const fromBase = relative3(absBasePath, absPath);
3023
3562
  if (fromBase.length === 0 || fromBase.startsWith("..") || isAbsolute3(fromBase)) {
3024
3563
  throw new Error(`Subagent path escapes AI_OPS_HOME: ${relativePath}`);
@@ -3030,11 +3569,11 @@ var installSubagentPackages = (basePath, packages) => {
3030
3569
  for (const subagentPackage of packages) {
3031
3570
  for (const file of subagentPackage.files) {
3032
3571
  const absPath = resolveInsideBasePath(basePath, file.relativePath);
3033
- if (existsSync6(absPath)) {
3034
- rmSync5(absPath, { recursive: true, force: true });
3572
+ if (existsSync7(absPath)) {
3573
+ rmSync6(absPath, { recursive: true, force: true });
3035
3574
  }
3036
- mkdirSync9(dirname11(absPath), { recursive: true });
3037
- writeFileSync9(absPath, file.content.trimEnd() + "\n", "utf-8");
3575
+ mkdirSync10(dirname12(absPath), { recursive: true });
3576
+ writeFileSync10(absPath, file.content.trimEnd() + "\n", "utf-8");
3038
3577
  written.push(file.relativePath);
3039
3578
  }
3040
3579
  }
@@ -3044,8 +3583,8 @@ var removeSubagentFiles = (basePath, relativePaths) => {
3044
3583
  const removed = [];
3045
3584
  for (const relativePath of relativePaths) {
3046
3585
  const absPath = resolveInsideBasePath(basePath, relativePath);
3047
- if (!existsSync6(absPath)) continue;
3048
- rmSync5(absPath, { recursive: true, force: true });
3586
+ if (!existsSync7(absPath)) continue;
3587
+ rmSync6(absPath, { recursive: true, force: true });
3049
3588
  removed.push(relativePath);
3050
3589
  }
3051
3590
  return removed;
@@ -3095,7 +3634,7 @@ var writeUserSubagentState = (params) => {
3095
3634
  const previous = readSubagentManifest(manifestPath);
3096
3635
  const subagents = params.removeSubagentId ? removeInstalledSubagent(previous?.subagents ?? [], params.removeSubagentId) : params.nextSubagent ? upsertInstalledSubagent(previous?.subagents ?? [], params.nextSubagent) : previous?.subagents ?? [];
3097
3636
  if (subagents.length === 0) {
3098
- rmSync6(manifestPath, { force: true });
3637
+ rmSync7(manifestPath, { force: true });
3099
3638
  return;
3100
3639
  }
3101
3640
  writeSubagentManifest(manifestPath, {
@@ -3106,7 +3645,7 @@ var writeUserSubagentState = (params) => {
3106
3645
  };
3107
3646
  var readInstalledSubagents = (basePath) => readSubagentManifest(resolveSubagentManifestPath(basePath))?.subagents ?? [];
3108
3647
  var warnMissingSkills = (requiredSkills) => {
3109
- const missing = requiredSkills.filter((skill) => !existsSync7(skill.path));
3648
+ const missing = requiredSkills.filter((skill) => !existsSync8(skill.path));
3110
3649
  if (missing.length === 0) {
3111
3650
  return;
3112
3651
  }
@@ -3417,13 +3956,13 @@ var parseMax = (max) => {
3417
3956
  }
3418
3957
  return parsed;
3419
3958
  };
3420
- var readStdin = async () => new Promise((resolve10, reject) => {
3959
+ var readStdin = async () => new Promise((resolve11, reject) => {
3421
3960
  let raw = "";
3422
3961
  process.stdin.setEncoding("utf-8");
3423
3962
  process.stdin.on("data", (chunk) => {
3424
3963
  raw += chunk;
3425
3964
  });
3426
- process.stdin.on("end", () => resolve10(raw));
3965
+ process.stdin.on("end", () => resolve11(raw));
3427
3966
  process.stdin.on("error", reject);
3428
3967
  });
3429
3968
  var reportContextPromotionError = (error) => {
@@ -3524,8 +4063,8 @@ var contextPromotionPostToolUseHookCommand = async () => {
3524
4063
 
3525
4064
  // src/commands/codex-hook.ts
3526
4065
  import * as p11 from "@clack/prompts";
3527
- import { existsSync as existsSync8 } from "fs";
3528
- import { join as join16 } from "path";
4066
+ import { existsSync as existsSync9 } from "fs";
4067
+ import { join as join18 } from "path";
3529
4068
  var CONTEXT_PROMOTION_REVIEW_SKILL_ID = "context-promotion-review";
3530
4069
  var resolveCodexHomePath = () => {
3531
4070
  const codexHome = process.env.CODEX_HOME;
@@ -3563,7 +4102,7 @@ var resolveContextPromotionReviewSkill = () => {
3563
4102
  };
3564
4103
  var hasInstalledContextPromotionReviewSkill = (basePath) => {
3565
4104
  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"));
4105
+ return installedSkill?.tools.includes(SKILL_TOOL.CODEX) === true && existsSync9(join18(basePath, ".agents/skills/context-promotion-review/SKILL.md"));
3567
4106
  };
3568
4107
  var ensureContextPromotionReviewSkill = (basePath) => {
3569
4108
  const skill = resolveContextPromotionReviewSkill();
@@ -3577,7 +4116,7 @@ var ensureContextPromotionReviewSkill = (basePath) => {
3577
4116
  skill,
3578
4117
  requestedTools
3579
4118
  });
3580
- const alreadyInstalled = existingInstalledSkill?.sourceHash === installedSkill.sourceHash && existingInstalledSkill.tools.includes(SKILL_TOOL.CODEX) && existsSync8(join16(basePath, ".agents/skills/context-promotion-review/SKILL.md"));
4119
+ const alreadyInstalled = existingInstalledSkill?.sourceHash === installedSkill.sourceHash && existingInstalledSkill.tools.includes(SKILL_TOOL.CODEX) && existsSync9(join18(basePath, ".agents/skills/context-promotion-review/SKILL.md"));
3581
4120
  if (alreadyInstalled) {
3582
4121
  return { changed: false, installedSkill };
3583
4122
  }
@@ -3638,9 +4177,389 @@ var codexHookUninstallCommand = async (hookId) => {
3638
4177
  p11.outro("ai-ops codex-hook uninstall \uC644\uB8CC");
3639
4178
  };
3640
4179
 
4180
+ // src/commands/integration.ts
4181
+ import * as p12 from "@clack/prompts";
4182
+ import { existsSync as existsSync10, rmSync as rmSync8 } from "fs";
4183
+ import { join as join19 } from "path";
4184
+ var CODEX_HOOK_DEFINITIONS = [CONTEXT_PROMOTION_CODEX_HOOK, PC_CODEX_HOOK];
4185
+ var resolveCodexHomePath2 = () => {
4186
+ const codexHome = process.env.CODEX_HOME;
4187
+ if (codexHome && codexHome.length > 0) {
4188
+ return codexHome;
4189
+ }
4190
+ const home = process.env.HOME;
4191
+ if (!home) {
4192
+ throw new Error("CODEX_HOME or HOME is required for Codex hook commands");
4193
+ }
4194
+ return `${home}/.codex`;
4195
+ };
4196
+ var resolvePersonalContextRoot = () => {
4197
+ const home = process.env.HOME;
4198
+ if (!home) {
4199
+ throw new Error("HOME is required for pc integration commands");
4200
+ }
4201
+ return `${home}/.personal-project-contexts`;
4202
+ };
4203
+ var resolveCodexHookDefinition = (hookId) => {
4204
+ const hookDefinition = CODEX_HOOK_DEFINITIONS.find((definition) => definition.id === hookId);
4205
+ if (!hookDefinition) {
4206
+ throw new Error(`Unknown Codex hook for integration: ${hookId}`);
4207
+ }
4208
+ return hookDefinition;
4209
+ };
4210
+ var resolveCatalogSkillComponent = (entry) => {
4211
+ const component = entry.components.find((candidate) => candidate.type === INTEGRATION_COMPONENT_TYPE.SKILL);
4212
+ if (!component || component.type !== INTEGRATION_COMPONENT_TYPE.SKILL) {
4213
+ throw new Error(`Integration catalog entry must declare a skill component: ${entry.id}`);
4214
+ }
4215
+ return component;
4216
+ };
4217
+ var resolveCatalogHookComponent = (entry) => {
4218
+ const component = entry.components.find((candidate) => candidate.type === INTEGRATION_COMPONENT_TYPE.CODEX_HOOK);
4219
+ if (!component || component.type !== INTEGRATION_COMPONENT_TYPE.CODEX_HOOK) {
4220
+ throw new Error(`Integration catalog entry must declare a codex-hook component: ${entry.id}`);
4221
+ }
4222
+ return component;
4223
+ };
4224
+ var resolveCatalogReceiptConfigComponents = (entry) => entry.components.filter(
4225
+ (component) => component.type === INTEGRATION_COMPONENT_TYPE.RECEIPT_CONFIG
4226
+ );
4227
+ var loadIntegrationDefinitions = () => loadAllIntegrations(resolveIntegrationsDir()).map((entry) => {
4228
+ const hookComponent = resolveCatalogHookComponent(entry);
4229
+ return {
4230
+ ...entry,
4231
+ skillComponent: resolveCatalogSkillComponent(entry),
4232
+ hookComponent,
4233
+ receiptConfigComponents: resolveCatalogReceiptConfigComponents(entry),
4234
+ hookDefinition: resolveCodexHookDefinition(hookComponent.id)
4235
+ };
4236
+ });
4237
+ var parseIntegrationId = (integrationId) => {
4238
+ const definition = loadIntegrationDefinitions().find((candidate) => candidate.id === integrationId);
4239
+ if (!definition) {
4240
+ throw new Error(`Unknown integration: ${integrationId}`);
4241
+ }
4242
+ return definition.id;
4243
+ };
4244
+ var resolveIntegrationDefinition = (integrationId) => {
4245
+ const definition = loadIntegrationDefinitions().find((candidate) => candidate.id === integrationId);
4246
+ if (!definition) {
4247
+ throw new Error(`Unknown integration: ${integrationId}`);
4248
+ }
4249
+ return definition;
4250
+ };
4251
+ var reportIntegrationError = (error) => {
4252
+ const message = error instanceof Error ? error.message : "unknown error";
4253
+ p12.log.error(message);
4254
+ process.exitCode = 1;
4255
+ };
4256
+ var readStdin2 = async () => new Promise((resolve11, reject) => {
4257
+ let raw = "";
4258
+ process.stdin.setEncoding("utf-8");
4259
+ process.stdin.on("data", (chunk) => {
4260
+ raw += chunk;
4261
+ });
4262
+ process.stdin.on("end", () => resolve11(raw));
4263
+ process.stdin.on("error", reject);
4264
+ });
4265
+ var readInstalledSkills3 = (basePath) => (readSkillRegistry(resolveSkillRegistryPath(basePath))?.skills ?? []).map((installedSkill) => ({
4266
+ ...installedSkill,
4267
+ id: resolveCanonicalSkillId(installedSkill.id)
4268
+ }));
4269
+ var resolveSkillById2 = (skillId) => {
4270
+ const skill = loadAllSkills(resolveSkillsDir()).find((candidate) => candidate.id === skillId);
4271
+ if (!skill) {
4272
+ throw new Error(`Unknown skill: ${skillId}`);
4273
+ }
4274
+ return skill;
4275
+ };
4276
+ var hasInstalledCodexSkill = (params) => {
4277
+ const installedSkill = findInstalledSkill(readInstalledSkills3(params.basePath), params.skillId);
4278
+ return installedSkill?.tools.includes(SKILL_TOOL.CODEX) === true && existsSync10(join19(params.basePath, ".agents/skills", params.skillId, "SKILL.md"));
4279
+ };
4280
+ var writeUserSkillState2 = (params) => {
4281
+ const registryPath = resolveSkillRegistryPath(params.basePath);
4282
+ const previous = readSkillRegistry(registryPath);
4283
+ const skills = params.removeSkillId ? removeInstalledSkill(previous?.skills ?? [], params.removeSkillId) : params.nextSkill ? upsertInstalledSkill(previous?.skills ?? [], params.nextSkill) : previous?.skills ?? [];
4284
+ if (skills.length === 0) {
4285
+ rmSync8(registryPath, { force: true });
4286
+ return;
4287
+ }
4288
+ writeSkillRegistry(registryPath, {
4289
+ skills,
4290
+ cliVersion: params.cliVersion,
4291
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
4292
+ });
4293
+ };
4294
+ var ensureSkillComponent = (params) => {
4295
+ const skill = resolveSkillById2(params.skillId);
4296
+ const installedSkills = readInstalledSkills3(params.basePath);
4297
+ const existingInstalledSkill = findInstalledSkill(installedSkills, skill.id);
4298
+ const requestedTools = mergeSkillTools({
4299
+ existing: existingInstalledSkill?.tools,
4300
+ requested: [SKILL_TOOL.CODEX]
4301
+ });
4302
+ const { packages, installedSkill } = buildSkillInstallPlan({
4303
+ skill,
4304
+ requestedTools
4305
+ });
4306
+ const alreadyCurrent = existingInstalledSkill?.sourceHash === installedSkill.sourceHash && existingInstalledSkill.tools.includes(SKILL_TOOL.CODEX) && existsSync10(join19(params.basePath, ".agents/skills", params.skillId, "SKILL.md"));
4307
+ if (alreadyCurrent) {
4308
+ return {
4309
+ type: INTEGRATION_COMPONENT_TYPE.SKILL,
4310
+ id: params.skillId,
4311
+ tools: [SKILL_TOOL.CODEX],
4312
+ owned: params.previouslyOwned
4313
+ };
4314
+ }
4315
+ installSkillPackages(params.basePath, packages);
4316
+ writeUserSkillState2({
4317
+ basePath: params.basePath,
4318
+ cliVersion: params.cliVersion,
4319
+ nextSkill: installedSkill
4320
+ });
4321
+ return {
4322
+ type: INTEGRATION_COMPONENT_TYPE.SKILL,
4323
+ id: params.skillId,
4324
+ tools: [SKILL_TOOL.CODEX],
4325
+ owned: true
4326
+ };
4327
+ };
4328
+ var ensureHookComponent = (params) => {
4329
+ const command = buildCodexHookCommand({
4330
+ definition: params.definition,
4331
+ overrideCommand: params.command
4332
+ });
4333
+ const installedBefore = inspectCodexHook({
4334
+ hooksPath: params.hooksPath,
4335
+ definition: params.definition
4336
+ }).installed;
4337
+ const result = installCodexHook({
4338
+ hooksPath: params.hooksPath,
4339
+ definition: params.definition,
4340
+ command
4341
+ });
4342
+ return {
4343
+ type: INTEGRATION_COMPONENT_TYPE.CODEX_HOOK,
4344
+ id: params.hookId,
4345
+ command,
4346
+ owned: params.previouslyOwned || result.changed || !installedBefore
4347
+ };
4348
+ };
4349
+ var buildReceiptConfigComponents = (components) => components.map((component) => ({
4350
+ type: INTEGRATION_COMPONENT_TYPE.RECEIPT_CONFIG,
4351
+ id: component.id,
4352
+ storagePath: component.storage_path,
4353
+ owned: false
4354
+ }));
4355
+ var componentWasOwned = (params) => params.previous?.components.some(
4356
+ (component) => component.type === params.type && component.id === params.id && component.owned
4357
+ ) ?? false;
4358
+ var buildInstalledIntegration = (params) => {
4359
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4360
+ return {
4361
+ id: params.definition.id,
4362
+ components: [...params.components],
4363
+ installedAt: params.previous?.installedAt ?? now,
4364
+ updatedAt: now
4365
+ };
4366
+ };
4367
+ var removeOwnedSkill = (params) => {
4368
+ const installedSkill = findInstalledSkill(readInstalledSkills3(params.basePath), params.skillId);
4369
+ if (!installedSkill) {
4370
+ return [];
4371
+ }
4372
+ const removed = removeDirectories(params.basePath, installedSkill.installed_paths);
4373
+ writeUserSkillState2({
4374
+ basePath: params.basePath,
4375
+ cliVersion: params.cliVersion,
4376
+ removeSkillId: params.skillId
4377
+ });
4378
+ return removed;
4379
+ };
4380
+ var formatComponentStatus = (component) => {
4381
+ const owner = component.owned ? "owned" : "pre-existing";
4382
+ if (component.type === INTEGRATION_COMPONENT_TYPE.SKILL) {
4383
+ return `skill:${component.id} (${owner})`;
4384
+ }
4385
+ if (component.type === INTEGRATION_COMPONENT_TYPE.CODEX_HOOK) {
4386
+ return `codex-hook:${component.id} (${owner})`;
4387
+ }
4388
+ return `receipt-config:${component.id} (${owner})`;
4389
+ };
4390
+ var integrationListCommand = async () => {
4391
+ p12.intro("ai-ops integration list");
4392
+ try {
4393
+ const manifest = readIntegrationManifest(resolveIntegrationManifestPath(resolveUserBasePath()));
4394
+ const installed = new Set((manifest?.integrations ?? []).map((integration) => integration.id));
4395
+ const lines = loadIntegrationDefinitions().map((definition) => {
4396
+ const suffix = installed.has(definition.id) ? "installed" : "not installed";
4397
+ return `- ${definition.id} - ${suffix} - ${definition.description}`;
4398
+ });
4399
+ p12.log.info(lines.join("\n"));
4400
+ } catch (error) {
4401
+ reportIntegrationError(error);
4402
+ }
4403
+ p12.outro("ai-ops integration list \uC644\uB8CC");
4404
+ };
4405
+ var integrationInstallCommand = async (integrationId, opts = {}) => {
4406
+ p12.intro(`ai-ops integration install ${integrationId}`);
4407
+ try {
4408
+ const definition = resolveIntegrationDefinition(integrationId);
4409
+ const basePath = resolveUserBasePath();
4410
+ const cliVersion = getCliVersion();
4411
+ const manifestPath = resolveIntegrationManifestPath(basePath);
4412
+ const previous = findInstalledIntegration(readIntegrationManifest(manifestPath)?.integrations ?? [], definition.id);
4413
+ const skillComponent = ensureSkillComponent({
4414
+ basePath,
4415
+ cliVersion,
4416
+ skillId: definition.skillComponent.id,
4417
+ previouslyOwned: componentWasOwned({
4418
+ previous,
4419
+ type: INTEGRATION_COMPONENT_TYPE.SKILL,
4420
+ id: definition.skillComponent.id
4421
+ })
4422
+ });
4423
+ const hookComponent = ensureHookComponent({
4424
+ hooksPath: resolveCodexHooksPath(resolveCodexHomePath2()),
4425
+ hookId: definition.hookComponent.id,
4426
+ definition: definition.hookDefinition,
4427
+ command: opts.command,
4428
+ previouslyOwned: componentWasOwned({
4429
+ previous,
4430
+ type: INTEGRATION_COMPONENT_TYPE.CODEX_HOOK,
4431
+ id: definition.hookComponent.id
4432
+ })
4433
+ });
4434
+ const installedIntegration = buildInstalledIntegration({
4435
+ definition,
4436
+ previous,
4437
+ components: [skillComponent, hookComponent, ...buildReceiptConfigComponents(definition.receiptConfigComponents)]
4438
+ });
4439
+ writeUserIntegrationState({
4440
+ manifestPath,
4441
+ cliVersion,
4442
+ nextIntegration: installedIntegration
4443
+ });
4444
+ p12.log.success(`integration \uC124\uCE58 \uC644\uB8CC: ${definition.id}`);
4445
+ p12.log.info(installedIntegration.components.map(formatComponentStatus).join("\n"));
4446
+ } catch (error) {
4447
+ reportIntegrationError(error);
4448
+ }
4449
+ p12.outro("ai-ops integration install \uC644\uB8CC");
4450
+ };
4451
+ var integrationStatusCommand = async (integrationId) => {
4452
+ p12.intro(`ai-ops integration status ${integrationId}`);
4453
+ try {
4454
+ const definition = resolveIntegrationDefinition(integrationId);
4455
+ const basePath = resolveUserBasePath();
4456
+ const manifest = readIntegrationManifest(resolveIntegrationManifestPath(basePath));
4457
+ const installedIntegration = findInstalledIntegration(manifest?.integrations ?? [], definition.id);
4458
+ const hookStatus = inspectCodexHook({
4459
+ hooksPath: resolveCodexHooksPath(resolveCodexHomePath2()),
4460
+ definition: definition.hookDefinition
4461
+ });
4462
+ const lines = [
4463
+ `integration installed: ${installedIntegration ? "yes" : "no"}`,
4464
+ `skill installed: ${hasInstalledCodexSkill({ basePath, skillId: definition.skillComponent.id }) ? "yes" : "no"}`,
4465
+ `hook installed: ${hookStatus.installed ? "yes" : "no"}`,
4466
+ `hooks file: ${hookStatus.hooksPath}`
4467
+ ];
4468
+ if (definition.id === INTEGRATION_ID.PC) {
4469
+ const pcStatus = getPcHandoffStatus({
4470
+ cwd: resolveBasePath(),
4471
+ contextRoot: resolvePersonalContextRoot()
4472
+ });
4473
+ lines.push(
4474
+ `pc context ready: ${pcStatus.ready ? "yes" : "no"}`,
4475
+ `pc skip reason: ${pcStatus.skipReason ?? "none"}`,
4476
+ `pc workspace: ${pcStatus.workspaceId ?? "not found"}`,
4477
+ `pc active workstream: ${pcStatus.activeWorkstreamId ?? "not found"}`,
4478
+ `pc last confirmed commit: ${pcStatus.lastConfirmedCommitHash ?? "not found"}`
4479
+ );
4480
+ }
4481
+ if (installedIntegration) {
4482
+ lines.push(`owned components: ${installedIntegration.components.map(formatComponentStatus).join(", ")}`);
4483
+ }
4484
+ p12.log.info(lines.join("\n"));
4485
+ } catch (error) {
4486
+ reportIntegrationError(error);
4487
+ }
4488
+ p12.outro("ai-ops integration status \uC644\uB8CC");
4489
+ };
4490
+ var integrationUninstallCommand = async (integrationId) => {
4491
+ p12.intro(`ai-ops integration uninstall ${integrationId}`);
4492
+ try {
4493
+ const definition = resolveIntegrationDefinition(integrationId);
4494
+ const basePath = resolveUserBasePath();
4495
+ const cliVersion = getCliVersion();
4496
+ const manifestPath = resolveIntegrationManifestPath(basePath);
4497
+ const installedIntegration = findInstalledIntegration(
4498
+ readIntegrationManifest(manifestPath)?.integrations ?? [],
4499
+ definition.id
4500
+ );
4501
+ if (!installedIntegration) {
4502
+ p12.log.warn("\uC124\uCE58\uB41C integration manifest entry\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
4503
+ p12.outro("ai-ops integration uninstall \uC644\uB8CC");
4504
+ return;
4505
+ }
4506
+ const removed = [];
4507
+ for (const component of installedIntegration.components) {
4508
+ if (!component.owned) {
4509
+ continue;
4510
+ }
4511
+ if (component.type === INTEGRATION_COMPONENT_TYPE.SKILL) {
4512
+ removed.push(...removeOwnedSkill({ basePath, cliVersion, skillId: component.id }));
4513
+ }
4514
+ if (component.type === INTEGRATION_COMPONENT_TYPE.CODEX_HOOK) {
4515
+ const result = uninstallCodexHook({
4516
+ hooksPath: resolveCodexHooksPath(resolveCodexHomePath2()),
4517
+ definition: definition.hookDefinition
4518
+ });
4519
+ if (result.removed) {
4520
+ removed.push(result.hooksPath);
4521
+ }
4522
+ }
4523
+ }
4524
+ writeUserIntegrationState({
4525
+ manifestPath,
4526
+ cliVersion,
4527
+ removeIntegrationId: definition.id
4528
+ });
4529
+ p12.log.success(removed.length > 0 ? `\uC81C\uAC70 \uC644\uB8CC: ${removed.join(", ")}` : "\uC81C\uAC70\uD560 owned component \uC5C6\uC74C");
4530
+ } catch (error) {
4531
+ reportIntegrationError(error);
4532
+ }
4533
+ p12.outro("ai-ops integration uninstall \uC644\uB8CC");
4534
+ };
4535
+ var integrationPostToolUseHookCommand = async (integrationId) => {
4536
+ try {
4537
+ const id = parseIntegrationId(integrationId);
4538
+ const raw = await readStdin2();
4539
+ const hookInput = raw.trim().length > 0 ? JSON.parse(raw) : {};
4540
+ if (id !== INTEGRATION_ID.PC) {
4541
+ return;
4542
+ }
4543
+ const output = evaluatePcPostToolUseHook({
4544
+ hookInput,
4545
+ contextRoot: resolvePersonalContextRoot()
4546
+ });
4547
+ if (output) {
4548
+ process.stdout.write(JSON.stringify(output) + "\n");
4549
+ }
4550
+ } catch (error) {
4551
+ const message = error instanceof Error ? error.message : "unknown error";
4552
+ process.stdout.write(
4553
+ JSON.stringify({
4554
+ systemMessage: `ai-ops integration hook skipped: ${message}`
4555
+ }) + "\n"
4556
+ );
4557
+ }
4558
+ };
4559
+
3641
4560
  // src/bin/index.ts
3642
4561
  var program = new Command();
3643
- program.name("ai-ops").description("AI agent operating layer manager").version("0.1.0");
4562
+ program.name("ai-ops").description("AI agent operating layer manager").version(getCliVersion());
3644
4563
  program.command("init").description("project operating layer \uCD08\uAE30 \uC124\uCE58").option("--tool <tool...>", "\uB300\uC0C1 \uB3C4\uAD6C adapter \uC9C0\uC815 (codex|gemini|claude-code)").action((opts) => initCommand(opts));
3645
4564
  program.command("update").description("project operating layer \uAC31\uC2E0").option("--force", "\uBCC0\uACBD \uC5C6\uC5B4\uB3C4 \uAC15\uC81C \uC7AC\uC124\uCE58", false).action((opts) => updateCommand(opts));
3646
4565
  program.command("diff").description("project operating layer drift \uBE44\uAD50").action(() => diffCommand());
@@ -3683,5 +4602,12 @@ var codexHookCommand = program.command("codex-hook").description("Codex hooks \u
3683
4602
  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
4603
  codexHookCommand.command("status <hookId>").description("Codex hook \uC124\uCE58 \uC0C1\uD0DC \uD655\uC778").action((hookId) => codexHookStatusCommand(hookId));
3685
4604
  codexHookCommand.command("uninstall <hookId>").description("Codex hook \uC81C\uAC70").action((hookId) => codexHookUninstallCommand(hookId));
4605
+ var integrationCommand = program.command("integration").description("user/global runtime integration \uC124\uCE58/\uC870\uD68C/\uC81C\uAC70");
4606
+ integrationCommand.command("list").description("\uC0AC\uC6A9 \uAC00\uB2A5\uD55C integration \uBAA9\uB85D").action(() => integrationListCommand());
4607
+ 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));
4608
+ integrationCommand.command("status <integrationId>").description("integration \uC124\uCE58 \uC0C1\uD0DC \uD655\uC778").action((integrationId) => integrationStatusCommand(integrationId));
4609
+ integrationCommand.command("uninstall <integrationId>").description("integration \uC81C\uAC70").action((integrationId) => integrationUninstallCommand(integrationId));
4610
+ var integrationHookCommand = integrationCommand.command("hook").description("integration hook \uB0B4\uBD80 \uBA85\uB839");
4611
+ integrationHookCommand.command("post-tool-use <integrationId>").description("Codex PostToolUse integration hook entrypoint").action((integrationId) => integrationPostToolUseHookCommand(integrationId));
3686
4612
  program.parse();
3687
4613
  //# sourceMappingURL=index.js.map