ai-ops-cli 1.3.1 → 1.4.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
@@ -363,6 +363,156 @@ var PackCatalogSchema = z10.object({
363
363
  packs: z10.array(PackCatalogEntrySchema)
364
364
  }).strict();
365
365
 
366
+ // src/core/schemas/studio-snapshot.schema.ts
367
+ import { z as z11 } from "zod";
368
+ var nullableString = z11.string().nullable();
369
+ var StudioProjectStateSchema = z11.union([
370
+ z11.literal("ready"),
371
+ z11.literal("uninitialized"),
372
+ z11.literal("degraded")
373
+ ]);
374
+ var StudioProjectDocumentProvenanceSchema = z11.union([
375
+ z11.literal("ai-ops-managed"),
376
+ z11.literal("project-owned"),
377
+ z11.literal("pack-document"),
378
+ z11.literal("context-only")
379
+ ]);
380
+ var StudioRuntimeFileStateSchema = z11.object({
381
+ path: z11.string().min(1),
382
+ exists: z11.boolean()
383
+ }).strict();
384
+ var StudioSourceStateSchema = z11.object({
385
+ path: z11.string().min(1),
386
+ exists: z11.boolean(),
387
+ parsed: z11.boolean(),
388
+ generatedAt: nullableString,
389
+ error: nullableString
390
+ }).strict();
391
+ var StudioProjectIssueSourceSchema = z11.union([
392
+ z11.literal("manifest"),
393
+ z11.literal("context-layer"),
394
+ z11.literal("docs-status"),
395
+ z11.literal("frontmatter"),
396
+ z11.literal("managed-section"),
397
+ z11.literal("file-system"),
398
+ z11.literal("source-hash"),
399
+ z11.literal("unknown")
400
+ ]);
401
+ var StudioProjectIssueSchema = z11.object({
402
+ level: z11.union([z11.literal("error"), z11.literal("warning")]),
403
+ code: z11.string().min(1),
404
+ message: z11.string().min(1),
405
+ source: StudioProjectIssueSourceSchema,
406
+ affectedPath: nullableString,
407
+ suggestedActionLabel: nullableString
408
+ }).strict();
409
+ var StudioProjectDocumentSchema = z11.object({
410
+ path: z11.string().min(1),
411
+ status: ProjectLayerDocumentStatusSchema,
412
+ layer: z11.string().min(1),
413
+ owner: z11.string().min(1),
414
+ read_when: z11.array(z11.string().min(1)),
415
+ update_when: z11.array(z11.string().min(1)),
416
+ indexedContentHash: z11.string().min(1),
417
+ currentContentHash: nullableString,
418
+ contentHashMatches: z11.boolean().nullable(),
419
+ provenance: StudioProjectDocumentProvenanceSchema,
420
+ content: nullableString,
421
+ trustWarning: nullableString,
422
+ readError: nullableString
423
+ }).strict();
424
+ var StudioProjectSnapshotSchema = z11.object({
425
+ root: z11.string().min(1),
426
+ state: StudioProjectStateSchema,
427
+ files: z11.object({
428
+ manifest: StudioSourceStateSchema,
429
+ contextIndex: StudioSourceStateSchema,
430
+ docsStatus: StudioSourceStateSchema
431
+ }).strict(),
432
+ audit: z11.object({
433
+ currentSourceHash: nullableString,
434
+ hasErrors: z11.boolean(),
435
+ hasWarnings: z11.boolean(),
436
+ issues: z11.array(StudioProjectIssueSchema)
437
+ }).strict(),
438
+ documents: z11.array(StudioProjectDocumentSchema)
439
+ }).strict();
440
+ var StudioIntegrationComponentStatusSchema = z11.object({
441
+ type: z11.union([
442
+ z11.literal(INTEGRATION_COMPONENT_TYPE.SKILL),
443
+ z11.literal(INTEGRATION_COMPONENT_TYPE.CODEX_HOOK),
444
+ z11.literal(INTEGRATION_COMPONENT_TYPE.RECEIPT_CONFIG)
445
+ ]),
446
+ id: z11.string().min(1),
447
+ installed: z11.boolean(),
448
+ owned: z11.boolean().nullable(),
449
+ catalog: IntegrationCatalogComponentSchema,
450
+ installedComponent: IntegrationComponentSchema.nullable()
451
+ }).strict();
452
+ var StudioIntegrationSnapshotSchema = z11.object({
453
+ id: IntegrationIdSchema,
454
+ description: z11.string().min(1),
455
+ installed: z11.boolean(),
456
+ installedAt: nullableString,
457
+ updatedAt: nullableString,
458
+ components: z11.array(StudioIntegrationComponentStatusSchema)
459
+ }).strict();
460
+ var StudioInstalledPathStateSchema = z11.object({
461
+ path: z11.string().min(1),
462
+ exists: z11.boolean()
463
+ }).strict();
464
+ var StudioSkillSnapshotSchema = z11.object({
465
+ id: z11.string().min(1),
466
+ kind: SkillKindSchema,
467
+ description: z11.string().min(1),
468
+ supported_tools: z11.array(SkillToolSchema),
469
+ groups: z11.array(z11.string().min(1)),
470
+ installed: z11.boolean(),
471
+ installedTools: z11.array(SkillToolSchema),
472
+ installedPaths: z11.array(StudioInstalledPathStateSchema),
473
+ sourceHash: nullableString
474
+ }).strict();
475
+ var StudioSubagentSnapshotSchema = z11.object({
476
+ id: SubagentIdSchema,
477
+ description: z11.string().min(1),
478
+ supported_tools: z11.array(SkillToolSchema),
479
+ installed: z11.boolean(),
480
+ installedTools: z11.array(SkillToolSchema),
481
+ installedPaths: z11.array(StudioInstalledPathStateSchema),
482
+ sourceHash: nullableString
483
+ }).strict();
484
+ var StudioHookSnapshotSchema = z11.object({
485
+ id: z11.string().min(1),
486
+ statusMessage: z11.string().min(1),
487
+ hooksPath: nullableString,
488
+ installed: z11.boolean(),
489
+ error: nullableString
490
+ }).strict();
491
+ var StudioRuntimeSnapshotSchema = z11.object({
492
+ available: z11.boolean(),
493
+ unavailableReason: nullableString,
494
+ userBasePath: nullableString,
495
+ codexHomePath: nullableString,
496
+ manifests: z11.object({
497
+ integrations: StudioSourceStateSchema,
498
+ skills: StudioSourceStateSchema,
499
+ subagents: StudioSourceStateSchema,
500
+ hooks: StudioSourceStateSchema
501
+ }).strict(),
502
+ integrations: z11.array(StudioIntegrationSnapshotSchema),
503
+ skills: z11.array(StudioSkillSnapshotSchema),
504
+ subagents: z11.array(StudioSubagentSnapshotSchema),
505
+ hooks: z11.array(StudioHookSnapshotSchema)
506
+ }).strict();
507
+ var StudioSnapshotSchema = z11.object({
508
+ schemaVersion: z11.literal(1),
509
+ kind: z11.literal("ai-ops-studio-snapshot"),
510
+ generatedAt: z11.string().datetime({ offset: true }),
511
+ cliVersion: z11.string().min(1),
512
+ project: StudioProjectSnapshotSchema,
513
+ runtime: StudioRuntimeSnapshotSchema
514
+ }).strict();
515
+
366
516
  // src/core/loader.ts
367
517
  import { readFileSync, readdirSync } from "fs";
368
518
  import { join as join2, resolve } from "path";
@@ -1576,24 +1726,914 @@ var removeEmptyDirs = (basePath, relativePaths) => {
1576
1726
  }
1577
1727
  }
1578
1728
  };
