import {
  createWuPaths
} from "./chunk-6HO4GWJE.js";
import {
  STRING_LITERALS,
  WU_DEFAULTS
} from "./chunk-DWMLTXKQ.js";
import {
  WU_EXPOSURE_VALUES,
  WU_STATUS_GROUPS
} from "./chunk-V6OJGLBA.js";
import {
  ErrorCodes,
  createError,
  getErrorMessage
} from "./chunk-RXRKBBSM.js";

// ../core/dist/date-utils.js
import { format } from "date-fns";
function todayISO() {
  return format(/* @__PURE__ */ new Date(), "yyyy-MM-dd");
}
var DATE_FORMAT_ISO = "yyyy-MM-dd";
function normalizeToDateString(value) {
  if (value == null) {
    return void 0;
  }
  if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value)) {
    return value;
  }
  if (value instanceof Date) {
    return format(value, DATE_FORMAT_ISO);
  }
  if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}T/.test(value)) {
    return format(new Date(value), DATE_FORMAT_ISO);
  }
  try {
    const date = new Date(String(value));
    if (!isNaN(date.getTime())) {
      return format(date, DATE_FORMAT_ISO);
    }
  } catch {
  }
  return void 0;
}
function normalizeISODateTime(value) {
  if (value == null) {
    return void 0;
  }
  if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value)) {
    return value;
  }
  let dateInput = value;
  if (typeof value === "string" && /^\d+$/.test(value)) {
    dateInput = Number(value);
  }
  const date = new Date(dateInput);
  if (isNaN(date.getTime())) {
    return void 0;
  }
  return date.toISOString();
}

