ai-ops-cli 1.0.2 → 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 +1037 -36
- package/dist/bin/index.js.map +1 -1
- package/package.json +1 -1
package/dist/bin/index.js
CHANGED
|
@@ -866,14 +866,45 @@ var parseProjectLayerDocument = (path, rawContent) => {
|
|
|
866
866
|
content
|
|
867
867
|
};
|
|
868
868
|
};
|
|
869
|
+
var parseMarkdownTableCells = (line) => {
|
|
870
|
+
const trimmed = line.trim();
|
|
871
|
+
if (!trimmed.startsWith("|") || !trimmed.endsWith("|")) {
|
|
872
|
+
return null;
|
|
873
|
+
}
|
|
874
|
+
return trimmed.slice(1, -1).split("|").map((cell) => cell.trim());
|
|
875
|
+
};
|
|
876
|
+
var isDocsStatusHeaderLine = (line) => {
|
|
877
|
+
const cells = parseMarkdownTableCells(line);
|
|
878
|
+
return cells !== null && cells.length === 3 && cells[0] === "path" && cells[1] === "status" && cells[2] === "owner";
|
|
879
|
+
};
|
|
880
|
+
var isMarkdownDividerCell = (cell) => /^:?-{3,}:?$/.test(cell);
|
|
881
|
+
var isDocsStatusDividerLine = (line) => {
|
|
882
|
+
const cells = parseMarkdownTableCells(line);
|
|
883
|
+
return cells !== null && cells.length === 3 && cells.every(isMarkdownDividerCell);
|
|
884
|
+
};
|
|
885
|
+
var findDocsStatusTableBounds = (lines) => {
|
|
886
|
+
const headerIndex = lines.findIndex(isDocsStatusHeaderLine);
|
|
887
|
+
const dividerIndex = headerIndex + 1;
|
|
888
|
+
if (headerIndex < 0 || !isDocsStatusDividerLine(lines[dividerIndex] ?? "")) {
|
|
889
|
+
return null;
|
|
890
|
+
}
|
|
891
|
+
let tableEndIndex = dividerIndex + 1;
|
|
892
|
+
while (tableEndIndex < lines.length && parseMarkdownTableCells(lines[tableEndIndex] ?? "") !== null) {
|
|
893
|
+
tableEndIndex += 1;
|
|
894
|
+
}
|
|
895
|
+
return { headerIndex, dividerIndex, tableEndIndex };
|
|
896
|
+
};
|
|
869
897
|
var parseDocsStatusEntries = (content) => {
|
|
870
898
|
const document = parseProjectLayerDocument("docs/docs-status.md", content);
|
|
871
|
-
const
|
|
899
|
+
const lines = document.content.split("\n");
|
|
900
|
+
const tableBounds = findDocsStatusTableBounds(lines);
|
|
901
|
+
if (tableBounds === null) {
|
|
902
|
+
return [];
|
|
903
|
+
}
|
|
904
|
+
const rows = lines.slice(tableBounds.dividerIndex + 1, tableBounds.tableEndIndex);
|
|
872
905
|
return rows.flatMap((line) => {
|
|
873
|
-
const cells = line
|
|
874
|
-
if (cells.length < 3) return [];
|
|
875
|
-
if (cells[0] === "path") return [];
|
|
876
|
-
if (cells[0].startsWith("---")) return [];
|
|
906
|
+
const cells = parseMarkdownTableCells(line);
|
|
907
|
+
if (cells === null || cells.length < 3) return [];
|
|
877
908
|
return [
|
|
878
909
|
{
|
|
879
910
|
path: cells[0],
|
|
@@ -1050,16 +1081,11 @@ var buildDocsStatusRowsFromDisk = (params) => params.documentPaths.map((path) =>
|
|
|
1050
1081
|
});
|
|
1051
1082
|
var replaceDocsStatusRows = (content, rows) => {
|
|
1052
1083
|
const lines = content.trimEnd().split("\n");
|
|
1053
|
-
const
|
|
1054
|
-
|
|
1055
|
-
if (headerIndex < 0 || !lines[dividerIndex]?.trim().startsWith("| ---")) {
|
|
1084
|
+
const tableBounds = findDocsStatusTableBounds(lines);
|
|
1085
|
+
if (tableBounds === null) {
|
|
1056
1086
|
throw new Error("docs/docs-status.md table header not found");
|
|
1057
1087
|
}
|
|
1058
|
-
|
|
1059
|
-
while (tableEndIndex < lines.length && lines[tableEndIndex]?.trim().startsWith("|")) {
|
|
1060
|
-
tableEndIndex += 1;
|
|
1061
|
-
}
|
|
1062
|
-
return [...lines.slice(0, dividerIndex + 1), ...rows, ...lines.slice(tableEndIndex)].join("\n") + "\n";
|
|
1088
|
+
return [...lines.slice(0, tableBounds.dividerIndex + 1), ...rows, ...lines.slice(tableBounds.tableEndIndex)].join("\n") + "\n";
|
|
1063
1089
|
};
|
|
1064
1090
|
var updateDocsStatusTable = (basePath, documentPaths) => {
|
|
1065
1091
|
const docsStatusPath = "docs/docs-status.md";
|
|
@@ -1793,11 +1819,714 @@ var diffProjectLayerPack = (params) => {
|
|
|
1793
1819
|
// src/core/uninstall-plan.ts
|
|
1794
1820
|
import { join as join12 } from "path";
|
|
1795
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
|
+
|
|
1796
2525
|
// src/lib/paths.ts
|
|
1797
|
-
import { join as
|
|
1798
|
-
var resolveSkillsDir = () =>
|
|
1799
|
-
var resolveSubagentsDir = () =>
|
|
1800
|
-
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");
|
|
1801
2530
|
var resolveBasePath = () => process.cwd();
|
|
1802
2531
|
var resolveUserBasePath = () => {
|
|
1803
2532
|
const userBasePath = process.env.AI_OPS_HOME ?? process.env.HOME;
|
|
@@ -2081,19 +2810,19 @@ var findInstalledSkill = (installedSkills, skillId) => {
|
|
|
2081
2810
|
};
|
|
2082
2811
|
|
|
2083
2812
|
// src/lib/skill-install.ts
|
|
2084
|
-
import { existsSync as
|
|
2085
|
-
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";
|
|
2086
2815
|
var installSkillPackages = (basePath, packages) => {
|
|
2087
2816
|
const writtenRoots = [];
|
|
2088
2817
|
for (const skillPackage of packages) {
|
|
2089
|
-
const absRoot =
|
|
2090
|
-
if (
|
|
2818
|
+
const absRoot = resolve8(basePath, skillPackage.rootDir);
|
|
2819
|
+
if (existsSync5(absRoot)) {
|
|
2091
2820
|
rmSync3(absRoot, { recursive: true, force: true });
|
|
2092
2821
|
}
|
|
2093
2822
|
for (const file of skillPackage.files) {
|
|
2094
|
-
const absPath =
|
|
2095
|
-
|
|
2096
|
-
|
|
2823
|
+
const absPath = resolve8(basePath, file.relativePath);
|
|
2824
|
+
mkdirSync8(dirname10(absPath), { recursive: true });
|
|
2825
|
+
writeFileSync8(absPath, file.content + "\n", "utf-8");
|
|
2097
2826
|
}
|
|
2098
2827
|
writtenRoots.push(skillPackage.rootDir);
|
|
2099
2828
|
}
|
|
@@ -2102,8 +2831,8 @@ var installSkillPackages = (basePath, packages) => {
|
|
|
2102
2831
|
var removeDirectories = (basePath, relativeDirs) => {
|
|
2103
2832
|
const removed = [];
|
|
2104
2833
|
for (const relativeDir of relativeDirs) {
|
|
2105
|
-
const absPath =
|
|
2106
|
-
if (!
|
|
2834
|
+
const absPath = resolve8(basePath, relativeDir);
|
|
2835
|
+
if (!existsSync5(absPath)) continue;
|
|
2107
2836
|
rmSync3(absPath, { recursive: true, force: true });
|
|
2108
2837
|
removed.push(relativeDir);
|
|
2109
2838
|
}
|
|
@@ -2282,14 +3011,14 @@ var skillUninstallCommand = async (skillId) => {
|
|
|
2282
3011
|
|
|
2283
3012
|
// src/commands/subagent.ts
|
|
2284
3013
|
import * as p8 from "@clack/prompts";
|
|
2285
|
-
import { existsSync as
|
|
3014
|
+
import { existsSync as existsSync7, rmSync as rmSync6 } from "fs";
|
|
2286
3015
|
|
|
2287
3016
|
// src/lib/subagent-install.ts
|
|
2288
|
-
import { existsSync as
|
|
2289
|
-
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";
|
|
2290
3019
|
var resolveInsideBasePath = (basePath, relativePath) => {
|
|
2291
|
-
const absBasePath =
|
|
2292
|
-
const absPath =
|
|
3020
|
+
const absBasePath = resolve9(basePath);
|
|
3021
|
+
const absPath = resolve9(absBasePath, relativePath);
|
|
2293
3022
|
const fromBase = relative3(absBasePath, absPath);
|
|
2294
3023
|
if (fromBase.length === 0 || fromBase.startsWith("..") || isAbsolute3(fromBase)) {
|
|
2295
3024
|
throw new Error(`Subagent path escapes AI_OPS_HOME: ${relativePath}`);
|
|
@@ -2301,11 +3030,11 @@ var installSubagentPackages = (basePath, packages) => {
|
|
|
2301
3030
|
for (const subagentPackage of packages) {
|
|
2302
3031
|
for (const file of subagentPackage.files) {
|
|
2303
3032
|
const absPath = resolveInsideBasePath(basePath, file.relativePath);
|
|
2304
|
-
if (
|
|
3033
|
+
if (existsSync6(absPath)) {
|
|
2305
3034
|
rmSync5(absPath, { recursive: true, force: true });
|
|
2306
3035
|
}
|
|
2307
|
-
|
|
2308
|
-
|
|
3036
|
+
mkdirSync9(dirname11(absPath), { recursive: true });
|
|
3037
|
+
writeFileSync9(absPath, file.content.trimEnd() + "\n", "utf-8");
|
|
2309
3038
|
written.push(file.relativePath);
|
|
2310
3039
|
}
|
|
2311
3040
|
}
|
|
@@ -2315,7 +3044,7 @@ var removeSubagentFiles = (basePath, relativePaths) => {
|
|
|
2315
3044
|
const removed = [];
|
|
2316
3045
|
for (const relativePath of relativePaths) {
|
|
2317
3046
|
const absPath = resolveInsideBasePath(basePath, relativePath);
|
|
2318
|
-
if (!
|
|
3047
|
+
if (!existsSync6(absPath)) continue;
|
|
2319
3048
|
rmSync5(absPath, { recursive: true, force: true });
|
|
2320
3049
|
removed.push(relativePath);
|
|
2321
3050
|
}
|
|
@@ -2377,7 +3106,7 @@ var writeUserSubagentState = (params) => {
|
|
|
2377
3106
|
};
|
|
2378
3107
|
var readInstalledSubagents = (basePath) => readSubagentManifest(resolveSubagentManifestPath(basePath))?.subagents ?? [];
|
|
2379
3108
|
var warnMissingSkills = (requiredSkills) => {
|
|
2380
|
-
const missing = requiredSkills.filter((skill) => !
|
|
3109
|
+
const missing = requiredSkills.filter((skill) => !existsSync7(skill.path));
|
|
2381
3110
|
if (missing.length === 0) {
|
|
2382
3111
|
return;
|
|
2383
3112
|
}
|
|
@@ -2650,6 +3379,265 @@ ${result.preserved.map((file) => ` ${file}`).join("\n")}`);
|
|
|
2650
3379
|
p9.outro("ai-ops pack uninstall \uC644\uB8CC");
|
|
2651
3380
|
};
|
|
2652
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
|
+
|
|
2653
3641
|
// src/bin/index.ts
|
|
2654
3642
|
var program = new Command();
|
|
2655
3643
|
program.name("ai-ops").description("AI agent operating layer manager").version("0.1.0");
|
|
@@ -2682,5 +3670,18 @@ packCommand.command("install <packId>").description("pack \uC124\uCE58").action(
|
|
|
2682
3670
|
packCommand.command("diff [packId]").description("pack \uBCC0\uACBD \uBE44\uAD50").action((packId) => packDiffCommand(packId));
|
|
2683
3671
|
packCommand.command("update [packId]").description("pack \uAC31\uC2E0").action((packId) => packUpdateCommand(packId));
|
|
2684
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));
|
|
2685
3686
|
program.parse();
|
|
2686
3687
|
//# sourceMappingURL=index.js.map
|