1579
- var uninstallProjectLayer = (basePath, manifest) => {
1580
- const managedResults = manifest.managed_files.map((file) => removeManagedProjectFile(basePath, file.path));
1581
- const projectResults = manifest.project_files.map((file) => removeCreateOnlyProjectFile(basePath, file));
1582
- const packResults = manifest.packs.flatMap(
1583
- (pack) => [...pack.documents, ...pack.files].map((file) => removePackOwnedFile(basePath, file))
1584
- );
1585
- const stateFiles = [PROJECT_LAYER_CONTEXT_INDEX_RELATIVE_PATH, PROJECT_LAYER_MANIFEST_RELATIVE_PATH];
1586
- for (const stateFile of stateFiles) {
1587
- rmSync2(resolveProjectLayerFilePath(basePath, stateFile), { force: true });
1729
+ var uninstallProjectLayer = (basePath, manifest) => {
1730
+ const managedResults = manifest.managed_files.map((file) => removeManagedProjectFile(basePath, file.path));
1731
+ const projectResults = manifest.project_files.map((file) => removeCreateOnlyProjectFile(basePath, file));
1732
+ const packResults = manifest.packs.flatMap(
1733
+ (pack) => [...pack.documents, ...pack.files].map((file) => removePackOwnedFile(basePath, file))
1734
+ );
1735
+ const stateFiles = [PROJECT_LAYER_CONTEXT_INDEX_RELATIVE_PATH, PROJECT_LAYER_MANIFEST_RELATIVE_PATH];
1736
+ for (const stateFile of stateFiles) {
1737
+ rmSync2(resolveProjectLayerFilePath(basePath, stateFile), { force: true });
1738
+ }
1739
+ const result = mergeRemoveResults([...managedResults, ...projectResults, ...packResults]);
1740
+ removeEmptyDirs(basePath, [...result.deleted, ...stateFiles]);
1741
+ return result;
1742
+ };
1743
+
1744
+ // src/core/studio-snapshot.ts
1745
+ import { existsSync as existsSync3, readFileSync as readFileSync8 } from "fs";
1746
+ import { join as join10, resolve as resolve6 } from "path";
1747
+ import { z as z12 } from "zod";
1748
+
1749
+ // src/core/codex-hook.ts
1750
+ import { existsSync as existsSync2, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
1751
+ import { dirname as dirname7, join as join9 } from "path";
1752
+ var CONTEXT_PROMOTION_HOOK_ID = "context-promotion";
1753
+ var CONTEXT_PROMOTION_HOOK_COMMAND_MARKER = "context-promotion hook post-tool-use";
1754
+ var CONTEXT_PROMOTION_LEGACY_HOOK_COMMAND_MARKER = "context-promotion hook pre-tool-use";
1755
+ var CONTEXT_PROMOTION_DEFAULT_HOOK_COMMAND = `ai-ops ${CONTEXT_PROMOTION_HOOK_COMMAND_MARKER}`;
1756
+ var PC_HOOK_ID = "pc";
1757
+ var PC_HOOK_COMMAND_MARKER = "integration hook post-tool-use pc";
1758
+ var PC_DEFAULT_HOOK_COMMAND = `ai-ops ${PC_HOOK_COMMAND_MARKER}`;
1759
+ var PRE_TOOL_USE_EVENT = "PreToolUse";
1760
+ var POST_TOOL_USE_EVENT = "PostToolUse";
1761
+ var BASH_MATCHER = "^Bash$";
1762
+ var CONTEXT_PROMOTION_CODEX_HOOK = {
1763
+ id: CONTEXT_PROMOTION_HOOK_ID,
1764
+ commandMarker: CONTEXT_PROMOTION_HOOK_COMMAND_MARKER,
1765
+ legacyCommandMarkers: [CONTEXT_PROMOTION_LEGACY_HOOK_COMMAND_MARKER],
1766
+ defaultCommand: CONTEXT_PROMOTION_DEFAULT_HOOK_COMMAND,
1767
+ statusMessage: "Checking context promotion review"
1768
+ };
1769
+ var PC_CODEX_HOOK = {
1770
+ id: PC_HOOK_ID,
1771
+ commandMarker: PC_HOOK_COMMAND_MARKER,
1772
+ legacyCommandMarkers: [],
1773
+ defaultCommand: PC_DEFAULT_HOOK_COMMAND,
1774
+ statusMessage: "Checking pc handoff"
1775
+ };
1776
+ var isJsonRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1777
+ var readJsonRecord = (filePath) => {
1778
+ if (!existsSync2(filePath)) {
1779
+ return {};
1780
+ }
1781
+ const parsed = JSON.parse(readFileSync7(filePath, "utf-8"));
1782
+ if (!isJsonRecord(parsed)) {
1783
+ throw new Error("hooks.json must contain a JSON object");
1784
+ }
1785
+ return parsed;
1786
+ };
1787
+ var writeJsonRecord = (filePath, value) => {
1788
+ mkdirSync5(dirname7(filePath), { recursive: true });
1789
+ writeFileSync5(filePath, JSON.stringify(value, null, 2) + "\n", "utf-8");
1790
+ };
1791
+ var getOrCreateRecord = (record, key) => {
1792
+ const existing = record[key];
1793
+ if (isJsonRecord(existing)) {
1794
+ return existing;
1795
+ }
1796
+ const next = {};
1797
+ record[key] = next;
1798
+ return next;
1799
+ };
1800
+ var getArray = (record, key) => {
1801
+ const existing = record[key];
1802
+ return Array.isArray(existing) ? existing : [];
1803
+ };
1804
+ var handlerMatchesDefinition = (definition) => (handler) => isJsonRecord(handler) && typeof handler.command === "string" && [definition.commandMarker, ...definition.legacyCommandMarkers].some((marker) => handler.command.includes(marker));
1805
+ var handlerMatchesCommand = (handler, command) => isJsonRecord(handler) && handler.command === command;
1806
+ var groupHasDefinitionHook = (definition) => (group) => isJsonRecord(group) && getArray(group, "hooks").some(handlerMatchesDefinition(definition));
1807
+ var groupHasCurrentDefinitionHook = (group, command) => isJsonRecord(group) && getArray(group, "hooks").some((handler) => handlerMatchesCommand(handler, command));
1808
+ var countDefinitionHandlers = (groups, definition) => groups.reduce((count, group) => {
1809
+ if (!isJsonRecord(group)) {
1810
+ return count;
1811
+ }
1812
+ return count + getArray(group, "hooks").filter(handlerMatchesDefinition(definition)).length;
1813
+ }, 0);
1814
+ var configHasDefinitionHook = (config, definition) => {
1815
+ const hooks = config.hooks;
1816
+ if (!isJsonRecord(hooks)) {
1817
+ return false;
1818
+ }
1819
+ return getArray(hooks, POST_TOOL_USE_EVENT).some(groupHasDefinitionHook(definition));
1820
+ };
1821
+ var configHasOnlyCurrentDefinitionHook = (config, definition, command) => {
1822
+ const hooks = config.hooks;
1823
+ if (!isJsonRecord(hooks)) {
1824
+ return false;
1825
+ }
1826
+ const hasLegacy = getArray(hooks, PRE_TOOL_USE_EVENT).some(groupHasDefinitionHook(definition));
1827
+ const postGroups = getArray(hooks, POST_TOOL_USE_EVENT);
1828
+ const hasCurrent = postGroups.some((group) => groupHasCurrentDefinitionHook(group, command));
1829
+ return hasCurrent && !hasLegacy && countDefinitionHandlers(postGroups, definition) === 1;
1830
+ };
1831
+ var removeDefinitionHooksFromEvent = (hooks, eventName, definition) => {
1832
+ const previousGroups = getArray(hooks, eventName);
1833
+ let removed = false;
1834
+ const nextGroups = previousGroups.map((group) => {
1835
+ if (!isJsonRecord(group)) {
1836
+ return group;
1837
+ }
1838
+ const previousHandlers = getArray(group, "hooks");
1839
+ const nextHandlers = previousHandlers.filter((handler) => {
1840
+ const matches = handlerMatchesDefinition(definition)(handler);
1841
+ if (matches) {
1842
+ removed = true;
1843
+ }
1844
+ return !matches;
1845
+ });
1846
+ if (nextHandlers.length === 0) {
1847
+ return null;
1848
+ }
1849
+ return {
1850
+ ...group,
1851
+ hooks: nextHandlers
1852
+ };
1853
+ }).filter((group) => group !== null);
1854
+ if (!removed) {
1855
+ return false;
1856
+ }
1857
+ if (nextGroups.length > 0) {
1858
+ hooks[eventName] = nextGroups;
1859
+ } else {
1860
+ delete hooks[eventName];
1861
+ }
1862
+ return true;
1863
+ };
1864
+ var resolveCodexHooksPath = (codexHomePath) => join9(codexHomePath, "hooks.json");
1865
+ var buildCodexHookCommand = (params) => {
1866
+ const command = params.overrideCommand?.trim() ?? params.definition.defaultCommand;
1867
+ if (!command.includes(params.definition.commandMarker)) {
1868
+ throw new Error(`${params.definition.id} hook command must include: ${params.definition.commandMarker}`);
1869
+ }
1870
+ return command;
1871
+ };
1872
+ var buildContextPromotionHookCommand = (overrideCommand) => buildCodexHookCommand({
1873
+ definition: CONTEXT_PROMOTION_CODEX_HOOK,
1874
+ overrideCommand
1875
+ });
1876
+ var inspectCodexHook = (params) => ({
1877
+ hooksPath: params.hooksPath,
1878
+ installed: configHasDefinitionHook(readJsonRecord(params.hooksPath), params.definition)
1879
+ });
1880
+ var inspectContextPromotionHook = (hooksPath) => ({
1881
+ hooksPath,
1882
+ installed: inspectCodexHook({ hooksPath, definition: CONTEXT_PROMOTION_CODEX_HOOK }).installed
1883
+ });
1884
+ var installCodexHook = (params) => {
1885
+ const config = readJsonRecord(params.hooksPath);
1886
+ if (configHasOnlyCurrentDefinitionHook(config, params.definition, params.command)) {
1887
+ return {
1888
+ hooksPath: params.hooksPath,
1889
+ installed: true,
1890
+ changed: false
1891
+ };
1892
+ }
1893
+ const hooks = getOrCreateRecord(config, "hooks");
1894
+ removeDefinitionHooksFromEvent(hooks, PRE_TOOL_USE_EVENT, params.definition);
1895
+ removeDefinitionHooksFromEvent(hooks, POST_TOOL_USE_EVENT, params.definition);
1896
+ const existingGroups = getArray(hooks, POST_TOOL_USE_EVENT);
1897
+ const nextGroup = {
1898
+ matcher: BASH_MATCHER,
1899
+ hooks: [
1900
+ {
1901
+ type: "command",
1902
+ command: params.command,
1903
+ timeout: 30,
1904
+ statusMessage: params.definition.statusMessage
1905
+ }
1906
+ ]
1907
+ };
1908
+ hooks[POST_TOOL_USE_EVENT] = [...existingGroups, nextGroup];
1909
+ writeJsonRecord(params.hooksPath, config);
1910
+ return {
1911
+ hooksPath: params.hooksPath,
1912
+ installed: true,
1913
+ changed: true
1914
+ };
1915
+ };
1916
+ var installContextPromotionHook = (params) => installCodexHook({
1917
+ hooksPath: params.hooksPath,
1918
+ definition: CONTEXT_PROMOTION_CODEX_HOOK,
1919
+ command: params.command
1920
+ });
1921
+ var uninstallCodexHook = (params) => {
1922
+ const config = readJsonRecord(params.hooksPath);
1923
+ const hooks = config.hooks;
1924
+ if (!isJsonRecord(hooks)) {
1925
+ return { hooksPath: params.hooksPath, removed: false, changed: false };
1926
+ }
1927
+ const removedLegacy = removeDefinitionHooksFromEvent(hooks, PRE_TOOL_USE_EVENT, params.definition);
1928
+ const removedCurrent = removeDefinitionHooksFromEvent(hooks, POST_TOOL_USE_EVENT, params.definition);
1929
+ const removed = removedLegacy || removedCurrent;
1930
+ if (!removed) {
1931
+ return { hooksPath: params.hooksPath, removed: false, changed: false };
1932
+ }
1933
+ writeJsonRecord(params.hooksPath, config);
1934
+ return { hooksPath: params.hooksPath, removed: true, changed: true };
1935
+ };
1936
+ var uninstallContextPromotionHook = (hooksPath) => uninstallCodexHook({
1937
+ hooksPath,
1938
+ definition: CONTEXT_PROMOTION_CODEX_HOOK
1939
+ });
1940
+
1941
+ // src/core/studio-snapshot.ts
1942
+ var INTEGRATIONS_DATA_DIR = join10(COMPILER_DATA_DIR, "integrations");
1943
+ var SKILLS_DATA_DIR = join10(COMPILER_DATA_DIR, "skills");
1944
+ var SUBAGENTS_DATA_DIR = join10(COMPILER_DATA_DIR, "subagents");
1945
+ var DOCS_STATUS_RELATIVE_PATH = "docs/docs-status.md";
1946
+ var INTEGRATIONS_MANIFEST_FALLBACK_PATH = ".ai-ops/integrations-manifest.json";
1947
+ var SKILLS_MANIFEST_FALLBACK_PATH = ".ai-ops/skills-manifest.json";
1948
+ var SUBAGENTS_MANIFEST_FALLBACK_PATH = ".ai-ops/subagents-manifest.json";
1949
+ var HOOKS_FALLBACK_PATH = ".codex/hooks.json";
1950
+ var KNOWN_CODEX_HOOK_DEFINITIONS = [CONTEXT_PROMOTION_CODEX_HOOK, PC_CODEX_HOOK];
1951
+ var AUDIT_ISSUE_SOURCES_BY_CODE = {
1952
+ "missing-manifest": "manifest",
1953
+ "invalid-manifest": "manifest",
1954
+ "manifest-missing-managed-file": "manifest",
1955
+ "missing-context-index": "context-layer",
1956
+ "invalid-context-index": "context-layer",
1957
+ "context-missing-document": "context-layer",
1958
+ "context-document-mismatch": "context-layer",
1959
+ "context-extra-document": "context-layer",
1960
+ "missing-docs-status": "docs-status",
1961
+ "invalid-docs-status": "docs-status",
1962
+ "docs-status-missing-document": "docs-status",
1963
+ "docs-status-mismatch": "docs-status",
1964
+ "docs-status-extra-document": "docs-status",
1965
+ "missing-file": "file-system",
1966
+ "invalid-frontmatter": "frontmatter",
1967
+ "missing-managed-section": "managed-section",
1968
+ "source-hash-drift": "source-hash",
1969
+ "managed-source-hash-drift": "source-hash"
1970
+ };
1971
+ var AUDIT_ISSUE_ACTION_LABELS_BY_SOURCE = {
1972
+ manifest: "Review manifest record",
1973
+ "context-layer": "Review context index",
1974
+ "docs-status": "Review docs status",
1975
+ frontmatter: "Review frontmatter",
1976
+ "managed-section": "Review managed section",
1977
+ "file-system": "Review missing file",
1978
+ "source-hash": "Review source hash",
1979
+ unknown: null
1980
+ };
1981
+ var RecoverableContextDocumentSchema = z12.object({
1982
+ status: ProjectLayerDocumentStatusSchema,
1983
+ layer: z12.string().min(1),
1984
+ owner: z12.string().min(1),
1985
+ read_when: z12.array(z12.string().min(1)),
1986
+ update_when: z12.array(z12.string().min(1)),
1987
+ path: z12.string().min(1),
1988
+ contentHash: z12.string().min(1)
1989
+ });
1990
+ var RecoverableContextIndexSchema = z12.object({
1991
+ schemaVersion: z12.literal(1),
1992
+ kind: z12.literal("context-layer-index"),
1993
+ documents: z12.array(z12.unknown()),
1994
+ generatedAt: z12.string().min(1)
1995
+ });
1996
+ var getErrorMessage = (error) => error instanceof Error ? error.message : "unknown error";
1997
+ var resolveDefaultUserBasePath = () => process.env.AI_OPS_HOME ?? process.env.HOME ?? null;
1998
+ var resolveDefaultCodexHomePath = () => {
1999
+ if (process.env.CODEX_HOME && process.env.CODEX_HOME.length > 0) {
2000
+ return process.env.CODEX_HOME;
2001
+ }
2002
+ if (process.env.HOME && process.env.HOME.length > 0) {
2003
+ return join10(process.env.HOME, ".codex");
2004
+ }
2005
+ return null;
2006
+ };
2007
+ var buildSourceState = (params) => ({
2008
+ path: params.path,
2009
+ exists: params.exists,
2010
+ parsed: params.parsed,
2011
+ generatedAt: params.generatedAt ?? null,
2012
+ error: params.error ?? null
2013
+ });
2014
+ var createMissingSourceState = (path) => buildSourceState({ path, exists: false, parsed: false });
2015
+ var createUnavailableSourceState = (params) => buildSourceState({
2016
+ path: params.path,
2017
+ exists: false,
2018
+ parsed: false,
2019
+ error: params.reason
2020
+ });
2021
+ var hasErrors = (issues) => issues.some((issue2) => issue2.level === "error");
2022
+ var hasWarnings = (issues) => issues.some((issue2) => issue2.level === "warning");
2023
+ var getTrustWarning = (status) => {
2024
+ if (status === "Reserved") {
2025
+ return "Reserved document is not current decision-making evidence.";
2026
+ }
2027
+ if (status === "Draft") {
2028
+ return "Draft document requires review before use as decision-making evidence.";
2029
+ }
2030
+ if (status === "Archived") {
2031
+ return "Archived document is historical record and should not guide current operation.";
2032
+ }
2033
+ return null;
2034
+ };
2035
+ var uniqueStrings = (values) => [...new Set(values.filter((value) => value.length > 0))];
2036
+ var getManifestDocumentPaths = (manifest) => manifest === null ? [] : [
2037
+ ...manifest.managed_files.map((file) => file.path),
2038
+ ...manifest.project_files.map((file) => file.path),
2039
+ ...manifest.packs.flatMap((pack) => pack.documents.map((file) => file.path)),
2040
+ ...manifest.packs.flatMap((pack) => pack.files.map((file) => file.path))
2041
+ ];
2042
+ var buildKnownAuditPaths = (params) => uniqueStrings([
2043
+ PROJECT_LAYER_MANIFEST_RELATIVE_PATH,
2044
+ PROJECT_LAYER_CONTEXT_INDEX_RELATIVE_PATH,
2045
+ DOCS_STATUS_RELATIVE_PATH,
2046
+ ...getManifestDocumentPaths(params.manifest),
2047
+ ...params.contextIndex?.documents.map((document) => document.path) ?? [],
2048
+ ...params.documents.map((document) => document.path)
2049
+ ]).sort((left, right) => right.length - left.length || left.localeCompare(right));
2050
+ var findKnownPathInMessage = (message, knownPaths) => knownPaths.find((path) => message.includes(path)) ?? null;
2051
+ var parsePathLikeToken = (value) => {
2052
+ const trimmed = value.trim().replace(/[.,;)]$/, "");
2053
+ if (trimmed.length === 0) {
2054
+ return null;
2055
+ }
2056
+ try {
2057
+ resolveProjectLayerFilePath("/", trimmed);
2058
+ return trimmed;
2059
+ } catch {
2060
+ return null;
2061
+ }
2062
+ };
2063
+ var extractTrailingIssuePath = (message) => {
2064
+ const trailingSegment = message.split(":").at(-1);
2065
+ if (trailingSegment === void 0) {
2066
+ return null;
2067
+ }
2068
+ const [firstToken] = trailingSegment.trim().split(/\s+/);
2069
+ return firstToken === void 0 ? null : parsePathLikeToken(firstToken);
2070
+ };
2071
+ var resolveIssueSource = (issue2) => AUDIT_ISSUE_SOURCES_BY_CODE[issue2.code] ?? "unknown";
2072
+ var resolveIssueAffectedPath = (params) => {
2073
+ if (params.issue.code === "missing-manifest" || params.issue.code === "invalid-manifest") {
2074
+ return PROJECT_LAYER_MANIFEST_RELATIVE_PATH;
2075
+ }
2076
+ if (params.issue.code === "missing-context-index" || params.issue.code === "invalid-context-index") {
2077
+ return PROJECT_LAYER_CONTEXT_INDEX_RELATIVE_PATH;
2078
+ }
2079
+ if (params.issue.code === "missing-docs-status" || params.issue.code === "invalid-docs-status") {
2080
+ return DOCS_STATUS_RELATIVE_PATH;
2081
+ }
2082
+ if (params.issue.code === "source-hash-drift") {
2083
+ return null;
2084
+ }
2085
+ const knownPath = findKnownPathInMessage(params.issue.message, params.knownPaths);
2086
+ if (knownPath !== null) {
2087
+ return knownPath;
2088
+ }
2089
+ if (params.source === "unknown") {
2090
+ return null;
2091
+ }
2092
+ return extractTrailingIssuePath(params.issue.message);
2093
+ };
2094
+ var normalizeStudioProjectIssue = (issue2, knownPaths = []) => {
2095
+ const source = resolveIssueSource(issue2);
2096
+ return {
2097
+ level: issue2.level,
2098
+ code: issue2.code,
2099
+ message: issue2.message,
2100
+ source,
2101
+ affectedPath: resolveIssueAffectedPath({ issue: issue2, source, knownPaths }),
2102
+ suggestedActionLabel: AUDIT_ISSUE_ACTION_LABELS_BY_SOURCE[source]
2103
+ };
2104
+ };
2105
+ var readProjectManifestSnapshot = (basePath) => {
2106
+ const manifestPath = resolveProjectLayerManifestPath(basePath);
2107
+ if (!existsSync3(manifestPath)) {
2108
+ return {
2109
+ source: createMissingSourceState(PROJECT_LAYER_MANIFEST_RELATIVE_PATH),
2110
+ manifest: null
2111
+ };
2112
+ }
2113
+ try {
2114
+ const manifest = readProjectLayerManifest(basePath);
2115
+ return {
2116
+ source: buildSourceState({
2117
+ path: PROJECT_LAYER_MANIFEST_RELATIVE_PATH,
2118
+ exists: true,
2119
+ parsed: manifest !== null,
2120
+ generatedAt: manifest?.generatedAt ?? null
2121
+ }),
2122
+ manifest
2123
+ };
2124
+ } catch (error) {
2125
+ return {
2126
+ source: buildSourceState({
2127
+ path: PROJECT_LAYER_MANIFEST_RELATIVE_PATH,
2128
+ exists: true,
2129
+ parsed: false,
2130
+ error: getErrorMessage(error)
2131
+ }),
2132
+ manifest: null
2133
+ };
2134
+ }
2135
+ };
2136
+ var readProjectContextIndexSnapshot = (basePath) => {
2137
+ const contextIndexPath = resolveProjectLayerContextIndexPath(basePath);
2138
+ if (!existsSync3(contextIndexPath)) {
2139
+ return {
2140
+ source: createMissingSourceState(PROJECT_LAYER_CONTEXT_INDEX_RELATIVE_PATH),
2141
+ contextIndex: null
2142
+ };
2143
+ }
2144
+ try {
2145
+ const parsedJson = JSON.parse(readFileSync8(contextIndexPath, "utf-8"));
2146
+ const strictContextIndex = ProjectLayerContextIndexSchema.safeParse(parsedJson);
2147
+ if (strictContextIndex.success) {
2148
+ return {
2149
+ source: buildSourceState({
2150
+ path: PROJECT_LAYER_CONTEXT_INDEX_RELATIVE_PATH,
2151
+ exists: true,
2152
+ parsed: true,
2153
+ generatedAt: strictContextIndex.data.generatedAt
2154
+ }),
2155
+ contextIndex: strictContextIndex.data
2156
+ };
2157
+ }
2158
+ const recoverableContextIndex = RecoverableContextIndexSchema.safeParse(parsedJson);
2159
+ if (!recoverableContextIndex.success) {
2160
+ return {
2161
+ source: buildSourceState({
2162
+ path: PROJECT_LAYER_CONTEXT_INDEX_RELATIVE_PATH,
2163
+ exists: true,
2164
+ parsed: false,
2165
+ error: strictContextIndex.error.message
2166
+ }),
2167
+ contextIndex: null
2168
+ };
2169
+ }
2170
+ const documents = recoverableContextIndex.data.documents.flatMap((document) => {
2171
+ const parsedDocument = RecoverableContextDocumentSchema.safeParse(document);
2172
+ return parsedDocument.success ? [parsedDocument.data] : [];
2173
+ });
2174
+ return {
2175
+ source: buildSourceState({
2176
+ path: PROJECT_LAYER_CONTEXT_INDEX_RELATIVE_PATH,
2177
+ exists: true,
2178
+ parsed: false,
2179
+ generatedAt: recoverableContextIndex.data.generatedAt,
2180
+ error: strictContextIndex.error.message
2181
+ }),
2182
+ contextIndex: {
2183
+ schemaVersion: 1,
2184
+ kind: "context-layer-index",
2185
+ documents,
2186
+ generatedAt: recoverableContextIndex.data.generatedAt
2187
+ }
2188
+ };
2189
+ } catch (error) {
2190
+ return {
2191
+ source: buildSourceState({
2192
+ path: PROJECT_LAYER_CONTEXT_INDEX_RELATIVE_PATH,
2193
+ exists: true,
2194
+ parsed: false,
2195
+ error: getErrorMessage(error)
2196
+ }),
2197
+ contextIndex: null
2198
+ };
2199
+ }
2200
+ };
2201
+ var readDocsStatusSourceState = (basePath) => {
2202
+ const docsStatusPath = resolveProjectLayerFilePath(basePath, DOCS_STATUS_RELATIVE_PATH);
2203
+ if (!existsSync3(docsStatusPath)) {
2204
+ return createMissingSourceState(DOCS_STATUS_RELATIVE_PATH);
2205
+ }
2206
+ try {
2207
+ parseProjectLayerDocument(DOCS_STATUS_RELATIVE_PATH, readFileSync8(docsStatusPath, "utf-8"));
2208
+ return buildSourceState({
2209
+ path: DOCS_STATUS_RELATIVE_PATH,
2210
+ exists: true,
2211
+ parsed: true
2212
+ });
2213
+ } catch (error) {
2214
+ return buildSourceState({
2215
+ path: DOCS_STATUS_RELATIVE_PATH,
2216
+ exists: true,
2217
+ parsed: false,
2218
+ error: getErrorMessage(error)
2219
+ });
2220
+ }
2221
+ };
2222
+ var buildDocumentProvenance = (manifest, path) => {
2223
+ if (manifest?.managed_files.some((file) => file.path === path) === true) {
2224
+ return "ai-ops-managed";
2225
+ }
2226
+ if (manifest?.project_files.some((file) => file.path === path) === true) {
2227
+ return "project-owned";
2228
+ }
2229
+ if (manifest?.packs.some((pack) => pack.documents.some((document) => document.path === path)) === true) {
2230
+ return "pack-document";
2231
+ }
2232
+ return "context-only";
2233
+ };
2234
+ var buildDocumentReadError = (code, message) => `${code}: ${message}`;
2235
+ var buildProjectDocumentSnapshot = (params) => {
2236
+ let absolutePath;
2237
+ try {
2238
+ absolutePath = resolveProjectLayerFilePath(params.basePath, params.indexed.path);
2239
+ } catch (error) {
2240
+ return {
2241
+ path: params.indexed.path,
2242
+ status: params.indexed.status,
2243
+ layer: params.indexed.layer,
2244
+ owner: params.indexed.owner,
2245
+ read_when: params.indexed.read_when,
2246
+ update_when: params.indexed.update_when,
2247
+ indexedContentHash: params.indexed.contentHash,
2248
+ currentContentHash: null,
2249
+ contentHashMatches: null,
2250
+ provenance: params.provenance,
2251
+ content: null,
2252
+ trustWarning: getTrustWarning(params.indexed.status),
2253
+ readError: buildDocumentReadError("unsafe-path", getErrorMessage(error))
2254
+ };
2255
+ }
2256
+ if (!existsSync3(absolutePath)) {
2257
+ return {
2258
+ path: params.indexed.path,
2259
+ status: params.indexed.status,
2260
+ layer: params.indexed.layer,
2261
+ owner: params.indexed.owner,
2262
+ read_when: params.indexed.read_when,
2263
+ update_when: params.indexed.update_when,
2264
+ indexedContentHash: params.indexed.contentHash,
2265
+ currentContentHash: null,
2266
+ contentHashMatches: null,
2267
+ provenance: params.provenance,
2268
+ content: null,
2269
+ trustWarning: getTrustWarning(params.indexed.status),
2270
+ readError: buildDocumentReadError("missing-file", `\uD30C\uC77C \uC5C6\uC74C: ${params.indexed.path}`)
2271
+ };
2272
+ }
2273
+ try {
2274
+ const document = parseProjectLayerDocument(params.indexed.path, readFileSync8(absolutePath, "utf-8"));
2275
+ return {
2276
+ path: params.indexed.path,
2277
+ status: params.indexed.status,
2278
+ layer: params.indexed.layer,
2279
+ owner: params.indexed.owner,
2280
+ read_when: params.indexed.read_when,
2281
+ update_when: params.indexed.update_when,
2282
+ indexedContentHash: params.indexed.contentHash,
2283
+ currentContentHash: document.contentHash,
2284
+ contentHashMatches: document.contentHash === params.indexed.contentHash,
2285
+ provenance: params.provenance,
2286
+ content: document.content,
2287
+ trustWarning: getTrustWarning(params.indexed.status),
2288
+ readError: null
2289
+ };
2290
+ } catch (error) {
2291
+ return {
2292
+ path: params.indexed.path,
2293
+ status: params.indexed.status,
2294
+ layer: params.indexed.layer,
2295
+ owner: params.indexed.owner,
2296
+ read_when: params.indexed.read_when,
2297
+ update_when: params.indexed.update_when,
2298
+ indexedContentHash: params.indexed.contentHash,
2299
+ currentContentHash: null,
2300
+ contentHashMatches: null,
2301
+ provenance: params.provenance,
2302
+ content: null,
2303
+ trustWarning: getTrustWarning(params.indexed.status),
2304
+ readError: buildDocumentReadError("invalid-frontmatter", getErrorMessage(error))
2305
+ };
2306
+ }
2307
+ };
2308
+ var resolveProjectState = (params) => {
2309
+ const isUninitialized = !params.manifest.source.exists && !params.contextIndex.source.exists;
2310
+ if (isUninitialized) {
2311
+ return "uninitialized";
2312
+ }
2313
+ const hasParseError = params.manifest.source.exists && !params.manifest.source.parsed || params.contextIndex.source.exists && !params.contextIndex.source.parsed || params.docsStatus.exists && !params.docsStatus.parsed;
2314
+ const hasDocumentReadError = params.documents.some((document) => document.readError !== null);
2315
+ if (hasParseError || params.hasAuditErrors || hasDocumentReadError) {
2316
+ return "degraded";
2317
+ }
2318
+ return "ready";
2319
+ };
2320
+ var buildProjectSnapshot = (basePath) => {
2321
+ const root = resolve6(basePath);
2322
+ const manifest = readProjectManifestSnapshot(root);
2323
+ const contextIndex = readProjectContextIndexSnapshot(root);
2324
+ const docsStatus = readDocsStatusSourceState(root);
2325
+ const auditReport = auditProjectLayer(root);
2326
+ const documents = contextIndex.contextIndex?.documents.map(
2327
+ (indexed) => buildProjectDocumentSnapshot({
2328
+ basePath: root,
2329
+ indexed,
2330
+ provenance: buildDocumentProvenance(manifest.manifest, indexed.path)
2331
+ })
2332
+ ) ?? [];
2333
+ const auditHasErrors = hasErrors(auditReport.issues);
2334
+ const knownAuditPaths = buildKnownAuditPaths({
2335
+ manifest: manifest.manifest,
2336
+ contextIndex: contextIndex.contextIndex,
2337
+ documents
2338
+ });
2339
+ const auditIssues = auditReport.issues.map((issue2) => normalizeStudioProjectIssue(issue2, knownAuditPaths));
2340
+ return {
2341
+ root,
2342
+ state: resolveProjectState({
2343
+ manifest,
2344
+ contextIndex,
2345
+ docsStatus,
2346
+ documents,
2347
+ hasAuditErrors: auditHasErrors
2348
+ }),
2349
+ files: {
2350
+ manifest: manifest.source,
2351
+ contextIndex: contextIndex.source,
2352
+ docsStatus
2353
+ },
2354
+ audit: {
2355
+ currentSourceHash: auditReport.currentSourceHash,
2356
+ hasErrors: auditHasErrors,
2357
+ hasWarnings: hasWarnings(auditIssues),
2358
+ issues: auditIssues
2359
+ },
2360
+ documents
2361
+ };
2362
+ };
2363
+ var readRuntimeManifest = (params) => {
2364
+ if (params.manifestPath === null) {
2365
+ return {
2366
+ source: createUnavailableSourceState({
2367
+ path: params.fallbackPath,
2368
+ reason: params.unavailableReason ?? "Runtime home is unavailable."
2369
+ }),
2370
+ value: null
2371
+ };
2372
+ }
2373
+ if (!existsSync3(params.manifestPath)) {
2374
+ return {
2375
+ source: createMissingSourceState(params.manifestPath),
2376
+ value: null
2377
+ };
2378
+ }
2379
+ try {
2380
+ const value = params.read(params.manifestPath);
2381
+ return {
2382
+ source: buildSourceState({
2383
+ path: params.manifestPath,
2384
+ exists: true,
2385
+ parsed: value !== null,
2386
+ generatedAt: value === null ? null : params.getGeneratedAt(value)
2387
+ }),
2388
+ value
2389
+ };
2390
+ } catch (error) {
2391
+ return {
2392
+ source: buildSourceState({
2393
+ path: params.manifestPath,
2394
+ exists: true,
2395
+ parsed: false,
2396
+ error: getErrorMessage(error)
2397
+ }),
2398
+ value: null
2399
+ };
2400
+ }
2401
+ };
2402
+ var isJsonRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2403
+ var readHooksSourceState = (codexHomePath, unavailableReason) => {
2404
+ if (codexHomePath === null) {
2405
+ return createUnavailableSourceState({
2406
+ path: HOOKS_FALLBACK_PATH,
2407
+ reason: unavailableReason ?? "CODEX_HOME or HOME is required for Codex hooks."
2408
+ });
2409
+ }
2410
+ const hooksPath = resolveCodexHooksPath(codexHomePath);
2411
+ if (!existsSync3(hooksPath)) {
2412
+ return createMissingSourceState(hooksPath);
2413
+ }
2414
+ try {
2415
+ const parsed = JSON.parse(readFileSync8(hooksPath, "utf-8"));
2416
+ if (!isJsonRecord2(parsed)) {
2417
+ return buildSourceState({
2418
+ path: hooksPath,
2419
+ exists: true,
2420
+ parsed: false,
2421
+ error: "hooks.json must contain a JSON object"
2422
+ });
2423
+ }
2424
+ return buildSourceState({
2425
+ path: hooksPath,
2426
+ exists: true,
2427
+ parsed: true
2428
+ });
2429
+ } catch (error) {
2430
+ return buildSourceState({
2431
+ path: hooksPath,
2432
+ exists: true,
2433
+ parsed: false,
2434
+ error: getErrorMessage(error)
2435
+ });
2436
+ }
2437
+ };
2438
+ var getCatalogComponentId = (component) => component.id;
2439
+ var getInstalledComponentId = (component) => component.id;
2440
+ var findInstalledComponent = (integration, catalogComponent) => integration?.components.find(
2441
+ (component) => component.type === catalogComponent.type && getInstalledComponentId(component) === getCatalogComponentId(catalogComponent)
2442
+ ) ?? null;
2443
+ var buildIntegrationComponentStatus = (params) => ({
2444
+ type: params.catalogComponent.type,
2445
+ id: getCatalogComponentId(params.catalogComponent),
2446
+ installed: params.installedComponent !== null,
2447
+ owned: params.installedComponent?.owned ?? null,
2448
+ catalog: params.catalogComponent,
2449
+ installedComponent: params.installedComponent
2450
+ });
2451
+ var buildIntegrationSnapshots = (installedIntegrations) => {
2452
+ const catalog = loadAllIntegrations(INTEGRATIONS_DATA_DIR);
2453
+ return catalog.map((entry) => {
2454
+ const installedIntegration = findInstalledIntegration(installedIntegrations, entry.id);
2455
+ return {
2456
+ id: entry.id,
2457
+ description: entry.description,
2458
+ installed: installedIntegration !== void 0,
2459
+ installedAt: installedIntegration?.installedAt ?? null,
2460
+ updatedAt: installedIntegration?.updatedAt ?? null,
2461
+ components: entry.components.map(
2462
+ (catalogComponent) => buildIntegrationComponentStatus({
2463
+ catalogComponent,
2464
+ installedComponent: findInstalledComponent(installedIntegration, catalogComponent)
2465
+ })
2466
+ )
2467
+ };
2468
+ });
2469
+ };
2470
+ var buildInstalledPathStates = (params) => {
2471
+ if (params.userBasePath === null) {
2472
+ return params.installedPaths.map((path) => ({ path, exists: false }));
2473
+ }
2474
+ return params.installedPaths.map((path) => ({
2475
+ path,
2476
+ exists: existsSync3(join10(params.userBasePath ?? "", path))
2477
+ }));
2478
+ };
2479
+ var buildInstalledSkillMap = (installedSkills) => new Map(
2480
+ installedSkills.map((skill) => [
2481
+ resolveCanonicalSkillId(skill.id),
2482
+ {
2483
+ ...skill,
2484
+ id: resolveCanonicalSkillId(skill.id)
2485
+ }
2486
+ ])
2487
+ );
2488
+ var buildSkillSnapshots = (params) => {
2489
+ const installed = buildInstalledSkillMap(params.installedSkills);
2490
+ return loadAllSkills(SKILLS_DATA_DIR).map((skill) => {
2491
+ const installedSkill = installed.get(skill.id);
2492
+ return {
2493
+ id: skill.id,
2494
+ kind: skill.kind,
2495
+ description: skill.description,
2496
+ supported_tools: skill.supported_tools,
2497
+ groups: skill.groups,
2498
+ installed: installedSkill !== void 0,
2499
+ installedTools: installedSkill?.tools ?? [],
2500
+ installedPaths: buildInstalledPathStates({
2501
+ userBasePath: params.userBasePath,
2502
+ installedPaths: installedSkill?.installed_paths ?? []
2503
+ }),
2504
+ sourceHash: installedSkill?.sourceHash ?? null
2505
+ };
2506
+ });
2507
+ };
2508
+ var buildInstalledSubagentMap = (installedSubagents) => new Map(installedSubagents.map((subagent) => [subagent.id, subagent]));
2509
+ var buildSubagentSnapshots = (params) => {
2510
+ const installed = buildInstalledSubagentMap(params.installedSubagents);
2511
+ return loadAllSubagents(SUBAGENTS_DATA_DIR).map((subagent) => {
2512
+ const installedSubagent = installed.get(subagent.id);
2513
+ return {
2514
+ id: subagent.id,
2515
+ description: subagent.frontmatter.codex.parsed.description,
2516
+ supported_tools: subagent.supported_tools,
2517
+ installed: installedSubagent !== void 0,
2518
+ installedTools: installedSubagent?.tools ?? [],
2519
+ installedPaths: buildInstalledPathStates({
2520
+ userBasePath: params.userBasePath,
2521
+ installedPaths: installedSubagent?.installed_paths ?? []
2522
+ }),
2523
+ sourceHash: installedSubagent?.sourceHash ?? null
2524
+ };
2525
+ });
2526
+ };
2527
+ var buildHookSnapshot = (params) => {
2528
+ if (params.codexHomePath === null) {
2529
+ return {
2530
+ id: params.definition.id,
2531
+ statusMessage: params.definition.statusMessage,
2532
+ hooksPath: null,
2533
+ installed: false,
2534
+ error: params.unavailableReason ?? "CODEX_HOME or HOME is required for Codex hooks."
2535
+ };
1588
2536
  }
1589
- const result = mergeRemoveResults([...managedResults, ...projectResults, ...packResults]);
1590
- removeEmptyDirs(basePath, [...result.deleted, ...stateFiles]);
1591
- return result;
2537
+ const hooksPath = resolveCodexHooksPath(params.codexHomePath);
2538
+ try {
2539
+ const status = inspectCodexHook({
2540
+ hooksPath,
2541
+ definition: params.definition
2542
+ });
2543
+ return {
2544
+ id: params.definition.id,
2545
+ statusMessage: params.definition.statusMessage,
2546
+ hooksPath: status.hooksPath,
2547
+ installed: status.installed,
2548
+ error: null
2549
+ };
2550
+ } catch (error) {
2551
+ return {
2552
+ id: params.definition.id,
2553
+ statusMessage: params.definition.statusMessage,
2554
+ hooksPath,
2555
+ installed: false,
2556
+ error: getErrorMessage(error)
2557
+ };
2558
+ }
2559
+ };
2560
+ var buildRuntimeSnapshot = (params) => {
2561
+ const userUnavailableReason = params.userBasePath === null ? "AI_OPS_HOME or HOME is required for user/global runtime manifests." : null;
2562
+ const codexUnavailableReason = params.codexHomePath === null ? "CODEX_HOME or HOME is required for Codex hooks." : null;
2563
+ const integrationManifest = readRuntimeManifest({
2564
+ manifestPath: params.userBasePath === null ? null : resolveIntegrationManifestPath(params.userBasePath),
2565
+ fallbackPath: INTEGRATIONS_MANIFEST_FALLBACK_PATH,
2566
+ unavailableReason: userUnavailableReason,
2567
+ read: readIntegrationManifest,
2568
+ getGeneratedAt: (manifest) => manifest.generatedAt
2569
+ });
2570
+ const skillRegistry = readRuntimeManifest({
2571
+ manifestPath: params.userBasePath === null ? null : resolveSkillRegistryPath(params.userBasePath),
2572
+ fallbackPath: SKILLS_MANIFEST_FALLBACK_PATH,
2573
+ unavailableReason: userUnavailableReason,
2574
+ read: readSkillRegistry,
2575
+ getGeneratedAt: (registry) => registry.generatedAt
2576
+ });
2577
+ const subagentManifest = readRuntimeManifest({
2578
+ manifestPath: params.userBasePath === null ? null : resolveSubagentManifestPath(params.userBasePath),
2579
+ fallbackPath: SUBAGENTS_MANIFEST_FALLBACK_PATH,
2580
+ unavailableReason: userUnavailableReason,
2581
+ read: readSubagentManifest,
2582
+ getGeneratedAt: (manifest) => manifest.generatedAt
2583
+ });
2584
+ const hooks = readHooksSourceState(params.codexHomePath, codexUnavailableReason);
2585
+ const installedIntegrations = integrationManifest.value?.integrations ?? [];
2586
+ const installedSkills = skillRegistry.value?.skills ?? [];
2587
+ const installedSubagents = subagentManifest.value?.subagents ?? [];
2588
+ return {
2589
+ available: params.userBasePath !== null,
2590
+ unavailableReason: userUnavailableReason,
2591
+ userBasePath: params.userBasePath,
2592
+ codexHomePath: params.codexHomePath,
2593
+ manifests: {
2594
+ integrations: integrationManifest.source,
2595
+ skills: skillRegistry.source,
2596
+ subagents: subagentManifest.source,
2597
+ hooks
2598
+ },
2599
+ integrations: buildIntegrationSnapshots(installedIntegrations),
2600
+ skills: buildSkillSnapshots({
2601
+ userBasePath: params.userBasePath,
2602
+ installedSkills
2603
+ }),
2604
+ subagents: buildSubagentSnapshots({
2605
+ userBasePath: params.userBasePath,
2606
+ installedSubagents
2607
+ }),
2608
+ hooks: KNOWN_CODEX_HOOK_DEFINITIONS.map(
2609
+ (definition) => buildHookSnapshot({
2610
+ codexHomePath: params.codexHomePath,
2611
+ definition,
2612
+ unavailableReason: codexUnavailableReason
2613
+ })
2614
+ )
2615
+ };
2616
+ };
2617
+ var buildStudioSnapshot = (params) => {
2618
+ const generatedAt = params.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
2619
+ const userBasePath = params.userBasePath === void 0 ? resolveDefaultUserBasePath() : params.userBasePath;
2620
+ const codexHomePath = params.codexHomePath === void 0 ? resolveDefaultCodexHomePath() : params.codexHomePath;
2621
+ return StudioSnapshotSchema.parse({
2622
+ schemaVersion: 1,
2623
+ kind: "ai-ops-studio-snapshot",
2624
+ generatedAt,
2625
+ cliVersion: params.cliVersion ?? getCliVersion(),
2626
+ project: buildProjectSnapshot(params.basePath),
2627
+ runtime: buildRuntimeSnapshot({
2628
+ userBasePath,
2629
+ codexHomePath
2630
+ })
2631
+ });
1592
2632
  };
1593
2633
 
1594
2634
  // src/core/pack.ts
1595
- import { existsSync as existsSync2, mkdirSync as mkdirSync5, readFileSync as readFileSync7, readdirSync as readdirSync4, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "fs";
1596
- import { dirname as dirname7, isAbsolute as isAbsolute2, join as join9, relative as relative2, resolve as resolve6 } from "path";
2635
+ import { existsSync as existsSync4, mkdirSync as mkdirSync6, readFileSync as readFileSync9, readdirSync as readdirSync4, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "fs";
2636
+ import { dirname as dirname8, isAbsolute as isAbsolute2, join as join11, relative as relative2, resolve as resolve7 } from "path";
1597
2637
  var PACK_REGISTRY_FILENAME = "pack-registry.json";
1598
2638
  var SPEC_LIFECYCLE_PACK_ID = "spec-lifecycle";
1599
2639
  var PACK_INSTALL_ROOT = "docs/specs/";
@@ -1601,9 +2641,9 @@ var RESERVED_DOCUMENT_WARNINGS2 = [
1601
2641
  "\uD310\uB2E8 \uADFC\uAC70\uB85C \uC0AC\uC6A9\uD558\uC9C0 \uB9C8\uC138\uC694",
1602
2642
  "Do not use this document as current decision-making evidence"
1603
2643
  ];
1604
- var DEFAULT_PACKS_DIR = join9(COMPILER_DATA_DIR, "packs");
2644
+ var DEFAULT_PACKS_DIR = join11(COMPILER_DATA_DIR, "packs");
1605
2645
  var includesReservedDocumentWarning2 = (content) => RESERVED_DOCUMENT_WARNINGS2.some((warning) => content.includes(warning));
1606
- var readPackCatalog = (packsDir) => PackCatalogSchema.parse(JSON.parse(readFileSync7(join9(packsDir, PACK_REGISTRY_FILENAME), "utf-8")));
2646
+ var readPackCatalog = (packsDir) => PackCatalogSchema.parse(JSON.parse(readFileSync9(join11(packsDir, PACK_REGISTRY_FILENAME), "utf-8")));
1607
2647
  var assertPackInstallPath = (path) => {
1608
2648
  if (!isSafeProjectLayerPath(path) || !path.startsWith(PACK_INSTALL_ROOT)) {
1609
2649
  throw new Error(`Unsafe pack path: ${path}`);
@@ -1612,16 +2652,16 @@ var assertPackInstallPath = (path) => {
1612
2652
  var readPackSourceFiles = (packDir) => {
1613
2653
  const files = [];
1614
2654
  const walk = (relativeDir = "") => {
1615
- const absoluteDir = relativeDir.length > 0 ? join9(packDir, relativeDir) : packDir;
2655
+ const absoluteDir = relativeDir.length > 0 ? join11(packDir, relativeDir) : packDir;
1616
2656
  const entries = readdirSync4(absoluteDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
1617
2657
  for (const entry of entries) {
1618
- const nextRelativePath = relativeDir.length > 0 ? join9(relativeDir, entry.name) : entry.name;
2658
+ const nextRelativePath = relativeDir.length > 0 ? join11(relativeDir, entry.name) : entry.name;
1619
2659
  if (entry.isDirectory()) {
1620
2660
  walk(nextRelativePath);
1621
2661
  continue;
1622
2662
  }
1623
2663
  assertPackInstallPath(nextRelativePath);
1624
- const content = readFileSync7(join9(packDir, nextRelativePath), "utf-8");
2664
+ const content = readFileSync9(join11(packDir, nextRelativePath), "utf-8");
1625
2665
  files.push({
1626
2666
  path: nextRelativePath,
1627
2667
  content,
@@ -1656,8 +2696,8 @@ var loadAllPacks = (packsDir) => {
1656
2696
  if (entry.id !== SPEC_LIFECYCLE_PACK_ID) {
1657
2697
  throw new Error(`Unsupported pack id: ${entry.id}`);
1658
2698
  }
1659
- const packDir = resolve6(packsDir, entry.source_path);
1660
- const relativeFromPacks = relative2(resolve6(packsDir), packDir);
2699
+ const packDir = resolve7(packsDir, entry.source_path);
2700
+ const relativeFromPacks = relative2(resolve7(packsDir), packDir);
1661
2701
  if (relativeFromPacks.length === 0 || relativeFromPacks.startsWith("..") || isAbsolute2(relativeFromPacks)) {
1662
2702
  throw new Error(`Pack source path escapes packs dir: ${entry.source_path}`);
1663
2703
  }
@@ -1679,11 +2719,11 @@ var resolvePackById = (packsDir, packId) => {
1679
2719
  return pack;
1680
2720
  };
1681
2721
  var serializePackFileContent = (content) => content.length === 0 ? "" : content.trimEnd() + "\n";
1682
- var readProjectFileHash = (basePath, relativePath) => computeHash([readFileSync7(resolveProjectLayerFilePath(basePath, relativePath), "utf-8").trimEnd()]);
2722
+ var readProjectFileHash = (basePath, relativePath) => computeHash([readFileSync9(resolveProjectLayerFilePath(basePath, relativePath), "utf-8").trimEnd()]);
1683
2723
  var writePackFile = (basePath, file) => {
1684
2724
  const absolutePath = resolveProjectLayerFilePath(basePath, file.path);
1685
- mkdirSync5(dirname7(absolutePath), { recursive: true });
1686
- writeFileSync5(absolutePath, serializePackFileContent(file.content), "utf-8");
2725
+ mkdirSync6(dirname8(absolutePath), { recursive: true });
2726
+ writeFileSync6(absolutePath, serializePackFileContent(file.content), "utf-8");
1687
2727
  };
1688
2728
  var buildPackFileRecords = (files) => files.map((file) => ({
1689
2729
  path: file.path,
@@ -1713,7 +2753,7 @@ var applyPackSourceFiles = (params) => {
1713
2753
  for (const file of sourceFiles) {
1714
2754
  const absolutePath = resolveProjectLayerFilePath(params.basePath, file.path);
1715
2755
  const previous = previousByPath.get(file.path);
1716
- if (!existsSync2(absolutePath)) {
2756
+ if (!existsSync4(absolutePath)) {
1717
2757
  writePackFile(params.basePath, file);
1718
2758
  written.push(file.path);
1719
2759
  continue;
@@ -1737,7 +2777,7 @@ var applyPackSourceFiles = (params) => {
1737
2777
  continue;
1738
2778
  }
1739
2779
  const absolutePath = resolveProjectLayerFilePath(params.basePath, previous.path);
1740
- if (!existsSync2(absolutePath)) {
2780
+ if (!existsSync4(absolutePath)) {
1741
2781
  notFound.push(previous.path);
1742
2782
  continue;
1743
2783
  }
@@ -1756,7 +2796,7 @@ var removePackFiles = (basePath, record) => {
1756
2796
  const notFound = [];
1757
2797
  for (const file of [...record.documents, ...record.files]) {
1758
2798
  const absolutePath = resolveProjectLayerFilePath(basePath, file.path);
1759
- if (!existsSync2(absolutePath)) {
2799
+ if (!existsSync4(absolutePath)) {
1760
2800
  notFound.push(file.path);
1761
2801
  continue;
1762
2802
  }
@@ -1770,12 +2810,12 @@ var removePackFiles = (basePath, record) => {
1770
2810
  return { written: [], refreshed: [], preserved, deleted, notFound };
1771
2811
  };
1772
2812
  var removeEmptyDirs2 = (basePath, relativePaths) => {
1773
- const dirs = [...new Set(relativePaths.map((path) => dirname7(path)).filter((dir) => dir !== "."))].sort(
2813
+ const dirs = [...new Set(relativePaths.map((path) => dirname8(path)).filter((dir) => dir !== "."))].sort(
1774
2814
  (a, b) => b.length - a.length
1775
2815
  );
1776
2816
  for (const dir of dirs) {
1777
2817
  const absoluteDir = resolveProjectLayerFilePath(basePath, dir);
1778
- if (!existsSync2(absoluteDir)) {
2818
+ if (!existsSync4(absoluteDir)) {
1779
2819
  continue;
1780
2820
  }
1781
2821
  try {
@@ -1891,7 +2931,7 @@ var diffProjectLayerPack = (params) => {
1891
2931
  }
1892
2932
  for (const file of [...record.documents, ...record.files]) {
1893
2933
  const absolutePath = resolveProjectLayerFilePath(params.basePath, file.path);
1894
- if (!existsSync2(absolutePath)) {
2934
+ if (!existsSync4(absolutePath)) {
1895
2935
  issues.push(packIssue("error", "missing-file", `\uD30C\uC77C \uC5C6\uC74C: ${file.path}`));
1896
2936
  }
1897
2937
  }
@@ -1902,21 +2942,21 @@ var diffProjectLayerPack = (params) => {
1902
2942
  // src/core/context-promotion.ts
1903
2943
  import { createHash as createHash2 } from "crypto";
1904
2944
  import { execFileSync } from "child_process";
1905
- import { existsSync as existsSync3, mkdirSync as mkdirSync6, readFileSync as readFileSync8, statSync, writeFileSync as writeFileSync6 } from "fs";
1906
- import { dirname as dirname8, join as join10, resolve as resolve7 } from "path";
1907
- import { z as z12 } from "zod";
2945
+ import { existsSync as existsSync5, mkdirSync as mkdirSync7, readFileSync as readFileSync10, statSync, writeFileSync as writeFileSync7 } from "fs";
2946
+ import { dirname as dirname9, join as join12, resolve as resolve8 } from "path";
2947
+ import { z as z14 } from "zod";
1908
2948
 
1909
2949
  // src/core/tool-use-hook.ts
1910
- import { z as z11 } from "zod";
1911
- var HookToolInputSchema = z11.object({
1912
- command: z11.string().optional()
2950
+ import { z as z13 } from "zod";
2951
+ var HookToolInputSchema = z13.object({
2952
+ command: z13.string().optional()
1913
2953
  }).passthrough();
1914
- var ToolUseHookInputSchema = z11.object({
1915
- hook_event_name: z11.string(),
1916
- cwd: z11.string(),
1917
- tool_name: z11.string().optional(),
1918
- tool_input: z11.unknown().optional(),
1919
- tool_response: z11.unknown().optional()
2954
+ var ToolUseHookInputSchema = z13.object({
2955
+ hook_event_name: z13.string(),
2956
+ cwd: z13.string(),
2957
+ tool_name: z13.string().optional(),
2958
+ tool_input: z13.unknown().optional(),
2959
+ tool_response: z13.unknown().optional()
1920
2960
  }).passthrough();
1921
2961
  var SHELL_CONTROL_TOKENS = /* @__PURE__ */ new Set(["&&", "||", ";", "|", "(", ")"]);
1922
2962
  var SHELL_SCRIPT_FLAGS = /* @__PURE__ */ new Set(["-c", "-lc"]);
@@ -2047,7 +3087,7 @@ var isGitCommitCommand = (command) => {
2047
3087
  (segment) => segmentInvokesGitCommit(segment) || segmentInvokesShellScriptWithGitCommit(segment)
2048
3088
  );
2049
3089
  };
2050
- var isJsonRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3090
+ var isJsonRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2051
3091
  var numberField = (record, keys) => {
2052
3092
  for (const key of keys) {
2053
3093
  const value = record[key];
@@ -2091,7 +3131,7 @@ var toolResponseIndicatesFailure = (toolResponse) => {
2091
3131
  if (typeof toolResponse === "string") {
2092
3132
  return stringIndicatesGitCommitFailureOrSuccess(toolResponse) === true;
2093
3133
  }
2094
- if (!isJsonRecord(toolResponse)) {
3134
+ if (!isJsonRecord3(toolResponse)) {
2095
3135
  return false;
2096
3136
  }
2097
3137
  const success = booleanField(toolResponse, ["success", "ok"]);
@@ -2133,35 +3173,35 @@ var CONTEXT_PROMOTION_SCOPE = {
2133
3173
  PROJECT_LOCAL: "project-local",
2134
3174
  GLOBAL: "global"
2135
3175
  };
2136
- var ContextPromotionDecisionSchema = z12.union([
2137
- z12.literal(CONTEXT_PROMOTION_DECISION.PROMOTED),
2138
- z12.literal(CONTEXT_PROMOTION_DECISION.NO_PROMOTION)
3176
+ var ContextPromotionDecisionSchema = z14.union([
3177
+ z14.literal(CONTEXT_PROMOTION_DECISION.PROMOTED),
3178
+ z14.literal(CONTEXT_PROMOTION_DECISION.NO_PROMOTION)
2139
3179
  ]);
2140
- var ContextPromotionScopeSchema = z12.union([
2141
- z12.literal(CONTEXT_PROMOTION_SCOPE.CORE),
2142
- z12.literal(CONTEXT_PROMOTION_SCOPE.PROJECT_LOCAL),
2143
- z12.literal(CONTEXT_PROMOTION_SCOPE.GLOBAL)
3180
+ var ContextPromotionScopeSchema = z14.union([
3181
+ z14.literal(CONTEXT_PROMOTION_SCOPE.CORE),
3182
+ z14.literal(CONTEXT_PROMOTION_SCOPE.PROJECT_LOCAL),
3183
+ z14.literal(CONTEXT_PROMOTION_SCOPE.GLOBAL)
2144
3184
  ]);
2145
- var ContextPromotionReceiptSchema = z12.object({
2146
- fingerprint: z12.string().regex(/^[a-f0-9]{16}$/),
2147
- commitHash: z12.string().regex(/^(NO_HEAD|[a-f0-9]{40})$/).optional(),
3185
+ var ContextPromotionReceiptSchema = z14.object({
3186
+ fingerprint: z14.string().regex(/^[a-f0-9]{16}$/),
3187
+ commitHash: z14.string().regex(/^(NO_HEAD|[a-f0-9]{40})$/).optional(),
2148
3188
  decision: ContextPromotionDecisionSchema,
2149
- scopes: z12.array(ContextPromotionScopeSchema),
2150
- targets: z12.array(z12.string().min(1)),
2151
- summary: z12.string().min(1),
2152
- resolvedAt: z12.string().min(1)
3189
+ scopes: z14.array(ContextPromotionScopeSchema),
3190
+ targets: z14.array(z14.string().min(1)),
3191
+ summary: z14.string().min(1),
3192
+ resolvedAt: z14.string().min(1)
2153
3193
  }).strict();
2154
- var ContextPromotionReceiptIndexSchema = z12.object({
2155
- schemaVersion: z12.literal(1),
2156
- kind: z12.literal("context-promotion-receipts"),
2157
- projectKey: z12.string().regex(/^[a-f0-9]{12}$/),
2158
- projectRoot: z12.string().min(1),
2159
- receipts: z12.array(ContextPromotionReceiptSchema)
3194
+ var ContextPromotionReceiptIndexSchema = z14.object({
3195
+ schemaVersion: z14.literal(1),
3196
+ kind: z14.literal("context-promotion-receipts"),
3197
+ projectKey: z14.string().regex(/^[a-f0-9]{12}$/),
3198
+ projectRoot: z14.string().min(1),
3199
+ receipts: z14.array(ContextPromotionReceiptSchema)
2160
3200
  }).strict();
2161
3201
  var RECEIPT_INDEX_FILENAME = "receipts-index.json";
2162
3202
  var DEFAULT_PRUNE_MAX = 50;
2163
3203
  var hashHex = (parts, length) => createHash2("sha256").update(parts.join("\0")).digest("hex").slice(0, length);
2164
- var buildContextPromotionProjectKey = (gitRoot) => hashHex([resolve7(gitRoot)], 12);
3204
+ var buildContextPromotionProjectKey = (gitRoot) => hashHex([resolve8(gitRoot)], 12);
2165
3205
  var runGit = (cwd, args) => execFileSync("git", [...args], {
2166
3206
  cwd,
2167
3207
  encoding: "utf-8",
@@ -2186,13 +3226,13 @@ var readUntrackedFingerprintParts = (gitRoot) => {
2186
3226
  const raw = runGit(gitRoot, ["ls-files", "--others", "--exclude-standard", "-z"]);
2187
3227
  const paths = raw.split("\0").filter((path) => path.length > 0).sort((a, b) => a.localeCompare(b));
2188
3228
  return paths.map((relativePath) => {
2189
- const absolutePath = join10(gitRoot, relativePath);
3229
+ const absolutePath = join12(gitRoot, relativePath);
2190
3230
  try {
2191
3231
  const stat = statSync(absolutePath);
2192
3232
  if (!stat.isFile()) {
2193
3233
  return `${relativePath}:non-file`;
2194
3234
  }
2195
- const content = readFileSync8(absolutePath);
3235
+ const content = readFileSync10(absolutePath);
2196
3236
  return `${relativePath}:${createHash2("sha256").update(content).digest("hex")}`;
2197
3237
  } catch {
2198
3238
  throw new Error(`Unable to read untracked path for context promotion fingerprint: ${relativePath}`);
@@ -2206,15 +3246,15 @@ var readTrackedWorkingTreeFingerprintParts = (gitRoot) => {
2206
3246
  return [
2207
3247
  `raw:${rawDiff}`,
2208
3248
  ...paths.map((relativePath) => {
2209
- const absolutePath = join10(gitRoot, relativePath);
2210
- if (!existsSync3(absolutePath)) {
3249
+ const absolutePath = join12(gitRoot, relativePath);
3250
+ if (!existsSync5(absolutePath)) {
2211
3251
  return `${relativePath}:deleted`;
2212
3252
  }
2213
3253
  const stat = statSync(absolutePath);
2214
3254
  if (!stat.isFile()) {
2215
3255
  return `${relativePath}:non-file`;
2216
3256
  }
2217
- const content = readFileSync8(absolutePath);
3257
+ const content = readFileSync10(absolutePath);
2218
3258
  return `${relativePath}:${createHash2("sha256").update(content).digest("hex")}`;
2219
3259
  })
2220
3260
  ];
@@ -2232,19 +3272,19 @@ var computeContextPromotionFingerprint = (gitRoot) => hashHex(
2232
3272
  ],
2233
3273
  16
2234
3274
  );
2235
- var resolveContextPromotionReceiptIndexPath = (params) => join10(params.userBasePath, ".ai-ops", "context-promotion", "projects", params.projectKey, RECEIPT_INDEX_FILENAME);
3275
+ var resolveContextPromotionReceiptIndexPath = (params) => join12(params.userBasePath, ".ai-ops", "context-promotion", "projects", params.projectKey, RECEIPT_INDEX_FILENAME);
2236
3276
  var parseContextPromotionReceiptIndex = (json) => ContextPromotionReceiptIndexSchema.parse(JSON.parse(json));
2237
3277
  var serializeContextPromotionReceiptIndex = (index) => JSON.stringify(index, null, 2) + "\n";
2238
3278
  var readContextPromotionReceiptIndex = (indexPath) => {
2239
3279
  try {
2240
- return parseContextPromotionReceiptIndex(readFileSync8(indexPath, "utf-8"));
3280
+ return parseContextPromotionReceiptIndex(readFileSync10(indexPath, "utf-8"));
2241
3281
  } catch {
2242
3282
  return null;
2243
3283
  }
2244
3284
  };
2245
3285
  var writeContextPromotionReceiptIndex = (indexPath, index) => {
2246
- mkdirSync6(dirname8(indexPath), { recursive: true });
2247
- writeFileSync6(indexPath, serializeContextPromotionReceiptIndex(index), "utf-8");
3286
+ mkdirSync7(dirname9(indexPath), { recursive: true });
3287
+ writeFileSync7(indexPath, serializeContextPromotionReceiptIndex(index), "utf-8");
2248
3288
  };
2249
3289
  var buildEmptyReceiptIndex = (params) => ({
2250
3290
  schemaVersion: 1,
@@ -2292,9 +3332,9 @@ var pruneContextPromotionReceipts = (params) => {
2292
3332
  writeContextPromotionReceiptIndex(params.indexPath, nextIndex);
2293
3333
  return nextIndex;
2294
3334
  };
2295
- var hasContextPromotionLayer = (gitRoot) => existsSync3(join10(gitRoot, PROJECT_LAYER_CONTEXT_INDEX_RELATIVE_PATH));
3335
+ var hasContextPromotionLayer = (gitRoot) => existsSync5(join12(gitRoot, PROJECT_LAYER_CONTEXT_INDEX_RELATIVE_PATH));
2296
3336
  var getContextPromotionStatus = (params) => {
2297
- const cwd = resolve7(params.cwd);
3337
+ const cwd = resolve8(params.cwd);
2298
3338
  const gitRoot = resolveContextPromotionGitRoot(cwd);
2299
3339
  if (!gitRoot) {
2300
3340
  return {
@@ -2383,24 +3423,28 @@ var buildContextPromotionReviewPrompt = (status) => {
2383
3423
  const projectRoot = status.gitRoot ?? status.cwd;
2384
3424
  const cdCommand = `cd ${JSON.stringify(projectRoot)}`;
2385
3425
  return [
2386
- "Context Promotion Review should run for the completed work commit.",
3426
+ "Context Promotion Review should run for the completed work commit and review-loop learnings.",
2387
3427
  "",
2388
3428
  `Project root: ${projectRoot}`,
2389
3429
  "This project root is authoritative for this review.",
2390
3430
  "",
2391
- "Use the `context-promotion-review` skill to review the just-created HEAD commit for reusable operating knowledge.",
3431
+ "Use the `context-promotion-review` skill to review the just-created HEAD commit plus current conversation/review-loop learnings for reusable operating knowledge.",
2392
3432
  "",
2393
3433
  "Scope boundary:",
2394
3434
  `- Before inspecting files, anchor shell work in the project root above. If needed, run \`${cdCommand}\` first.`,
2395
3435
  "- Do not inspect other repositories, parent directories, or earlier conversation workspaces.",
2396
3436
  "- Do not search the web or external documentation for this review.",
2397
3437
  "- If `AGENTS.md`, `docs/agent/*`, `docs/docs-status.md`, or other context-layer files are absent, report them as absent; do not substitute files from another repo.",
2398
- "- Use only the just-created `HEAD` commit, this conversation, and files under the project root.",
3438
+ "- Use only the just-created `HEAD` commit, current conversation/review-loop learnings, post-commit worktree state, and files under the project root.",
2399
3439
  "",
2400
3440
  "Review requirements:",
2401
3441
  "- Do not amend, rewrite, or mix changes into the work commit.",
3442
+ "- Inspect the post-commit worktree state before deciding: run `git status --short`, `git diff --name-only`, `git diff --cached --name-only`, and `git ls-files --others --exclude-standard`.",
2402
3443
  "- Inspect the completed commit before deciding: run `git show --stat HEAD`, `git show --name-only HEAD`, and `git show HEAD` when detail is needed.",
2403
3444
  "- Cross-check existing `AGENTS.md`, `docs/agent/*`, `docs/docs-status.md`, and `.ai-ops/context-layer.json` first.",
3445
+ "- Treat `already-covered` as valid only when the Active context layer already has the same agent behavior rule; plans, tests, README, runbooks, and operator docs are evidence, not automatic coverage.",
3446
+ "- Check whether user corrections, repeated review findings, command routines, dirty worktree, untracked files, changeset pollution, or staging-scope hygiene produced a reusable `project-local` candidate.",
3447
+ "- Before `no-promotion`, briefly report near-miss or discarded candidates with reasons.",
2404
3448
  "- Classify candidates as `core`, `project-local`, `global`, `already-covered`, or `no-promotion`.",
2405
3449
  "- Ask the user before editing any file.",
2406
3450
  "- If promotion is approved, edit only the approved context/global files, then stop for user inspection without committing.",
@@ -2456,9 +3500,9 @@ var evaluateContextPromotionPostToolUseHook = (params) => {
2456
3500
 
2457
3501
  // src/core/pc-integration.ts
2458
3502
  import { execFileSync as execFileSync2 } from "child_process";
2459
- import { existsSync as existsSync4, readFileSync as readFileSync9, readdirSync as readdirSync5 } from "fs";
2460
- import { join as join11, resolve as resolve8, sep } from "path";
2461
- var normalizePath = (path) => resolve8(path.replace(/^~(?=$|\/)/, process.env.HOME ?? "~"));
3503
+ import { existsSync as existsSync6, readFileSync as readFileSync11, readdirSync as readdirSync5 } from "fs";
3504
+ import { join as join13, resolve as resolve9, sep } from "path";
3505
+ var normalizePath = (path) => resolve9(path.replace(/^~(?=$|\/)/, process.env.HOME ?? "~"));
2462
3506
  var pathContains = (parentPath, childPath) => {
2463
3507
  const parent = normalizePath(parentPath);
2464
3508
  const child = normalizePath(childPath);
@@ -2495,7 +3539,7 @@ var parseListField = (content, labels) => {
2495
3539
  };
2496
3540
  var readTextFileOrNull = (filePath) => {
2497
3541
  try {
2498
- return readFileSync9(filePath, "utf-8");
3542
+ return readFileSync11(filePath, "utf-8");
2499
3543
  } catch {
2500
3544
  return null;
2501
3545
  }
@@ -2520,11 +3564,11 @@ var readGitHead2 = (cwd) => {
2520
3564
  }
2521
3565
  };
2522
3566
  var listWorkspaceStatePaths = (contextRoot) => {
2523
- const workspacesDir = join11(contextRoot, "workspaces");
2524
- if (!existsSync4(workspacesDir)) {
3567
+ const workspacesDir = join13(contextRoot, "workspaces");
3568
+ if (!existsSync6(workspacesDir)) {
2525
3569
  return [];
2526
3570
  }
2527
- return readdirSync5(workspacesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join11(workspacesDir, entry.name, "workspace-state.md")).filter((statePath) => existsSync4(statePath)).sort((a, b) => a.localeCompare(b));
3571
+ return readdirSync5(workspacesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join13(workspacesDir, entry.name, "workspace-state.md")).filter((statePath) => existsSync6(statePath)).sort((a, b) => a.localeCompare(b));
2528
3572
  };
2529
3573
  var parseWorkspaceCandidate = (statePath) => {
2530
3574
  const content = readTextFileOrNull(statePath);
@@ -2537,7 +3581,7 @@ var parseWorkspaceCandidate = (statePath) => {
2537
3581
  }
2538
3582
  const activeSection = extractSection(content, ["\uD65C\uC131 Workstream", "Active Workstream"]);
2539
3583
  const activeWorkstreamId = parseListField(activeSection, ["ID", "Workstream ID", "Active Workstream"]);
2540
- const workspaceDir = resolve8(statePath, "..");
3584
+ const workspaceDir = resolve9(statePath, "..");
2541
3585
  const id = parseListField(content, ["\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 ID", "Workspace ID"]) ?? workspaceDir.split(sep).at(-1) ?? "unknown";
2542
3586
  return {
2543
3587
  id,
@@ -2567,11 +3611,11 @@ var parseRepoEntry = (entryPath) => {
2567
3611
  };
2568
3612
  };
2569
3613
  var findCurrentEntry = (params) => {
2570
- const reposDir = join11(params.workspaceDir, "repos");
2571
- if (!existsSync4(reposDir)) {
3614
+ const reposDir = join13(params.workspaceDir, "repos");
3615
+ if (!existsSync6(reposDir)) {
2572
3616
  return null;
2573
3617
  }
2574
- const entries = readdirSync5(reposDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => parseRepoEntry(join11(reposDir, entry.name))).filter((entry) => entry !== null).filter((entry) => {
3618
+ const entries = readdirSync5(reposDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => parseRepoEntry(join13(reposDir, entry.name))).filter((entry) => entry !== null).filter((entry) => {
2575
3619
  const paths = [entry.path, entry.gitRoot].filter((path) => path !== null);
2576
3620
  return paths.some((path) => pathContains(path, params.cwd));
2577
3621
  }).sort((a, b) => {
@@ -2623,7 +3667,7 @@ var parseLastConfirmedCommitHash = (params) => {
2623
3667
  var getPcHandoffStatus = (params) => {
2624
3668
  const cwd = normalizePath(params.cwd);
2625
3669
  const contextRoot = normalizePath(params.contextRoot);
2626
- if (!existsSync4(contextRoot)) {
3670
+ if (!existsSync6(contextRoot)) {
2627
3671
  return {
2628
3672
  cwd,
2629
3673
  contextRoot,
@@ -2666,7 +3710,7 @@ var getPcHandoffStatus = (params) => {
2666
3710
  skipReason: "active pc workstream not selected"
2667
3711
  };
2668
3712
  }
2669
- const activeWorkstreamPath = join11(workspace.workspaceDir, "workstreams", `${workspace.activeWorkstreamId}.md`);
3713
+ const activeWorkstreamPath = join13(workspace.workspaceDir, "workstreams", `${workspace.activeWorkstreamId}.md`);
2670
3714
  const activeWorkstreamContent = readTextFileOrNull(activeWorkstreamPath);
2671
3715
  if (!activeWorkstreamContent) {
2672
3716
  return {
@@ -2783,204 +3827,12 @@ var evaluatePcPostToolUseHook = (params) => {
2783
3827
  return buildPostToolUseOutput2(buildPcDonePrompt({ status, head, gitRoot }));
2784
3828
  };
2785
3829
 
2786
- // src/core/codex-hook.ts
2787
- import { existsSync as existsSync5, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "fs";
2788
- import { dirname as dirname9, join as join12 } from "path";
2789
- var CONTEXT_PROMOTION_HOOK_ID = "context-promotion";
2790
- var CONTEXT_PROMOTION_HOOK_COMMAND_MARKER = "context-promotion hook post-tool-use";
2791
- var CONTEXT_PROMOTION_LEGACY_HOOK_COMMAND_MARKER = "context-promotion hook pre-tool-use";
2792
- var CONTEXT_PROMOTION_DEFAULT_HOOK_COMMAND = `ai-ops ${CONTEXT_PROMOTION_HOOK_COMMAND_MARKER}`;
2793
- var PC_HOOK_ID = "pc";
2794
- var PC_HOOK_COMMAND_MARKER = "integration hook post-tool-use pc";
2795
- var PC_DEFAULT_HOOK_COMMAND = `ai-ops ${PC_HOOK_COMMAND_MARKER}`;
2796
- var PRE_TOOL_USE_EVENT = "PreToolUse";
2797
- var POST_TOOL_USE_EVENT = "PostToolUse";
2798
- var BASH_MATCHER = "^Bash$";
2799
- var CONTEXT_PROMOTION_CODEX_HOOK = {
2800
- id: CONTEXT_PROMOTION_HOOK_ID,
2801
- commandMarker: CONTEXT_PROMOTION_HOOK_COMMAND_MARKER,
2802
- legacyCommandMarkers: [CONTEXT_PROMOTION_LEGACY_HOOK_COMMAND_MARKER],
2803
- defaultCommand: CONTEXT_PROMOTION_DEFAULT_HOOK_COMMAND,
2804
- statusMessage: "Checking context promotion review"
2805
- };
2806
- var PC_CODEX_HOOK = {
2807
- id: PC_HOOK_ID,
2808
- commandMarker: PC_HOOK_COMMAND_MARKER,
2809
- legacyCommandMarkers: [],
2810
- defaultCommand: PC_DEFAULT_HOOK_COMMAND,
2811
- statusMessage: "Checking pc handoff"
2812
- };
2813
- var isJsonRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2814
- var readJsonRecord = (filePath) => {
2815
- if (!existsSync5(filePath)) {
2816
- return {};
2817
- }
2818
- const parsed = JSON.parse(readFileSync10(filePath, "utf-8"));
2819
- if (!isJsonRecord2(parsed)) {
2820
- throw new Error("hooks.json must contain a JSON object");
2821
- }
2822
- return parsed;
2823
- };
2824
- var writeJsonRecord = (filePath, value) => {
2825
- mkdirSync7(dirname9(filePath), { recursive: true });
2826
- writeFileSync7(filePath, JSON.stringify(value, null, 2) + "\n", "utf-8");
2827
- };
2828
- var getOrCreateRecord = (record, key) => {
2829
- const existing = record[key];
2830
- if (isJsonRecord2(existing)) {
2831
- return existing;
2832
- }
2833
- const next = {};
2834
- record[key] = next;
2835
- return next;
2836
- };
2837
- var getArray = (record, key) => {
2838
- const existing = record[key];
2839
- return Array.isArray(existing) ? existing : [];
2840
- };
2841
- var handlerMatchesDefinition = (definition) => (handler) => isJsonRecord2(handler) && typeof handler.command === "string" && [definition.commandMarker, ...definition.legacyCommandMarkers].some((marker) => handler.command.includes(marker));
2842
- var handlerMatchesCommand = (handler, command) => isJsonRecord2(handler) && handler.command === command;
2843
- var groupHasDefinitionHook = (definition) => (group) => isJsonRecord2(group) && getArray(group, "hooks").some(handlerMatchesDefinition(definition));
2844
- var groupHasCurrentDefinitionHook = (group, command) => isJsonRecord2(group) && getArray(group, "hooks").some((handler) => handlerMatchesCommand(handler, command));
2845
- var countDefinitionHandlers = (groups, definition) => groups.reduce((count, group) => {
2846
- if (!isJsonRecord2(group)) {
2847
- return count;
2848
- }
2849
- return count + getArray(group, "hooks").filter(handlerMatchesDefinition(definition)).length;
2850
- }, 0);
2851
- var configHasDefinitionHook = (config, definition) => {
2852
- const hooks = config.hooks;
2853
- if (!isJsonRecord2(hooks)) {
2854
- return false;
2855
- }
2856
- return getArray(hooks, POST_TOOL_USE_EVENT).some(groupHasDefinitionHook(definition));
2857
- };
2858
- var configHasOnlyCurrentDefinitionHook = (config, definition, command) => {
2859
- const hooks = config.hooks;
2860
- if (!isJsonRecord2(hooks)) {
2861
- return false;
2862
- }
2863
- const hasLegacy = getArray(hooks, PRE_TOOL_USE_EVENT).some(groupHasDefinitionHook(definition));
2864
- const postGroups = getArray(hooks, POST_TOOL_USE_EVENT);
2865
- const hasCurrent = postGroups.some((group) => groupHasCurrentDefinitionHook(group, command));
2866
- return hasCurrent && !hasLegacy && countDefinitionHandlers(postGroups, definition) === 1;
2867
- };
2868
- var removeDefinitionHooksFromEvent = (hooks, eventName, definition) => {
2869
- const previousGroups = getArray(hooks, eventName);
2870
- let removed = false;
2871
- const nextGroups = previousGroups.map((group) => {
2872
- if (!isJsonRecord2(group)) {
2873
- return group;
2874
- }
2875
- const previousHandlers = getArray(group, "hooks");
2876
- const nextHandlers = previousHandlers.filter((handler) => {
2877
- const matches = handlerMatchesDefinition(definition)(handler);
2878
- if (matches) {
2879
- removed = true;
2880
- }
2881
- return !matches;
2882
- });
2883
- if (nextHandlers.length === 0) {
2884
- return null;
2885
- }
2886
- return {
2887
- ...group,
2888
- hooks: nextHandlers
2889
- };
2890
- }).filter((group) => group !== null);
2891
- if (!removed) {
2892
- return false;
2893
- }
2894
- if (nextGroups.length > 0) {
2895
- hooks[eventName] = nextGroups;
2896
- } else {
2897
- delete hooks[eventName];
2898
- }
2899
- return true;
2900
- };
2901
- var resolveCodexHooksPath = (codexHomePath) => join12(codexHomePath, "hooks.json");
2902
- var buildCodexHookCommand = (params) => {
2903
- const command = params.overrideCommand?.trim() ?? params.definition.defaultCommand;
2904
- if (!command.includes(params.definition.commandMarker)) {
2905
- throw new Error(`${params.definition.id} hook command must include: ${params.definition.commandMarker}`);
2906
- }
2907
- return command;
2908
- };
2909
- var buildContextPromotionHookCommand = (overrideCommand) => buildCodexHookCommand({
2910
- definition: CONTEXT_PROMOTION_CODEX_HOOK,
2911
- overrideCommand
2912
- });
2913
- var inspectCodexHook = (params) => ({
2914
- hooksPath: params.hooksPath,
2915
- installed: configHasDefinitionHook(readJsonRecord(params.hooksPath), params.definition)
2916
- });
2917
- var inspectContextPromotionHook = (hooksPath) => ({
2918
- hooksPath,
2919
- installed: inspectCodexHook({ hooksPath, definition: CONTEXT_PROMOTION_CODEX_HOOK }).installed
2920
- });
2921
- var installCodexHook = (params) => {
2922
- const config = readJsonRecord(params.hooksPath);
2923
- if (configHasOnlyCurrentDefinitionHook(config, params.definition, params.command)) {
2924
- return {
2925
- hooksPath: params.hooksPath,
2926
- installed: true,
2927
- changed: false
2928
- };
2929
- }
2930
- const hooks = getOrCreateRecord(config, "hooks");
2931
- removeDefinitionHooksFromEvent(hooks, PRE_TOOL_USE_EVENT, params.definition);
2932
- removeDefinitionHooksFromEvent(hooks, POST_TOOL_USE_EVENT, params.definition);
2933
- const existingGroups = getArray(hooks, POST_TOOL_USE_EVENT);
2934
- const nextGroup = {
2935
- matcher: BASH_MATCHER,
2936
- hooks: [
2937
- {
2938
- type: "command",
2939
- command: params.command,
2940
- timeout: 30,
2941
- statusMessage: params.definition.statusMessage
2942
- }
2943
- ]
2944
- };
2945
- hooks[POST_TOOL_USE_EVENT] = [...existingGroups, nextGroup];
2946
- writeJsonRecord(params.hooksPath, config);
2947
- return {
2948
- hooksPath: params.hooksPath,
2949
- installed: true,
2950
- changed: true
2951
- };
2952
- };
2953
- var installContextPromotionHook = (params) => installCodexHook({
2954
- hooksPath: params.hooksPath,
2955
- definition: CONTEXT_PROMOTION_CODEX_HOOK,
2956
- command: params.command
2957
- });
2958
- var uninstallCodexHook = (params) => {
2959
- const config = readJsonRecord(params.hooksPath);
2960
- const hooks = config.hooks;
2961
- if (!isJsonRecord2(hooks)) {
2962
- return { hooksPath: params.hooksPath, removed: false, changed: false };
2963
- }
2964
- const removedLegacy = removeDefinitionHooksFromEvent(hooks, PRE_TOOL_USE_EVENT, params.definition);
2965
- const removedCurrent = removeDefinitionHooksFromEvent(hooks, POST_TOOL_USE_EVENT, params.definition);
2966
- const removed = removedLegacy || removedCurrent;
2967
- if (!removed) {
2968
- return { hooksPath: params.hooksPath, removed: false, changed: false };
2969
- }
2970
- writeJsonRecord(params.hooksPath, config);
2971
- return { hooksPath: params.hooksPath, removed: true, changed: true };
2972
- };
2973
- var uninstallContextPromotionHook = (hooksPath) => uninstallCodexHook({
2974
- hooksPath,
2975
- definition: CONTEXT_PROMOTION_CODEX_HOOK
2976
- });
2977
-
2978
3830
  // src/lib/paths.ts
2979
- import { join as join13 } from "path";
2980
- var resolveSkillsDir = () => join13(COMPILER_DATA_DIR, "skills");
2981
- var resolveSubagentsDir = () => join13(COMPILER_DATA_DIR, "subagents");
2982
- var resolvePacksDir = () => join13(COMPILER_DATA_DIR, "packs");
2983
- var resolveIntegrationsDir = () => join13(COMPILER_DATA_DIR, "integrations");
3831
+ import { join as join14 } from "path";
3832
+ var resolveSkillsDir = () => join14(COMPILER_DATA_DIR, "skills");
3833
+ var resolveSubagentsDir = () => join14(COMPILER_DATA_DIR, "subagents");
3834
+ var resolvePacksDir = () => join14(COMPILER_DATA_DIR, "packs");
3835
+ var resolveIntegrationsDir = () => join14(COMPILER_DATA_DIR, "integrations");
2984
3836
  var resolveBasePath = () => process.cwd();
2985
3837
  var resolveUserBasePath = () => {
2986
3838
  const userBasePath = process.env.AI_OPS_HOME ?? process.env.HOME;
@@ -3228,6 +4080,43 @@ ${result.notFound.map((file) => ` ${file}`).join("\n")}`);
3228
4080
  p6.outro("ai-ops uninstall \uC644\uB8CC");
3229
4081
  };
3230
4082
 
4083
+ // src/commands/studio.ts
4084
+ import { join as join15 } from "path";
4085
+ var resolveOptionalUserBasePath = () => process.env.AI_OPS_HOME ?? process.env.HOME ?? null;
4086
+ var resolveOptionalCodexHomePath = () => {
4087
+ if (process.env.CODEX_HOME && process.env.CODEX_HOME.length > 0) {
4088
+ return process.env.CODEX_HOME;
4089
+ }
4090
+ if (process.env.HOME && process.env.HOME.length > 0) {
4091
+ return join15(process.env.HOME, ".codex");
4092
+ }
4093
+ return null;
4094
+ };
4095
+ var reportStudioSnapshotError = (error) => {
4096
+ const message = error instanceof Error ? error.message : "unknown error";
4097
+ process.stderr.write(`[studio-snapshot] ${message}
4098
+ `);
4099
+ process.exitCode = 1;
4100
+ };
4101
+ var studioSnapshotCommand = async (opts) => {
4102
+ if (opts.json !== true) {
4103
+ process.stderr.write("[studio-snapshot] --json is required\n");
4104
+ process.exitCode = 1;
4105
+ return;
4106
+ }
4107
+ try {
4108
+ const snapshot = buildStudioSnapshot({
4109
+ basePath: resolveBasePath(),
4110
+ userBasePath: resolveOptionalUserBasePath(),
4111
+ codexHomePath: resolveOptionalCodexHomePath()
4112
+ });
4113
+ process.stdout.write(`${JSON.stringify(snapshot, null, 2)}
4114
+ `);
4115
+ } catch (error) {
4116
+ reportStudioSnapshotError(error);
4117
+ }
4118
+ };
4119
+
3231
4120
  // src/commands/skill.ts
3232
4121
  import * as p7 from "@clack/prompts";
3233
4122
  import { rmSync as rmSync5 } from "fs";
@@ -3264,17 +4153,17 @@ var findInstalledSkill = (installedSkills, skillId) => {
3264
4153
  };
3265
4154
 
3266
4155
  // src/lib/skill-install.ts
3267
- import { existsSync as existsSync6, mkdirSync as mkdirSync8, rmSync as rmSync4, writeFileSync as writeFileSync8 } from "fs";
3268
- import { dirname as dirname10, resolve as resolve9 } from "path";
4156
+ import { existsSync as existsSync7, mkdirSync as mkdirSync8, rmSync as rmSync4, writeFileSync as writeFileSync8 } from "fs";
4157
+ import { dirname as dirname10, resolve as resolve10 } from "path";
3269
4158
  var installSkillPackages = (basePath, packages) => {
3270
4159
  const writtenRoots = [];
3271
4160
  for (const skillPackage of packages) {
3272
- const absRoot = resolve9(basePath, skillPackage.rootDir);
3273
- if (existsSync6(absRoot)) {
4161
+ const absRoot = resolve10(basePath, skillPackage.rootDir);
4162
+ if (existsSync7(absRoot)) {
3274
4163
  rmSync4(absRoot, { recursive: true, force: true });
3275
4164
  }
3276
4165
  for (const file of skillPackage.files) {
3277
- const absPath = resolve9(basePath, file.relativePath);
4166
+ const absPath = resolve10(basePath, file.relativePath);
3278
4167
  mkdirSync8(dirname10(absPath), { recursive: true });
3279
4168
  writeFileSync8(absPath, file.content + "\n", "utf-8");
3280
4169
  }
@@ -3285,8 +4174,8 @@ var installSkillPackages = (basePath, packages) => {
3285
4174
  var removeDirectories = (basePath, relativeDirs) => {
3286
4175
  const removed = [];
3287
4176
  for (const relativeDir of relativeDirs) {
3288
- const absPath = resolve9(basePath, relativeDir);
3289
- if (!existsSync6(absPath)) continue;
4177
+ const absPath = resolve10(basePath, relativeDir);
4178
+ if (!existsSync7(absPath)) continue;
3290
4179
  rmSync4(absPath, { recursive: true, force: true });
3291
4180
  removed.push(relativeDir);
3292
4181
  }
@@ -3465,14 +4354,14 @@ var skillUninstallCommand = async (skillId) => {
3465
4354
 
3466
4355
  // src/commands/subagent.ts
3467
4356
  import * as p8 from "@clack/prompts";
3468
- import { existsSync as existsSync8, rmSync as rmSync7 } from "fs";
4357
+ import { existsSync as existsSync9, rmSync as rmSync7 } from "fs";
3469
4358
 
3470
4359
  // src/lib/subagent-install.ts
3471
- import { existsSync as existsSync7, mkdirSync as mkdirSync9, rmSync as rmSync6, writeFileSync as writeFileSync9 } from "fs";
3472
- import { dirname as dirname11, isAbsolute as isAbsolute3, relative as relative3, resolve as resolve10 } from "path";
4360
+ import { existsSync as existsSync8, mkdirSync as mkdirSync9, rmSync as rmSync6, writeFileSync as writeFileSync9 } from "fs";
4361
+ import { dirname as dirname11, isAbsolute as isAbsolute3, relative as relative3, resolve as resolve11 } from "path";
3473
4362
  var resolveInsideBasePath = (basePath, relativePath) => {
3474
- const absBasePath = resolve10(basePath);
3475
- const absPath = resolve10(absBasePath, relativePath);
4363
+ const absBasePath = resolve11(basePath);
4364
+ const absPath = resolve11(absBasePath, relativePath);
3476
4365
  const fromBase = relative3(absBasePath, absPath);
3477
4366
  if (fromBase.length === 0 || fromBase.startsWith("..") || isAbsolute3(fromBase)) {
3478
4367
  throw new Error(`Subagent path escapes AI_OPS_HOME: ${relativePath}`);
@@ -3484,7 +4373,7 @@ var installSubagentPackages = (basePath, packages) => {
3484
4373
  for (const subagentPackage of packages) {
3485
4374
  for (const file of subagentPackage.files) {
3486
4375
  const absPath = resolveInsideBasePath(basePath, file.relativePath);
3487
- if (existsSync7(absPath)) {
4376
+ if (existsSync8(absPath)) {
3488
4377
  rmSync6(absPath, { recursive: true, force: true });
3489
4378
  }
3490
4379
  mkdirSync9(dirname11(absPath), { recursive: true });
@@ -3498,7 +4387,7 @@ var removeSubagentFiles = (basePath, relativePaths) => {
3498
4387
  const removed = [];
3499
4388
  for (const relativePath of relativePaths) {
3500
4389
  const absPath = resolveInsideBasePath(basePath, relativePath);
3501
- if (!existsSync7(absPath)) continue;
4390
+ if (!existsSync8(absPath)) continue;
3502
4391
  rmSync6(absPath, { recursive: true, force: true });
3503
4392
  removed.push(relativePath);
3504
4393
  }
@@ -3560,7 +4449,7 @@ var writeUserSubagentState = (params) => {
3560
4449
  };
3561
4450
  var readInstalledSubagents = (basePath) => readSubagentManifest(resolveSubagentManifestPath(basePath))?.subagents ?? [];
3562
4451
  var warnMissingSkills = (requiredSkills) => {
3563
- const missing = requiredSkills.filter((skill) => !existsSync8(skill.path));
4452
+ const missing = requiredSkills.filter((skill) => !existsSync9(skill.path));
3564
4453
  if (missing.length === 0) {
3565
4454
  return;
3566
4455
  }
@@ -3871,13 +4760,13 @@ var parseMax = (max) => {
3871
4760
  }
3872
4761
  return parsed;
3873
4762
  };
3874
- var readStdin = async () => new Promise((resolve11, reject) => {
4763
+ var readStdin = async () => new Promise((resolve12, reject) => {
3875
4764
  let raw = "";
3876
4765
  process.stdin.setEncoding("utf-8");
3877
4766
  process.stdin.on("data", (chunk) => {
3878
4767
  raw += chunk;
3879
4768
  });
3880
- process.stdin.on("end", () => resolve11(raw));
4769
+ process.stdin.on("end", () => resolve12(raw));
3881
4770
  process.stdin.on("error", reject);
3882
4771
  });
3883
4772
  var reportContextPromotionError = (error) => {
@@ -3978,8 +4867,8 @@ var contextPromotionPostToolUseHookCommand = async () => {
3978
4867
 
3979
4868
  // src/commands/codex-hook.ts
3980
4869
  import * as p11 from "@clack/prompts";
3981
- import { existsSync as existsSync9 } from "fs";
3982
- import { join as join14 } from "path";
4870
+ import { existsSync as existsSync10 } from "fs";
4871
+ import { join as join16 } from "path";
3983
4872
  var CONTEXT_PROMOTION_REVIEW_SKILL_ID = "context-promotion-review";
3984
4873
  var resolveCodexHomePath = () => {
3985
4874
  const codexHome = process.env.CODEX_HOME;
@@ -4017,7 +4906,7 @@ var resolveContextPromotionReviewSkill = () => {
4017
4906
  };
4018
4907
  var hasInstalledContextPromotionReviewSkill = (basePath) => {
4019
4908
  const installedSkill = findInstalledSkill(readInstalledSkills2(basePath), CONTEXT_PROMOTION_REVIEW_SKILL_ID);
4020
- return installedSkill?.tools.includes(SKILL_TOOL.CODEX) === true && existsSync9(join14(basePath, ".agents/skills/context-promotion-review/SKILL.md"));
4909
+ return installedSkill?.tools.includes(SKILL_TOOL.CODEX) === true && existsSync10(join16(basePath, ".agents/skills/context-promotion-review/SKILL.md"));
4021
4910
  };
4022
4911
  var ensureContextPromotionReviewSkill = (basePath) => {
4023
4912
  const skill = resolveContextPromotionReviewSkill();
@@ -4031,7 +4920,7 @@ var ensureContextPromotionReviewSkill = (basePath) => {
4031
4920
  skill,
4032
4921
  requestedTools
4033
4922
  });
4034
- const alreadyInstalled = existingInstalledSkill?.sourceHash === installedSkill.sourceHash && existingInstalledSkill.tools.includes(SKILL_TOOL.CODEX) && existsSync9(join14(basePath, ".agents/skills/context-promotion-review/SKILL.md"));
4923
+ const alreadyInstalled = existingInstalledSkill?.sourceHash === installedSkill.sourceHash && existingInstalledSkill.tools.includes(SKILL_TOOL.CODEX) && existsSync10(join16(basePath, ".agents/skills/context-promotion-review/SKILL.md"));
4035
4924
  if (alreadyInstalled) {
4036
4925
  return { changed: false, installedSkill };
4037
4926
  }
@@ -4094,8 +4983,8 @@ var codexHookUninstallCommand = async (hookId) => {
4094
4983
 
4095
4984
  // src/commands/integration.ts
4096
4985
  import * as p12 from "@clack/prompts";
4097
- import { existsSync as existsSync10, rmSync as rmSync8 } from "fs";
4098
- import { join as join15 } from "path";
4986
+ import { existsSync as existsSync11, rmSync as rmSync8 } from "fs";
4987
+ import { join as join17 } from "path";
4099
4988
  var CODEX_HOOK_DEFINITIONS = [CONTEXT_PROMOTION_CODEX_HOOK, PC_CODEX_HOOK];
4100
4989
  var resolveCodexHomePath2 = () => {
4101
4990
  const codexHome = process.env.CODEX_HOME;
@@ -4168,13 +5057,13 @@ var reportIntegrationError = (error) => {
4168
5057
  p12.log.error(message);
4169
5058
  process.exitCode = 1;
4170
5059
  };
4171
- var readStdin2 = async () => new Promise((resolve11, reject) => {
5060
+ var readStdin2 = async () => new Promise((resolve12, reject) => {
4172
5061
  let raw = "";
4173
5062
  process.stdin.setEncoding("utf-8");
4174
5063
  process.stdin.on("data", (chunk) => {
4175
5064
  raw += chunk;
4176
5065
  });
4177
- process.stdin.on("end", () => resolve11(raw));
5066
+ process.stdin.on("end", () => resolve12(raw));
4178
5067
  process.stdin.on("error", reject);
4179
5068
  });
4180
5069
  var readInstalledSkills3 = (basePath) => (readSkillRegistry(resolveSkillRegistryPath(basePath))?.skills ?? []).map((installedSkill) => ({
@@ -4190,7 +5079,7 @@ var resolveSkillById2 = (skillId) => {
4190
5079
  };
4191
5080
  var hasInstalledCodexSkill = (params) => {
4192
5081
  const installedSkill = findInstalledSkill(readInstalledSkills3(params.basePath), params.skillId);
4193
- return installedSkill?.tools.includes(SKILL_TOOL.CODEX) === true && existsSync10(join15(params.basePath, ".agents/skills", params.skillId, "SKILL.md"));
5082
+ return installedSkill?.tools.includes(SKILL_TOOL.CODEX) === true && existsSync11(join17(params.basePath, ".agents/skills", params.skillId, "SKILL.md"));
4194
5083
  };
4195
5084
  var writeUserSkillState2 = (params) => {
4196
5085
  const registryPath = resolveSkillRegistryPath(params.basePath);
@@ -4218,7 +5107,7 @@ var ensureSkillComponent = (params) => {
4218
5107
  skill,
4219
5108
  requestedTools
4220
5109
  });
4221
- const alreadyCurrent = existingInstalledSkill?.sourceHash === installedSkill.sourceHash && existingInstalledSkill.tools.includes(SKILL_TOOL.CODEX) && existsSync10(join15(params.basePath, ".agents/skills", params.skillId, "SKILL.md"));
5110
+ const alreadyCurrent = existingInstalledSkill?.sourceHash === installedSkill.sourceHash && existingInstalledSkill.tools.includes(SKILL_TOOL.CODEX) && existsSync11(join17(params.basePath, ".agents/skills", params.skillId, "SKILL.md"));
4222
5111
  if (alreadyCurrent) {
4223
5112
  return {
4224
5113
  type: INTEGRATION_COMPONENT_TYPE.SKILL,
@@ -4480,6 +5369,8 @@ program.command("update").description("project operating layer \uAC31\uC2E0").op
4480
5369
  program.command("diff").description("project operating layer drift \uBE44\uAD50").action(() => diffCommand());
4481
5370
  program.command("audit").description("project operating layer \uC0C1\uD0DC \uAC80\uC0AC").action(() => auditCommand());
4482
5371
  program.command("uninstall").description("project operating layer \uC81C\uAC70").option("--yes", "\uD655\uC778 \uD504\uB86C\uD504\uD2B8 \uC5C6\uC774 \uC81C\uAC70", false).action((opts) => uninstallCommand(opts));
5372
+ var studioCommand = program.command("studio").description("ai-ops Studio read-only helpers");
5373
+ studioCommand.command("snapshot").description("Studio read-only snapshot JSON \uC0DD\uC131").requiredOption("--json", "JSON\uC73C\uB85C \uCD9C\uB825").action((opts) => studioSnapshotCommand(opts));
4483
5374
  var skillCommand = program.command("skill").description("\uC5D0\uC774\uC804\uD2B8 skill \uC124\uCE58/\uC870\uD68C/\uAC31\uC2E0");
4484
5375
  var applySkillInstallOptions = (command) => command.option("--tool <tool...>", "\uB300\uC0C1 \uB3C4\uAD6C \uC9C0\uC815");
4485
5376
  skillCommand.command("list").description("\uC0AC\uC6A9 \uAC00\uB2A5\uD55C skill \uBAA9\uB85D").action(() => skillListCommand());