// ../core/dist/wu-schema.js
import { z } from "zod";
var VALID_STATUSES = [
  "todo",
  "ready",
  "backlog",
  "in_progress",
  "blocked",
  "done",
  "completed",
  "cancelled",
  "abandoned",
  "deferred",
  "closed",
  "superseded"
];
var PLACEHOLDER_SENTINEL = "[PLACEHOLDER]";
var MIN_DESCRIPTION_LENGTH = 50;
var WU_ID_FORMAT_MESSAGE = "Must be WU-XXX format";
var ACCEPTANCE_REQUIRED_MSG = "At least one acceptance criterion required";
var NEWLINE_PATTERN = /\\n|\n/;
var normalizedStringArray = z.array(z.string()).transform((arr) => arr.flatMap((s) => s.split(NEWLINE_PATTERN)).map((s) => s.trim()).filter(Boolean));
var _normalizedMultilineString = z.string().transform((s) => s.replace(/\\n/g, "\n"));
var filePathItem = z.string().refine((s) => !s.includes("\n") && !s.includes("\\n"), {
  message: "File path cannot contain newlines - split into separate array items"
});
var normalizedCodePaths = normalizedStringArray.pipe(z.array(filePathItem)).default([]);
var normalizedTestPaths = z.object({
  manual: normalizedStringArray.optional(),
  unit: normalizedStringArray.optional(),
  integration: normalizedStringArray.optional(),
  e2e: normalizedStringArray.optional()
}).optional();
var baseDescriptionField = z.string().min(1, "Description is required").transform((s) => s.replace(/\\n/g, "\n")).refine((val) => val.trim().length >= MIN_DESCRIPTION_LENGTH, {
  // WU-1539 fix: Use function message for dynamic interpolation
  message: `Description must be at least ${MIN_DESCRIPTION_LENGTH} characters`
});
var strictDescriptionField = z.string().min(1, "Description is required").transform((s) => s.replace(/\\n/g, "\n")).refine((val) => !val.includes(PLACEHOLDER_SENTINEL), {
  message: `Description cannot contain ${PLACEHOLDER_SENTINEL} marker`
}).refine((val) => val.trim().length >= MIN_DESCRIPTION_LENGTH, {
  // WU-1539 fix: Use function message for dynamic interpolation
  message: `Description must be at least ${MIN_DESCRIPTION_LENGTH} characters`
});
var hasItems = (value) => {
  if (Array.isArray(value)) {
    return value.length > 0;
  }
  if (typeof value === "object" && value !== null) {
    return Object.values(value).some(hasItems);
  }
  return false;
};
var checkStringsForPlaceholder = (value) => {
  if (typeof value === "string") {
    return !value.includes(PLACEHOLDER_SENTINEL);
  }
  if (Array.isArray(value)) {
    return value.every(checkStringsForPlaceholder);
  }
  if (typeof value === "object" && value !== null) {
    return Object.values(value).every(checkStringsForPlaceholder);
  }
  return true;
};
var baseAcceptanceField = z.union([
  // Flat array format (legacy): acceptance: ["item1", "item2"]
  // WU-1750: Normalize embedded newlines: ["1. a\n2. b"] → ["1. a", "2. b"]
  normalizedStringArray.pipe(z.array(z.string()).min(1, ACCEPTANCE_REQUIRED_MSG)),
  // Nested object format (structured): acceptance: { category1: ["item1"], category2: ["item2"] }
  z.record(z.string(), normalizedStringArray).refine((obj) => Object.values(obj).some(hasItems), {
    message: ACCEPTANCE_REQUIRED_MSG
  })
]);
var strictAcceptanceField = z.union([
  // Flat array format (legacy): acceptance: ["item1", "item2"]
  // WU-1750: Normalize embedded newlines: ["1. a\n2. b"] → ["1. a", "2. b"]
  normalizedStringArray.pipe(z.array(z.string()).min(1, ACCEPTANCE_REQUIRED_MSG)).refine((arr) => !arr.some((item) => item.includes(PLACEHOLDER_SENTINEL)), {
    message: `Acceptance criteria cannot contain ${PLACEHOLDER_SENTINEL} markers`
  }),
  // Nested object format (structured): acceptance: { category1: ["item1"], category2: ["item2"] }
  z.record(z.string(), normalizedStringArray).refine((obj) => Object.values(obj).some(hasItems), {
    message: ACCEPTANCE_REQUIRED_MSG
  }).refine((obj) => checkStringsForPlaceholder(obj), {
    message: `Acceptance criteria cannot contain ${PLACEHOLDER_SENTINEL} markers`
  })
]);
var sharedFields = {
  /** WU identifier (e.g., WU-1162) */
  id: z.string().regex(/^WU-\d+$/, "ID must match pattern WU-XXX"),
  /** Short title describing the work */
  title: z.string().min(1, "Title is required"),
  /** Lane assignment (parent or sub-lane) */
  lane: z.string().min(1, "Lane is required"),
  /** Work type classification */
  type: z.enum(["feature", "bug", "documentation", "process", "tooling", "chore", "refactor"], {
    error: "Invalid type"
  }).default(WU_DEFAULTS.type),
  /** Current status in workflow */
  status: z.enum(VALID_STATUSES, {
    error: `Invalid status. Valid values: ${VALID_STATUSES.join(", ")}`
  }).default(WU_DEFAULTS.status),
  /** Priority level */
  priority: z.enum(["P0", "P1", "P2", "P3"], {
    error: "Invalid priority"
  }).default(WU_DEFAULTS.priority),
  /** Creation date (YYYY-MM-DD) */
  created: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Created must be YYYY-MM-DD"),
  /** Files modified by this WU - WU-1750: Normalized to split embedded newlines */
  code_paths: normalizedCodePaths,
  /** Test specifications - WU-1750: All test arrays normalized */
  tests: normalizedTestPaths.default(WU_DEFAULTS.tests),
  /** Output artifacts (stamps, docs, etc.) - WU-1750: Normalized */
  artifacts: normalizedStringArray.optional().default(WU_DEFAULTS.artifacts),
  /** Upstream WU dependencies (informational, legacy field) - WU-1750: Normalized */
  dependencies: normalizedStringArray.optional().default(WU_DEFAULTS.dependencies),
  // === Initiative System Fields (WU-1246) ===
  /** Parent initiative reference (format: INIT-{number} or slug) */
  initiative: z.string().optional(),
  /** Phase number within parent initiative */
  phase: z.number().int().positive().optional(),
  /** WU IDs that this WU blocks (downstream dependencies) - WU-1750: Normalized + validated */
  blocks: normalizedStringArray.pipe(z.array(z.string().regex(/^WU-\d+$/, WU_ID_FORMAT_MESSAGE))).optional(),
  /** WU IDs that block this WU (upstream dependencies) - WU-1750: Normalized + validated */
  blocked_by: normalizedStringArray.pipe(z.array(z.string().regex(/^WU-\d+$/, WU_ID_FORMAT_MESSAGE))).optional(),
  /** Cross-cutting tags (orthogonal to initiative) - WU-1750: Normalized */
  labels: normalizedStringArray.optional(),
  // === End Initiative System Fields ===
  /**
   * WU-1683: First-class plan field, symmetric with initiative `related_plan`.
   * Set via wu:create --plan, wu:edit --plan, or plan:link --id WU-XXX.
   */
  plan: z.string().optional(),
  /**
   * WU-1833: References to plans, design docs, external specifications
   * WU-1834: Supports both flat string array AND nested object format for backwards compatibility
   *
   * Flat format (WU-1833+):  ['docs/plans/WU-XXX-plan.md']
   * Nested format (legacy):  [{file: 'docs/path.md', section: 'heading'}]
   * Mixed format allowed:    ['path.md', {section: 'heading'}]
   * Bare object (WU-428):    {file: 'docs/path.md', section: 'heading'}
   */
  spec_refs: z.union([
    // Single object format (WU-428 style): {file: '...', section: '...'}
    z.object({
      file: z.string().optional(),
      section: z.string()
    }),
    // Array format (WU-1833+): strings, objects, or mixed
    z.array(z.union([
      z.string(),
      // Flat format: 'docs/path.md'
      z.object({
        // Nested format: {file: 'path', section: 'heading'}
        file: z.string().optional(),
        section: z.string()
      })
    ]))
  ]).optional(),
  /** Known risks or constraints - WU-1750: Normalized */
  risks: normalizedStringArray.optional().default(WU_DEFAULTS.risks),
  /**
   * Free-form notes - supports string or array (auto-converted to string)
   * WU-1750: Normalizes escaped newlines (\\n → actual newlines)
   */
  notes: z.union([
    z.string(),
    z.array(z.string())
    // Legacy array format - will be converted
  ]).optional().transform((val) => {
    if (Array.isArray(val)) {
      return val.filter((s) => s.trim().length > 0).join(STRING_LITERALS.NEWLINE);
    }
    if (typeof val === "string") {
      return val.replace(/\\n/g, "\n");
    }
    return val ?? WU_DEFAULTS.notes;
  }),
  /** Requires human review before merge */
  requires_review: z.boolean().optional().default(WU_DEFAULTS.requires_review),
  /** Locked state (done WUs only) */
  locked: z.boolean().optional(),
  /** Completion date (done WUs only) - auto-normalized to ISO datetime */
  completed_at: z.string().optional().transform((val) => normalizeISODateTime(val)),
  /** Claimed mode (worktree/branch-only/worktree-pr/branch-pr) */
  claimed_mode: z.enum(["worktree", "branch-only", "worktree-pr", "branch-pr"]).optional(),
  /**
   * WU-1589: Canonical branch name for this WU claim.
   *
   * Set at claim time to record the actual branch used.
   * Used by defaultBranchFrom() as highest-priority source for branch resolution.
   * Essential for branch-pr mode where cloud agents may use non-lane-derived branch names.
   * Cleared on rollback/release/recover when resetting to ready.
   */
  claimed_branch: z.string().optional(),
  /** Assigned agent email */
  assigned_to: z.string().email().optional(),
  /** Claim timestamp - auto-normalized to ISO datetime */
  claimed_at: z.string().optional().transform((val) => normalizeISODateTime(val)),
  /** Block reason (blocked WUs only) */
  blocked_reason: z.string().optional(),
  /** Worktree path (claimed WUs only) */
  worktree_path: z.string().optional(),
  /** Current active session ID (WU-1438: auto-set on claim, cleared on done) */
  session_id: z.string().uuid().optional(),
  /** Agent sessions (issue logging metadata, WU-1231) */
  agent_sessions: z.array(z.object({
    session_id: z.string().uuid(),
    started: z.string().datetime(),
    completed: z.string().datetime().optional(),
    agent_type: z.enum([
      "claude-code",
      "codex-cli",
      "cursor",
      "gemini-cli",
      "windsurf",
      "copilot",
      "other"
    ]),
    context_tier: z.union([z.literal(1), z.literal(2), z.literal(3)]),
    incidents_logged: z.number().int().min(0).default(0),
    incidents_major: z.number().int().min(0).default(0),
    artifacts: z.array(z.string()).optional()
  })).optional(),
  // === Exposure System Fields (WU-1998) ===
  /**
   * WU-1998: Exposure level - defines how the WU exposes functionality to users
   *
   * Valid values:
   * - 'ui': User-facing UI changes (pages, components, widgets)
   * - 'api': API endpoints called by UI or external clients
   * - 'backend-only': Backend-only changes (no user visibility)
   * - 'documentation': Documentation changes only
   *
   * Optional during transition period, will become required after backlog update.
   */
  exposure: z.enum(WU_EXPOSURE_VALUES, {
    error: `Invalid exposure value. Valid values: ${WU_EXPOSURE_VALUES.join(", ")}`
  }).optional(),
  /**
   * WU-1998: User journey description for user-facing WUs
   *
   * Recommended for exposure: 'ui' and 'api'.
   * Describes the end-user interaction flow affected by this WU.
   */
  user_journey: z.string().optional(),
  /**
   * WU-1998: Related UI WUs for backend/API changes
   *
   * For WUs with exposure: 'api', this field lists UI WUs that consume the API.
   * Ensures backend features have corresponding UI coverage.
   * Each entry must match WU-XXX format.
   */
  ui_pairing_wus: normalizedStringArray.pipe(z.array(z.string().regex(/^WU-\d+$/, WU_ID_FORMAT_MESSAGE))).optional(),
  /**
   * WU-2022: Navigation path for UI-exposed features
   *
   * For WUs with exposure: 'ui', specifies the route where the feature is accessible.
   * Used by wu:done to verify that UI features are actually navigable.
   * Prevents "orphaned code" where features exist but users cannot access them.
   *
   * Example: '/dashboard', '/settings/preferences', '/space'
   */
  navigation_path: z.string().optional(),
  // === End Exposure System Fields ===
  // === Sizing Estimate Fields (WU-2141) ===
  /**
   * WU-2141: Optional sizing estimate metadata.
   *
   * Records expected WU complexity for tooling-backed sizing enforcement.
   * Absent for historical WUs (backward compatible).
   *
   * Fields:
   * - estimated_files: Expected number of files to modify
   * - estimated_tool_calls: Expected tool call count
   * - strategy: Execution strategy from wu-sizing-guide.md
   * - exception_type: Override type when thresholds intentionally exceeded
   * - exception_reason: Justification for the exception (required with exception_type)
   */
  sizing_estimate: z.object({
    estimated_files: z.number().int().min(0),
    estimated_tool_calls: z.number().int().min(0),
    strategy: z.enum([
      "single-session",
      "checkpoint-resume",
      "orchestrator-worker",
      "decomposition"
    ]),
    exception_type: z.enum(["docs-only", "shallow-multi-file"]).optional(),
    exception_reason: z.string().optional()
  }).refine((data) => {
    if (data.exception_type !== void 0) {
      return data.exception_reason !== void 0 && data.exception_reason.trim().length > 0;
    }
    return true;
  }, {
    message: "sizing_estimate.exception_reason is required and must be non-empty when exception_type is set",
    path: ["exception_reason"]
  }).optional(),
  // === End Sizing Estimate Fields ===
  // === Agent-First Approval Fields (WU-2079 → WU-2080) ===
  /**
   * WU-2080: Escalation triggers detected for this WU
   *
   * Agent-first model: agents auto-approve by default.
   * Human escalation only when these triggers are detected:
   * - sensitive_data: Changes to sensitive data handling
   * - security_p0: P0 security incident or vulnerability
   * - budget: Budget/resource allocation above threshold
   * - cross_lane_arch: Cross-lane architectural decision
   *
   * Empty array = no escalation needed, agent proceeds autonomously.
   */
  escalation_triggers: z.array(z.enum(["sensitive_data", "security_p0", "budget", "cross_lane_arch"])).optional().default([]),
  /**
   * WU-2080: Human escalation required flag
   *
   * Auto-set to true when escalation_triggers is non-empty.
   * When true, wu:done requires human confirmation before completion.
   */
  requires_human_escalation: z.boolean().optional().default(false),
  /**
   * WU-2080: Email(s) of approvers who signed off
   *
   * Auto-populated with claiming agent at wu:claim.
   * Additional human approvers added when escalation is resolved.
   */
  approved_by: z.array(z.string().email()).optional(),
  /**
   * WU-2080: Timestamp when approval was granted
   *
   * Auto-set at wu:claim for agent auto-approval.
   * Updated when human escalation is resolved.
   */
  approved_at: z.string().optional().transform((val) => normalizeISODateTime(val)),
  /**
   * WU-2080: Human who resolved escalation (if UnsafeAny)
   *
   * Only set when requires_human_escalation was true and resolved.
   */
  escalation_resolved_by: z.string().email().optional(),
  /**
   * WU-2080: Timestamp when human resolved escalation
   */
  escalation_resolved_at: z.string().optional().transform((val) => normalizeISODateTime(val)),
  // Legacy fields (deprecated, kept for backwards compatibility)
  /** @deprecated Use escalation_triggers instead */
  requires_cso_approval: z.boolean().optional().default(false),
  /** @deprecated Use escalation_triggers instead */
  requires_cto_approval: z.boolean().optional().default(false),
  /** @deprecated Use escalation_triggers instead */
  requires_design_approval: z.boolean().optional().default(false)
  // === End Agent-First Approval Fields ===
};
var BaseWUSchema = z.object({
  ...sharedFields,
  description: baseDescriptionField,
  acceptance: baseAcceptanceField
});
var ReadyWUSchema = BaseWUSchema;
var WUSchema = z.object({
  ...sharedFields,
  description: strictDescriptionField,
  acceptance: strictAcceptanceField
});
function validateWU(data) {
  return WUSchema.safeParse(data);
}
function validateReadyWU(data) {
  return ReadyWUSchema.safeParse(data);
}
function validateDoneWU(wu) {
  const errors = [];
  if (wu.type !== "documentation" && wu.type !== "process") {
    if (!wu.code_paths || wu.code_paths.length === 0) {
      errors.push("Code paths required for non-documentation WUs");
    }
  }
  return {
    valid: errors.length === 0,
    errors
  };
}
function validateApprovalGates(wu) {
  const errors = [];
  const warnings = [];
  const triggers = wu.escalation_triggers || [];
  const requiresEscalation = wu.requires_human_escalation || triggers.length > 0;
  if (requiresEscalation) {
    const resolved = wu.escalation_resolved_by && wu.escalation_resolved_at;
    if (!resolved) {
      errors.push(`Human escalation required for: ${triggers.join(", ")}
   To resolve: pnpm wu:escalate --resolve --id ${wu.id}`);
    }
  }
  if (wu.requires_cso_approval || wu.requires_cto_approval || wu.requires_design_approval) {
    warnings.push("Using deprecated requires_X_approval fields. Migrate to escalation_triggers model.");
  }
  return {
    valid: errors.length === 0,
    errors,
    warnings
  };
}
function detectEscalationTriggers(wu) {
  const triggers = [];
  const lane = (wu.lane || "").toLowerCase();
  const codePaths = wu.code_paths || [];
  const sensitivePatterns = ["pii", "user-data", "auth", "credentials"];
  const touchesSensitive = codePaths.some((p) => sensitivePatterns.some((pat) => p.toLowerCase().includes(pat)));
  if (touchesSensitive || lane.includes("pii")) {
    triggers.push("sensitive_data");
  }
  if (wu.priority === "P0" && lane.includes("security")) {
    triggers.push("security_p0");
  }
  return triggers;
}
function generateAutoApproval(wu, agentEmail) {
  const triggers = detectEscalationTriggers(wu);
  const now = (/* @__PURE__ */ new Date()).toISOString();
  return {
    approved_by: [agentEmail],
    approved_at: now,
    escalation_triggers: triggers,
    requires_human_escalation: triggers.length > 0
  };
}
function validateAndNormalizeWUYAML(data) {
  const result = WUSchema.safeParse(data);
  if (!result.success) {
    const errors = result.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`);
    return {
      valid: false,
      normalized: null,
      errors,
      wasNormalized: false
    };
  }
  const normalized = result.data;
  const original = typeof data === "object" && data !== null ? data : {};
  const wasNormalized = detectNormalizationChanges(original, normalized);
  return {
    valid: true,
    normalized,
    errors: [],
    wasNormalized
  };
}
function validateWUCompleteness(wu) {
  const warnings = [];
  const status = wu.status ?? "";
  const isTerminal = WU_STATUS_GROUPS.TERMINAL.includes(status);
  if (isTerminal) {
    return { warnings };
  }
  const type = wu.type || "feature";
  const requiresContext = ["feature", "bug", "refactor"].includes(type);
  if (!requiresContext) {
    return { warnings };
  }
  if (!wu.notes || wu.notes.trim().length === 0) {
    warnings.push(`${wu.id}: Missing 'notes' field. Add implementation context, deployment instructions, or plan links.`);
  }
  const hasManualTests = wu.tests?.manual && wu.tests.manual.length > 0;
  if (!hasManualTests) {
    warnings.push(`${wu.id}: Missing 'tests.manual' field. Add manual verification steps for acceptance criteria.`);
  }
  if (type === "feature") {
    const specRefs = wu.spec_refs;
    const hasSpecRefs = !!specRefs && (specRefs.length ?? 0) > 0;
    if (!hasSpecRefs) {
      const plansDirHint = `${createWuPaths().PLANS_DIR().replace(/\/+$/, "")}/`;
      warnings.push(`${wu.id}: Missing 'spec_refs' field. Link to plan file (${plansDirHint}, lumenflow://plans/, or ~/.lumenflow/plans/) for traceability.`);
    }
  }
  return { warnings };
}
function detectNormalizationChanges(original, normalized) {
  if (original.description !== normalized.description) {
    return true;
  }
  const origPaths = Array.isArray(original.code_paths) ? original.code_paths : [];
  const normPaths = Array.isArray(normalized.code_paths) ? normalized.code_paths : [];
  if (origPaths.length !== normPaths.length) {
    return true;
  }
  for (let i = 0; i < origPaths.length; i++) {
    if (origPaths[i] !== normPaths[i]) {
      return true;
    }
  }
  if (Array.isArray(original.acceptance) && Array.isArray(normalized.acceptance)) {
    if (original.acceptance.length !== normalized.acceptance.length) {
      return true;
    }
    for (let i = 0; i < original.acceptance.length; i++) {
      if (original.acceptance[i] !== normalized.acceptance[i]) {
        return true;
      }
    }
  }
  return false;
}

