ai-ops-cli 1.0.3 → 1.1.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
@@ -1819,11 +1819,714 @@ var diffProjectLayerPack = (params) => {
1819
1819
  // src/core/uninstall-plan.ts
1820
1820
  import { join as join12 } from "path";
1821
1821
 
1822
+ // src/core/context-promotion.ts
1823
+ import { createHash as createHash2 } from "crypto";
1824
+ 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";
1828
+ var CONTEXT_PROMOTION_DECISION = {
1829
+ PROMOTED: "promoted",
1830
+ NO_PROMOTION: "no-promotion"
1831
+ };
1832
+ var CONTEXT_PROMOTION_SCOPE = {
1833
+ CORE: "core",
1834
+ PROJECT_LOCAL: "project-local",
1835
+ GLOBAL: "global"
1836
+ };
1837
+ var ContextPromotionDecisionSchema = z12.union([
1838
+ z12.literal(CONTEXT_PROMOTION_DECISION.PROMOTED),
1839
+ z12.literal(CONTEXT_PROMOTION_DECISION.NO_PROMOTION)
1840
+ ]);
1841
+ var ContextPromotionScopeSchema = z12.union([
1842
+ z12.literal(CONTEXT_PROMOTION_SCOPE.CORE),
1843
+ z12.literal(CONTEXT_PROMOTION_SCOPE.PROJECT_LOCAL),
1844
+ z12.literal(CONTEXT_PROMOTION_SCOPE.GLOBAL)
1845
+ ]);
1846
+ var ContextPromotionReceiptSchema = z12.object({
1847
+ fingerprint: z12.string().regex(/^[a-f0-9]{16}$/),
1848
+ commitHash: z12.string().regex(/^(NO_HEAD|[a-f0-9]{40})$/).optional(),
1849
+ decision: ContextPromotionDecisionSchema,
1850
+ scopes: z12.array(ContextPromotionScopeSchema),
1851
+ targets: z12.array(z12.string().min(1)),
1852
+ summary: z12.string().min(1),
1853
+ resolvedAt: z12.string().min(1)
1854
+ }).strict();
1855
+ var ContextPromotionReceiptIndexSchema = z12.object({
1856
+ schemaVersion: z12.literal(1),
1857
+ kind: z12.literal("context-promotion-receipts"),
1858
+ projectKey: z12.string().regex(/^[a-f0-9]{12}$/),
1859
+ projectRoot: z12.string().min(1),
1860
+ receipts: z12.array(ContextPromotionReceiptSchema)
1861
+ }).strict();
1862
+ var RECEIPT_INDEX_FILENAME = "receipts-index.json";
1863
+ var DEFAULT_PRUNE_MAX = 50;
1864
+ var hashHex = (parts, length) => createHash2("sha256").update(parts.join("\0")).digest("hex").slice(0, length);
1865
+ var buildContextPromotionProjectKey = (gitRoot) => hashHex([resolve7(gitRoot)], 12);
1866
+ var runGit = (cwd, args) => execFileSync("git", [...args], {
1867
+ cwd,
1868
+ encoding: "utf-8",
1869
+ maxBuffer: 20 * 1024 * 1024,
1870
+ stdio: ["ignore", "pipe", "ignore"]
1871
+ });
1872
+ var resolveContextPromotionGitRoot = (cwd) => {
1873
+ try {
1874
+ return runGit(cwd, ["rev-parse", "--show-toplevel"]).trim();
1875
+ } catch {
1876
+ return null;
1877
+ }
1878
+ };
1879
+ var readGitHead = (gitRoot) => {
1880
+ try {
1881
+ return runGit(gitRoot, ["rev-parse", "--verify", "HEAD"]).trim();
1882
+ } catch {
1883
+ return "NO_HEAD";
1884
+ }
1885
+ };
1886
+ var readUntrackedFingerprintParts = (gitRoot) => {
1887
+ const raw = runGit(gitRoot, ["ls-files", "--others", "--exclude-standard", "-z"]);
1888
+ const paths = raw.split("\0").filter((path) => path.length > 0).sort((a, b) => a.localeCompare(b));
1889
+ return paths.map((relativePath) => {
1890
+ const absolutePath = join13(gitRoot, relativePath);
1891
+ try {
1892
+ const stat = statSync(absolutePath);
1893
+ if (!stat.isFile()) {
1894
+ return `${relativePath}:non-file`;
1895
+ }
1896
+ const content = readFileSync8(absolutePath);
1897
+ return `${relativePath}:${createHash2("sha256").update(content).digest("hex")}`;
1898
+ } catch {
1899
+ throw new Error(`Unable to read untracked path for context promotion fingerprint: ${relativePath}`);
1900
+ }
1901
+ });
1902
+ };
1903
+ var readTrackedWorkingTreeFingerprintParts = (gitRoot) => {
1904
+ const rawDiff = runGit(gitRoot, ["diff", "--raw", "-z"]);
1905
+ const rawNames = runGit(gitRoot, ["diff", "--name-only", "-z"]);
1906
+ const paths = rawNames.split("\0").filter((path) => path.length > 0).sort((a, b) => a.localeCompare(b));
1907
+ return [
1908
+ `raw:${rawDiff}`,
1909
+ ...paths.map((relativePath) => {
1910
+ const absolutePath = join13(gitRoot, relativePath);
1911
+ if (!existsSync3(absolutePath)) {
1912
+ return `${relativePath}:deleted`;
1913
+ }
1914
+ const stat = statSync(absolutePath);
1915
+ if (!stat.isFile()) {
1916
+ return `${relativePath}:non-file`;
1917
+ }
1918
+ const content = readFileSync8(absolutePath);
1919
+ return `${relativePath}:${createHash2("sha256").update(content).digest("hex")}`;
1920
+ })
1921
+ ];
1922
+ };
1923
+ var readGitIndexFingerprintParts = (gitRoot) => [
1924
+ `index:${runGit(gitRoot, ["ls-files", "-s", "-z"])}`,
1925
+ `staged-raw:${runGit(gitRoot, ["diff", "--cached", "--raw", "-z"])}`
1926
+ ];
1927
+ var computeContextPromotionFingerprint = (gitRoot) => hashHex(
1928
+ [
1929
+ `head:${readGitHead(gitRoot)}`,
1930
+ ...readGitIndexFingerprintParts(gitRoot),
1931
+ ...readTrackedWorkingTreeFingerprintParts(gitRoot).map((part) => `tracked-working-tree:${part}`),
1932
+ ...readUntrackedFingerprintParts(gitRoot).map((part) => `untracked:${part}`)
1933
+ ],
1934
+ 16
1935
+ );
1936
+ var resolveContextPromotionReceiptIndexPath = (params) => join13(
1937
+ params.userBasePath,
1938
+ ".ai-ops",
1939
+ "context-promotion",
1940
+ "projects",
1941
+ params.projectKey,
1942
+ RECEIPT_INDEX_FILENAME
1943
+ );
1944
+ var parseContextPromotionReceiptIndex = (json) => ContextPromotionReceiptIndexSchema.parse(JSON.parse(json));
1945
+ var serializeContextPromotionReceiptIndex = (index) => JSON.stringify(index, null, 2) + "\n";
1946
+ var readContextPromotionReceiptIndex = (indexPath) => {
1947
+ try {
1948
+ return parseContextPromotionReceiptIndex(readFileSync8(indexPath, "utf-8"));
1949
+ } catch {
1950
+ return null;
1951
+ }
1952
+ };
1953
+ var writeContextPromotionReceiptIndex = (indexPath, index) => {
1954
+ mkdirSync6(dirname8(indexPath), { recursive: true });
1955
+ writeFileSync6(indexPath, serializeContextPromotionReceiptIndex(index), "utf-8");
1956
+ };
1957
+ var buildEmptyReceiptIndex = (params) => ({
1958
+ schemaVersion: 1,
1959
+ kind: "context-promotion-receipts",
1960
+ projectKey: params.projectKey,
1961
+ projectRoot: params.projectRoot,
1962
+ receipts: []
1963
+ });
1964
+ var sortReceiptsByResolvedAtDesc = (receipts) => [...receipts].sort((a, b) => b.resolvedAt.localeCompare(a.resolvedAt));
1965
+ var findContextPromotionReceipt = (params) => {
1966
+ const receipts = params.index?.receipts ?? [];
1967
+ return receipts.find((receipt) => receipt.commitHash === params.commitHash) ?? receipts.find((receipt) => receipt.fingerprint === params.fingerprint) ?? null;
1968
+ };
1969
+ var upsertContextPromotionReceipt = (params) => {
1970
+ const previous = readContextPromotionReceiptIndex(params.indexPath);
1971
+ const index = previous?.projectKey === params.projectKey ? previous : buildEmptyReceiptIndex({ projectKey: params.projectKey, projectRoot: params.projectRoot });
1972
+ const remaining = index.receipts.filter((receipt) => {
1973
+ if (receipt.fingerprint === params.receipt.fingerprint) {
1974
+ return false;
1975
+ }
1976
+ if (params.receipt.commitHash && receipt.commitHash === params.receipt.commitHash) {
1977
+ return false;
1978
+ }
1979
+ return true;
1980
+ });
1981
+ const maxReceipts = params.maxReceipts ?? DEFAULT_PRUNE_MAX;
1982
+ const receipts = sortReceiptsByResolvedAtDesc([params.receipt, ...remaining]).slice(0, maxReceipts);
1983
+ const nextIndex = {
1984
+ ...index,
1985
+ projectRoot: params.projectRoot,
1986
+ receipts
1987
+ };
1988
+ writeContextPromotionReceiptIndex(params.indexPath, nextIndex);
1989
+ return nextIndex;
1990
+ };
1991
+ var pruneContextPromotionReceipts = (params) => {
1992
+ const index = readContextPromotionReceiptIndex(params.indexPath);
1993
+ if (!index) {
1994
+ return null;
1995
+ }
1996
+ const nextIndex = {
1997
+ ...index,
1998
+ receipts: sortReceiptsByResolvedAtDesc(index.receipts).slice(0, params.maxReceipts)
1999
+ };
2000
+ writeContextPromotionReceiptIndex(params.indexPath, nextIndex);
2001
+ return nextIndex;
2002
+ };
2003
+ var hasContextPromotionLayer = (gitRoot) => existsSync3(join13(gitRoot, PROJECT_LAYER_CONTEXT_INDEX_RELATIVE_PATH));
2004
+ var getContextPromotionStatus = (params) => {
2005
+ const cwd = resolve7(params.cwd);
2006
+ const gitRoot = resolveContextPromotionGitRoot(cwd);
2007
+ if (!gitRoot) {
2008
+ return {
2009
+ cwd,
2010
+ gitRoot: null,
2011
+ hasContextLayer: false,
2012
+ projectKey: null,
2013
+ commitHash: null,
2014
+ fingerprint: null,
2015
+ receipt: null,
2016
+ receiptIndexPath: null
2017
+ };
2018
+ }
2019
+ const hasContextLayer = hasContextPromotionLayer(gitRoot);
2020
+ const projectKey = buildContextPromotionProjectKey(gitRoot);
2021
+ const commitHash = readGitHead(gitRoot);
2022
+ const receiptIndexPath = resolveContextPromotionReceiptIndexPath({
2023
+ userBasePath: params.userBasePath,
2024
+ projectKey
2025
+ });
2026
+ if (!hasContextLayer) {
2027
+ return {
2028
+ cwd,
2029
+ gitRoot,
2030
+ hasContextLayer,
2031
+ projectKey,
2032
+ commitHash,
2033
+ fingerprint: null,
2034
+ receipt: null,
2035
+ receiptIndexPath
2036
+ };
2037
+ }
2038
+ const fingerprint = computeContextPromotionFingerprint(gitRoot);
2039
+ const index = readContextPromotionReceiptIndex(receiptIndexPath);
2040
+ return {
2041
+ cwd,
2042
+ gitRoot,
2043
+ hasContextLayer,
2044
+ projectKey,
2045
+ commitHash,
2046
+ fingerprint,
2047
+ receipt: findContextPromotionReceipt({ index, fingerprint, commitHash }),
2048
+ receiptIndexPath
2049
+ };
2050
+ };
2051
+ var buildContextPromotionReceipt = (params) => {
2052
+ const summary = params.input.summary.trim();
2053
+ if (summary.length === 0) {
2054
+ throw new Error("summary is required");
2055
+ }
2056
+ if (params.input.decision === CONTEXT_PROMOTION_DECISION.PROMOTED && params.input.scopes.length === 0) {
2057
+ throw new Error("at least one scope is required for promoted decisions");
2058
+ }
2059
+ return ContextPromotionReceiptSchema.parse({
2060
+ fingerprint: params.fingerprint,
2061
+ commitHash: params.commitHash,
2062
+ decision: params.input.decision,
2063
+ scopes: [...params.input.scopes],
2064
+ targets: [...params.input.targets],
2065
+ summary,
2066
+ resolvedAt: params.resolvedAt ?? (/* @__PURE__ */ new Date()).toISOString()
2067
+ });
2068
+ };
2069
+ var resolveContextPromotion = (params) => {
2070
+ const status = getContextPromotionStatus({ cwd: params.cwd, userBasePath: params.userBasePath });
2071
+ if (!status.gitRoot || !status.projectKey || !status.commitHash || !status.fingerprint || !status.receiptIndexPath) {
2072
+ throw new Error("git repository is required for context promotion receipts");
2073
+ }
2074
+ if (!status.hasContextLayer) {
2075
+ throw new Error("ai-ops context layer is required for context promotion receipts");
2076
+ }
2077
+ const receipt = buildContextPromotionReceipt({
2078
+ fingerprint: status.fingerprint,
2079
+ commitHash: status.commitHash,
2080
+ input: params.input
2081
+ });
2082
+ upsertContextPromotionReceipt({
2083
+ indexPath: status.receiptIndexPath,
2084
+ projectKey: status.projectKey,
2085
+ projectRoot: status.gitRoot,
2086
+ receipt
2087
+ });
2088
+ return getContextPromotionStatus({ cwd: params.cwd, userBasePath: params.userBasePath });
2089
+ };
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
+ var buildContextPromotionReviewPrompt = (status) => {
2228
+ const projectRoot = status.gitRoot ?? status.cwd;
2229
+ const cdCommand = `cd ${JSON.stringify(projectRoot)}`;
2230
+ return [
2231
+ "Context Promotion Review should run for the completed work commit.",
2232
+ "",
2233
+ `Project root: ${projectRoot}`,
2234
+ "This project root is authoritative for this review.",
2235
+ "",
2236
+ "Use the `context-promotion-review` skill to review the just-created HEAD commit for reusable operating knowledge.",
2237
+ "",
2238
+ "Scope boundary:",
2239
+ `- Before inspecting files, anchor shell work in the project root above. If needed, run \`${cdCommand}\` first.`,
2240
+ "- Do not inspect other repositories, parent directories, or earlier conversation workspaces.",
2241
+ "- Do not search the web or external documentation for this review.",
2242
+ "- 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.",
2243
+ "- Use only the just-created `HEAD` commit, this conversation, and files under the project root.",
2244
+ "",
2245
+ "Review requirements:",
2246
+ "- Do not amend, rewrite, or mix changes into the work commit.",
2247
+ "- Inspect the completed commit before deciding: run `git show --stat HEAD`, `git show --name-only HEAD`, and `git show HEAD` when detail is needed.",
2248
+ "- Cross-check existing `AGENTS.md`, `docs/agent/*`, `docs/docs-status.md`, and `.ai-ops/context-layer.json` first.",
2249
+ "- Classify candidates as `core`, `project-local`, `global`, `already-covered`, or `no-promotion`.",
2250
+ "- Ask the user before editing any file.",
2251
+ "- If promotion is approved, edit only the approved context/global files, then stop for user inspection without committing.",
2252
+ '- After approved updates or a no-promotion/already-covered decision, run `ai-ops context-promotion resolve --decision <promoted|no-promotion> --summary "<summary>"` with any approved `--scope` and `--target` values.',
2253
+ "- Re-run `ai-ops context-promotion status` and confirm a receipt exists for the current HEAD.",
2254
+ "",
2255
+ `Project: ${projectRoot}`,
2256
+ `HEAD: ${status.commitHash ?? "unknown"}`,
2257
+ `Fingerprint: ${status.fingerprint ?? "unknown"}`
2258
+ ].join("\n");
2259
+ };
2260
+ var buildContextPromotionStatusFailurePrompt = (cwd, error) => {
2261
+ const message = error instanceof Error ? error.message : "unknown error";
2262
+ return [
2263
+ "Context Promotion Review could not inspect the completed work commit.",
2264
+ "",
2265
+ "The work command has already finished; do not amend or rewrite it for this hook.",
2266
+ "Do not search the web or inspect another repository to repair this hook review.",
2267
+ "",
2268
+ "Run `ai-ops context-promotion status` to inspect the failure, then decide whether a manual promotion review is needed.",
2269
+ "",
2270
+ `Project cwd: ${cwd}`,
2271
+ `Error: ${message}`
2272
+ ].join("\n");
2273
+ };
2274
+ var buildPostToolUseOutput = (prompt) => ({
2275
+ decision: "block",
2276
+ reason: prompt,
2277
+ hookSpecificOutput: {
2278
+ hookEventName: "PostToolUse",
2279
+ additionalContext: prompt
2280
+ }
2281
+ });
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") {
2287
+ return value;
2288
+ }
2289
+ }
2290
+ return null;
2291
+ };
2292
+ var booleanField = (record, keys) => {
2293
+ for (const key of keys) {
2294
+ const value = record[key];
2295
+ if (typeof value === "boolean") {
2296
+ return value;
2297
+ }
2298
+ }
2299
+ return null;
2300
+ };
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;
2325
+ }
2326
+ if (!isJsonRecord(toolResponse)) {
2327
+ return false;
2328
+ }
2329
+ const success = booleanField(toolResponse, ["success", "ok"]);
2330
+ if (success === false) {
2331
+ return true;
2332
+ }
2333
+ const exitCode = numberField(toolResponse, ["exit_code", "exitCode", "status", "code"]);
2334
+ if (exitCode !== null && exitCode !== 0) {
2335
+ return true;
2336
+ }
2337
+ return recordStringFieldsIndicateGitCommitFailure(toolResponse);
2338
+ };
2339
+ var evaluateContextPromotionPostToolUseHook = (params) => {
2340
+ const hookInput = ToolUseHookInputSchema.safeParse(params.hookInput);
2341
+ if (!hookInput.success) {
2342
+ return null;
2343
+ }
2344
+ if (hookInput.data.hook_event_name !== "PostToolUse" || hookInput.data.tool_name !== "Bash") {
2345
+ return null;
2346
+ }
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)) {
2350
+ return null;
2351
+ }
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));
2360
+ }
2361
+ if (!status.hasContextLayer || status.receipt) {
2362
+ return null;
2363
+ }
2364
+ return buildPostToolUseOutput(buildContextPromotionReviewPrompt(status));
2365
+ };
2366
+
2367
+ // 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";
2370
+ var CONTEXT_PROMOTION_HOOK_ID = "context-promotion";
2371
+ var CONTEXT_PROMOTION_HOOK_COMMAND_MARKER = "context-promotion hook post-tool-use";
2372
+ var CONTEXT_PROMOTION_LEGACY_HOOK_COMMAND_MARKER = "context-promotion hook pre-tool-use";
2373
+ var CONTEXT_PROMOTION_DEFAULT_HOOK_COMMAND = `ai-ops ${CONTEXT_PROMOTION_HOOK_COMMAND_MARKER}`;
2374
+ var PRE_TOOL_USE_EVENT = "PreToolUse";
2375
+ var POST_TOOL_USE_EVENT = "PostToolUse";
2376
+ var BASH_MATCHER = "^Bash$";
2377
+ var isJsonRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2378
+ var readJsonRecord = (filePath) => {
2379
+ if (!existsSync4(filePath)) {
2380
+ return {};
2381
+ }
2382
+ const parsed = JSON.parse(readFileSync9(filePath, "utf-8"));
2383
+ if (!isJsonRecord2(parsed)) {
2384
+ throw new Error("hooks.json must contain a JSON object");
2385
+ }
2386
+ return parsed;
2387
+ };
2388
+ var writeJsonRecord = (filePath, value) => {
2389
+ mkdirSync7(dirname9(filePath), { recursive: true });
2390
+ writeFileSync7(filePath, JSON.stringify(value, null, 2) + "\n", "utf-8");
2391
+ };
2392
+ var getOrCreateRecord = (record, key) => {
2393
+ const existing = record[key];
2394
+ if (isJsonRecord2(existing)) {
2395
+ return existing;
2396
+ }
2397
+ const next = {};
2398
+ record[key] = next;
2399
+ return next;
2400
+ };
2401
+ var getArray = (record, key) => {
2402
+ const existing = record[key];
2403
+ return Array.isArray(existing) ? existing : [];
2404
+ };
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));
2406
+ 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) => {
2410
+ if (!isJsonRecord2(group)) {
2411
+ return count;
2412
+ }
2413
+ return count + getArray(group, "hooks").filter(handlerMatchesContextPromotion).length;
2414
+ }, 0);
2415
+ var configHasContextPromotionHook = (config) => {
2416
+ const hooks = config.hooks;
2417
+ if (!isJsonRecord2(hooks)) {
2418
+ return false;
2419
+ }
2420
+ return getArray(hooks, POST_TOOL_USE_EVENT).some(groupHasContextPromotionHook);
2421
+ };
2422
+ var configHasOnlyCurrentContextPromotionHook = (config, command) => {
2423
+ const hooks = config.hooks;
2424
+ if (!isJsonRecord2(hooks)) {
2425
+ return false;
2426
+ }
2427
+ const hasLegacy = getArray(hooks, PRE_TOOL_USE_EVENT).some(groupHasContextPromotionHook);
2428
+ const postGroups = getArray(hooks, POST_TOOL_USE_EVENT);
2429
+ const hasCurrent = postGroups.some((group) => groupHasCurrentContextPromotionHook(group, command));
2430
+ return hasCurrent && !hasLegacy && countContextPromotionHandlers(postGroups) === 1;
2431
+ };
2432
+ var removeContextPromotionHooksFromEvent = (hooks, eventName) => {
2433
+ const previousGroups = getArray(hooks, eventName);
2434
+ let removed = false;
2435
+ const nextGroups = previousGroups.map((group) => {
2436
+ if (!isJsonRecord2(group)) {
2437
+ return group;
2438
+ }
2439
+ const previousHandlers = getArray(group, "hooks");
2440
+ const nextHandlers = previousHandlers.filter((handler) => {
2441
+ const matches = handlerMatchesContextPromotion(handler);
2442
+ if (matches) {
2443
+ removed = true;
2444
+ }
2445
+ return !matches;
2446
+ });
2447
+ if (nextHandlers.length === 0) {
2448
+ return null;
2449
+ }
2450
+ return {
2451
+ ...group,
2452
+ hooks: nextHandlers
2453
+ };
2454
+ }).filter((group) => group !== null);
2455
+ if (!removed) {
2456
+ return false;
2457
+ }
2458
+ if (nextGroups.length > 0) {
2459
+ hooks[eventName] = nextGroups;
2460
+ } else {
2461
+ delete hooks[eventName];
2462
+ }
2463
+ return true;
2464
+ };
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}`);
2470
+ }
2471
+ return command;
2472
+ };
2473
+ var inspectContextPromotionHook = (hooksPath) => ({
2474
+ hooksPath,
2475
+ installed: configHasContextPromotionHook(readJsonRecord(hooksPath))
2476
+ });
2477
+ var installContextPromotionHook = (params) => {
2478
+ const config = readJsonRecord(params.hooksPath);
2479
+ if (configHasOnlyCurrentContextPromotionHook(config, params.command)) {
2480
+ return {
2481
+ hooksPath: params.hooksPath,
2482
+ installed: true,
2483
+ changed: false
2484
+ };
2485
+ }
2486
+ const hooks = getOrCreateRecord(config, "hooks");
2487
+ removeContextPromotionHooksFromEvent(hooks, PRE_TOOL_USE_EVENT);
2488
+ removeContextPromotionHooksFromEvent(hooks, POST_TOOL_USE_EVENT);
2489
+ const existingGroups = getArray(hooks, POST_TOOL_USE_EVENT);
2490
+ const nextGroup = {
2491
+ matcher: BASH_MATCHER,
2492
+ hooks: [
2493
+ {
2494
+ type: "command",
2495
+ command: params.command,
2496
+ timeout: 30,
2497
+ statusMessage: "Checking context promotion review"
2498
+ }
2499
+ ]
2500
+ };
2501
+ hooks[POST_TOOL_USE_EVENT] = [...existingGroups, nextGroup];
2502
+ writeJsonRecord(params.hooksPath, config);
2503
+ return {
2504
+ hooksPath: params.hooksPath,
2505
+ installed: true,
2506
+ changed: true
2507
+ };
2508
+ };
2509
+ var uninstallContextPromotionHook = (hooksPath) => {
2510
+ const config = readJsonRecord(hooksPath);
2511
+ const hooks = config.hooks;
2512
+ if (!isJsonRecord2(hooks)) {
2513
+ return { hooksPath, removed: false, changed: false };
2514
+ }
2515
+ const removedLegacy = removeContextPromotionHooksFromEvent(hooks, PRE_TOOL_USE_EVENT);
2516
+ const removedCurrent = removeContextPromotionHooksFromEvent(hooks, POST_TOOL_USE_EVENT);
2517
+ const removed = removedLegacy || removedCurrent;
2518
+ if (!removed) {
2519
+ return { hooksPath, removed: false, changed: false };
2520
+ }
2521
+ writeJsonRecord(hooksPath, config);
2522
+ return { hooksPath, removed: true, changed: true };
2523
+ };
2524
+
1822
2525
  // src/lib/paths.ts
1823
- import { join as join13 } from "path";
1824
- var resolveSkillsDir = () => join13(COMPILER_DATA_DIR, "skills");
1825
- var resolveSubagentsDir = () => join13(COMPILER_DATA_DIR, "subagents");
1826
- var resolvePacksDir = () => join13(COMPILER_DATA_DIR, "packs");
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");
1827
2530
  var resolveBasePath = () => process.cwd();
1828
2531
  var resolveUserBasePath = () => {
1829
2532
  const userBasePath = process.env.AI_OPS_HOME ?? process.env.HOME;
@@ -2107,19 +2810,19 @@ var findInstalledSkill = (installedSkills, skillId) => {
2107
2810
  };
2108
2811
 
2109
2812
  // src/lib/skill-install.ts
2110
- import { existsSync as existsSync3, mkdirSync as mkdirSync6, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "fs";
2111
- import { dirname as dirname8, resolve as resolve7 } from "path";
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";
2112
2815
  var installSkillPackages = (basePath, packages) => {
2113
2816
  const writtenRoots = [];
2114
2817
  for (const skillPackage of packages) {
2115
- const absRoot = resolve7(basePath, skillPackage.rootDir);
2116
- if (existsSync3(absRoot)) {
2818
+ const absRoot = resolve8(basePath, skillPackage.rootDir);
2819
+ if (existsSync5(absRoot)) {
2117
2820
  rmSync3(absRoot, { recursive: true, force: true });
2118
2821
  }
2119
2822
  for (const file of skillPackage.files) {
2120
- const absPath = resolve7(basePath, file.relativePath);
2121
- mkdirSync6(dirname8(absPath), { recursive: true });
2122
- writeFileSync6(absPath, file.content + "\n", "utf-8");
2823
+ const absPath = resolve8(basePath, file.relativePath);
2824
+ mkdirSync8(dirname10(absPath), { recursive: true });
2825
+ writeFileSync8(absPath, file.content + "\n", "utf-8");
2123
2826
  }
2124
2827
  writtenRoots.push(skillPackage.rootDir);
2125
2828
  }
@@ -2128,8 +2831,8 @@ var installSkillPackages = (basePath, packages) => {
2128
2831
  var removeDirectories = (basePath, relativeDirs) => {
2129
2832
  const removed = [];
2130
2833
  for (const relativeDir of relativeDirs) {
2131
- const absPath = resolve7(basePath, relativeDir);
2132
- if (!existsSync3(absPath)) continue;
2834
+ const absPath = resolve8(basePath, relativeDir);
2835
+ if (!existsSync5(absPath)) continue;
2133
2836
  rmSync3(absPath, { recursive: true, force: true });
2134
2837
  removed.push(relativeDir);
2135
2838
  }
@@ -2308,14 +3011,14 @@ var skillUninstallCommand = async (skillId) => {
2308
3011
 
2309
3012
  // src/commands/subagent.ts
2310
3013
  import * as p8 from "@clack/prompts";
2311
- import { existsSync as existsSync5, rmSync as rmSync6 } from "fs";
3014
+ import { existsSync as existsSync7, rmSync as rmSync6 } from "fs";
2312
3015
 
2313
3016
  // src/lib/subagent-install.ts
2314
- import { existsSync as existsSync4, mkdirSync as mkdirSync7, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "fs";
2315
- import { dirname as dirname9, isAbsolute as isAbsolute3, relative as relative3, resolve as resolve8 } from "path";
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";
2316
3019
  var resolveInsideBasePath = (basePath, relativePath) => {
2317
- const absBasePath = resolve8(basePath);
2318
- const absPath = resolve8(absBasePath, relativePath);
3020
+ const absBasePath = resolve9(basePath);
3021
+ const absPath = resolve9(absBasePath, relativePath);
2319
3022
  const fromBase = relative3(absBasePath, absPath);
2320
3023
  if (fromBase.length === 0 || fromBase.startsWith("..") || isAbsolute3(fromBase)) {
2321
3024
  throw new Error(`Subagent path escapes AI_OPS_HOME: ${relativePath}`);
@@ -2327,11 +3030,11 @@ var installSubagentPackages = (basePath, packages) => {
2327
3030
  for (const subagentPackage of packages) {
2328
3031
  for (const file of subagentPackage.files) {
2329
3032
  const absPath = resolveInsideBasePath(basePath, file.relativePath);
2330
- if (existsSync4(absPath)) {
3033
+ if (existsSync6(absPath)) {
2331
3034
  rmSync5(absPath, { recursive: true, force: true });
2332
3035
  }
2333
- mkdirSync7(dirname9(absPath), { recursive: true });
2334
- writeFileSync7(absPath, file.content.trimEnd() + "\n", "utf-8");
3036
+ mkdirSync9(dirname11(absPath), { recursive: true });
3037
+ writeFileSync9(absPath, file.content.trimEnd() + "\n", "utf-8");
2335
3038
  written.push(file.relativePath);
2336
3039
  }
2337
3040
  }
@@ -2341,7 +3044,7 @@ var removeSubagentFiles = (basePath, relativePaths) => {
2341
3044
  const removed = [];
2342
3045
  for (const relativePath of relativePaths) {
2343
3046
  const absPath = resolveInsideBasePath(basePath, relativePath);
2344
- if (!existsSync4(absPath)) continue;
3047
+ if (!existsSync6(absPath)) continue;
2345
3048
  rmSync5(absPath, { recursive: true, force: true });
2346
3049
  removed.push(relativePath);
2347
3050
  }
@@ -2403,7 +3106,7 @@ var writeUserSubagentState = (params) => {
2403
3106
  };
2404
3107
  var readInstalledSubagents = (basePath) => readSubagentManifest(resolveSubagentManifestPath(basePath))?.subagents ?? [];
2405
3108
  var warnMissingSkills = (requiredSkills) => {
2406
- const missing = requiredSkills.filter((skill) => !existsSync5(skill.path));
3109
+ const missing = requiredSkills.filter((skill) => !existsSync7(skill.path));
2407
3110
  if (missing.length === 0) {
2408
3111
  return;
2409
3112
  }
@@ -2676,6 +3379,265 @@ ${result.preserved.map((file) => ` ${file}`).join("\n")}`);
2676
3379
  p9.outro("ai-ops pack uninstall \uC644\uB8CC");
