import { d as CleoError, E as ExitCode, o as resolveOrCwd, U as getCleoDir, b as getLogger, f as getConfigPath } from './paths-2M_UxD_H.js';
import { j as vacuumIntoBackup } from './sqlite-Dsbe0wec.js';
import { r as readJson } from './json-DXl7iDYg.js';
import { c as createDataAccessor } from './data-accessor-CVbz8X_7.js';
import { s as schemaMeta } from './conduit-Ca_OFWR_.js';
import { s as setMetaValue } from './sqlite-data-accessor-C59wZ0zp.js';
import { existsSync, readFileSync, renameSync } from 'node:fs';
import { join, resolve } from 'node:path';
import { execFile } from 'node:child_process';
import { writeFile, readFile } from 'node:fs/promises';
import { promisify } from 'node:util';

//#region ../core/dist/sequence/index.js
/**
* Task ID sequence management core module.
* @task T4538
* @epic T4454
*/
var SEQUENCE_META_KEY = "task_id_sequence";
function getLegacySequenceJsonPath(cwd) {
	return join(resolveOrCwd(cwd), ".cleo", ".sequence.json");
}
function getLegacySequencePath(cwd) {
	return join(resolveOrCwd(cwd), ".cleo", ".sequence");
}
function isValidSequenceState(value) {
	if (!value || typeof value !== "object") return false;
	const seq = value;
	return typeof seq.counter === "number" && typeof seq.lastId === "string" && typeof seq.checksum === "string";
}
function isSeedSequence(value) {
	return value.counter === 0 && value.lastId === "T000" && value.checksum === "seed";
}
function readLegacySequenceFile(path) {
	if (!existsSync(path)) return null;
	try {
		const parsed = JSON.parse(readFileSync(path, "utf-8"));
		return isValidSequenceState(parsed) ? parsed : null;
	} catch {
		return null;
	}
}
function renameLegacyFile(path) {
	if (!existsSync(path)) return;
	const migratedPath = `${path}.migrated`;
	try {
		if (!existsSync(migratedPath)) {
			renameSync(path, migratedPath);
			return;
		}
		renameSync(path, `${migratedPath}.${Date.now()}`);
	} catch {}
}
async function readSequenceFromDb(cwd, accessor) {
	const { getDb } = await import('./sqlite2-UG45Z53q.js');
	const { eq } = await import('./index-DIWrtHjY.js');
	const raw = (await (await getDb(cwd)).select().from(schemaMeta).where(eq(schemaMeta.key, SEQUENCE_META_KEY)).all())[0]?.value;
	if (!raw) return null;
	try {
		const parsed = JSON.parse(raw);
		return isValidSequenceState(parsed) ? parsed : null;
	} catch {
		return null;
	}
}
async function writeSequenceToDb(state, cwd, accessor) {
	await setMetaValue(cwd, SEQUENCE_META_KEY, state);
}
async function maybeMigrateLegacySequence(dbState, cwd, accessor) {
	const legacyJsonPath = getLegacySequenceJsonPath(cwd);
	const legacyPath = getLegacySequencePath(cwd);
	const candidates = [readLegacySequenceFile(legacyJsonPath), readLegacySequenceFile(legacyPath)].filter((value) => value !== null);
	if (candidates.length === 0) return dbState;
	const preferredLegacy = candidates.reduce((best, current) => current.counter > best.counter ? current : best);
	const shouldMigrate = !dbState || isSeedSequence(dbState) || preferredLegacy.counter > dbState.counter;
	if (shouldMigrate) await writeSequenceToDb(preferredLegacy, cwd);
	renameLegacyFile(legacyJsonPath);
	renameLegacyFile(legacyPath);
	return shouldMigrate ? preferredLegacy : dbState;
}
async function readSequence(cwd, accessor) {
	return maybeMigrateLegacySequence(await readSequenceFromDb(cwd), cwd);
}
function getMaxIdFromTasks(tasks) {
	let max = 0;
	for (const t of tasks) {
		const match = t.id.match(/^T(\d+)$/);
		if (match) {
			const num = parseInt(match[1], 10);
			if (num > max) max = num;
		}
	}
	return max;
}
async function loadAllTasks(cwd, accessor) {
	let localAccessor = null;
	const activeAccessor = await createDataAccessor(cwd);
	localAccessor = activeAccessor;
	try {
		const { tasks: activeTasks } = await activeAccessor.queryTasks({});
		const archiveData = await activeAccessor.loadArchive();
		return [...activeTasks ?? [], ...archiveData?.archivedTasks ?? []];
	} finally {
		if (localAccessor) await localAccessor.close();
	}
}
/** Check sequence integrity. */
async function checkSequence(cwd, accessor) {
	const seq = await readSequence(cwd);
	if (!seq) return {
		valid: false,
		counter: 0,
		maxIdInData: 0,
		missing: true
	};
	const maxId = getMaxIdFromTasks(await loadAllTasks(cwd));
	const valid = seq.counter >= maxId;
	return {
		counter: seq.counter,
		maxIdInData: maxId,
		valid,
		...valid ? {} : {
			issue: `Counter (${seq.counter}) is behind max ID (T${maxId})`,
			fix: "Run cleo sequence repair"
		}
	};
}
/** Repair sequence if behind. */
async function repairSequence(cwd, accessor) {
	const seq = await readSequence(cwd);
	const maxId = getMaxIdFromTasks(await loadAllTasks(cwd));
	const oldCounter = seq?.counter ?? 0;
	if (!seq) {
		const initialized = {
			counter: maxId,
			lastId: `T${maxId}`,
			checksum: `seed-${Date.now()}`
		};
		await writeSequenceToDb(initialized, cwd);
		return {
			repaired: true,
			counter: initialized.counter,
			oldCounter,
			newCounter: initialized.counter,
			message: `Sequence initialized at ${initialized.counter}`
		};
	}
	if (oldCounter >= maxId) return {
		repaired: false,
		message: "Sequence already valid",
		counter: oldCounter,
		oldCounter,
		newCounter: oldCounter
	};
	const newCounter = maxId;
	await writeSequenceToDb({
		counter: newCounter,
		lastId: `T${newCounter}`,
		checksum: `repair-${Date.now()}`
	}, cwd);
	return {
		repaired: true,
		counter: newCounter,
		oldCounter,
		newCounter,
		message: `Sequence repaired: ${oldCounter} -> ${newCounter}`
	};
}
/** Maximum retries for allocateNextTaskId when collision detected. */
var MAX_ALLOC_RETRIES = 3;
/**
* Atomically allocate the next task ID via SQLite.
*
* Uses a SAVEPOINT (instead of BEGIN IMMEDIATE) so that this function
* can be called from within an outer transaction without causing
* "cannot start a transaction within a transaction" errors (T9814).
*
* SQLite SAVEPOINTs nest correctly:
* - Called outside a transaction: the SAVEPOINT implicitly begins one.
* - Called inside a transaction (e.g., addBatchTasks outer tx):
*   the SAVEPOINT creates a logical checkpoint within the outer tx.
*   RELEASE commits the savepoint; ROLLBACK TO reverts only to it.
*
* Falls back to repair+retry if the sequence counter is behind
* the actual max task ID (e.g., stale counter from installations
* that never incremented it).
*
* @task T5184
* @task T9814 — SAVEPOINT for nestability inside batch-insert outer transaction
*/
async function allocateNextTaskId(cwd, retryCount = 0) {
	const { getDb, getNativeDb } = await import('./sqlite2-UG45Z53q.js');
	await getDb(cwd);
	const nativeDb = getNativeDb();
	if (!nativeDb) throw new CleoError(ExitCode.FILE_ERROR, "Native database not available for atomic ID allocation");
	const spName = `_cleo_seq_alloc_${Date.now()}_${retryCount}`;
	nativeDb.prepare(`SAVEPOINT ${spName}`).run();
	try {
		nativeDb.prepare(`
      UPDATE schema_meta
      SET value = json_set(value,
        '$.counter', json_extract(value, '$.counter') + 1,
        '$.lastId', 'T' || printf('%03d', json_extract(value, '$.counter') + 1),
        '$.checksum', 'alloc-' || strftime('%s','now')
      )
      WHERE key = 'task_id_sequence'
    `).run();
		const row = nativeDb.prepare(`
      SELECT json_extract(value, '$.counter') AS counter
      FROM schema_meta WHERE key = 'task_id_sequence'
    `).get();
		if (!row) throw new CleoError(ExitCode.FILE_ERROR, "Sequence counter not found after increment");
		const newId = `T${String(row.counter).padStart(3, "0")}`;
		if (nativeDb.prepare("SELECT id FROM tasks_tasks WHERE id = ?").get(newId)) {
			nativeDb.prepare(`ROLLBACK TO SAVEPOINT ${spName}`).run();
			nativeDb.prepare(`RELEASE SAVEPOINT ${spName}`).run();
			if (retryCount >= MAX_ALLOC_RETRIES) throw new CleoError(ExitCode.ID_COLLISION, `Failed to allocate unique task ID after ${MAX_ALLOC_RETRIES} retries (last attempted: ${newId})`);
			await repairSequence(cwd);
			return allocateNextTaskId(cwd, retryCount + 1);
		}
		nativeDb.prepare(`RELEASE SAVEPOINT ${spName}`).run();
		return newId;
	} catch (err) {
		try {
			nativeDb.prepare(`ROLLBACK TO SAVEPOINT ${spName}`).run();
			nativeDb.prepare(`RELEASE SAVEPOINT ${spName}`).run();
		} catch {}
		throw err;
	}
}