// ../core/dist/wu-yaml.js
import { existsSync, readFileSync, writeFileSync, promises as fs } from "fs";
import { parse, stringify } from "yaml";
var YAML_SCALAR_TYPES = Object.freeze({
  /** Unquoted string (when safe) */
  PLAIN: "PLAIN",
  /** Single-quoted string */
  QUOTE_SINGLE: "QUOTE_SINGLE",
  /** Double-quoted string */
  QUOTE_DOUBLE: "QUOTE_DOUBLE",
  /** Block literal (|) */
  BLOCK_LITERAL: "BLOCK_LITERAL",
  /** Block folded (>) */
  BLOCK_FOLDED: "BLOCK_FOLDED"
});
var YAML_LINE_WIDTH = 100;
var YAML_STRINGIFY_OPTIONS = Object.freeze({
  lineWidth: YAML_LINE_WIDTH,
  singleQuote: true,
  defaultKeyType: YAML_SCALAR_TYPES.PLAIN
});
function parseWUYaml(text, sourcePath) {
  try {
    return parse(text);
  } catch (e) {
    const msg = getErrorMessage(e);
    throw createError(ErrorCodes.YAML_PARSE_ERROR, `Failed to parse YAML ${sourcePath}: ${msg}`, {
      path: sourcePath,
      originalError: msg
    });
  }
}
function validateWUId(doc, expectedId, wuPath) {
  const parsed = doc;
  if (!parsed || parsed.id !== expectedId) {
    throw createError(ErrorCodes.WU_NOT_FOUND, `WU YAML id mismatch. Expected ${expectedId}, found ${parsed && parsed.id}`, { path: wuPath, expectedId, foundId: parsed && parsed.id });
  }
  return parsed;
}
function readWU(wuPath, expectedId) {
  if (!existsSync(wuPath)) {
    throw createError(ErrorCodes.FILE_NOT_FOUND, `WU file not found: ${wuPath}`, {
      path: wuPath,
      expectedId
    });
  }
  const text = readFileSync(wuPath, { encoding: "utf-8" });
  const doc = parseWUYaml(text, wuPath);
  return validateWUId(doc, expectedId, wuPath);
}
async function readWUAsync(wuPath, expectedId) {
  try {
    const text = await fs.readFile(wuPath, { encoding: "utf-8" });
    const doc = parseWUYaml(text, wuPath);
    return validateWUId(doc, expectedId, wuPath);
  } catch (err) {
    const errObj = err;
    if (errObj.code === "ENOENT") {
      throw createError(ErrorCodes.FILE_NOT_FOUND, `WU file not found: ${wuPath}`, {
        path: wuPath,
        expectedId
      });
    }
    throw err;
  }
}
function parseYAML(text) {
  return parse(text);
}
function stringifyYAML(doc, options = {}) {
  return stringify(doc, { ...YAML_STRINGIFY_OPTIONS, ...options });
}
function readWURaw(yamlPath) {
  if (!existsSync(yamlPath)) {
    throw createError(ErrorCodes.FILE_NOT_FOUND, `YAML file not found: ${yamlPath}`, {
      path: yamlPath
    });
  }
  const text = readFileSync(yamlPath, { encoding: "utf-8" });
  return parseWUYaml(text, yamlPath);
}
function writeWU(wuPath, doc) {
  BaseWUSchema.parse(doc);
  const out = stringify(doc, YAML_STRINGIFY_OPTIONS);
  writeFileSync(wuPath, out, { encoding: "utf-8" });
}
function appendNote(doc, note) {
  if (!note)
    return;
  const existing = doc.notes;
  if (existing === void 0 || existing === null || existing === "") {
    doc.notes = note;
  } else if (Array.isArray(existing)) {
    const joined = existing.filter(Boolean).join(STRING_LITERALS.NEWLINE).trimEnd();
    doc.notes = joined ? `${joined}${STRING_LITERALS.NEWLINE}${note}` : note;
  } else if (typeof existing === "string") {
    const trimmed = existing.trimEnd();
    doc.notes = trimmed ? `${trimmed}${STRING_LITERALS.NEWLINE}${note}` : note;
  } else {
    doc.notes = note;
  }
}

export {
  todayISO,
  normalizeToDateString,
  PLACEHOLDER_SENTINEL,
  BaseWUSchema,
  validateWU,
  validateReadyWU,
  validateDoneWU,
  validateApprovalGates,
  generateAutoApproval,
  validateAndNormalizeWUYAML,
  validateWUCompleteness,
  readWU,
  readWUAsync,
  parseYAML,
  stringifyYAML,
  readWURaw,
  writeWU,
  appendNote
};