2677
3380
  };
2678
3381
 
3382
+ // src/commands/context-promotion.ts
3383
+ import * as p10 from "@clack/prompts";
3384
+ var VALID_DECISIONS = [
3385
+ CONTEXT_PROMOTION_DECISION.PROMOTED,
3386
+ CONTEXT_PROMOTION_DECISION.NO_PROMOTION
3387
+ ];
3388
+ var VALID_SCOPES = [
3389
+ CONTEXT_PROMOTION_SCOPE.CORE,
3390
+ CONTEXT_PROMOTION_SCOPE.PROJECT_LOCAL,
3391
+ CONTEXT_PROMOTION_SCOPE.GLOBAL
3392
+ ];
3393
+ var parseDecision = (decision) => {
3394
+ const parsed = VALID_DECISIONS.find((candidate) => candidate === decision);
3395
+ if (parsed) {
3396
+ return parsed;
3397
+ }
3398
+ throw new Error(`decision must be one of: ${VALID_DECISIONS.join(", ")}`);
3399
+ };
3400
+ var parseScopes = (scopes) => {
3401
+ const requestedScopes = scopes ?? [];
3402
+ return requestedScopes.map((scope) => {
3403
+ const parsed = VALID_SCOPES.find((candidate) => candidate === scope);
3404
+ if (!parsed) {
3405
+ throw new Error(`scope must be one of: ${VALID_SCOPES.join(", ")}`);
3406
+ }
3407
+ return parsed;
3408
+ });
3409
+ };
3410
+ var parseMax = (max) => {
3411
+ if (max === void 0) {
3412
+ return 50;
3413
+ }
3414
+ const parsed = Number.parseInt(max, 10);
3415
+ if (!Number.isFinite(parsed) || parsed < 1) {
3416
+ throw new Error("max must be a positive integer");
3417
+ }
3418
+ return parsed;
3419
+ };
3420
+ var readStdin = async () => new Promise((resolve10, reject) => {
3421
+ let raw = "";
3422
+ process.stdin.setEncoding("utf-8");
3423
+ process.stdin.on("data", (chunk) => {
3424
+ raw += chunk;
3425
+ });
3426
+ process.stdin.on("end", () => resolve10(raw));
3427
+ process.stdin.on("error", reject);
3428
+ });
3429
+ var reportContextPromotionError = (error) => {
3430
+ const message = error instanceof Error ? error.message : "unknown error";
3431
+ p10.log.error(message);
3432
+ process.exitCode = 1;
3433
+ };
3434
+ var contextPromotionStatusCommand = async (opts) => {
3435
+ try {
3436
+ const status = getContextPromotionStatus({
3437
+ cwd: resolveBasePath(),
3438
+ userBasePath: resolveUserBasePath()
3439
+ });
3440
+ if (opts.json) {
3441
+ process.stdout.write(JSON.stringify(status, null, 2) + "\n");
3442
+ return;
3443
+ }
3444
+ p10.intro("ai-ops context-promotion status");
3445
+ p10.log.info(
3446
+ [
3447
+ `git root: ${status.gitRoot ?? "not found"}`,
3448
+ `context layer: ${status.hasContextLayer ? "found" : "not found"}`,
3449
+ `HEAD: ${status.commitHash ?? "not available"}`,
3450
+ `fingerprint: ${status.fingerprint ?? "not available"}`,
3451
+ `receipt: ${status.receipt ? "found" : "missing"}`,
3452
+ `receipt store: ${status.receiptIndexPath ?? "not available"}`
3453
+ ].join("\n")
3454
+ );
3455
+ p10.outro("ai-ops context-promotion status \uC644\uB8CC");
3456
+ } catch (error) {
3457
+ reportContextPromotionError(error);
3458
+ }
3459
+ };
3460
+ var contextPromotionResolveCommand = async (opts) => {
3461
+ p10.intro("ai-ops context-promotion resolve");
3462
+ try {
3463
+ const nextStatus = resolveContextPromotion({
3464
+ cwd: resolveBasePath(),
3465
+ userBasePath: resolveUserBasePath(),
3466
+ input: {
3467
+ decision: parseDecision(opts.decision),
3468
+ summary: opts.summary ?? "",
3469
+ scopes: parseScopes(opts.scope),
3470
+ targets: opts.target ?? []
3471
+ }
3472
+ });
3473
+ p10.log.success(`receipt \uAE30\uB85D \uC644\uB8CC: ${nextStatus.commitHash ?? nextStatus.fingerprint ?? "unknown"}`);
3474
+ } catch (error) {
3475
+ reportContextPromotionError(error);
3476
+ }
3477
+ p10.outro("ai-ops context-promotion resolve \uC644\uB8CC");
3478
+ };
3479
+ var contextPromotionPruneCommand = async (opts) => {
3480
+ p10.intro("ai-ops context-promotion prune");
3481
+ try {
3482
+ const status = getContextPromotionStatus({
3483
+ cwd: resolveBasePath(),
3484
+ userBasePath: resolveUserBasePath()
3485
+ });
3486
+ if (!status.receiptIndexPath) {
3487
+ p10.log.warn("prune\uD560 receipt store\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
3488
+ p10.outro("ai-ops context-promotion prune \uC644\uB8CC");
3489
+ return;
3490
+ }
3491
+ const maxReceipts = parseMax(opts.max);
3492
+ const before = readContextPromotionReceiptIndex(status.receiptIndexPath)?.receipts.length ?? 0;
3493
+ const next = pruneContextPromotionReceipts({ indexPath: status.receiptIndexPath, maxReceipts });
3494
+ const after = next?.receipts.length ?? 0;
3495
+ p10.log.success(`receipt prune \uC644\uB8CC: ${before} -> ${after}`);
3496
+ } catch (error) {
3497
+ reportContextPromotionError(error);
3498
+ }
3499
+ p10.outro("ai-ops context-promotion prune \uC644\uB8CC");
3500
+ };
3501
+ var contextPromotionPreToolUseHookCommand = async () => {
3502
+ await readStdin();
3503
+ };
3504
+ var contextPromotionPostToolUseHookCommand = async () => {
3505
+ try {
3506
+ const raw = await readStdin();
3507
+ const hookInput = raw.trim().length > 0 ? JSON.parse(raw) : {};
3508
+ const output = evaluateContextPromotionPostToolUseHook({
3509
+ hookInput,
3510
+ userBasePath: resolveUserBasePath()
3511
+ });
3512
+ if (output) {
3513
+ process.stdout.write(JSON.stringify(output) + "\n");
3514
+ }
3515
+ } catch (error) {
3516
+ const message = error instanceof Error ? error.message : "unknown error";
3517
+ process.stdout.write(
3518
+ JSON.stringify({
3519
+ systemMessage: `ai-ops context promotion hook skipped: ${message}`
3520
+ }) + "\n"
3521
+ );
3522
+ }
3523
+ };
3524
+
3525
+ // src/commands/codex-hook.ts
3526
+ import * as p11 from "@clack/prompts";
3527
+ import { existsSync as existsSync8 } from "fs";
3528
+ import { join as join16 } from "path";
3529
+ var CONTEXT_PROMOTION_REVIEW_SKILL_ID = "context-promotion-review";
3530
+ var resolveCodexHomePath = () => {
3531
+ const codexHome = process.env.CODEX_HOME;
3532
+ if (codexHome && codexHome.length > 0) {
3533
+ return codexHome;
3534
+ }
3535
+ const home = process.env.HOME;
3536
+ if (!home) {
3537
+ throw new Error("CODEX_HOME or HOME is required for Codex hook commands");
3538
+ }
3539
+ return `${home}/.codex`;
3540
+ };
3541
+ var assertContextPromotionHookId = (hookId) => {
3542
+ if (hookId !== CONTEXT_PROMOTION_HOOK_ID) {
3543
+ throw new Error(`Unknown Codex hook: ${hookId}`);
3544
+ }
3545
+ };
3546
+ var reportCodexHookError = (error) => {
3547
+ const message = error instanceof Error ? error.message : "unknown error";
3548
+ p11.log.error(message);
3549
+ process.exitCode = 1;
3550
+ };
3551
+ var readInstalledSkills2 = (basePath) => (readSkillRegistry(resolveSkillRegistryPath(basePath))?.skills ?? []).map((installedSkill) => ({
3552
+ ...installedSkill,
3553
+ id: resolveCanonicalSkillId(installedSkill.id)
3554
+ }));
3555
+ var resolveContextPromotionReviewSkill = () => {
3556
+ const skill = loadAllSkills(resolveSkillsDir()).find(
3557
+ (candidate) => candidate.id === CONTEXT_PROMOTION_REVIEW_SKILL_ID
3558
+ );
3559
+ if (!skill) {
3560
+ throw new Error(`Unknown skill: ${CONTEXT_PROMOTION_REVIEW_SKILL_ID}`);
3561
+ }
3562
+ return skill;
3563
+ };
3564
+ var hasInstalledContextPromotionReviewSkill = (basePath) => {
3565
+ 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"));
3567
+ };
3568
+ var ensureContextPromotionReviewSkill = (basePath) => {
3569
+ const skill = resolveContextPromotionReviewSkill();
3570
+ const installedSkills = readInstalledSkills2(basePath);
3571
+ const existingInstalledSkill = findInstalledSkill(installedSkills, skill.id);
3572
+ const requestedTools = mergeSkillTools({
3573
+ existing: existingInstalledSkill?.tools,
3574
+ requested: [SKILL_TOOL.CODEX]
3575
+ });
3576
+ const { packages, installedSkill } = buildSkillInstallPlan({
3577
+ skill,
3578
+ requestedTools
3579
+ });
3580
+ const alreadyInstalled = existingInstalledSkill?.sourceHash === installedSkill.sourceHash && existingInstalledSkill.tools.includes(SKILL_TOOL.CODEX) && existsSync8(join16(basePath, ".agents/skills/context-promotion-review/SKILL.md"));
3581
+ if (alreadyInstalled) {
3582
+ return { changed: false, installedSkill };
3583
+ }
3584
+ installSkillPackages(basePath, packages);
3585
+ writeSkillRegistry(resolveSkillRegistryPath(basePath), {
3586
+ skills: upsertInstalledSkill(installedSkills, installedSkill),
3587
+ cliVersion: getCliVersion(),
3588
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
3589
+ });
3590
+ return { changed: true, installedSkill };
3591
+ };
3592
+ var codexHookInstallCommand = async (hookId, opts = {}) => {
3593
+ p11.intro(`ai-ops codex-hook install ${hookId}`);
3594
+ try {
3595
+ assertContextPromotionHookId(hookId);
3596
+ const skillResult = ensureContextPromotionReviewSkill(resolveUserBasePath());
3597
+ const hooksPath = resolveCodexHooksPath(resolveCodexHomePath());
3598
+ const result = installContextPromotionHook({
3599
+ hooksPath,
3600
+ command: buildContextPromotionHookCommand(opts.command)
3601
+ });
3602
+ p11.log.success(
3603
+ skillResult.changed ? `skill \uC124\uCE58 \uC644\uB8CC: ${skillResult.installedSkill.id}` : `skill \uC774\uBBF8 \uC124\uCE58\uB428: ${skillResult.installedSkill.id}`
3604
+ );
3605
+ p11.log.success(result.changed ? `hook \uC124\uCE58 \uC644\uB8CC: ${result.hooksPath}` : `hook \uC774\uBBF8 \uC124\uCE58\uB428: ${result.hooksPath}`);
3606
+ } catch (error) {
3607
+ reportCodexHookError(error);
3608
+ }
3609
+ p11.outro("ai-ops codex-hook install \uC644\uB8CC");
3610
+ };
3611
+ var codexHookStatusCommand = async (hookId) => {
3612
+ p11.intro(`ai-ops codex-hook status ${hookId}`);
3613
+ try {
3614
+ assertContextPromotionHookId(hookId);
3615
+ const result = inspectContextPromotionHook(resolveCodexHooksPath(resolveCodexHomePath()));
3616
+ const skillInstalled = hasInstalledContextPromotionReviewSkill(resolveUserBasePath());
3617
+ p11.log.info(
3618
+ [
3619
+ `hooks file: ${result.hooksPath}`,
3620
+ `hook installed: ${result.installed ? "yes" : "no"}`,
3621
+ `skill installed: ${skillInstalled ? "yes" : "no"}`
3622
+ ].join("\n")
3623
+ );
3624
+ } catch (error) {
3625
+ reportCodexHookError(error);
3626
+ }
3627
+ p11.outro("ai-ops codex-hook status \uC644\uB8CC");
3628
+ };
3629
+ var codexHookUninstallCommand = async (hookId) => {
3630
+ p11.intro(`ai-ops codex-hook uninstall ${hookId}`);
3631
+ try {
3632
+ assertContextPromotionHookId(hookId);
3633
+ const result = uninstallContextPromotionHook(resolveCodexHooksPath(resolveCodexHomePath()));
3634
+ p11.log.success(result.removed ? `hook \uC81C\uAC70 \uC644\uB8CC: ${result.hooksPath}` : `\uC124\uCE58\uB41C hook \uC5C6\uC74C: ${result.hooksPath}`);
3635
+ } catch (error) {
3636
+ reportCodexHookError(error);
3637
+ }
3638
+ p11.outro("ai-ops codex-hook uninstall \uC644\uB8CC");
3639
+ };
3640
+
2679
3641
  // src/bin/index.ts
2680
3642
  var program = new Command();
2681
3643
  program.name("ai-ops").description("AI agent operating layer manager").version("0.1.0");
@@ -2708,5 +3670,18 @@ packCommand.command("install <packId>").description("pack \uC124\uCE58").action(
2708
3670
  packCommand.command("diff [packId]").description("pack \uBCC0\uACBD \uBE44\uAD50").action((packId) => packDiffCommand(packId));
2709
3671
  packCommand.command("update [packId]").description("pack \uAC31\uC2E0").action((packId) => packUpdateCommand(packId));
2710
3672
  packCommand.command("uninstall <packId>").description("pack \uC81C\uAC70").action((packId) => packUninstallCommand(packId));
3673
+ var contextPromotionCommand = program.command("context-promotion").description("context promotion review receipt \uAD00\uB9AC");
3674
+ contextPromotionCommand.command("status").description("\uD604\uC7AC context promotion receipt \uC0C1\uD0DC \uD655\uC778").option("--json", "JSON\uC73C\uB85C \uCD9C\uB825", false).action((opts) => contextPromotionStatusCommand(opts));
3675
+ contextPromotionCommand.command("resolve").description("\uD604\uC7AC HEAD \uCEE4\uBC0B\uC5D0 \uB300\uD55C context promotion review receipt \uAE30\uB85D").requiredOption("--decision <decision>", "promoted|no-promotion").requiredOption("--summary <summary>", "review \uACB0\uC815 \uC694\uC57D").option("--scope <scope...>", "\uC2B9\uACA9 scope (core|project-local|global)").option("--target <path...>", "\uC2B9\uACA9 \uB300\uC0C1 \uD30C\uC77C \uB610\uB294 \uC790\uC0B0").action(
3676
+ (opts) => contextPromotionResolveCommand(opts)
3677
+ );
3678
+ contextPromotionCommand.command("prune").description("user-local context promotion receipts \uC815\uB9AC").option("--max <number>", "\uC720\uC9C0\uD560 receipt \uC218", "50").action((opts) => contextPromotionPruneCommand(opts));
3679
+ var contextPromotionHookCommand = contextPromotionCommand.command("hook").description("Codex hook \uB0B4\uBD80 \uBA85\uB839");
3680
+ contextPromotionHookCommand.command("pre-tool-use").description("Deprecated no-op Codex PreToolUse hook entrypoint").action(() => contextPromotionPreToolUseHookCommand());
3681
+ contextPromotionHookCommand.command("post-tool-use").description("Codex PostToolUse hook entrypoint").action(() => contextPromotionPostToolUseHookCommand());
3682
+ var codexHookCommand = program.command("codex-hook").description("Codex hooks \uC124\uC815 \uAD00\uB9AC");
3683
+ 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
+ codexHookCommand.command("status <hookId>").description("Codex hook \uC124\uCE58 \uC0C1\uD0DC \uD655\uC778").action((hookId) => codexHookStatusCommand(hookId));
3685
+ codexHookCommand.command("uninstall <hookId>").description("Codex hook \uC81C\uAC70").action((hookId) => codexHookUninstallCommand(hookId));
2711
3686
  program.parse();
2712
3687
  //# sourceMappingURL=index.js.map