import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js";
import { a as sha256Hex } from "./crypto-digest-IGAbV2KW.js";
import { t as getGlobalHookRunner } from "./hook-runner-global-BphT2xdR.js";
import { t as parseSkillFrontmatter } from "./frontmatter-BUnBwW_N.js";
import path from "node:path";
import fs from "node:fs/promises";
//#region src/skills/lifecycle/skill-change-hook.ts
const SKILL_FILE_CANDIDATES = [
	"SKILL.md",
	"skill.md",
	"skills.md",
	"SKILL.MD"
];
const EXCLUDED_ROOT_DIRS = /* @__PURE__ */ new Set([
	".clawhub",
	".clawdhub",
	".openclaw"
]);
async function collectSkillTreeFiles(skillDir, relativeDir = "") {
	const entries = await fs.readdir(path.join(skillDir, relativeDir), { withFileTypes: true });
	const files = [];
	for (const entry of entries.toSorted((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0)) {
		if (!relativeDir && EXCLUDED_ROOT_DIRS.has(entry.name)) continue;
		const relativePath = path.join(relativeDir, entry.name);
		const portablePath = relativePath.split(path.sep).join("/");
		const absolutePath = path.join(skillDir, relativePath);
		const stat = await fs.lstat(absolutePath);
		if (stat.isSymbolicLink() || !stat.isDirectory() && !stat.isFile()) throw new Error(`Skill tree contains unsupported entry ${JSON.stringify(portablePath)}.`);
		if (stat.isDirectory()) {
			files.push(...await collectSkillTreeFiles(skillDir, relativePath));
			continue;
		}
		if (stat.nlink > 1) throw new Error(`Skill tree contains hard-linked file ${JSON.stringify(portablePath)}.`);
		const content = await fs.readFile(absolutePath);
		files.push({
			path: portablePath,
			sha256: sha256Hex(content),
			sizeBytes: content.byteLength
		});
	}
	return files;
}
function parseSkillArtifactMetadata(content) {
	const text = content.toString("utf8");
	if (text.includes("\0") || !Buffer.from(text, "utf8").equals(content)) return {};
	try {
		const frontmatter = parseSkillFrontmatter(text);
		return {
			name: normalizeOptionalString(frontmatter.name),
			description: normalizeOptionalString(frontmatter.description),
			declaredVersion: normalizeOptionalString(frontmatter.version)
		};
	} catch {
		return {};
	}
}
function hasCommittedSkillChangeHooks() {
	return getGlobalHookRunner()?.hasHooks("skill_changed") ?? false;
}
function resolveCommittedSkillChangeSource(originType) {
	if (originType === "clawhub") return "clawhub";
	if (originType === "upload") return "upload";
	return "source-install";
}
async function snapshotCommittedSkillArtifact(params) {
	const skillDir = path.resolve(params.skillDir);
	const files = await collectSkillTreeFiles(skillDir);
	const skillFileEntry = SKILL_FILE_CANDIDATES.map((candidate) => files.find((file) => file.path === candidate)).find((entry) => entry !== void 0);
	if (!skillFileEntry) throw new Error(`Skill tree is missing SKILL.md: ${skillDir}`);
	const skillFile = path.join(skillDir, skillFileEntry.path);
	const frontmatter = parseSkillArtifactMetadata(await fs.readFile(skillFile));
	const treeSha256 = sha256Hex(JSON.stringify(files));
	return {
		name: frontmatter.name ?? params.skillKey,
		skillKey: params.skillKey,
		...frontmatter.description ? { description: frontmatter.description } : {},
		skillFile,
		skillDir,
		source: params.source,
		revision: {
			...frontmatter.declaredVersion ? { declaredVersion: frontmatter.declaredVersion } : {},
			contentSha256: `sha256:${skillFileEntry.sha256}`,
			treeSha256: `sha256:${treeSha256}`,
			...params.sourceVersion ? { sourceVersion: params.sourceVersion } : {}
		}
	};
}
async function snapshotCommittedSkillArtifactBestEffort(params) {
	try {
		return await snapshotCommittedSkillArtifact(params);
	} catch (error) {
		params.logger?.warn?.(`Could not snapshot committed skill change: ${String(error)}`);
		return;
	}
}
async function dispatchCommittedSkillChangeBestEffort(params) {
	const runner = getGlobalHookRunner();
	if (!runner?.hasHooks("skill_changed")) return;
	try {
		await runner.runSkillChanged({
			action: params.action,
			source: params.source,
			occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
			...params.before ? { before: params.before } : {},
			...params.after ? { after: params.after } : {},
			...params.proposal ? { proposal: params.proposal } : {}
		}, { workspaceDir: params.workspaceDir });
	} catch (error) {
		params.logger?.warn?.(`Committed skill change hook failed: ${String(error)}`);
	}
}
//#endregion
export { snapshotCommittedSkillArtifactBestEffort as i, hasCommittedSkillChangeHooks as n, resolveCommittedSkillChangeSource as r, dispatchCommittedSkillChangeBestEffort as t };
