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/README.ko.md +17 -0
- package/README.md +17 -0
- package/data/skills/README.ko.md +9 -0
- package/data/skills/README.md +9 -0
- package/data/skills/skill-registry.json +8 -0
- package/data/skills/task-skills/context-promotion-review/SKILL.md +88 -0
- package/data/skills/task-skills/context-promotion-review/agents/openai.yaml +6 -0
- package/dist/bin/index.js +998 -23
- package/dist/bin/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
1824
|
-
var resolveSkillsDir = () =>
|
|
1825
|
-
var resolveSubagentsDir = () =>
|
|
1826
|
-
var resolvePacksDir = () =>
|
|
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
|
|
2111
|
-
import { dirname as
|
|
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 =
|
|
2116
|
-
if (
|
|
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 =
|
|
2121
|
-
|
|
2122
|
-
|
|
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 =
|
|
2132
|
-
if (!
|
|
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
|
|
3014
|
+
import { existsSync as existsSync7, rmSync as rmSync6 } from "fs";
|
|
2312
3015
|
|
|
2313
3016
|
// src/lib/subagent-install.ts
|
|
2314
|
-
import { existsSync as
|
|
2315
|
-
import { dirname as
|
|
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 =
|
|
2318
|
-
const absPath =
|
|
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 (
|
|
3033
|
+
if (existsSync6(absPath)) {
|
|
2331
3034
|
rmSync5(absPath, { recursive: true, force: true });
|
|
2332
3035
|
}
|
|
2333
|
-
|
|
2334
|
-
|
|
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 (!
|
|
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) => !
|
|
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
|