//#region ../core/dist/store/git-checkpoint.js
/**
* Git checkpoint system for CLEO state files.
* Ported from lib/data/git-checkpoint.sh
*
* Opt-in automatic git commits of .cleo/ state files at semantic
* boundaries (save_json, session end) with debounce to prevent commit noise.
* All git errors are suppressed - checkpointing is never fatal.
*
* @task T4552
* @task T4872
* @epic T4545
*/
var execFileAsync = promisify(execFile);
/**
* Build environment variables that point git at the isolated .cleo/.git repo.
* @task T4872
*/
function makeCleoGitEnv(cleoDir) {
	const abs = resolve(cleoDir);
	return {
		...process.env,
		GIT_DIR: join(abs, ".git"),
		GIT_WORK_TREE: abs
	};
}
/**
* Run a git command against the isolated .cleo/.git repo, suppressing errors.
* @task T4872
*/
async function cleoGitCommand(args, cleoDir) {
	try {
		return {
			stdout: (await execFileAsync("git", args, {
				cwd: resolve(cleoDir),
				env: makeCleoGitEnv(cleoDir),
				timeout: 1e4
			})).stdout.trim(),
			success: true
		};
	} catch {
		return {
			stdout: "",
			success: false
		};
	}
}
/**
* Check whether the isolated .cleo/.git repo has been initialized.
* @task T4872
*/
function isCleoGitInitialized(cleoDir) {
	return existsSync(join(cleoDir, ".git", "HEAD"));
}
/**
* Core state files and directories eligible for checkpointing (relative to .cleo/).
*
* Only human-editable JSON config files (per ADR-006) and documentation
* output directories. All operational data lives in tasks.db (SQLite).
* tasks.db is excluded from git — backed up via VACUUM INTO rotation instead.
*
* Directory entries (trailing slash) are passed directly to git; git handles
* them recursively for add/diff/ls-files operations.
*
* Users can add custom entries via config.json `checkpoint.stateFileAllowlist`.
*/
var STATE_FILES = [
	"config.json",
	"project-info.json",
	"project-context.json",
	"adrs/",
	"agent-outputs/"
];
/**
* Load additional state file paths from config.json `checkpoint.stateFileAllowlist`.
* Returns an empty array if config is missing, malformed, or the key is absent.
*/
async function loadStateFileAllowlist(cwd) {
	try {
		const allowlist = ((await readJson(getConfigPath(cwd)))?.checkpoint)?.stateFileAllowlist;
		if (!Array.isArray(allowlist)) return [];
		return allowlist.filter((entry) => typeof entry === "string");
	} catch {
		return [];
	}
}
/**
* Get the full list of state files: core defaults merged with config allowlist.
*/
async function getAllStateFiles(cwd) {
	const custom = await loadStateFileAllowlist(cwd);
	const all = [...STATE_FILES, ...custom];
	return [...new Set(all)];
}
/** Debounce state file name (relative to .cleo/). */
var CHECKPOINT_STATE_FILE = ".git-checkpoint-state";
/**
* Load checkpoint configuration from config.json.
* @task T4552
*/
async function loadCheckpointConfig(cwd) {
	try {
		const gc = (await readJson(getConfigPath(cwd)))?.gitCheckpoint;
		return {
			enabled: gc?.enabled !== false,
			debounceMinutes: typeof gc?.debounceMinutes === "number" ? gc.debounceMinutes : 5,
			messagePrefix: typeof gc?.messagePrefix === "string" ? gc.messagePrefix : "chore(cleo):",
			noVerify: gc?.noVerify !== false
		};
	} catch {
		return {
			enabled: true,
			debounceMinutes: 5,
			messagePrefix: "chore(cleo):",
			noVerify: true
		};
	}
}
/**
* Check if the isolated .cleo/.git repo is a valid git work tree.
* @task T4552
* @task T4872
*/
async function isCleoGitRepo(cleoDir) {
	const result = await cleoGitCommand(["rev-parse", "--is-inside-work-tree"], cleoDir);
	return result.success && (result.stdout === "true" || isCleoGitInitialized(cleoDir));
}
/**
* Check if a merge is in progress in the .cleo/.git repo.
* @task T4552
* @task T4872
*/
function isMergeInProgress(cleoDir) {
	return existsSync(join(cleoDir, ".git", "MERGE_HEAD"));
}
/**
* Check if HEAD is detached in the .cleo/.git repo.
* @task T4552
* @task T4872
*/
async function isDetachedHead(cleoDir) {
	return !(await cleoGitCommand(["symbolic-ref", "HEAD"], cleoDir)).success;
}
/**
* Check if a rebase is in progress in the .cleo/.git repo.
* @task T4552
* @task T4872
*/
function isRebaseInProgress(cleoDir) {
	return existsSync(join(cleoDir, ".git", "rebase-merge")) || existsSync(join(cleoDir, ".git", "rebase-apply"));
}
/**
* Record the current time as the last checkpoint time.
* @task T4552
*/
async function recordCheckpointTime(cleoDir) {
	try {
		await writeFile(join(cleoDir, CHECKPOINT_STATE_FILE), String(Math.floor(Date.now() / 1e3)));
	} catch {}
}
/**
* Get the epoch time of the last checkpoint.
* @task T4552
*/
async function getLastCheckpointTime(cleoDir) {
	try {
		const content = await readFile(join(cleoDir, CHECKPOINT_STATE_FILE), "utf-8");
		const epoch = parseInt(content.trim(), 10);
		return Number.isNaN(epoch) ? 0 : epoch;
	} catch {
		return 0;
	}
}
/**
* Get list of state files with pending changes in the .cleo/.git repo.
* Paths are relative to cleoDir (the git work tree).
* @task T4552
* @task T4872
*/
async function getChangedStateFiles(cleoDir, cwd) {
	const changed = [];
	const allStateFiles = await getAllStateFiles(cwd);
	for (const stateFile of allStateFiles) {
		if (!existsSync(join(cleoDir, stateFile))) continue;
		const diffResult = await cleoGitCommand([
			"diff",
			"--quiet",
			"--",
			stateFile
		], cleoDir);
		const cachedResult = await cleoGitCommand([
			"diff",
			"--cached",
			"--quiet",
			"--",
			stateFile
		], cleoDir);
		const untrackedResult = await cleoGitCommand([
			"ls-files",
			"--others",
			"--exclude-standard",
			"--",
			stateFile
		], cleoDir);
		if (!diffResult.success || !cachedResult.success) changed.push({
			path: stateFile,
			status: "modified"
		});
		else if (untrackedResult.stdout.length > 0) changed.push({
			path: stateFile,
			status: "untracked"
		});
	}
	return changed;
}
/**
* Check whether a checkpoint should be performed.
* Evaluates: enabled, .cleo/.git initialized, debounce elapsed, files changed.
* @task T4552
* @task T4872
*/
async function shouldCheckpoint(options) {
	const force = options?.force ?? false;
	const cwd = options?.cwd;
	if (process.env["GIT_CHECKPOINT_SUPPRESS"] === "true") return false;
	const config = await loadCheckpointConfig(cwd);
	if (!config.enabled) return false;
	const cleoDir = getCleoDir(cwd);
	if (!existsSync(cleoDir)) return false;
	if (!isCleoGitInitialized(cleoDir)) return false;
	if (!await isCleoGitRepo(cleoDir)) return false;
	if (isMergeInProgress(cleoDir)) return false;
	if (await isDetachedHead(cleoDir)) return false;
	if (isRebaseInProgress(cleoDir)) return false;
	if (!force) {
		const lastCheckpoint = await getLastCheckpointTime(cleoDir);
		if (Math.floor(Date.now() / 1e3) - lastCheckpoint < config.debounceMinutes * 60) return false;
	}
	return (await getChangedStateFiles(cleoDir, cwd)).length > 0;
}
/**
* Stage .cleo/ state files and commit to the isolated .cleo/.git repo.
* Never fatal - all git errors are suppressed.
* @task T4552
* @task T4872
*/
async function gitCheckpoint(trigger = "auto", context, cwd) {
	if (process.env["GIT_CHECKPOINT_SUPPRESS"] === "true") return;
	if (!await shouldCheckpoint({
		force: trigger === "manual",
		cwd
	})) return;
	const config = await loadCheckpointConfig(cwd);
	const cleoDir = getCleoDir(cwd);
	const changed = await getChangedStateFiles(cleoDir, cwd);
	if (changed.length === 0) return;
	let stagedCount = 0;
	for (const file of changed) if ((await cleoGitCommand(["add", file.path], cleoDir)).success) stagedCount++;
	if (stagedCount === 0) return;
	let commitMsg = `${config.messagePrefix} ${trigger} checkpoint`;
	if (context) commitMsg = `${config.messagePrefix} ${trigger} checkpoint (${context})`;
	const commitArgs = [
		"commit",
		"-m",
		commitMsg
	];
	if (config.noVerify) commitArgs.push("--no-verify");
	commitArgs.push("--", ...changed.map((f) => f.path));
	if (!(await cleoGitCommand(commitArgs, cleoDir)).success) {
		for (const file of changed) await cleoGitCommand([
			"reset",
			"HEAD",
			"--",
			file.path
		], cleoDir);
		return;
	}
	await recordCheckpointTime(cleoDir);
}
//#endregion
//#region ../core/dist/store/data-safety-central.js
var log = getLogger("data-safety");
/** Safety violation error */
var DataSafetyError = class extends Error {
	code;
	context;
	constructor(message, code, context) {
		super(message);
		this.code = code;
		this.context = context;
		this.name = "DataSafetyError";
	}
};
var DEFAULT_SAFETY = {
	verify: true,
	checkpoint: true,
	validateSequence: true,
	strict: true
};
var stats = {
	writes: 0,
	verifications: 0,
	checkpoints: 0,
	errors: 0,
	lastCheckpoint: null
};
/**
* Verify sequence integrity before write.
* Auto-repairs if sequence is behind database.
*/
async function ensureSequenceValid(cwd, options) {
	if (!options?.validateSequence) return;
	const check = await checkSequence(cwd);
	if (!check.valid) {
		log.warn({
			counter: check.counter,
			maxId: check.maxIdInData
		}, "Sequence behind, repairing");
		const repair = await repairSequence(cwd);
		if (!repair.repaired && options.strict) throw new DataSafetyError(`Sequence repair failed: ${repair.message}`, "SEQUENCE_INVALID", {
			check,
			repair
		});
	}
}
/**
* Create a checkpoint after successful write.
* Non-blocking - failures are logged but don't fail the operation.
*/
async function checkpoint(context, cwd, options) {
	if (!options?.checkpoint) return;
	try {
		await gitCheckpoint("auto", context, cwd);
		stats.checkpoints++;
		stats.lastCheckpoint = /* @__PURE__ */ new Date();
	} catch (err) {
		log.warn({ err }, "Checkpoint failed (non-fatal)");
	}
	vacuumIntoBackup({ cwd }).catch(() => {});
}
/**
* Verify sessions were written correctly.
*/
async function verifySessions(data, accessor, options) {
	if (!options?.verify) return;
	stats.verifications++;
	const readBack = await accessor.loadSessions();
	if (readBack.length !== data.length) throw new DataSafetyError(`Sessions verification failed: count mismatch. Expected ${data.length}, got ${readBack.length}`, "VERIFICATION_FAILED", {
		expected: data.length,
		actual: readBack.length
	});
}
/**
* Verify ArchiveFile was written correctly.
*/
async function verifyArchiveFile(data, accessor, options) {
	if (!options?.verify) return;
	stats.verifications++;
	const readBack = await accessor.loadArchive();
	if (!readBack) throw new DataSafetyError("ArchiveFile verification failed: file not found after write", "VERIFICATION_FAILED");
	if (readBack.archivedTasks.length !== data.archivedTasks.length) throw new DataSafetyError(`ArchiveFile verification failed: count mismatch. Expected ${data.archivedTasks.length}, got ${readBack.archivedTasks.length}`, "VERIFICATION_FAILED", {
		expected: data.archivedTasks.length,
		actual: readBack.archivedTasks.length
	});
}
/**
* Safe wrapper for DataAccessor.saveSessions()
*/
async function safeSaveSessions(accessor, data, cwd, options) {
	const opts = {
		...DEFAULT_SAFETY,
		...options
	};
	await accessor.saveSessions(data);
	stats.writes++;
	await verifySessions(data, accessor, opts);
	await checkpoint(`saved Sessions (${data.length} sessions)`, cwd, opts);
}
/**
* Safe wrapper for DataAccessor.saveArchive()
*/
async function safeSaveArchive(accessor, data, cwd, options) {
	const opts = {
		...DEFAULT_SAFETY,
		...options
	};
	await accessor.saveArchive(data);
	stats.writes++;
	await verifyArchiveFile(data, accessor, opts);
	await checkpoint(`saved Archive (${data.archivedTasks.length} tasks)`, cwd, opts);
}
/**
* Safe wrapper for single-task write operations (T5034).
*
* Performs:
* 1. Sequence validation
* 2. Write operation (caller-provided function)
* 3. Git checkpoint
*
* Verification is lightweight — no full-file read-back. The write
* itself is a targeted SQL operation that either succeeds or throws.
*/
async function safeSingleTaskWrite(_accessor, taskId, writeFn, cwd, options) {
	const opts = {
		...DEFAULT_SAFETY,
		...options
	};
	await ensureSequenceValid(cwd, opts);
	await writeFn();
	stats.writes++;
	await checkpoint(`single-task ${taskId}`, cwd, opts);
}
/**
* Safe wrapper for DataAccessor.appendLog()
*
* Note: Log appends are fire-and-forget (no verification)
* but we still checkpoint to ensure data is committed.
*/
async function safeAppendLog(accessor, entry, cwd, options) {
	const opts = {
		...DEFAULT_SAFETY,
		...options};
	await accessor.appendLog(entry);
	stats.writes++;
	await checkpoint("log entry", cwd, opts);
}

export { safeSaveArchive as a, safeAppendLog as b, safeSingleTaskWrite as c, allocateNextTaskId as d, safeSaveSessions as s };
//# sourceMappingURL=data-safety-central-vmfu_Y4J.js.map
