UNPKG

paqad-ai

Version:

Spec-driven development framework — AI agents that think before they type

9,152 lines • 328 kB
import { createRequire as __paqadCreateRequire } from 'module'; const require = __paqadCreateRequire(import.meta.url);
import {
  formatStageAgentWriteSummary,
  stageAgentWriteDecision
} from "./chunk-E4ZQOAV4.js";
import {
  findAgentPaths,
  findSkillPaths,
  readSkillPathContext,
  resolveFlooredMode,
  shouldStripAiAttribution
} from "./chunk-KXN6TRQP.js";
import {
  subtractUnchangedBaselineFiles
} from "./chunk-Z4GLUCC5.js";
import {
  FREEZE_NEXT_LINE,
  FREEZE_NEXT_V2_LINE,
  GATE_NO_DESIGN_LINE,
  gateBlockLine,
  readHld
} from "./chunk-LGAFOBYN.js";
import {
  readStackSnapshotSync
} from "./chunk-WRHJCBRI.js";
import {
  expertBudget,
  isExpertRole
} from "./chunk-IKAOR4NY.js";
import {
  formatUnresolvedStageSummary,
  unresolvedStageAgent
} from "./chunk-GCMANDHS.js";
import {
  compileRules,
  detectDecisionForks,
  isAlwaysLoadRule,
  isCompiledRulesStale,
  readCompiledRules,
  ruleTriggersMatch,
  writeCompiledRules
} from "./chunk-7Q65STES.js";
import {
  atomicWriteFile,
  getPacksForFrameworks
} from "./chunk-K4E6ACLH.js";
import {
  DecisionStore,
  computeDecisionFingerprint,
  createPendingDecision,
  readContractDecisions,
  recordDecisionReuse
} from "./chunk-4ZAWKFXT.js";
import {
  appendHooksLog
} from "./chunk-4S3XFJAH.js";
import {
  getPrimaryStack
} from "./chunk-HNYZUZ7D.js";
import {
  readProjectProfile
} from "./chunk-AK3HYELM.js";
import {
  DECISION_CATEGORY_DEFAULTS
} from "./chunk-6XDDIYNJ.js";
import {
  recordProjectEvent
} from "./chunk-I2G54BH7.js";
import {
  computeRuleScriptsDigest,
  enforceRuleScripts,
  executeRuleScriptAsync,
  getCapability,
  isOnPath,
  loadRuleScriptMap,
  missingBinaries,
  parseScriptHeader,
  projectRelativeTargets,
  readCapabilityDigest,
  readCapabilityVersions,
  runPool
} from "./chunk-SS625UO5.js";
import {
  hudLastBlockPath,
  safeSessionId,
  writeFileAtomic
} from "./chunk-3UV6TK6X.js";
import {
  appendRuleRun,
  readFeatureEvidence
} from "./chunk-5ETIY5I3.js";
import {
  activeChangeFiles,
  loadChangeEvidence
} from "./chunk-EQYAXXZO.js";
import {
  execa
} from "./chunk-MDXUST3Z.js";
import {
  markerBatchNarration
} from "./chunk-22I2D24D.js";
import {
  bundleWriterFor,
  classifyBundlePath,
  normalizeArtifactPath,
  parseAndRecordMarkers
} from "./chunk-OAEANL22.js";
import {
  isFeatureDevelopmentRoute,
  readWorkflowState
} from "./chunk-VOJO7HJ6.js";
import {
  backfillFeatureSlug,
  readPendingImpact,
  resolveEffectiveLane
} from "./chunk-7D5MLTG3.js";
import {
  levenshtein
} from "./chunk-Y3O45QYV.js";
import {
  stripTrailingChars,
  toPosixPath,
  toProjectRelativePath
} from "./chunk-LYNDVGNE.js";
import {
  appendFeatureStageRow,
  currentFeature,
  documentSessionId,
  foldFeature,
  isPaqadArtifactPath,
  pendingTicketActions,
  readChangeStartPoint,
  readFeatureDelivery,
  readFeatureStageUnit,
  recordShip,
  resolveDeliveryFeatureByBranch,
  stampFeatureDocument,
  withFileLockSync
} from "./chunk-5M55PDWL.js";
import {
  releaseLock,
  tryAcquireLock
} from "./chunk-TINPSX3N.js";
import {
  PRE_CODE_STAGES,
  SPEC_CORRECTION_KIND,
  SPEC_STEP_KIND,
  STAGE_AGENT_KIND,
  STAGE_EVIDENCE_DOC_TYPE,
  isArtifactBearingStage,
  isBundleClosed,
  isBundleMaterialized,
  isStageAgent,
  listFeatureDirs,
  listInFlightFeatures,
  readFeatureRecord,
  requiredPreCodeStages,
  updateFeatureRecord,
  validatePlanRecord,
  validateReviewRecord
} from "./chunk-7TBVBCDH.js";
import {
  ENVELOPE_HEADER_KEYS,
  buildDocumentEnvelope,
  buildHeadingPath,
  buildPlanRecord,
  buildReviewRecord,
  buildTextHeader,
  classifySection,
  makeDeterministicGeneratedId,
  normalizeCell,
  parseHeadings,
  renderFrontMatter,
  rowRecordedAt,
  sha256Hex,
  splitFrontMatter,
  splitLines
} from "./chunk-RJMJYPHL.js";
import {
  AUTHORING_BATCH_MODES,
  AUTHORING_SECTION,
  FRAMEWORK_CONFIG_SPECS,
  configSaysPaqadDisabled,
  envConfigValue,
  layeredConfigMap,
  loadGitWorkflow,
  readConfigsDir,
  readDotConfig,
  require_ajv,
  require_dist,
  resolveFrameworkConfig,
  resolveNumericConfig,
  resolveRuleGateConfig,
  setConfigValue
} from "./chunk-ACJQW7SB.js";
import {
  renameWithRetrySync
} from "./chunk-Z3NGWHFX.js";
import {
  getRuntimeRoot
} from "./chunk-YWAORJG6.js";
import {
  readUnitFile,
  sessionLedgerDir
} from "./chunk-Q353EDKY.js";
import {
  resolveSessionId
} from "./chunk-SG6LXLDH.js";
import {
  readSessionControl
} from "./chunk-FMQ5YRSP.js";
import {
  CLARIFICATION_DOC_TYPE,
  EXPERTS_DOC_TYPE,
  FEATURE_BUNDLE_FILES,
  FEATURE_DOC_SCHEMA_VERSION,
  REQUEST_DOC_TYPE,
  SCREENSHOTS_DIR,
  SPECIFICATION_DOC_TYPE,
  SPEC_SOURCE_DOC_TYPE,
  dirname,
  featureChangeKey,
  featureDir,
  featureFilePath,
  isAbsolute,
  join,
  parseFeatureDirName,
  relative,
  resolve,
  slugifyAscii
} from "./chunk-AZLA53A6.js";
import {
  PATHS
} from "./chunk-IHMBVS6H.js";
import {
  __toESM,
  init_esm_shims
} from "./chunk-IDFRWBMN.js";

// src/compliance/constants.ts
init_esm_shims();
var COMPLIANCE_SCHEMA_VERSION = 1;
var SPEC_REVIEW_SCHEMA_VERSION = 1;
var DEFAULT_OBLIGATION_INDEX_PATH = ".paqad/compliance/obligation-index.json";
function slugifySpec(specFile) {
  const segments = specFile.replace(/\.[^.]+$/, "").split(/[\\/]/);
  const base = segments[segments.length - 1];
  return slugifyAscii(base);
}
function specIndexPath(specFile) {
  return `.paqad/compliance/${slugifySpec(specFile)}/obligations.json`;
}
function specReportPath(specFile) {
  return `.paqad/compliance/${slugifySpec(specFile)}/report.json`;
}
function specReviewPath(specFile) {
  return `.paqad/compliance/${slugifySpec(specFile)}/spec-review.json`;
}

// src/compliance/spec-review.ts
init_esm_shims();

// src/compliance/spec-review-detectors/registry.ts
init_esm_shims();

// src/compliance/spec-review-detectors/boundary-detector.ts
init_esm_shims();

// src/compliance/spec-review-detectors/shared.ts
init_esm_shims();
function normalizeText(value) {
  return value.toLowerCase().replace(/[`*_]/g, "").replace(/\s+/g, " ").trim();
}
function makeLocation(line) {
  return {
    section: line.section,
    line_range: [line.line, line.line],
    text_excerpt: line.text.trim()
  };
}

// src/compliance/spec-review-detectors/boundary-detector.ts
var boundaryDetector = {
  name: "boundary_gap",
  detect(context) {
    const defects = [];
    for (const line of context.review_lines) {
      const normalized = normalizeText(line.text);
      const limitMatch = /\b(?:maximum|max(?:imum)?|minimum|min|threshold|limit)\b.*?(\d[\d,]*)/.exec(normalized);
      if (!limitMatch) continue;
      if (/exactly|equal to|at the boundary|inclusive/.test(normalized)) continue;
      const value = limitMatch[1].replace(/,/g, "");
      const relatedLines = context.review_lines.filter((candidate) => {
        const text = normalizeText(candidate.text);
        return text.includes(value) || /\btruncate|truncation|append truncation marker|clamp|reject\b/.test(text);
      });
      const hasBoundaryRule = relatedLines.some(
        (candidate) => /exactly|equal to|at the boundary|inclusive/.test(normalizeText(candidate.text))
      );
      const hasTransitionBehavior = relatedLines.some(
        (candidate) => /\btruncate|truncation|append truncation marker|clamp|reject\b/.test(
          normalizeText(candidate.text)
        )
      );
      if (hasTransitionBehavior && !hasBoundaryRule) {
        defects.push({
          category: "boundary_gap",
          severity: "major",
          description: `The spec defines a ${value}-value limit but never states the exact boundary behavior.`,
          locations: [makeLocation(line)],
          suggested_resolution: `State what happens at exactly ${value}.`
        });
      }
    }
    return defects;
  }
};

// src/compliance/spec-review-detectors/contradiction-detector.ts
init_esm_shims();
var contradictionDetector = {
  name: "contradiction",
  detect(context) {
    const defects = [];
    const exclusionLine = context.review_lines.find(
      (line) => /exclude(?:d)? .*denominator|excluded from the denominator/.test(normalizeText(line.text))
    );
    const formulaLine = context.review_lines.find(
      (line) => /(?:compliance_)?ratio\s*=.*\/\s*total\b|covered\s*\/\s*total\b/.test(
        normalizeText(line.text)
      )
    );
    if (exclusionLine && formulaLine) {
      defects.push({
        category: "contradiction",
        severity: "critical",
        description: "The denominator rule excludes obligations, but the stated formula still divides by total.",
        locations: [makeLocation(exclusionLine), makeLocation(formulaLine)].sort(
          (a, b) => a.line_range[0] - b.line_range[0]
        ),
        suggested_resolution: "Clarify one denominator rule and align the formula with that rule."
      });
    }
    const alwaysLines = context.review_lines.filter(
      (line) => /\balways\b/.test(normalizeText(line.text))
    );
    const neverLines = context.review_lines.filter(
      (line) => /\bnever\b/.test(normalizeText(line.text))
    );
    for (const left of alwaysLines) {
      const leftText = normalizeText(left.text);
      const quotedSubject = extractQuotedSubject(left.text);
      for (const right of neverLines) {
        const rightText = normalizeText(right.text);
        const subject = quotedSubject ?? extractQuotedSubject(right.text) ?? inferSharedSubject(leftText, rightText);
        if (!subject) continue;
        if (left.line === right.line) continue;
        if (!leftText.includes(subject) || !rightText.includes(subject)) continue;
        defects.push({
          category: "contradiction",
          severity: "major",
          description: `The spec gives both "always" and "never" guidance for ${subject}.`,
          locations: [makeLocation(left), makeLocation(right)].sort(
            (a, b) => a.line_range[0] - b.line_range[0]
          ),
          suggested_resolution: `Choose one rule for ${subject} or define when each rule applies.`
        });
        break;
      }
    }
    return defects;
  }
};
function extractQuotedSubject(text) {
  const match = /[`'"]([^`'"]+)[`'"]/.exec(text);
  return match ? normalizeText(match[1]) : null;
}
function inferSharedSubject(left, right) {
  const stopWords = /* @__PURE__ */ new Set([
    "the",
    "a",
    "an",
    "and",
    "or",
    "to",
    "for",
    "of",
    "in",
    "on",
    "with",
    "must",
    "should",
    "always",
    "never",
    "be",
    "is",
    "are",
    "when",
    "if",
    "then"
  ]);
  const leftTokens = tokenize(left).filter((token) => !stopWords.has(token));
  const rightTokens = new Set(tokenize(right).filter((token) => !stopWords.has(token)));
  const overlap = leftTokens.filter((token) => rightTokens.has(token));
  if (overlap.length === 0) return null;
  return overlap.sort((a, b) => b.length - a.length)[0];
}
function tokenize(text) {
  return text.split(/[^a-z0-9_]+/g).map((token) => token.trim()).filter((token) => token.length >= 3);
}

// src/compliance/spec-review-detectors/formula-detector.ts
init_esm_shims();
var DIVISION_PATTERN = /\b([a-z_]\w*)\s*\/\s*(?:\(([^)]+)\)|([a-z_]\w*))/;
var ZERO_CASE_PATTERN = /all\s+\w+(?:\s+\w+)*\s+(?:may|can|could)\s+be\s+(?:indeterminate|zero|empty|null|absent)|all\s+\w+(?:\s+\w+)*\s+are\s+(?:indeterminate|zero|empty|absent)|(?:may|can|could)\s+have\s+(?:zero|no)\s+\w+|\bno\s+\w+\s+(?:exist|remain|are\s+present)/;
var formulaDetector = {
  name: "formula_inconsistency",
  detect(context) {
    const defects = [];
    const canonicalFormulaLine = context.review_lines.find(
      (line) => /\/\s*\(\s*total\s*-\s*indeterminate\s*\)/.test(normalizeText(line.text))
    );
    const canonicalZeroCaseLine = context.review_lines.find(
      (line) => /all obligations .* indeterminate|all .* may be indeterminate/.test(normalizeText(line.text))
    );
    if (canonicalFormulaLine && canonicalZeroCaseLine) {
      defects.push({
        category: "formula_inconsistency",
        severity: "critical",
        description: "The formula can divide by zero when the spec allows every obligation to be indeterminate.",
        locations: [makeLocation(canonicalFormulaLine), makeLocation(canonicalZeroCaseLine)].sort(
          (a, b) => a.line_range[0] - b.line_range[0]
        ),
        suggested_resolution: "Define the zero-denominator behavior or revise the formula so that this input remains valid."
      });
    }
    for (const line of context.review_lines) {
      const normalized = normalizeText(line.text);
      const divMatch = DIVISION_PATTERN.exec(normalized);
      if (!divMatch) continue;
      const denomExpr = (divMatch[2] ?? divMatch[3]).trim();
      const denomWord = /\b([a-z_]\w*)\b/.exec(denomExpr)?.[1];
      if (!denomWord || denomWord.length < 3) continue;
      if (/http|url|path|file|true|false/.test(denomWord)) continue;
      const zeroCaseLine = context.review_lines.find((candidate) => {
        if (candidate.line === line.line) return false;
        const candidateText = normalizeText(candidate.text);
        return candidateText.includes(denomWord) && ZERO_CASE_PATTERN.test(candidateText);
      });
      if (zeroCaseLine) {
        defects.push({
          category: "formula_inconsistency",
          severity: "critical",
          description: `The formula divides by an expression containing "${denomWord}", but the spec allows a state where that value reaches zero.`,
          locations: [makeLocation(line), makeLocation(zeroCaseLine)].sort(
            (a, b) => a.line_range[0] - b.line_range[0]
          ),
          suggested_resolution: `Guard the formula against a zero "${denomWord}" value, or define behavior for that edge case.`
        });
      }
    }
    return dedupe(defects);
  }
};
function dedupe(items) {
  const seen = /* @__PURE__ */ new Set();
  return items.filter((item) => {
    const key = item.locations.map((l) => l.line_range[0]).join(":");
    if (seen.has(key)) return false;
    seen.add(key);
    return true;
  });
}

// src/compliance/spec-review-detectors/goal-conflict-detector.ts
init_esm_shims();
var CONFLICT_RULES = [
  {
    description: "A lossless extraction goal conflicts with a rule that ignores partially matching tables.",
    preservePattern: /\blossless\b|must preserve all data/,
    discardPattern: /\bignore\b.*\btable\b|non-matching tables .* ignored/,
    suggested_resolution: "Define which rule wins when a structure is partially recognized."
  },
  {
    description: 'A "must preserve all" completeness requirement conflicts with a rule that excludes or skips items.',
    preservePattern: /\bmust preserve all\b|\bno data (?:may be )?lost\b|\bcompleteness (?:is )?required\b|\bnothing (?:should|must) be (?:lost|dropped|discarded)\b/,
    discardPattern: /(?:\b(?:exclud\w*|skip\w*|filter(?:\s+out)?|discard\w*|omit\w*)\b.*\b(?:items?|entries?|records?|rows?|fields?)\b|\b(?:items?|entries?|records?|rows?|fields?)\b.*\b(?:exclud\w*|skip\w*|filter(?:\s+out)?|discard\w*|omit\w*)\b)/,
    suggested_resolution: "Specify what takes priority: completeness or the exclusion rule, and define behavior for items caught by both."
  },
  {
    description: 'An all-or-nothing "must succeed for all items" requirement conflicts with language that allows partial or incomplete processing.',
    preservePattern: /\bmust (?:succeed|complete|process)\s+(?:for\s+)?all\b|\ball\s+\w+\s+must\s+be\s+(?:processed|handled|covered)\b/,
    discardPattern: /\bpartial(?:ly)?\b|\bmay\s+(?:fail|be\s+skipped|be\s+incomplete)\b|\bnot\s+all\s+\w+\s+(?:will|may|might)\b/,
    suggested_resolution: "Reconcile the all-or-nothing requirement with the partial-processing allowance."
  }
];
var goalConflictDetector = {
  name: "goal_conflict",
  detect(context) {
    const defects = [];
    for (const rule of CONFLICT_RULES) {
      const preserveLine = context.review_lines.find(
        (line) => rule.preservePattern.test(normalizeText(line.text))
      );
      const discardLine = context.review_lines.find(
        (line) => rule.discardPattern.test(normalizeText(line.text))
      );
      if (preserveLine && discardLine) {
        defects.push({
          category: "goal_conflict",
          severity: "major",
          description: rule.description,
          locations: [makeLocation(preserveLine), makeLocation(discardLine)].sort(
            (a, b) => a.line_range[0] - b.line_range[0]
          ),
          suggested_resolution: rule.suggested_resolution
        });
      }
    }
    return defects;
  }
};

// src/compliance/spec-review-detectors/missing-negative-detector.ts
init_esm_shims();
var POSITIVE_PATH_PATTERN = /when[\s\S]*(100%|below threshold)/;
var INDETERMINATE_ALL_PATTERN = /all obligations[\s\S]*indeterminate[\s\S]*(return|warn|skip|pass|fail|must)/;
var NEGATIVE_PATH_PATTERN = /when not |otherwise|else|if not/;
var missingNegativeDetector = {
  name: "missing_negative_case",
  detect(context) {
    const defects = [];
    const positiveLine = context.review_lines.find((line) => {
      const text = normalizeText(line.text);
      return POSITIVE_PATH_PATTERN.test(text);
    });
    const negativeLine = context.review_lines.find(
      (line) => hasNegativePath(normalizeText(line.text))
    );
    if (positiveLine && !negativeLine) {
      defects.push({
        category: "missing_negative_case",
        severity: "major",
        description: "The spec defines conditional success or failure behavior but leaves the negative path undefined.",
        locations: [makeLocation(positiveLine)],
        suggested_resolution: "Define what happens when the stated condition does not hold, especially for indeterminate or empty inputs."
      });
    }
    return defects;
  }
};
function hasNegativePath(text) {
  return INDETERMINATE_ALL_PATTERN.test(text) || NEGATIVE_PATH_PATTERN.test(text);
}

// src/compliance/spec-review-detectors/reference-detector.ts
init_esm_shims();
var ID_PATTERN = /\b(?:FR|EC|AC|NFR|OQ)-[A-Z0-9][A-Z0-9.-]*\b/g;
var referenceDetector = {
  name: "references",
  detect(context) {
    const defects = [];
    const definedIds = collectDefinedIds(context.lines);
    for (const line of context.review_lines) {
      const normalized = normalizeText(line.text);
      if (/\bsee\b .*?\bspec\b/.test(normalized) && ![...line.text.matchAll(ID_PATTERN)].length) {
        defects.push({
          category: "unresolvable_reference",
          severity: "minor",
          description: "The spec references an external document that cannot be validated locally.",
          locations: [makeLocation(line)],
          suggested_resolution: "Inline the required contract details or replace the external note with a local reference."
        });
      }
      const references = [...line.text.matchAll(ID_PATTERN)].map((match) => match[0]);
      for (const reference of references) {
        if (definedIds.has(reference)) continue;
        defects.push({
          category: "dangling_reference",
          severity: "major",
          description: `The spec references ${reference}, but that identifier is not defined.`,
          locations: [makeLocation(line)],
          suggested_resolution: `Define ${reference} or update the reference to the correct identifier.`
        });
      }
    }
    return dedupeByLineAndCategory(defects);
  }
};
function collectDefinedIds(lines) {
  const defined = /* @__PURE__ */ new Set();
  for (const text of lines) {
    const trimmed = text.trim();
    const leadingMatches = [
      /^(?:#{1,6}\s+)?((?:FR|EC|AC|NFR|OQ)-[A-Z0-9][A-Z0-9.-]*)\b/,
      /^\*\*((?:FR|EC|AC|NFR|OQ)-[A-Z0-9][A-Z0-9.-]*)\*\*/,
      /^\|\s*((?:FR|EC|AC|NFR|OQ)-[A-Z0-9][A-Z0-9.-]*)\s*\|/
    ];
    for (const pattern of leadingMatches) {
      const match = pattern.exec(trimmed);
      if (match) {
        defined.add(match[1]);
      }
    }
  }
  return defined;
}
function dedupeByLineAndCategory(defects) {
  const seen = /* @__PURE__ */ new Set();
  return defects.filter((defect) => {
    const key = `${defect.category}:${defect.locations[0]?.line_range[0] ?? 0}:${defect.description ?? ""}`;
    if (seen.has(key)) return false;
    seen.add(key);
    return true;
  });
}

// src/compliance/spec-review-detectors/registry.ts
var SPEC_REVIEW_DETECTORS = [
  contradictionDetector,
  formulaDetector,
  boundaryDetector,
  goalConflictDetector,
  referenceDetector,
  missingNegativeDetector
];

// src/compliance/spec-review.ts
var SEVERITY_ORDER = {
  critical: 0,
  major: 1,
  minor: 2
};
function reviewSpecification(options) {
  const reviewedAt = options.reviewed_at ?? (/* @__PURE__ */ new Date()).toISOString();
  const context = buildReviewContext(options.spec_file, options.spec_markdown);
  const rawDefects = SPEC_REVIEW_DETECTORS.flatMap((detector) => detector.detect(context));
  const nextDefects = rawDefects.map((defect) => hydrateDefect(defect));
  const previousById = new Map(
    (options.previous_report?.defects ?? []).map((defect) => [defect.defect_id, defect])
  );
  const defects = nextDefects.map((defect) => ({
    ...defect,
    status: previousById.has(defect.defect_id) ? "existing" : "new"
  }));
  for (const previous of options.previous_report?.defects ?? []) {
    if (defects.some((defect) => defect.defect_id === previous.defect_id)) continue;
    defects.push({
      ...previous,
      status: "resolved",
      affected_obligation_ids: previous.affected_obligation_ids ?? []
    });
  }
  defects.sort(compareDefects);
  return {
    metadata: {
      spec_file: options.spec_file,
      spec_hash: sha256Hex(options.spec_markdown),
      reviewed_at: reviewedAt,
      defect_count: defects.filter((defect) => defect.status !== "resolved").length,
      schema_version: SPEC_REVIEW_SCHEMA_VERSION
    },
    defects,
    pattern_advisories: []
  };
}
function attachSpecDefectsToObligations(review, obligations) {
  const defects = review.defects.map((defect) => {
    if (defect.status === "resolved") {
      return { ...defect, affected_obligation_ids: defect.affected_obligation_ids ?? [] };
    }
    const affected = obligations.filter(
      (obligation) => defect.locations.some((location) => {
        const [start, end] = location.line_range;
        return obligation.source_line !== null && obligation.source_line >= start && obligation.source_line <= end;
      })
    ).map((obligation) => obligation.obligation_id).sort((a, b) => a.localeCompare(b));
    return {
      ...defect,
      affected_obligation_ids: affected
    };
  });
  return {
    ...review,
    defects
  };
}
function buildReviewContext(specFile, markdown) {
  const lines = splitLines(markdown);
  const headings = parseHeadings(lines);
  const openQuestionRanges = findOpenQuestionRanges(headings, lines.length);
  const reviewLines = lines.map((text, index) => ({
    line: index + 1,
    text,
    section: buildHeadingPath(headings, index + 1) || "Spec"
  })).filter((line) => isReviewableLine(line, openQuestionRanges));
  return {
    spec_file: specFile,
    spec_markdown: markdown,
    lines,
    review_lines: reviewLines
  };
}
function findOpenQuestionRanges(headings, lineCount) {
  const ranges = [];
  for (let index = 0; index < headings.length; index += 1) {
    const heading = headings[index];
    if (!/open questions/i.test(heading.text)) continue;
    let endLine = lineCount;
    for (let cursor = index + 1; cursor < headings.length; cursor += 1) {
      const candidate = headings[cursor];
      if (candidate.level <= heading.level) {
        endLine = candidate.line - 1;
        break;
      }
    }
    ranges.push([heading.line, endLine]);
  }
  return ranges;
}
function isReviewableLine(line, openQuestionRanges) {
  const trimmed = line.text.trim();
  if (!trimmed) return false;
  if (trimmed.startsWith("#")) return false;
  if (trimmed.startsWith("|---")) return false;
  if (openQuestionRanges.some(([start, end]) => line.line >= start && line.line <= end))
    return false;
  if (/\bTBD\b|to be determined/i.test(trimmed)) return false;
  return true;
}
function hydrateDefect(defect) {
  const defectId = sha256Hex(fingerprint(defect)).slice(0, 12).toUpperCase();
  return {
    defect_id: `SQ-${defectId}`,
    category: defect.category,
    severity: defect.severity,
    description: defect.description,
    locations: defect.locations.slice().sort((left, right) => left.line_range[0] - right.line_range[0]),
    suggested_resolution: defect.suggested_resolution,
    affected_obligation_ids: null,
    status: "new"
  };
}
function fingerprint(defect) {
  return JSON.stringify({
    category: defect.category,
    severity: defect.severity,
    description: defect.description,
    locations: defect.locations.map((location) => ({
      section: location.section,
      line_range: location.line_range,
      text_excerpt: location.text_excerpt
    })),
    suggested_resolution: defect.suggested_resolution
  });
}
function compareDefects(left, right) {
  const severityDelta = SEVERITY_ORDER[left.severity] - SEVERITY_ORDER[right.severity];
  if (severityDelta !== 0) return severityDelta;
  const lineDelta = (left.locations[0]?.line_range[0] ?? 0) - (right.locations[0]?.line_range[0] ?? 0);
  if (lineDelta !== 0) return lineDelta;
  return left.defect_id.localeCompare(right.defect_id);
}

// src/compliance/obligation-extractor.ts
init_esm_shims();
function extractObligationIndex(options) {
  const extractedAt = options.extracted_at ?? (/* @__PURE__ */ new Date()).toISOString();
  const warnings = [];
  const lines = splitLines(options.spec_markdown);
  const headings = parseHeadings(lines);
  const obligations = [];
  const seenIds = /* @__PURE__ */ new Set();
  const tables = extractRecognizedTables(lines);
  for (const row of tables.rows) {
    const sectionPath = buildHeadingPath(headings, row.line);
    const { category } = classifySection(sectionPath);
    const obligation = obligationFromTableRow(row, options.spec_file, sectionPath, category);
    if (!obligation) continue;
    if (seenIds.has(obligation.obligation_id)) {
      warnings.push(
        `Duplicate obligation_id "${obligation.obligation_id}" (table row at line ${row.line})`
      );
      continue;
    }
    seenIds.add(obligation.obligation_id);
    obligations.push(obligation);
  }
  const sectionItemCount = /* @__PURE__ */ new Map();
  for (const item of extractNumberedListItems(lines)) {
    const sectionPath = buildHeadingPath(headings, item.line);
    const { category } = classifySection(sectionPath);
    if (category !== "acceptance" || !isAcceptanceListSection(sectionPath)) {
      continue;
    }
    const count = (sectionItemCount.get(sectionPath) ?? 0) + 1;
    sectionItemCount.set(sectionPath, count);
    const generatedId = makeDeterministicGeneratedId(sectionPath, count);
    if (seenIds.has(generatedId)) {
      continue;
    }
    obligations.push({
      obligation_id: generatedId,
      category,
      description: normalizeCell(item.text),
      pass_criteria: null,
      source_section: sectionPath,
      source_line: item.line,
      spec_file: options.spec_file,
      affected_by_spec_defects: []
    });
    seenIds.add(generatedId);
  }
  for (const match of extractExplicitIds(lines, tables.table_lines)) {
    if (seenIds.has(match.id)) continue;
    const sectionPath = buildHeadingPath(headings, match.line);
    const { category } = classifySection(sectionPath);
    obligations.push({
      obligation_id: match.id,
      category,
      description: normalizeCell(match.text),
      pass_criteria: null,
      source_section: sectionPath,
      source_line: match.line,
      spec_file: options.spec_file,
      affected_by_spec_defects: []
    });
    seenIds.add(match.id);
  }
  if (obligations.length === 0) {
    warnings.push("No recognizable structured obligations found in spec.");
  }
  obligations.sort((left, right) => left.obligation_id.localeCompare(right.obligation_id));
  const linkedReview = options.spec_review ? attachSpecDefectsToObligations(options.spec_review, obligations) : null;
  const obligationsWithDefects = obligations.map((obligation) => ({
    ...obligation,
    affected_by_spec_defects: linkedReview === null ? [] : linkedReview.defects.filter((defect) => defect.affected_obligation_ids.includes(obligation.obligation_id)).map((defect) => defect.defect_id).sort((a, b) => a.localeCompare(b))
  }));
  return {
    metadata: {
      spec_file: options.spec_file,
      spec_hash: sha256Hex(options.spec_markdown),
      extracted_at: extractedAt,
      obligation_count: obligations.length,
      schema_version: COMPLIANCE_SCHEMA_VERSION,
      warnings
    },
    obligations: obligationsWithDefects
  };
}
function obligationFromTableRow(row, specFile, sectionPath, category) {
  if (row.cells.length < 2) return null;
  const normalizedCells = row.cells.map(normalizeCell);
  const headerCells = row.header_cells.map((cell) => cell.toLowerCase().trim());
  const [first, second, third, fourth] = normalizedCells;
  const obligationId = first || null;
  if (!obligationId) return null;
  const descriptionColumn = selectDescriptionColumn(headerCells, normalizedCells);
  const passCriteriaColumn = selectPassCriteriaColumn(headerCells, normalizedCells);
  const description = firstNonEmpty(descriptionColumn, second, third) ?? "";
  const passCriteria = firstNonEmpty(passCriteriaColumn, fourth);
  return {
    obligation_id: obligationId,
    category,
    description,
    pass_criteria: passCriteria,
    source_section: sectionPath,
    source_line: row.line,
    spec_file: specFile,
    affected_by_spec_defects: []
  };
}
function extractRecognizedTables(lines) {
  const rows = [];
  const tableLines = /* @__PURE__ */ new Set();
  for (let index = 0; index < lines.length; index += 1) {
    const headerLine = lines[index];
    if (!isTableRow(headerLine)) continue;
    const headerCells = parseTableRow(headerLine);
    const isRecognized = headerLooksLikeObligationTable(headerCells);
    if (!isRecognized) continue;
    const separatorLine = lines[index + 1];
    if (!separatorLine || !looksLikeTableSeparator(separatorLine)) continue;
    tableLines.add(index + 1);
    tableLines.add(index + 2);
    index += 2;
    while (index < lines.length) {
      const line = lines[index];
      if (!isTableRow(line)) break;
      const cells = parseTableRow(line);
      if (cells.every((cell) => cell.trim().length === 0)) {
        index += 1;
        continue;
      }
      rows.push({ cells, line: index + 1, header_cells: headerCells });
      tableLines.add(index + 1);
      index += 1;
    }
    index -= 1;
  }
  return { rows, table_lines: tableLines };
}
function headerLooksLikeObligationTable(cells) {
  const normalized = cells.map((cell) => cell.toLowerCase().trim());
  const joined = normalized.join(" | ");
  const hasId = /test id|obligation|id|#/.test(joined);
  const hasConditionOrCriterion = /condition|scenario|criterion|requirement|case|description|method/.test(joined);
  const hasPass = /pass criteria|expected|assert|measurement|result|outcome/.test(joined);
  return hasId && hasConditionOrCriterion && hasPass;
}
function isTableRow(line) {
  const trimmed = line.trim();
  return trimmed.startsWith("|") && trimmed.endsWith("|");
}
function looksLikeTableSeparator(line) {
  const trimmed = line.trim();
  return isTableRow(trimmed) && /^\|(?:\s*:?-+:?\s*\|)+$/.test(trimmed);
}
function parseTableRow(line) {
  const inner = line.trim().slice(1, -1);
  return inner.split("|").map((cell) => cell.trim());
}
function extractNumberedListItems(lines) {
  const items = [];
  let currentIndex = 0;
  for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
    const line = lines[lineIndex];
    const match = /^\s*(\d+)\.\s+(.*\S)\s*$/.exec(line);
    if (!match) continue;
    currentIndex += 1;
    items.push({ index: currentIndex, line: lineIndex + 1, text: match[2] });
  }
  return items;
}
function extractExplicitIds(lines, excludedLines) {
  const matches = [];
  for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
    if (excludedLines.has(lineIndex + 1)) continue;
    const line = lines[lineIndex];
    const id = extractLeadingExplicitId(line);
    if (!id) continue;
    matches.push({ id, line: lineIndex + 1, text: line });
  }
  return matches;
}
function isAcceptanceListSection(sectionPath) {
  return /(?:^|> )(?:\d+(?:\.\d+)*\.?\s*)?(acceptance criteria|definition of done|ac)\b/i.test(
    sectionPath
  );
}
function selectDescriptionColumn(headerCells, rowCells) {
  const descriptionIndex = headerCells.findIndex(
    (cell) => /condition|scenario|criterion|requirement|case|description|method/.test(cell)
  );
  if (descriptionIndex > 0) {
    return rowCells[descriptionIndex] || null;
  }
  return rowCells[1] || rowCells[2] || null;
}
function selectPassCriteriaColumn(headerCells, rowCells) {
  const passIndex = headerCells.findIndex(
    (cell) => /pass criteria|expected|assert|measurement|result|outcome/.test(cell)
  );
  if (passIndex > 0) {
    return rowCells[passIndex] || null;
  }
  return rowCells[2] || rowCells[3] || null;
}
function extractLeadingExplicitId(line) {
  const match = /^\s*(?:#{1,6}\s+|[-*+]\s+)?(?:\*\*)?(?<id>(?:FR|NFR)-\d+(?:\.\d+)?(?:-T\d+)?|AC-(?:TRACK-[A-Za-z0-9-]+|\d+)|EC-\d+(?:-T\d+)?)(?:\*\*)?(?=\b|:|\s)/.exec(
    line
  );
  if (match?.groups?.id) {
    return match.groups.id;
  }
  if (line.trim().startsWith("|")) {
    const rowMatch = /^\|\s*(?<id>(?:FR|NFR)-\d+(?:\.\d+)?(?:-T\d+)?|AC-\d+|EC-\d+(?:-T\d+)?)\s*\|/.exec(line);
    if (rowMatch?.groups?.id) {
      return rowMatch.groups.id;
    }
  }
  return null;
}
function firstNonEmpty(...values) {
  for (const value of values) {
    if (value && value.trim().length > 0) {
      return value;
    }
  }
  return null;
}

// src/core/framework-enabled.ts
init_esm_shims();
import { existsSync } from "fs";
import { join as join2 } from "path";
var ENV_TRUTHY = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
function isEnvDisabled(env = process.env) {
  const raw = env.PAQAD_DISABLED;
  return typeof raw === "string" && ENV_TRUTHY.has(raw.trim().toLowerCase());
}
function isManagedEnv(env = process.env) {
  const raw = env.PAQAD_MANAGED;
  return typeof raw === "string" && ENV_TRUTHY.has(raw.trim().toLowerCase());
}
function isFrameworkEnabled(profile, env = process.env) {
  if (isEnvDisabled(env)) {
    return false;
  }
  return profile?.paqad?.enabled !== false;
}
function isFrameworkEnabledForRoot(projectRoot, env = process.env) {
  if (isEnvDisabled(env)) {
    return false;
  }
  return !configSaysPaqadDisabled(projectRoot, env);
}
function isFrameworkDisabledForRoot(projectRoot, env = process.env) {
  return !isFrameworkEnabledForRoot(projectRoot, env);
}
function setFrameworkEnabled(projectRoot, enabled) {
  if (!existsSync(join2(projectRoot, PATHS.PROJECT_PROFILE))) {
    throw new Error(
      "paqad is not onboarded in this project (no .paqad/project-profile.yaml). Run `paqad-ai onboard` first."
    );
  }
  const config_path = setConfigValue(projectRoot, "paqad_enable", enabled ? "true" : "false");
  return { enabled, config_path };
}

// src/planning/decision-evidence.ts
init_esm_shims();
import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
import { join as join6, relative as relative2 } from "path";

// src/code-knowledge/store.ts
init_esm_shims();
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
import { dirname as dirname2, join as join3 } from "path";

// src/code-knowledge/schema.ts
init_esm_shims();
var import_ajv = __toESM(require_ajv(), 1);

// src/validators/schemas/code-knowledge.schema.json
var code_knowledge_schema_default = {
  $schema: "http://json-schema.org/draft-07/schema#",
  $id: "paqad://schemas/code-knowledge.json",
  title: "Code-knowledge index (issue #353)",
  type: "object",
  additionalProperties: false,
  required: [
    "schema_version",
    "header",
    "symbols",
    "files",
    "import_edges",
    "reference_edges",
    "dependencies"
  ],
  properties: {
    schema_version: { type: "integer", const: 1 },
    header: {
      type: "object",
      additionalProperties: false,
      required: ["generated_at", "branch", "head_commit", "schema_version", "entry_point_globs"],
      properties: {
        generated_at: { type: "string", minLength: 1 },
        branch: { type: ["string", "null"] },
        head_commit: { type: ["string", "null"] },
        schema_version: { type: "integer", const: 1 },
        entry_point_globs: { type: "array", items: { type: "string" } }
      }
    },
    symbols: {
      type: "array",
      items: {
        type: "object",
        additionalProperties: false,
        required: [
          "name",
          "kind",
          "file",
          "line",
          "signature",
          "exported",
          "module_slug",
          "extraction_tier",
          "caller_count",
          "orphan"
        ],
        properties: {
          name: { type: "string", minLength: 1 },
          kind: { enum: ["function", "class", "component", "const", "type"] },
          file: { type: "string", minLength: 1 },
          line: { type: "integer", minimum: 1 },
          signature: { type: "string" },
          exported: { type: "boolean" },
          module_slug: { type: ["string", "null"] },
          extraction_tier: { enum: ["regex", "ast"] },
          caller_count: { type: "integer", minimum: 0 },
          orphan: { type: "boolean" }
        }
      }
    },
    files: {
      type: "array",
      items: {
        type: "object",
        additionalProperties: false,
        required: ["path", "caller_count", "orphan", "entry_point"],
        properties: {
          path: { type: "string", minLength: 1 },
          caller_count: { type: "integer", minimum: 0 },
          orphan: { type: "boolean" },
          entry_point: { type: "boolean" }
        }
      }
    },
    import_edges: {
      type: "array",
      items: {
        type: "object",
        additionalProperties: false,
        required: ["from", "to"],
        properties: {
          from: { type: "string", minLength: 1 },
          to: { type: "string", minLength: 1 }
        }
      }
    },
    reference_edges: {
      type: "array",
      items: {
        type: "object",
        additionalProperties: false,
        required: ["from", "to", "symbol"],
        properties: {
          from: { type: "string", minLength: 1 },
          to: { type: "string", minLength: 1 },
          symbol: { type: "string", minLength: 1 }
        }
      }
    },
    dependencies: {
      type: "array",
      items: {
        type: "object",
        additionalProperties: false,
        required: ["name", "ecosystem", "imported"],
        properties: {
          name: { type: "string", minLength: 1 },
          ecosystem: { type: "string", minLength: 1 },
          imported: { type: "boolean" }
        }
      }
    }
  }
};

// src/code-knowledge/schema.ts
var ajv = new import_ajv.default({ allErrors: true, allowUnionTypes: true });
var compiled;
function validator() {
  if (!compiled) {
    compiled = ajv.compile(code_knowledge_schema_default);
  }
  return compiled;
}
function validateCodeKnowledgeIndex(data) {
  const validate = validator();
  if (validate(data)) {
    return { valid: true, errors: [] };
  }
  const rawErrors = validate.errors ?? [];
  const errors = rawErrors.map((error) => {
    const path = error.instancePath.length > 0 ? error.instancePath : "/";
    const message = error.message ?? "invalid";
    return `${path} ${message}`.trim();
  });
  return { valid: false, errors };
}

// src/code-knowledge/store.ts
function codeKnowledgeIndexPath(projectRoot) {
  return join3(projectRoot, PATHS.CODE_KNOWLEDGE_INDEX);
}
function writeCodeKnowledgeIndex(projectRoot, index) {
  const target = codeKnowledgeIndexPath(projectRoot);
  mkdirSync(dirname2(target), { recursive: true });
  const tmp = `${target}.tmp`;
  writeFileSync(tmp, JSON.stringify(index, null, 2) + "\n", "utf8");
  renameSync(tmp, target);
  return target;
}
function readCodeKnowledgeIndex(projectRoot) {
  const target = codeKnowledgeIndexPath(projectRoot);
  try {
    const parsed = JSON.parse(readFileSync(target, "utf8"));
    return validateCodeKnowledgeIndex(parsed).valid ? parsed : null;
  } catch {
    return null;
  }
}

// src/planning/decision-evidence-arm.ts
init_esm_shims();
import { statSync } from "fs";
import { join as join5 } from "path";

// src/duplication/decisions.ts
init_esm_shims();
import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync } from "fs";
import { join as join4 } from "path";
var DUPLICATION_DECISION_CATEGORY = "create-vs-reuse";
function findingKey(finding) {
  return `${finding.file}:${finding.line_range.start}:${finding.matched_file}`;
}
function findingToken(key) {
  return `[paqad-duplication ${key}]`;
}
function buildDuplicationDecisionContext(finding) {
  const percent = Math.round(finding.similarity * 100);
  return `Duplication finding accepted as a new copy.
- file: ${finding.file}:${finding.line_range.start}-${finding.line_range.end}
- similarity: ${percent}% of ${finding.matched_file}
- callers of the existing code: ${finding.matched_callers}
${findingToken(findingKey(finding))}`;
}
function applyResolvedDecisions(projectRoot, findings) {
  if (findings.length === 0) {
    return [];
  }
  const keys = findings.map(findingKey);
  const resolvedDir = join4(projectRoot, PATHS.DECISIONS_RESOLVED_DIR);
  if (!existsSync2(resolvedDir)) {
    return [];
  }
  const results = [];
  let files;
  try {
    files = readdirSync(resolvedDir).filter((name) => name.endsWith(".json"));
  } catch {
    return [];
  }
  for (const file of files.sort()) {
    const packet = readPacket(join4(resolvedDir, file));
    if (!packet || packet.category !== DUPLICATION_DECISION_CATEGORY) {
      continue;
    }
    const covered = keys.filter((key) => packet.context.includes(findingToken(key)));
    if (covered.length > 0) {
      results.push({ decisionId: packet.id, coveredFindingKeys: covered });
    }
  }
  return results;
}
function readPacket(path) {
  try {
    return JSON.parse(readFileSync2(path, "utf8"));
  } catch {
    return null;
  }
}

// src/planning/decision-arm-config.ts
init_esm_shims();
var DECISION_ARM_MODES = ["off", "warn", "strict"];
var DEFAULT_DECISION_ARM_MODE = "warn";
var DEFAULT_PLAN_THRESHOLD = 0.85;
var DEFAULT_MAX_PER_CHANGE = 1;
function resolveDecisionArmConfig(projectRoot, env = process.env) {
  return {
    mode: resolveDecisionArmMode(projectRoot, env),
    planThreshold: resolveNumericConfig(
      projectRoot,
      env,
      "decision_arm_plan_threshold",
      DEFAULT_PLAN_THRESHOLD,
      (value) => value > 0 && value <= 1
    ),
    maxPerChange: resolveNumericConfig(
      projectRoot,
      env,
      "decision_arm_max_per_change",
      DEFAULT_MAX_PER_CHANGE,
      (value) => Number.isInteger(value) && value >= 0
    )
  };
}
function resolveDecisionArmMode(projectRoot, env = process.env) {
  return resolveFlooredMode(
    {
      team: readConfigsDir(projectRoot).merged.get("decision_arm_mode"),
      local: readDotConfig(projectRoot).get("decision_arm_mode"),
      env: envConfigValue(env, "decision_arm_mode")
    },
    DECISION_ARM_MODES,
    DEFAULT_DECISION_ARM_MODE
  );
}

// src/planning/symbol-similarity.ts
init_esm_shims();
var HEAD_BONUS = 0.1;
var TAIL_BONUS = 0.1;
var MODULE_BONUS = 0.05;
function tokenizeSymbolName(name) {
  return name.replace(/([a-z0-9])([A-Z])/gu, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/gu, "$1 $2").split(/[^A-Za-z0-9]+/u).filter((token) => token.length > 0).map((token) => token.toLowerCase());
}
function tokenDice(left, right) {
  if (left.length === 0 || right.length === 0) {
    return 0;
  }
  const rightSet = new Set(right);
  const shared = new Set(left.filter((token) => rightSet.has(token))).size;
  return 2 * shared / (new Set(left).size + new Set(right).size);
}
function editSimilarity(left, right) {
  const longest = Math.max(left.length, right.length);
  if (longest === 0) {
    return 0;
  }
  return 1 - levenshtein(left, right, longest) / longest;
}
function symbolNameSimilarity(left, right, options = {}) {
  if (left === right) {
    return 1;
  }
  const leftTokens = tokenizeSymbolName(left);
  const rightTokens = tokenizeSymbolName(right);
  let tokenScore = tokenDice(leftTokens, rightTokens);
  if (tokenScore > 0) {
    if (leftTokens[0] === rightTokens[0]) {
      tokenScore += HEAD_BONUS;
    }
    if (leftTokens[leftTokens.length - 1] === rightTokens[rightTokens.length - 1]) {
      tokenScore += TAIL_BONUS;
    }
  }
  const base = Math.max(tokenScore, editSimilarity(left, right));
  const withModule = base + (options.sameModule === true ? MODULE_BONUS : 0);
  return Number(Math.min(1, Math.max(0, withModule)).toFixed(2));
}

// src/planning/decision-evidence-arm.ts
var RECOMMEND_REUSE_MIN_CALLERS = 3;
var CREATE_NEW_OPTION_KEY = "create-new";
function reuseOptionKey(symbol) {
  return `reuse:${symbol}`;
}
function forkToken(forkKey) {
  return `[paqad-arm ${forkKey}]`;
}
function changeToken(changeKey) {
  return `[paqad-arm-change ${changeKey}]`;
}
function planForkKey(newName, existingSymbol) {
  return `plan:${newName}~${existingSymbol}`;
}
function duplicationForkKey(finding) {
  return `dup:${findingKey(finding)}`;
}
function buildReuseForkEvidence(input) {
  const evidence = {
    file: input.file,
    callers: input.callers,
    similarity: Number(Math.min(1, Math.max(0, input.similarity)).toFixed(2))
  };
  try {
    evidence.last_modified = statSync(join5(input.projectRoot, input.file)).mtime.toISOString();
  } catch {
    evidence.evidence_partial = true;
  }
  return evidence;
}
function findPlanTimeReuseForks(input) {
  const index = input.index !== void 0 ? input.index : readCodeKnowledgeIndex(input.projectRoot);
  if (!index || input.constructs.length === 0) {
    return [];
  }
  const forks = [];
  for (const planned of input.constructs) {
    const best = closestSymbol(index.symbols, planned);
    if (best === null || best.similarity < input.threshold) {
      continue;
    }
    forks.push({
      forkKey: planForkKey(planned.name, best.symbol.name),
      newName: planned.name,
      justification: planned.justification ?? "",
      existing: best.symbol,
      similarity: best.similarity
    });
  }
  return forks.sort((a, b) => b.similarity - a.similarity);
}
function closestSymbol(symbols, planned) {
  let best = null;
  for (const symbol of symbols) {
    if (symbol.name === planned.name) {
      continue;
    }
    const similarity = symbolNameSimilarity(planned.name, symbol.name, {
      sameModule: planned.module !== void 0 && planned.module !== null && planned.module === symbol.module_slug
    });
    if (best === null || similarity > best.similarity) {
      best = { symbol, similarity };
    }
  }
  return best;
}
var EMPTY_RESULT = { minted: [], warnings: [], reusedDecisions: [] };
function armDecisionFromPlan(input) {
  return safely(() => {
    const config = input.config ?? resolveDecisionArmConfig(input.projectRoot, input.env);
    if (config.mode === "off") {
      return EMPTY_RESULT;
    }
    const forks = findPlanTimeReuseForks({
      projectRoot: input.projectRoot,
      constructs: input.constructs,
      threshold: config.planThreshold
    });
    return mintForks(
      input,
      config,
      forks.map((fork) => toMintable(input.projectRoot, fork))
    );
  });
}
function armDecisionFromDuplicationFinding(input) {
  return safely(() => {
    const config = input.config ?? resolveDecisionArmConfig(input.projectRoot, input.env);
    if (config.mode === "off") {
      return EMPTY_RESULT;
    }
    return mintForks(input, config, [toMintableFinding(input.projectRoot, input.finding)]);
  });
}
function toMintable(projectRoot, fork) {
  const evidence = buildReuseForkEvidence({
    projectRoot,
    file: fork.existing.file,
    callers: fork.existing.caller_count,
    similarity: fork.similarity
  });
  const percent = Math.round(fork.similarity * 100);
  return {
    forkKey: fork.forkKey,
    title: `Reuse ${fork.existing.name}, or build ${fork.newName}?`,
    context: `The plan introduces ${fork.newName}, which is ${percent}% similar to existing ${fork.existing.name}.
- file: ${fork.existing.file}:${fork.existing.line}
- callers of the existing code: ${fork.existing.caller_count}
- the plan's justification: ${fork.justification || "(none given)"}`,
    options: [
      {
        option_key: reuseOptionKey(fork.existing.name),
        label: `Reuse ${fork.existing.name} (${fork.existing.file})`,
        evidence
      },
      {
        option_key: CREATE_NEW_OPTION_KEY,
        label: `Build ${fork.newName} anyway \u2014 ${fork.justification || "no justification given"}`
      }
    ],
    recommendation: fork.existing.caller_count >= RECOMMEND_REUSE_MIN_CALLERS ? reuseOptionKey(fork.existing.name) : null,
    score: fork.similarity,
    warning: `${fork.newName} is ${percent}% similar to existing ${fork.existing.name} (${fork.existing.file}, ${fork.existing.caller_count} callers) \u2014 reuse it, or say why not`
  };
}
function toMintableFinding(projectRoot, finding) {
  const matchedName = finding.matched_symbol ?? finding.matched_file;
  const evidence = buildReuseForkEvidence({
    projectRoot,
    file: finding.matched_file,
    callers: finding.matched_callers,
    similarity: finding.similarity
  });
  return {
    forkKey: duplicationForkKey(finding),
    title: `Reuse ${matchedName}, or keep the new copy in ${finding.file}?`,
    context: buildDuplicationDecisionContext(finding),
    options: [
      {
        option_key: reuseOptionKey(matchedName),
        label: `Reuse ${matchedName} (${finding.matched_file})`,
        evidence
      },
      {
        option_key: CREATE_NEW_OPTION_KEY,
        label: `Keep the new copy in ${finding.file}`
      }
    ],
    recommendation: finding.matched_callers >= RECOMMEND_REUSE_MIN_CALLERS ? reuseOptionKey(matchedName) : null,
    score: finding.similarity,
    warning: finding.message
  };
}
function mintForks(input, config, forks) {
  if (forks.length === 0) {
    return EMPTY_RESULT;
  }
  const stored = readContractDecisions(input.projectRoot);
  const result = { minted: [], warnings: [], reusedDecisions: [] };
  let openedForChange = stored.filter(
    (row) => row.packet.context.includes(changeToken(input.changeKey))
  ).length;
  const ranked = [...forks].sort((a, b) => b.score - a.score);
  for (const fork of ranked) {
    const token = forkToken(fork.forkKey);
    const prior = stored.find((row) => row.packet.context.includes(token));
    if (prior !== void 0) {
      if (prior.status === "resolved") {
        recordDecisionReuse(
          input.projectRoot,
          {
            decisionId: prior.packet.id,
            category: prior.packet.category,
            matchKind: "exact",
            note: `evidence-armed fork ${fork.forkKey}`
          },
          { sessionId: input.sessionId ?? null }
        );
        result.reusedDecisions.push(`reused_decision:${prior.packet.id}`);
      }
      continue;
    }
    if (config.mode !== "strict" || openedForChange >= config.maxPerChange) {
      result.warnings.push(fork.warning);
      continue;
    }
    const created = createPendingDecision(input.projectRoot, {
      category: DUPLICATION_DECISION_CATEGORY,
      title: fork.title,
      context: `${fork.context}
${token}
${changeToken(input.changeKey)}`,
      options: fork.options,
      recommendation: fork.recommendation,
      origin: "evidence-armed",
      change: featureChangeKey(input.changeKey)
    });
    if (created.reused) {
      result.reusedDecisions.push(`reused_decision:${created.id}`);
      continue;
    }
    result.minted.push(created.id);
    openedForChange += 1;
  }
  return result;
}
function safely(run) {
  try {
    return run();
  } catch {
    return EMPTY_RESULT;
  }
}

// src/planning/decision-evidence.ts
var MIN_SIMILARITY = 0.01;
var MAX_SIMILARITY = 0.99;
function assembleDecisionEvidence(input) {
  const absolutePath = join6(input.projectRoot, input.file);
  const fileExists = existsSync3(absolutePath);
  const indexedCallers = indexedCallerCount(input.projectRoot, input.file);
  if (indexedCallers !== null) {
    const evidence2 = buildReuseForkEvidence({
      projectRoot: input.projectRoot,
      file: input.file,
      callers: indexedCallers,
      similarity: clampSimilarity(
        input.similarity ?? defaultSimilarityFor(input.category, fileExists, indexedCallers)
      )
    });
    const ruleMatch2 = findSupportingRule(input.projectRoot, input.file);
    if (ruleMatch2) {
      evidence2.rule_match = ruleMatch2;
    }
    return evidence2;
  }
  const callers = fileExists ? countFileReferences(input.projectRoot, input.file) : 0;
  const similarity = clampSimilarity(
    input.similarity ?? defaultSimilarityFor(input.category, fileExists, callers)
  );
  const ruleMatch = findSupportingRule(input.projectRoot, input.file);
  const evidence = {
    file: input.file,
    callers,
    similarity
  };
  if (ruleMatch) {
    evidence.rule_match = ruleMatch;
  }
  if (fileExists) {
    evidence.last_modified = statSync2(absolutePath).mtime.toISOString();
  } else {
    evidence.evidence_partial = true;
  }
  return evidence;
}
function countFileReferences(projectRoot, file) {
  const absoluteTarget = join6(projectRoot, file);
  if (!existsSync3(absoluteTarget)) {
    return 0;
  }
  const relativePath = normalizePath(relative2(projectRoot, absoluteTarget));
  const baseName = relativePath.split("/").pop() ?? relativePath;
  const stem = baseName.replace(/\.[^.]+$/, "");
  const matcher = new RegExp(`\\b${escapeRegExp(stem)}\\b|${escapeRegExp(relativePath)}`, "g");
  let count = 0;
  for (const candidate of walkTextFiles(projectRoot)) {
    if (normalizePath(candidate) === relativePath) {
      count += 1;
      continue;
    }
    const content = safeReadFile(join6(projectRoot, candidate));
    if (matcher.test(content)) {
      count += 1;
    }
  }
  return count;
}
function defaultSimilarityFor(category, fileExists, callers) {
  if (!fileExists) {
    return category === "workflow-or-tool" ? 0.44 : 0.38;
  }
  const callerBoost = callers >= 3 ? 0.06 : callers > 0 ? 0.03 : 0;
  switch (category) {
    case "component-reuse":
    case "create-vs-reuse":
      return Number((0.86 + callerBoost).toFixed(2));
    /* v8 ignore start -- individual category branches are exercised by integration; unit tests only hit the top two */
    case "shared-abstraction":
      return Number((0.78 + callerBoost).toFixed(2));
    case "ux-pattern":
      return Number((0.82 + callerBoost).toFixed(2));
    case "architecture-path":
      return Number((0.74 + callerBoost).toFixed(2));
    case "workflow-or-tool":
      return Number((0.7 + callerBoost).toFixed(2));
    /* v8 ignore stop */
    case "intake.requirement":
    case "intake.confirm_auto_resolution":
    case "intake.write_back":
    case "delivery.open_pr":
    case "delivery.ci_red":
    case "spec.change":
    case "spec.contradiction":
    case "spec.expert_conflict":
    case "fix.proof_method":
    case "test.flaky_judgement":
    case "finding.triage":
    case "quality.ratchet_exception":
    case "analytics.provider_version_mismatch":
    case "analytics.taxonomy_violation":
    case "analytics.pii_consent":
    case "analytics.no_provider_flag":
    case "analytics.architecture_conflict":
    case "analytics.new_event":
    case "authoring.overlap":
    case "authoring.risky_script":
    case "authoring.agent_permission":
    case "authoring.intake_gap":
    case "authoring.improvement":
    case "authoring.retirement":
    case "authoring.override":
    case "rules.proposal":
      return 0.5;
  }
}
function indexedCallerCount(projectRoot, file) {
  const index = readCodeKnowledgeIndex(projectRoot);
  if (!index) {
    return null;
  }
  const normalized = normalizePath(file);
  return index.files.find((entry) => entry.path === normalized)?.caller_count ?? null;
}
function findSupportingRule(projectRoot, file) {
  const compiledRules = readCompiledRulesSync(projectRoot);
  if (!compiledRules) {
    return void 0;
  }
  return compiledRules.rules.find(
    (rule) => rule.trigger_patterns.some((pattern) => matchesGlobish(file, pattern))
  )?.rule_id;
}
function readCompiledRulesSync(projectRoot) {
  try {
    return JSON.parse(readFileSync3(join6(projectRoot, PATHS.COMPILED_RULES), "utf8"));
  } catch {
    return null;
  }
}
function walkTextFiles(projectRoot, currentDir = "") {
  const absoluteDir = join6(projectRoot, currentDir);
  let entries;
  try {
    entries = readdirSync2(absoluteDir);
  } catch {
    return [];
  }
  const files = [];
  for (const entry of entries) {
    if (entry === "node_modules" || entry === ".git" || entry === ".paqad") {
      continue;
    }
    const relativeEntry = currentDir ? `${currentDir}/${entry}` : entry;
    const absoluteEntry = join6(projectRoot, relativeEntry);
    try {
      const stat = statSync2(absoluteEntry);
      if (stat.isDirectory()) {
        files.push(...walkTextFiles(projectRoot, relativeEntry));
      } else if (/\.(ts|tsx|js|jsx|md|json|yaml|yml|txt|mdc)$/.test(entry)) {
        files.push(normalizePath(relativeEntry));
      }
    } catch {
      continue;
    }
  }
  return files;
}
function safeReadFile(path) {
  try {
    return readFileSync3(path, "utf8");
  } catch {
    return "";
  }
}
function clampSimilarity(value) {
  return Math.min(MAX_SIMILARITY, Math.max(MIN_SIMILARITY, Number(value.toFixed(2))));
}
function normalizePath(value) {
  return value.replace(/\\/g, "/");
}
function matchesGlobish(value, pattern) {
  const normalizedValue = normalizePath(value);
  const normalizedPattern = normalizePath(pattern);
  if (normalizedPattern === "**") {
    return true;
  }
  const escaped = normalizedPattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
  const regex = new RegExp(
    `^${escaped.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\?/g, ".")}$`
  );
  return regex.test(normalizedValue) || normalizedValue.includes(normalizedPattern.replace(/\*/g, ""));
}
function escapeRegExp(value) {
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

// src/planning/decision-packet-builder.ts
init_esm_shims();
function buildDecisionPacket(input) {
  const primaryTouch = input.context.current_slice.touches[0] ?? "src/unknown.ts";
  const optionSet = decisionOptionsForCategory(input.projectRoot, input.category, primaryTouch);
  const now = /* @__PURE__ */ new Date();
  const linkedRequirements = input.context.current_slice.covers.filter(
    (cover) => input.manifest.requirement_graph.some((requirement) => requirement.id === cover)
  );
  const recommendation = chooseRecommendation(optionSet.options);
  const ttlDays = ttlDaysFor(input.projectRoot, input.category);
  return {
    decision_id: input.decisionId,
    fingerprint: computeDecisionFingerprint({
      category: input.category,
      question: decisionQuestionForCategory(input.category),
      option_keys: optionSet.options.map((option) => option.option_key),
      repo_state: {
        active_capabilities: ["coding"],
        stack: input.context.manifest_header.classification.stack,
        packs: input.context.manifest_header.classification.affected_modules
      }
    }),
    category: input.category,
    question: decisionQuestionForCategory(input.category),
    context: `${input.context.current_slice.goal} This fork affects ${primaryTouch}. Choose the path before implementation continues.`,
    options: optionSet.options,
    recommendation,
    recommendation_reason: recommendationReasonFor(recommendation, optionSet.options),
    confidence: computePacketConfidence(
      optionSet.options,
      recommendation,
      input.detectorConfidence
    ),
    requested_by: input.requestedBy,
    task_session_id: input.taskSessionId,
    linked_requirements: linkedRequirements,
    linked_slice_id: input.context.current_slice.slice_id,
    created_at: now.toISOString(),
    status: "pending",
    ttl_until: new Date(now.getTime() + ttlDays * 24 * 60 * 60 * 1e3).toISOString(),
    invalidation_watch: invalidationWatchFor(
      input.category,
      primaryTouch,
      input.context.current_slice.touches,
      optionSet.options
    )
  };
}
function computePacketConfidence(options, recommendation, detectorConfidence) {
  const recommendedOption = options.find((option) => option.option_key === recommendation) ?? options[0];
  const similarity = recommendedOption?.evidence.similarity ?? detectorConfidence;
  const callerBonus = (recommendedOption?.evidence.callers ?? 0) > 0 ? 0.05 : 0;
  const ruleBonus = recommendedOption?.evidence.rule_match ? 0.05 : 0;
  const score = similarity * 0.75 + detectorConfidence * 0.15 + callerBonus + ruleBonus;
  return Number(Math.min(0.99, Math.max(0, score)).toFixed(2));
}
function decisionQuestionForCategory(category) {
  switch (category) {
    case "component-reuse":
      return "Reuse the component or make new?";
    case "create-vs-reuse":
      return "Use what exists or make new?";
    case "shared-abstraction":
      return "Keep this local or share it?";
    case "ux-pattern":
      return "Stay with this pattern?";
    case "architecture-path":
      return "Which path should we take?";
    case "workflow-or-tool":
      return "Which workflow fits best?";
    case "intake.requirement":
      return "How should we pin this open question from the refined ticket?";
    case "intake.confirm_auto_resolution":
      return "We resolved these decisions from priors and rules \u2014 accept or override?";
    case "intake.write_back":
      return "Update the source ticket with the refined description and acceptance criteria?";
    case "delivery.open_pr":
      return "Open a pull request now (yes / draft / no)?";
    case "delivery.ci_red":
      return "CI is red \u2014 retry the gate, override and continue, or abandon delivery?";
    case "spec.change":
      return "The goal changed mid-build \u2014 update the frozen spec and re-freeze?";
    case "spec.contradiction":
      return "Work conflicts with the frozen spec \u2014 fix the code or change the spec?";
    case "spec.expert_conflict":
      return "Two experts disagree about the same target \u2014 which claim should the spec take?";
    case "fix.proof_method":
      return "This problem cannot be auto-checked \u2014 how should we confirm it is fixed?";
    case "test.flaky_judgement":
      return "Is this a flaky test or a rare real fault?";
    case "finding.triage":
      return "Which pile does this finding belong in?";
    case "quality.ratchet_exception":
      return "A quality measure would worsen \u2014 approve a legitimate exception or hold the line?";
    case "analytics.provider_version_mismatch":
      return "I'm not sure which analytics tool (or version) this project uses \u2014 which is right?";
    case "analytics.taxonomy_violation":
      return "This event name breaks your naming convention \u2014 keep mine or rename?";
    case "analytics.pii_consent":
      return "This property looks like personal data \u2014 I won't auto-track it. Track it anyway?";
    case "analytics.no_provider_flag":
      return "Analytics is on but no provider is wired up \u2014 set one up, or stay dormant?";
    case "analytics.architecture_conflict":
      return "Your module docs say tracking belongs elsewhere than this change \u2014 where should it go?";
    case "analytics.new_event":
      return "This adds a new tracked event \u2014 approve the name, rename it, or skip tracking?";
    case "authoring.overlap":
      return "This new skill overlaps one you already have. Extend that one, or create a new skill?";
    case "authoring.risky_script":
      return "A script in this skill reaches the network, installs packages or deletes files. Allow it?";
    case "authoring.agent_permission":
      return "This agent asks for risky powers (bypassPermissions, network, package install, delete rights, or more than the tool limit). Allow these powers?";
    case "authoring.intake_gap":
      return "Only you can answer this part of what the new skill is for. What should it be?";
    case "authoring.improvement":
      return "A skill in use could learn this correction. Add it to the skill?";
    case "authoring.retirement":
      return "The agent now does this right without the skill. Retire the skill?";
    case "authoring.override":
      return "This skill skipped one of its checks. Add it unverified, kept in this project?";
    case "rules.proposal":
      return "Sign off the proposed rule changes?";
  }
}
function decisionOptionsForCategory(projectRoot, category, targetFile) {
  const targetDir = targetFile.replace(/\/[^/]+$/, "");
  const extension = targetFile.includes(".") ? targetFile.slice(targetFile.lastIndexOf(".")) : ".ts";
  const newFile = `${targetDir}/new-${targetFile.split("/").pop() ?? `option${extension}`}`;
  const sharedFile = `${targetDir}/shared-${targetFile.split("/").pop() ?? `option${extension}`}`;
  switch (category) {
    case "shared-abstraction":
      return {
        options: [
          buildOption(projectRoot, category, {
            option_key: "keep-local",
            label: "Keep it local",
            one_line_preview: `If you pick this, we will keep the work in ${targetFile}.`,
            trade_off: "You give up: one shared place for later reuse.",
            technical_detail: `The first implementation stays in ${targetFile} and avoids new shared surface area.`,
            file: targetFile,
            similarity: 0.79
          }),
          buildOption(projectRoot, category, {
            option_key: "extract-shared",
            label: "Extract shared helper",
            one_line_preview: `If you pick this, we will create ${sharedFile}.`,
            trade_off: "You give up: the quickest path for this slice.",
            technical_detail: `A shared helper in ${sharedFile} makes later reuse easier but broadens the change now.`,
            file: sharedFile,
            similarity: 0.41
          })
        ]
      };
    case "architecture-path":
      return {
        options: [
          buildOption(projectRoot, category, {
            option_key: "keep-current-path",
            label: "Keep current path",
            one_line_preview: `If you pick this, we will update ${targetFile}.`,
            trade_off: "You give up: a broader cleanup right now.",
            technical_detail: "This path keeps implementation close to the current slice boundary.",
            file: targetFile,
            similarity: 0.74
          }),
          buildOption(projectRoot, category, {
            option_key: "take-new-path",
            label: "Take new path",
            one_line_preview: `If you pick this, we will create ${newFile}.`,
            trade_off: "You give up: the smallest possible diff.",
            technical_detail: "This path opens a new implementation route and needs more follow-up.",
            file: newFile,
            similarity: 0.46
          })
        ]
      };
    case "workflow-or-tool":
      return {
        options: [
          buildOption(projectRoot, category, {
            option_key: "use-current-workflow",
            label: "Use current workflow",
            one_line_preview: `If you pick this, we will keep the work inside ${targetFile}.`,
            trade_off: "You give up: trying a brand new tool path.",
            technical_detail: "This keeps the change aligned with the current workflow in the repo.",
            file: targetFile,
            similarity: 0.71
          }),
          buildOption(projectRoot, category, {
            option_key: "switch-workflow",
            label: "Switch workflow now",
            one_line_preview: `If you pick this, we will introduce ${newFile}.`,
            trade_off: "You give up: the simpler path for this task.",
            technical_detail: "This introduces a new workflow artifact and changes future execution.",
            file: newFile,
            similarity: 0.44
          })
        ]
      };
    case "ux-pattern":
      return {
        options: [
          buildOption(projectRoot, category, {
            option_key: "reuse-current-pattern",
            label: "Reuse current pattern",
            one_line_preview: `If you pick this, we will update ${targetFile}.`,
            trade_off: "You give up: a fresh interaction pattern.",
            technical_detail: "The current pattern already appears near this change.",
            file: targetFile,
            similarity: 0.83
          }),
          buildOption(projectRoot, category, {
            option_key: "make-new-pattern",
            label: "Make new pattern",
            one_line_preview: `If you pick this, we will create ${newFile}.`,
            trade_off: "You give up: the safest path for consistency.",
            technical_detail: "A new pattern gives flexibility now but increases UX drift risk.",
            file: newFile,
            similarity: 0.47
          })
        ]
      };
    case "component-reuse":
    case "create-vs-reuse":
      return {
        options: [
          buildOption(projectRoot, category, {
            option_key: "reuse-existing",
            label: "Reuse what exists",
            one_line_preview: `If you pick this, we will update ${targetFile}.`,
            trade_off: "You give up: a blank-slate implementation.",
            technical_detail: `Reusing ${targetFile} keeps behavior aligned with the existing project structure.`,
            file: targetFile,
            similarity: 0.91
          }),
          buildOption(projectRoot, category, {
            option_key: "make-new",
            label: "Make a new one",
            one_line_preview: `If you pick this, we will create ${newFile}.`,
            trade_off: "You give up: the shared path that already exists.",
            technical_detail: `Creating ${newFile} gives isolation but increases maintenance and consistency risk.`,
            file: newFile,
            similarity: 0.42
          })
        ]
      };
    case "intake.requirement":
    case "intake.confirm_auto_resolution":
    case "intake.write_back":
    case "delivery.open_pr":
    case "delivery.ci_red":
    case "spec.change":
    case "spec.contradiction":
    case "spec.expert_conflict":
    case "fix.proof_method":
    case "test.flaky_judgement":
    case "finding.triage":
    case "quality.ratchet_exception":
    case "analytics.provider_version_mismatch":
    case "analytics.taxonomy_violation":
    case "analytics.pii_consent":
    case "analytics.no_provider_flag":
    case "analytics.architecture_conflict":
    case "analytics.new_event":
    case "authoring.overlap":
    case "authoring.risky_script":
    case "authoring.agent_permission":
    case "authoring.intake_gap":
    case "authoring.improvement":
    case "authoring.retirement":
    case "authoring.override":
    case "rules.proposal":
      return { options: [] };
  }
}
function selectViableDecisionOptions(projectRoot, options) {
  const floor = readProjectProfile(projectRoot)?.custom?.decisions?.ask_threshold === "permissive" ? 0.4 : 0.55;
  const viable = options.filter(
    (option) => option.evidence.similarity === void 0 || option.evidence.similarity >= floor
  );
  return viable.length > 0 ? viable : options;
}
function buildOption(projectRoot, category, input) {
  return {
    option_key: input.option_key,
    label: input.label,
    one_line_preview: input.one_line_preview,
    trade_off: input.trade_off,
    technical_detail: input.technical_detail,
    evidence: assembleDecisionEvidence({
      projectRoot,
      file: input.file,
      category,
      similarity: input.similarity
    })
  };
}
function chooseRecommendation(options) {
  const ranked = [...options].sort((left, right) => scoreOption(right) - scoreOption(left));
  return ranked[0]?.option_key ?? null;
}
function recommendationReasonFor(recommendation, options) {
  const option = options.find((entry) => entry.option_key === recommendation);
  if (!option) {
    return void 0;
  }
  return option.evidence.rule_match ? "A project rule already points to this path." : (option.evidence.callers ?? 0) > 0 ? "This path already matches the way the repo works today." : (
    /* v8 ignore stop */
    "This is the safer and cheaper path for the first change."
  );
}
function scoreOption(option) {
  return (option.evidence.similarity ?? 0) + ((option.evidence.callers ?? 0) > 0 ? 0.05 : 0) + (option.evidence.rule_match ? 0.1 : 0);
}
function invalidationWatchFor(category, primaryTouch, touches, options) {
  const watch = /* @__PURE__ */ new Set([primaryTouch]);
  switch (category) {
    case "component-reuse":
      for (const option of options) {
        if (option.evidence.file) {
          watch.add(option.evidence.file);
        }
      }
      watch.add(PATHS.DESIGN_SYSTEM_DIR);
      break;
    case "create-vs-reuse":
      for (const option of options) {
        if (option.evidence.file) {
          watch.add(option.evidence.file);
        }
      }
      watch.add(PATHS.DESIGN_SYSTEM_DIR);
      break;
    case "architecture-path":
      for (const touch of touches) {
        watch.add(touch);
      }
      break;
    case "shared-abstraction":
      for (const option of options) {
        if (option.evidence.file) {
          watch.add(option.evidence.file);
        }
      }
      watch.add(PATHS.DESIGN_SYSTEM_DIR);
      break;
    case "ux-pattern":
      watch.add(PATHS.DESIGN_SYSTEM_DIR);
      break;
    default:
      break;
  }
  return [...watch];
}
function ttlDaysFor(projectRoot, category) {
  const override = readProjectProfile(projectRoot)?.custom?.decisions?.ttl_overrides_days?.[category];
  return override ?? DECISION_CATEGORY_DEFAULTS[category].ttl_days;
}

// src/kernel/capability.ts
init_esm_shims();
import { readFileSync as readFileSync13 } from "fs";

// src/pipeline/session-ownership.ts
init_esm_shims();
var AGENT_AUTHORED_SOURCES = /* @__PURE__ */ new Set(["live-mark", "redo"]);
var PRE_EDIT_STAGES = /* @__PURE__ */ new Set([
  "ticket_intake",
  "planning",
  "specification"
]);
function sessionOwnedRows(projectRoot, sessionId) {
  return ownedRowsByChange(projectRoot, sessionId).flatMap(({ rows }) => rows);
}
function ownedRowsByChange(projectRoot, sessionId) {
  let dirNames;
  try {
    dirNames = listInFlightFeatures(projectRoot);
  } catch {
    return [];
  }
  const ownBundle = readSessionControl(projectRoot, sessionId).active;
  if (ownBundle !== null && !dirNames.includes(ownBundle) && isReopened(projectRoot, ownBundle)) {
    dirNames = [...dirNames, ownBundle];
  }
  const lastPrompt = lastPromptAt(projectRoot, sessionId);
  return dirNames.map((dirName) => ({
    dirName,
    rows: readFeatureStageUnit(projectRoot, dirName).filter(
      (row) => row.session_id === sessionId && typeof row.evidence_source === "string" && AGENT_AUTHORED_SOURCES.has(row.evidence_source) || dirName === ownBundle && isStageAgentRow(row) && stageAgentRowIsOwn(row, sessionId, lastPrompt)
    )
  }));
}
function isReopened(projectRoot, dirName) {
  return isBundleMaterialized(projectRoot, dirName) && !isBundleClosed(projectRoot, dirName);
}
function sessionWroteInEpisode(projectRoot, sessionId, dirName, episodeStartAt) {
  const rows = readFeatureStageUnit(projectRoot, dirName);
  const since = episodeStartAt === null ? NaN : Date.parse(episodeStartAt);
  const wroteSince = rows.some((row) => {
    if (row.session_id !== sessionId) return false;
    if (Number.isNaN(since)) return true;
    const at = Date.parse(rowRecordedAt(row) ?? "");
    return !Number.isNaN(at) && at >= since;
  });
  return wroteSince || rows.some((row, index) => isOwnRunningStageStart(rows, row, index, sessionId));
}
function isOwnRunningStageStart(rows, row, index, sessionId) {
  if (row.kind !== "stage_start" || row.session_id !== sessionId) return false;
  return !rows.slice(index + 1).some((later) => later.kind === "stage_end" && later.stage === row.stage);
}
function lastPromptAt(projectRoot, sessionId) {
  const stamp = readWorkflowState(projectRoot, sessionId).turn_started_at;
  return stamp === void 0 ? NaN : Date.parse(stamp);
}
function stageAgentRowIsOwn(row, sessionId, lastPrompt) {
  if (Number.isNaN(lastPrompt)) {
    return true;
  }
  if (row.session_id !== sessionId) {
    return false;
  }
  const at = Date.parse(rowRecordedAt(row) ?? "");
  return !Number.isNaN(at) && at >= lastPrompt;
}
function isStageAgentRow(row) {
  return row.kind === STAGE_AGENT_KIND || isStageAgent(row.agent);
}
function editedThisTurn(owned, state) {
  const turnStart = state.turn_started_at === void 0 ? NaN : Date.parse(state.turn_started_at);
  if (Number.isNaN(turnStart)) {
    return owned.length > 0;
  }
  return owned.some((row) => {
    if (row.session_source === "cache") {
      return false;
    }
    const at = Date.parse(rowRecordedAt(row) ?? "");
    return !Number.isNaN(at) && at >= turnStart;
  });
}
function editedDevelopmentThisTurn(projectRoot, sessionId) {
  const development = sessionOwnedRows(projectRoot, sessionId).filter(
    (row) => row.stage === "development"
  );
  if (development.length === 0) {
    return false;
  }
  return editedThisTurn(development, readWorkflowState(projectRoot, sessionId));
}
function hasRowPastSpecification(rows) {
  return rows.some((row) => typeof row.stage === "string" && !PRE_EDIT_STAGES.has(row.stage));
}
function changePastSpecification(projectRoot, sessionId) {
  const dirName = currentFeature(projectRoot, sessionId);
  return dirName !== null && hasRowPastSpecification(readFeatureStageUnit(projectRoot, dirName));
}
function isEmptyChange(projectRoot, sessionId, state) {
  const turnStart = state.turn_started_at === void 0 ? NaN : Date.parse(state.turn_started_at);
  if (Number.isNaN(turnStart)) return false;
  const dirName = currentFeature(projectRoot, sessionId);
  if (!dirName) return false;
  const elsewhere = ownedRowsByChange(projectRoot, sessionId).filter((change) => change.dirName !== dirName).flatMap((change) => change.rows);
  if (editedThisTurn(elsewhere, state)) return false;
  const rows = readFeatureStageUnit(projectRoot, dirName);
  if (hasRowPastSpecification(rows)) return false;
  const endedThisTurn = rows.some((row) => {
    if (row.kind !== "stage_end") return false;
    const at = Date.parse(rowRecordedAt(row) ?? "");
    return Number.isNaN(at) || at >= turnStart;
  });
  if (endedThisTurn) return false;
  return activeChangeFiles(projectRoot, sessionId)?.length === 0;
}
function classifyCompletionEnforcement(projectRoot, sessionId) {
  let resolved;
  try {
    resolved = resolveSessionId(projectRoot, sessionId ?? null);
  } catch {
    return { enforce: false, reason: "not-owner", activeWorkflow: null };
  }
  const state = readWorkflowState(projectRoot, resolved);
  const activeWorkflow = state.active?.workflow ?? null;
  const owned = sessionOwnedRows(projectRoot, resolved);
  if (owned.length === 0) {
    return { enforce: false, reason: "not-owner", activeWorkflow };
  }
  if (isEmptyChange(projectRoot, resolved, state)) {
    return { enforce: false, reason: "empty-change", activeWorkflow };
  }
  if (editedThisTurn(owned, state)) {
    return { enforce: true, reason: "edited-this-turn", activeWorkflow };
  }
  if (activeWorkflow === null) {
    return { enforce: true, reason: "owner-unknown-route", activeWorkflow };
  }
  if (isFeatureDevelopmentRoute(activeWorkflow)) {
    return { enforce: true, reason: "owner-feature-dev", activeWorkflow };
  }
  return { enforce: false, reason: "detour", activeWorkflow };
}

// src/delivery/delivery-check.ts
init_esm_shims();

// src/delivery/ai-attribution.ts
init_esm_shims();
var AI_ATTRIBUTION_MARKERS = [
  {
    id: "claude-code",
    vendor: "Claude Code",
    pattern: /(?:co-authored-by:\s*claude\b|generated with \[?claude code)/i,
    configuredByPaqad: true,
    remediation: 'paqad writes `attribution: { commit: "", pr: "", sessionUrl: false }` into `.claude/settings.json` at onboarding. If the trailer is still appearing, that file was edited or a higher-precedence settings file overrides it (`.claude/settings.local.json`, then the command line, then managed settings).'
  },
  {
    id: "codex-cli",
    vendor: "Codex CLI",
    pattern: /co-authored-by:\s*codex\b/i,
    configuredByPaqad: false,
    remediation: 'Codex only exposes this in your home directory, which paqad will not write. Set `commit_attribution = ""` in `~/.codex/config.toml`.'
  },
  {
    id: "cursor",
    vendor: "Cursor",
    pattern: /(?:co-authored-by:\s*cursor\b|made[- ]with:?\s*cursor\b)/i,
    configuredByPaqad: false,
    remediation: "Cursor only exposes this in your home directory, which paqad will not write. Set `attribution.attributeCommitsToAgent` to false in `~/.cursor/cli-config.json`, or turn it off for everyone from the Cursor admin dashboard."
  },
  {
    id: "aider",
    vendor: "Aider",
    pattern: /co-authored-by:\s*aider\b/i,
    configuredByPaqad: true,
    remediation: "paqad writes `attribute-author`, `attribute-committer` and `attribute-co-authored-by` as false into `.aider.conf.yml` at onboarding. If the trailer is still appearing, that file was edited or aider was run with an explicit `--attribute-\u2026` flag."
  },
  {
    id: "github-copilot",
    vendor: "GitHub Copilot",
    pattern: /co-authored-by:\s*(?:copilot\b|copilot-swe-agent\b)/i,
    configuredByPaqad: false,
    remediation: "The Copilot coding agent commits under its own GitHub identity; there is no project file to set. Control it from your organisation\u2019s Copilot policy settings on GitHub."
  },
  {
    id: "gemini-cli",
    vendor: "Gemini CLI",
    pattern: /(?:co-authored-by:\s*gemini\b|generated with gemini\b)/i,
    configuredByPaqad: false,
    remediation: "Gemini CLI adds no attribution by default, so this was added by hand or by a local commit template. Remove it from whatever added it."
  }
];
function detectAiAttribution(text) {
  if (!text) {
    return [];
  }
  const matches = [];
  for (const marker of AI_ATTRIBUTION_MARKERS) {
    const found = marker.pattern.exec(text);
    if (found) {
      matches.push({ marker, matched: found[0] });
    }
  }
  return matches;
}

// src/delivery/delivery-ledger.ts
init_esm_shims();
var DELIVERY_EVIDENCE_DOC_TYPE = "delivery-evidence";
var DELIVERY_EVIDENCE_SCHEMA_VERSION = 1;

// src/delivery/runner.ts
init_esm_shims();

// src/git-workflow/patterns.ts
init_esm_shims();

// src/delivery/templates.ts
init_esm_shims();
var PLACEHOLDER = /\{([a-z_]+)\}/g;
var SEPARATORS = /* @__PURE__ */ new Set(["-", "_", ":", "/", "|", " "]);
function slugify(value, maxLength) {
  const slug = value.toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
  if (slug.length <= maxLength) {
    return slug;
  }
  return slug.slice(0, maxLength).replace(/-+$/, "");
}
function resolveBranchTypeName(config, ticketType) {
  if (ticketType) {
    const match = Object.entries(config.branches.types).filter(
      ([, type]) => type.ticket_types.includes(ticketType)
    );
    if (match.length === 1) return match[0][0];
  }
  return config.branches.default_type;
}
function known(vars, key) {
  return Object.prototype.hasOwnProperty.call(vars, key);
}
function placeholdersIn(line, vars) {
  return Array.from(line.matchAll(PLACEHOLDER), (match) => match[1]).filter(
    (key) => known(vars, key)
  );
}
function dropEmptyLines(template, vars) {
  const lines = template.split("\n");
  const kept = lines.filter((line, index) => {
    if (index === 0) return true;
    const keys = placeholdersIn(line, vars);
    return keys.length === 0 || keys.some((key) => vars[key] !== "");
  });
  while (kept.length > 1 && kept[kept.length - 1].trim() === "") kept.pop();
  return kept.join("\n");
}
function cleanEmptyPlaceholders(line, vars) {
  let out = line;
  for (; ; ) {
    const match = Array.from(out.matchAll(PLACEHOLDER)).find(
      (m) => known(vars, m[1]) && vars[m[1]] === ""
    );
    if (!match || match.index === void 0) return out;
    const start = match.index;
    const end = start + match[0].length;
    let after = end;
    while (after < out.length && SEPARATORS.has(out[after])) after += 1;
    const rest = out.slice(end).replace(PLACEHOLDER, (m, key) => known(vars, key) && vars[key] === "" ? "" : m);
    if ([...rest].every((ch) => SEPARATORS.has(ch))) {
      let before = start;
      while (before > 0 && SEPARATORS.has(out[before - 1])) before -= 1;
      out = out.slice(0, before) + out.slice(end);
    } else {
      out = out.slice(0, start) + out.slice(after);
    }
  }
}
function removeEmptyBrackets(text) {
  return text.replace(/ ?(\(\)|\[\])/g, "");
}
function cleanBranch(text) {
  return text.replace(/-{2,}/g, "-").replace(/\/{2,}/g, "/").replace(/^[-_/]+|[-_/]+$/g, "");
}
function renderTemplate(template, vars, options = {}) {
  const endsWithNewline = template.endsWith("\n");
  const lines = dropEmptyLines(template, vars).split("\n").map((line) => removeEmptyBrackets(cleanEmptyPlaceholders(line, vars)));
  let out = lines.join("\n").replace(PLACEHOLDER, (match, key) => known(vars, key) ? vars[key] : match);
  if (options.branch) out = cleanBranch(out);
  return endsWithNewline && !out.endsWith("\n") ? `${out}
` : out;
}
function withTechDebt(template, body, section) {
  if (section === "" || template.includes("{tech_debt}")) {
    return body;
  }
  const trimmed = stripTrailingChars(body, " 	\r\n");
  return trimmed === "" ? `${section}
` : `${trimmed}

${section}
`;
}
function withSection(body, section) {
  if (section === "") return body;
  const trimmed = stripTrailingChars(body, " 	\r\n");
  return trimmed === "" ? `${section}
` : `${trimmed}

${section}
`;
}
function renderVars(config, typeName, inputs) {
  const branchType = config.branches.types[typeName];
  return {
    prefix: branchType?.prefix ?? typeName,
    type: branchType?.commit_type ?? typeName,
    ticket: inputs.ticket || "",
    title: inputs.title,
    title_slug: slugify(inputs.title, config.branches.slug_max_length),
    summary: inputs.summary,
    scope: inputs.scope ?? "",
    tech_debt: inputs.tech_debt ?? ""
  };
}
function renderBranchName(config, typeName, inputs) {
  const vars = renderVars(config, typeName, inputs);
  const template = config.branches.types[typeName]?.template ?? config.branches.template;
  return renderTemplate(
    template,
    { ...vars, ticket: vars.ticket.replace(/^#/, "") },
    { branch: true }
  );
}
function renderDelivery(config, inputs, prBodyTemplate, typeName = resolveBranchTypeName(config, inputs.ticket_type)) {
  const vars = renderVars(config, typeName, inputs);
  return {
    branch: renderBranchName(config, typeName, inputs),
    commit: renderTemplate(config.commits.template, vars),
    pr_title: renderTemplate(config.pull_requests.title_template, vars),
    pr_body: withSection(
      withTechDebt(prBodyTemplate, renderTemplate(prBodyTemplate, vars), vars.tech_debt),
      inputs.design_sign_off ?? ""
    )
  };
}

// src/git-workflow/patterns.ts
var PLACEHOLDERS = [
  "prefix",
  "type",
  "ticket",
  "title",
  "title_slug",
  "summary",
  "scope",
  "tech_debt"
];
function escapeRegExp2(text) {
  return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function alternation(values, fallback) {
  const unique = Array.from(new Set(values.filter((value) => value !== "")));
  return unique.length === 0 ? fallback : `(?:${unique.map(escapeRegExp2).join("|")})`;
}
function ticketPatterns(config, branch = false) {
  return Object.values(config.tickets.sources).map((source) => source.pattern).filter((pattern) => pattern !== "").map((pattern) => branch ? pattern.replace(/^#/, "") : pattern);
}
function pieces(config, options) {
  const types = Object.values(config.branches.types);
  const tickets = ticketPatterns(config, options.branch === true);
  return {
    prefix: alternation(options.prefixes ?? types.map((type) => type.prefix), "[^/\\s]+"),
    type: alternation(
      types.map((type) => type.commit_type),
      "\\w+"
    ),
    ticket: options.ticketCapture !== void 0 ? `(?<ticket>${options.ticketCapture})` : tickets.length === 0 ? "\\S+" : `(?:${tickets.map((p) => `(?:${p})`).join("|")})`,
    title_slug: "[a-z0-9]+(?:-[a-z0-9]+)*",
    scope: "[^()\\s]+",
    title: ".+",
    summary: ".+",
    tech_debt: "[\\s\\S]*"
  };
}
function subsets(items) {
  return items.reduce(
    (all, item) => [...all, ...all.map((subset) => [...subset, item])],
    [[]]
  );
}
function templateToRegExp(template, config, options = {}) {
  const regexPieces = pieces(config, options);
  const present = PLACEHOLDERS.filter((key) => template.includes(`{${key}}`));
  const optional = (options.optional ?? []).filter((key) => present.includes(key));
  const patterns = subsets(optional).map((emptied) => {
    const vars = {};
    for (const key of PLACEHOLDERS) {
      vars[key] = emptied.includes(key) ? "" : `\uE000${key}\uE001`;
    }
    const rendered = renderTemplate(template, vars, { branch: options.branch === true });
    const source = rendered.split(/(\uE000[a-z_]+\uE001)/).map((part) => {
      const key = /^\uE000([a-z_]+)\uE001$/.exec(part)?.[1];
      return key ? regexPieces[key] : escapeRegExp2(part);
    }).join("");
    return new RegExp(`^${source}$`);
  });
  return { patterns, test: (text) => patterns.some((pattern) => pattern.test(text)) };
}
function ticketOptional(config) {
  return config.tickets.require_ticket ? ["scope"] : ["ticket", "scope"];
}
function firstLine(template) {
  return template.split("\n")[0];
}

// src/git-workflow/summary-style.ts
init_esm_shims();
var FILE_PATH = /\b[\w.-]+\/[\w./-]+\b/;
var FILE_NAME = /\b\w+\.(ts|tsx|js|jsx|php|py|rb|go|java|kt|cs|vue|css|scss|sql|yml|yaml|json|md)\b/;
var CODE_SYMBOLS = ["::", "->", "=>", "()"];
var CAMEL = /[a-z][A-Z]/;
var SNAKE = /[A-Za-z]_[A-Za-z]/;
var VAGUE = /* @__PURE__ */ new Set([
  "fix",
  "fixes",
  "fix bug",
  "fix bugs",
  "bug fix",
  "bugfix",
  "update",
  "updates",
  "change",
  "changes",
  "wip",
  "misc",
  "minor changes",
  "cleanup",
  "refactor",
  "tweaks",
  "small fixes"
]);
var IGNORED_SUBJECT = /^(Merge |Revert "|fixup! |squash! )/;
function isIgnoredSubject(subject) {
  return IGNORED_SUBJECT.test(subject);
}
function escapeRegExp3(text) {
  return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function ticketRegExps(config) {
  return Object.values(config.tickets.sources).flatMap((source) => {
    try {
      return [new RegExp(source.pattern, "g")];
    } catch {
      return [];
    }
  });
}
var SUMMARY_PIECES = {
  ticket: "[^\\s()\\[\\]:]*",
  scope: "[^()\\s]*",
  prefix: "[^/\\s]*",
  title: ".*?",
  title_slug: "[a-z0-9-]*",
  tech_debt: "[\\s\\S]*?"
};
function summaryPart(subject, template, config) {
  const line = firstLine(template);
  if (line.includes("{summary}")) {
    const types = Object.values(config.branches.types).map(
      (type) => escapeRegExp3(type.commit_type)
    );
    const typePiece = types.length > 0 ? `(?:${types.join("|")})` : "\\w+";
    const tickets = ticketPatterns(config);
    const ticketPiece = tickets.length > 0 ? `(?:${tickets.map((pattern) => `(?:${pattern})`).join("|")})?` : "";
    const source = line.split(/(\{[a-z_]+\})/).map((part) => {
      const key = /^\{([a-z_]+)\}$/.exec(part)?.[1];
      if (key === "summary") return "(?<summary>.+?)";
      if (key === "type") return typePiece;
      if (key === "ticket") return ticketPiece;
      if (key && SUMMARY_PIECES[key]) return SUMMARY_PIECES[key];
      return escapeRegExp3(part).replace(/[ :([\]\\)]+/g, (sep2) => `(?:${sep2})?`);
    }).join("");
    const match = new RegExp(`^${source}$`).exec(subject);
    const summary = match?.groups?.summary;
    if (summary !== void 0) return withoutTickets(summary, config);
  }
  return withoutTickets(subject, config);
}
function withoutTickets(text, config) {
  let out = text;
  for (const pattern of ticketRegExps(config)) out = out.replace(pattern, "");
  return out.replace(/\s{2,}/g, " ").trim();
}
function summaryStyleReasons(summary, maxLength) {
  const reasons = [];
  if (summary.includes("`")) reasons.push("it contains code formatting");
  if (FILE_PATH.test(summary) || FILE_NAME.test(summary)) reasons.push("it names a file");
  if (CODE_SYMBOLS.some((symbol) => summary.includes(symbol))) {
    reasons.push("it uses code symbols");
  }
  const words = summary.split(/\s+/).filter((word) => word !== "");
  if (words.some((word) => CAMEL.test(word) || SNAKE.test(word))) {
    reasons.push("it names a function or variable");
  }
  if (summary.length > maxLength) reasons.push(`it's longer than ${maxLength} characters`);
  const normalized = summary.toLowerCase().replace(/[.!?,;:]+$/, "").trim();
  if (VAGUE.has(normalized)) {
    reasons.push("it doesn't say what changed");
  } else if (words.length < 3) {
    reasons.push("it's too short to say what changed");
  }
  const first = (words[0] ?? "").replace(/[^A-Za-z]/g, "");
  if (first.length > 4 && first.toLowerCase().endsWith("ing")) {
    reasons.push('start with what it does, like "Show", not "Showing"');
  }
  return reasons;
}
function checkSummaryStyle(subject, template, config) {
  if (config.commits.summary.style !== "plain" || isIgnoredSubject(subject)) return [];
  return summaryStyleReasons(
    summaryPart(subject, template, config),
    config.commits.summary.max_length
  );
}
function summaryStyleMessage(subject, reason, what = "commit") {
  return `${what} "${subject}" reads like code, not plain language: ${reason}.`;
}

// src/delivery/runner.ts
var GIT_WORKFLOW_PATH = PATHS.GIT_WORKFLOW_DIR;
function stop(message) {
  return { ok: false, message };
}
function done(value) {
  return { ok: true, value };
}
function wholeMatch(pattern, ref) {
  try {
    return new RegExp(`^(?:${pattern})$`).test(ref);
  } catch {
    return false;
  }
}
function requireTicket(config, ticket) {
  if (config.tickets.require_ticket && ticket.trim() === "") {
    return stop(
      "This team's git workflow needs a ticket for every change. Re-run with --ticket <ref>."
    );
  }
  return done(null);
}
function matchTicketSource(config, ticket) {
  const sources = Object.entries(config.tickets.sources);
  if (ticket.trim() === "" || sources.length === 0) return done(null);
  const found = sources.find(([, source]) => wholeMatch(source.pattern, ticket));
  if (found) return done({ name: found[0], source: found[1] });
  const list = sources.map(([name, source]) => `${name}: ${source.pattern}`).join(", ");
  return stop(
    `${ticket} doesn't look like any ticket this team uses (${list}). Check the key or add a source in ${GIT_WORKFLOW_PATH}/tickets.yaml.`
  );
}
function branchPattern(config, typeName) {
  const type = config.branches.types[typeName];
  return templateToRegExp(type?.template ?? config.branches.template, config, {
    optional: ticketOptional(config),
    branch: true,
    prefixes: type ? [type.prefix] : void 0
  });
}
function typeOfBranch(config, branch) {
  return Object.keys(config.branches.types).filter(
    (name) => branchPattern(config, name).test(branch)
  );
}
function allows(config, typeName, source) {
  const tickets = config.branches.types[typeName]?.tickets ?? [];
  return source === null || tickets.length === 0 || tickets.includes(source);
}
function pickBranchType(config, input) {
  const names = Object.keys(config.branches.types);
  if (input.type) {
    if (!names.includes(input.type)) {
      return stop(
        `There's no branch type called ${input.type}. This team's types are: ${names.join(", ")}.`
      );
    }
    return done(input.type);
  }
  if (input.source) {
    const bySource = names.filter(
      (name) => config.branches.types[name].tickets.includes(input.source)
    );
    if (bySource.length === 1) return done(bySource[0]);
  }
  if (input.ticketType) {
    const byTicketType = names.filter(
      (name) => config.branches.types[name].ticket_types.includes(input.ticketType)
    );
    if (byTicketType.length === 1) return done(byTicketType[0]);
  }
  if (input.currentBranch) {
    const byBranch = typeOfBranch(config, input.currentBranch);
    if (byBranch.length === 1) return done(byBranch[0]);
  }
  const fallback = config.branches.default_type;
  if (!input.ticketGiven || allows(config, fallback, input.source)) return done(fallback);
  const candidates = names.filter((name) => allows(config, name, input.source));
  return stop(
    `This change could be ${(candidates.length > 0 ? candidates : names).join(", ")}. Re-run with --type <name>.`
  );
}
function compareVersions(a, b) {
  const split = (value) => value.split(/[.-]/);
  const pa = split(a);
  const pb = split(b);
  for (let i = 0; i < Math.max(pa.length, pb.length); i += 1) {
    const x = pa[i];
    const y = pb[i];
    if (x === void 0) return -1;
    if (y === void 0) return 1;
    const nx = Number(x);
    const ny = Number(y);
    if (Number.isInteger(nx) && Number.isInteger(ny) && nx !== ny) return nx - ny;
    if (!(Number.isInteger(nx) && Number.isInteger(ny)) && x !== y) return x < y ? -1 : 1;
  }
  return a < b ? -1 : a > b ? 1 : 0;
}
function highestVersionBranch(branches) {
  const last = (name) => name.split("/").pop() ?? name;
  return [...branches].sort((a, b) => compareVersions(last(a), last(b)) || (a < b ? -1 : 1)).pop() ?? null;
}
function isGlob(base) {
  return base.includes("*");
}
function globMatches(glob, name) {
  const source = glob.split("*").map((part) => part.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join(".*");
  return new RegExp(`^${source}$`).test(name);
}
async function resolveBase(shell, base) {
  if (!isGlob(base)) return done(base);
  const listed = await shell.run("git", ["ls-remote", "--heads", "origin", base]);
  const names = listed.exitCode === 0 ? listed.stdout.split("\n").map((line) => line.trim().split(/\s+/)[1] ?? "").filter((ref) => ref.startsWith("refs/heads/")).map((ref) => ref.slice("refs/heads/".length)).filter((name) => globMatches(base, name)) : [];
  const picked = highestVersionBranch(names);
  return picked ? done(picked) : stop(`No branch on origin matches ${base}.`);
}
function configuredBases(config) {
  return Array.from(
    /* @__PURE__ */ new Set([
      config.branches.base_branch,
      ...Object.values(config.branches.types).map((type) => type.base_branch),
      ...config.pull_requests.also_open_against
    ])
  );
}
function isConfiguredBase(config, branch) {
  return configuredBases(config).some(
    (base) => isGlob(base) ? globMatches(base, branch) : base === branch
  );
}
function planBranch(config, current, base, rendered, typeName) {
  if (current === base) return done({ branch: rendered, create: true });
  if (typeOfBranch(config, current).length > 0) return done({ branch: current, create: false });
  if (isConfiguredBase(config, current)) {
    return stop(
      `You're on ${current}, but this change should start from ${base}. Switch to ${base} and re-run paqad-ai deliver.`
    );
  }
  const template = config.branches.types[typeName]?.template ?? config.branches.template;
  return stop(
    `You're on ${current}, which doesn't follow this team's branch pattern ${template}. Switch to ${base} or rename the branch, then re-run paqad-ai deliver.`
  );
}
async function currentBranch(shell) {
  const result = await shell.run("git", ["rev-parse", "--abbrev-ref", "HEAD"]);
  const name = result.stdout.trim();
  return result.exitCode === 0 && name !== "" && name !== "HEAD" ? name : null;
}
async function hasStagedChanges(shell) {
  const result = await shell.run("git", ["diff", "--cached", "--quiet"]);
  return result.exitCode === 1;
}
async function commitsSince(shell, base) {
  for (const ref of [base, `origin/${base}`]) {
    const result = await shell.run("git", ["rev-list", "--count", `${ref}..HEAD`]);
    if (result.exitCode === 0) return Number.parseInt(result.stdout.trim(), 10) || 0;
  }
  return 0;
}
function styleVerdict(config, subject, template, what) {
  const reasons = checkSummaryStyle(subject, template, config);
  return {
    block: reasons.length > 0 && config.enforcement.agents === "block",
    messages: reasons.map((reason) => summaryStyleMessage(subject, reason, what))
  };
}
async function commitStep(shell, config, message, options) {
  const subject = message.split("\n")[0];
  if (await hasStagedChanges(shell)) {
    const style = styleVerdict(config, subject, config.commits.template, "commit");
    if (style.block) return stop(style.messages[0]);
    const args = ["commit", ...config.commits.sign_off ? ["-s"] : [], "-m", message];
    const result = await shell.run("git", args);
    if (result.exitCode !== 0) {
      return stop(
        `git commit failed: ${(result.stderr || result.stdout).trim() || "unknown error"}. Fix it and re-run; do not skip pre-commit hooks.`
      );
    }
    return done({ committed: true, output: result.stdout, warnings: style.messages });
  }
  if (!options.requireWork) {
    return stop("Nothing is staged. Stage your changes with git add first.");
  }
  if (options.base && await commitsSince(shell, options.base) > 0) {
    return done({ committed: false, output: "", warnings: [] });
  }
  return stop(
    `Nothing to deliver: no staged changes and no commits on ${options.branch} since ${options.base ?? "the base"}.`
  );
}
function placeholdersIn2(template) {
  return Array.from(template.matchAll(/\{([a-z_]+)\}/g), (match) => match[1]);
}
function markdownHeadings(text) {
  return text.split("\n").map((line) => line.trimEnd()).filter((line) => /^#{1,6}\s+\S/.test(line));
}
function missingHeadings(template, body) {
  const present = new Set(markdownHeadings(body).map((line) => line.trim()));
  return markdownHeadings(template).filter((heading) => !present.has(heading.trim()));
}
function linkTicket(body, ticket, matched, enabled) {
  if (!enabled || ticket.trim() === "" || body.includes(ticket)) return body;
  const line = matched?.source.tracker === "github-issues" ? `Closes ${ticket.startsWith("#") ? ticket : `#${ticket}`}` : `Ticket: ${ticket}`;
  if (body.includes(line)) return body;
  const trimmed = body.replace(/\s+$/, "");
  return trimmed === "" ? `${line}
` : `${trimmed}

${line}
`;
}
function uniqueInOrder(...lists) {
  return Array.from(new Set(lists.flat().filter((value) => value !== "")));
}
var MISSING_LABEL_HINT = `If a label is missing, create it on the repo or remove it from ${GIT_WORKFLOW_PATH}/pull-requests.yaml.`;

// src/delivery/workflow-findings.ts
init_esm_shims();
var FIELD_SEPARATOR = String.fromCharCode(31);
var RECORD_SEPARATOR = String.fromCharCode(30);
var BLOCKING_CODES = /* @__PURE__ */ new Set([
  "on-base-branch",
  "branch-shape",
  "commit-shape",
  "commit-summary-style",
  "pr-base",
  "pr-title-shape",
  "pr-labels",
  "pr-ticket-link"
]);
function invalidFinding(errors) {
  return {
    code: "git-workflow-invalid",
    message: `${GIT_WORKFLOW_PATH}/ has errors, so nothing was checked: ${errors[0]?.message ?? "unknown error"}`
  };
}
function onBaseFinding(branch) {
  return {
    code: "on-base-branch",
    message: `you're committing on ${branch}. This team works on a branch and opens a PR from it.`
  };
}
function followsBranchShape(config, branch) {
  return Object.values(config.branches.types).some((type) => {
    const template = type.template || config.branches.template;
    const optional = template.includes("{prefix}") ? ticketOptional(config) : ["scope"];
    return templateToRegExp(template, config, {
      optional,
      branch: true,
      prefixes: [type.prefix]
    }).test(branch);
  });
}
function branchFinding(config, branch) {
  if (isConfiguredBase(config, branch)) return null;
  if (Object.keys(config.branches.types).length === 0) return null;
  if (followsBranchShape(config, branch)) return null;
  return {
    code: "branch-shape",
    message: `branch ${branch} doesn't follow this team's pattern ${config.branches.template}.`
  };
}
function commitFindings(config, commit) {
  const { subject } = commit;
  if (commit.merge || isIgnoredSubject(subject)) return [];
  const template = firstLine(config.commits.template);
  const findings = [];
  const shape = templateToRegExp(template, config, { optional: ticketOptional(config) });
  if (!shape.test(subject)) {
    findings.push({
      code: "commit-shape",
      message: `commit "${subject}" doesn't follow this team's pattern ${template}.`
    });
  }
  for (const reason of checkSummaryStyle(subject, template, config)) {
    findings.push({ code: "commit-summary-style", message: summaryStyleMessage(subject, reason) });
  }
  return findings;
}
function ticketInTemplateSlot(config, pattern, branch) {
  const types = Object.values(config.branches.types);
  const shapes = types.length > 0 ? types.map((type) => ({
    template: type.template || config.branches.template,
    prefixes: [type.prefix]
  })) : [{ template: config.branches.template, prefixes: void 0 }];
  for (const { template, prefixes } of shapes) {
    const { patterns } = templateToRegExp(template, config, {
      optional: ["scope", "title_slug"],
      branch: true,
      prefixes,
      ticketCapture: pattern
    });
    for (const shape of patterns) {
      const ticket = shape.exec(branch)?.groups?.ticket;
      if (ticket) return `#${ticket}`;
    }
  }
  return "";
}
function ticketInBranch(config, branch) {
  for (const source of Object.values(config.tickets.sources)) {
    try {
      if (source.pattern.startsWith("#")) {
        const ticket = ticketInTemplateSlot(config, source.pattern.slice(1), branch);
        if (ticket) return ticket;
        continue;
      }
      const found = new RegExp(`(?<![A-Za-z0-9])(?:${source.pattern})(?![A-Za-z0-9])`).exec(branch);
      if (found) return found[0];
    } catch {
      continue;
    }
  }
  return "";
}
function baseMatches(base, actual) {
  return isGlob(base) ? globMatches(base, actual) : base === actual;
}
function prFindings(config, branch, pr) {
  const findings = [];
  const n = pr.number ?? "?";
  const typeName = typeOfBranch(config, branch)[0] ?? config.branches.default_type;
  const type = config.branches.types[typeName];
  const expected = type?.base_branch || config.branches.base_branch;
  if (pr.base) {
    const accepted = baseMatches(expected, pr.base) || config.pull_requests.also_open_against.includes(pr.base) || typeOfBranch(config, pr.base).length > 0;
    if (!accepted) {
      findings.push({
        code: "pr-base",
        message: `PR #${n} targets ${pr.base}, but this team sends ${typeName} changes to ${expected}.`
      });
    }
  }
  if (pr.title !== null) {
    const template = config.pull_requests.title_template;
    const shape = templateToRegExp(template, config, { optional: ticketOptional(config) });
    if (!shape.test(pr.title)) {
      findings.push({
        code: "pr-title-shape",
        message: `PR #${n} title "${pr.title}" doesn't follow this team's pattern ${template}.`
      });
    }
  }
  const required = Array.from(/* @__PURE__ */ new Set([...config.pull_requests.labels, ...type?.labels ?? []]));
  const missing = required.filter((label) => !pr.labels.includes(label));
  if (missing.length > 0) {
    findings.push({
      code: "pr-labels",
      message: `PR #${n} is missing the labels: ${missing.join(", ")}.`
    });
  }
  const ticket = ticketInBranch(config, branch);
  if (config.pull_requests.link_ticket && ticket !== "" && !pr.body.includes(ticket)) {
    findings.push({ code: "pr-ticket-link", message: `PR #${n} doesn't link ${ticket}.` });
  }
  return findings;
}
function expectedBase(config, branch) {
  const typeName = typeOfBranch(config, branch)[0] ?? config.branches.default_type;
  return config.branches.types[typeName]?.base_branch || config.branches.base_branch;
}
var SUBJECT_FORMAT = "%s%x1f%P%x1e";
function parseSubjects(stdout) {
  return stdout.split(RECORD_SEPARATOR).map((record) => record.replace(/^\n/, "")).filter((record) => record.includes(FIELD_SEPARATOR)).map((record) => {
    const [subject, parents = ""] = record.split(FIELD_SEPARATOR);
    return { subject, merge: parents.trim().split(/\s+/).length > 1 };
  });
}

// src/delivery/delivery-check.ts
var GH_TIMEOUT_MS = 1e4;
var NO_CHECK = {
  ran: false,
  branch: null,
  commit: null,
  ghAvailable: false,
  findings: []
};
var NO_OP = { ran: false, blocking: false, summary: "" };
async function branchSubjectFindings(run, projectRoot, policy, branch) {
  const base = expectedBase(policy, branch);
  if (isGlob(base)) return [];
  for (const ref of [base, `origin/${base}`]) {
    const result = await run("git", [
      "-C",
      projectRoot,
      "log",
      `${ref}..HEAD`,
      `--format=${SUBJECT_FORMAT}`
    ]);
    if (result.exitCode !== 0) continue;
    return parseSubjects(result.stdout).flatMap((commit) => commitFindings(policy, commit));
  }
  return [];
}
async function committedOnBase(run, projectRoot, branch) {
  const ahead = await readLine(run, "git", [
    "-C",
    projectRoot,
    "rev-list",
    "--count",
    `origin/${branch}..HEAD`
  ]);
  return ahead !== null && /^\d+$/.test(ahead) && Number.parseInt(ahead, 10) > 0;
}
function labelNames(value) {
  if (!Array.isArray(value)) return [];
  return value.map((entry) => entry.name).filter((name) => typeof name === "string");
}
async function readLine(run, command, args) {
  const result = await run(command, args);
  if (result.exitCode !== 0) {
    return null;
  }
  const line = result.stdout.trim();
  return line.length > 0 ? line : null;
}
async function readBranchCommitMessages(run, projectRoot, branch, base) {
  const range = branch === base ? ["-1"] : [`${base}..HEAD`];
  const result = await run("git", ["-C", projectRoot, "log", ...range, "--format=%B"]);
  return result.exitCode === 0 ? result.stdout : "";
}
function attributionFindings(commitMessages, prBody) {
  const inCommits = new Map(
    detectAiAttribution(commitMessages).map((match) => [match.marker.id, match.marker])
  );
  const inPrBody = new Map(
    detectAiAttribution(prBody).map((match) => [match.marker.id, match.marker])
  );
  const findings = [];
  for (const marker of AI_ATTRIBUTION_MARKERS) {
    const commits = inCommits.has(marker.id);
    const body = inPrBody.has(marker.id);
    if (!commits && !body) {
      continue;
    }
    const where = commits && body ? "your commits and the PR body" : commits ? "your commits" : "the PR body";
    findings.push({
      code: "ai-attribution",
      message: `${marker.vendor} attribution is on ${where}. That trailer is what makes the repo list an AI vendor as a contributor. ${marker.remediation}`
    });
  }
  return findings;
}
var FAILING = /* @__PURE__ */ new Set(["FAILURE", "ERROR", "TIMED_OUT", "CANCELLED", "ACTION_REQUIRED"]);
function rollupHasFailure(rollup) {
  if (!Array.isArray(rollup)) {
    return false;
  }
  return rollup.some((entry) => {
    const check = entry;
    const conclusion = typeof check.conclusion === "string" ? check.conclusion.toUpperCase() : "";
    const state = typeof check.state === "string" ? check.state.toUpperCase() : "";
    return FAILING.has(conclusion) || FAILING.has(state);
  });
}
var PASSING = /* @__PURE__ */ new Set(["SUCCESS", "NEUTRAL", "SKIPPED"]);
function upper(value) {
  return typeof value === "string" ? value.toUpperCase() : "";
}
function textOrNull(value) {
  return typeof value === "string" && value.length > 0 ? value : null;
}
var FULL_SHA = /^[0-9a-f]{40}$/;
function shaOrNull(value) {
  return typeof value === "string" && FULL_SHA.test(value) ? value : null;
}
function countOrNull(value) {
  return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : null;
}
function rollupJobs(rollup) {
  const entries = Array.isArray(rollup) ? rollup : [];
  return entries.map((entry) => ({
    status: textOrNull(entry.status) ?? textOrNull(entry.state),
    conclusion: textOrNull(entry.conclusion),
    started_at: textOrNull(entry.startedAt),
    completed_at: textOrNull(entry.completedAt)
  }));
}
var PR_STATES = {
  OPEN: "open",
  MERGED: "merged",
  CLOSED: "closed"
};
function readPrState(value) {
  return PR_STATES[upper(value)] ?? null;
}
function readRollup(rollup) {
  const entries = Array.isArray(rollup) ? rollup : [];
  if (entries.length === 0) {
    return { state: "unknown", done: 0, total: 0, reason: "no checks reported", jobs: [] };
  }
  let done2 = 0;
  let failed = false;
  for (const entry of entries) {
    const outcome = upper(entry.conclusion) || upper(entry.state);
    if (FAILING.has(outcome)) failed = true;
    if (FAILING.has(outcome) || PASSING.has(outcome)) done2 += 1;
  }
  const total = entries.length;
  const state = failed ? "fail" : done2 === total ? "pass" : "pending";
  return { state, done: done2, total, reason: null, jobs: rollupJobs(entries) };
}
function ghFailureReason(result) {
  const text = `${result.stderr ?? ""}
${result.stdout}`.toLowerCase();
  if (result.timedOut) return `gh timed out after ${GH_TIMEOUT_MS / 1e3} s`;
  if (result.missing) return "gh is not installed";
  if (text.includes("rate limit")) return "gh is rate-limited";
  if (text.includes("auth login") || text.includes("not logged in")) return "gh is signed out";
  if (text.includes("no pull requests found")) return "no pull request for this branch";
  return "gh could not read the pull request";
}
async function evaluateDelivery(input) {
  const { projectRoot, policy, run } = input;
  if (!policy.enforcement.enabled) {
    return NO_CHECK;
  }
  const branch = await readLine(run, "git", [
    "-C",
    projectRoot,
    "rev-parse",
    "--abbrev-ref",
    "HEAD"
  ]);
  if (!branch || branch === "HEAD") {
    return NO_CHECK;
  }
  const commit = await readLine(run, "git", ["-C", projectRoot, "rev-parse", "HEAD"]);
  const upstream = await readLine(run, "git", ["-C", projectRoot, "rev-parse", "@{upstream}"]);
  if (input.errors && input.errors.length > 0) {
    return {
      ran: true,
      branch,
      commit,
      ghAvailable: false,
      findings: [invalidFinding(input.errors)],
      upstream
    };
  }
  const findings = [];
  const base = policy.branches.base_branch;
  const onBase = isConfiguredBase(policy, branch);
  if (onBase) {
    if (await committedOnBase(run, projectRoot, branch)) findings.push(onBaseFinding(branch));
  } else {
    const shape = branchFinding(policy, branch);
    if (shape) findings.push(shape);
    findings.push(...await branchSubjectFindings(run, projectRoot, policy, branch));
  }
  let ghAvailable = false;
  let prBody = "";
  let prReading = null;
  let ciReading;
  const prView = await run("gh", [
    "pr",
    "view",
    branch,
    "--json",
    "number,url,state,headRefOid,statusCheckRollup,body,isDraft,changedFiles,additions,deletions,baseRefName,title,labels"
  ]);
  if (prView.exitCode === 0) {
    ghAvailable = true;
    try {
      const pr = JSON.parse(prView.stdout);
      prBody = typeof pr.body === "string" ? pr.body : "";
      prReading = {
        number: typeof pr.number === "number" ? pr.number : null,
        url: typeof pr.url === "string" ? pr.url : null,
        draft: typeof pr.isDraft === "boolean" ? pr.isDraft : null,
        state: readPrState(pr.state),
        head_sha: shaOrNull(pr.headRefOid),
        changed_files: countOrNull(pr.changedFiles),
        additions: countOrNull(pr.additions),
        deletions: countOrNull(pr.deletions)
      };
      ciReading = readRollup(pr.statusCheckRollup);
      if (!onBase) {
        findings.push(
          ...prFindings(policy, branch, {
            number: prReading.number,
            base: textOrNull(pr.baseRefName),
            title: typeof pr.title === "string" ? pr.title : null,
            labels: labelNames(pr.labels),
            body: prBody
          })
        );
      }
      if (policy.pull_requests.ci.gate === "wait_for_green" && rollupHasFailure(pr.statusCheckRollup)) {
        const label = typeof pr.number === "number" ? ` for PR #${pr.number}` : "";
        findings.push({
          code: "ci-red",
          message: `CI is red${label} \u2014 the delivery policy gates merge on green (\`wait_for_green\`).`
        });
      }
    } catch {
      ghAvailable = false;
      prReading = null;
      ciReading = {
        state: "unknown",
        done: null,
        total: null,
        reason: "gh returned unreadable output"
      };
    }
  } else {
    ciReading = { state: "unknown", done: null, total: null, reason: ghFailureReason(prView) };
  }
  if (shouldStripAiAttribution(projectRoot)) {
    const commitMessages = await readBranchCommitMessages(run, projectRoot, branch, base);
    findings.push(...attributionFindings(commitMessages, prBody));
  }
  findings.push(...pendingMoveFindings(projectRoot, branch));
  return {
    ran: true,
    branch,
    commit,
    ghAvailable,
    findings,
    pr: prReading,
    ci: ciReading,
    upstream
  };
}
function pendingMoveFindings(projectRoot, branch) {
  try {
    const dirName = resolveDeliveryFeatureByBranch(projectRoot, branch);
    if (!dirName) return [];
    return pendingTicketActions(readFeatureDelivery(projectRoot, dirName)).map((action) => ({
      code: "ticket-move-pending",
      message: `ticket move to "${action.to}" not confirmed`
    }));
  } catch {
    return [];
  }
}
function pushedHead(result) {
  const head = shaOrNull(result.commit);
  if (!head) return null;
  return result.upstream === head || result.pr?.head_sha === head ? head : null;
}
function ciReadOf(result) {
  if (result.ghAvailable) return { ok: true, reason: null };
  return { ok: false, reason: result.ci?.reason ?? "gh could not read the pull request" };
}
function saveDeliveryReading(projectRoot, result, now = () => /* @__PURE__ */ new Date(), options = {}) {
  if (!result.ran || !result.branch) return;
  try {
    const dirName = resolveDeliveryFeatureByBranch(projectRoot, result.branch);
    if (!dirName) return;
    const patch = { ci_read: ciReadOf(result) };
    if (options.startedAt) patch.read_at = options.startedAt;
    const pushed = pushedHead(result);
    if (pushed) patch.push = { state: "pushed", branch: result.branch, sha: pushed };
    const keep = options.keepOnGhFailure === true && !result.ghAvailable;
    if (result.pr && !keep) patch.pr = result.pr;
    if (result.ci && !keep) {
      const sha = shaOrNull(result.pr?.head_sha) ?? shaOrNull(result.commit);
      patch.ci = { sha, ...result.ci, source: "stop-check" };
    }
    recordShip(projectRoot, dirName, patch, now().toISOString());
  } catch (error) {
    appendHooksLog(
      projectRoot,
      `delivery-check: delivery.json not updated: ${error instanceof Error ? error.message : String(error)}`
    );
  }
}
function storedOutcome(projectRoot, feature, refreshed, reason) {
  const record = readFeatureDelivery(projectRoot, feature);
  const ci = record.ci ?? null;
  return {
    refreshed,
    feature,
    reason,
    push: record.push?.state ?? null,
    ci: ci ? { state: ci.state, done: ci.done, total: ci.total } : null
  };
}
function readIsFresh(read, minAgeSeconds, now) {
  if (!read || !minAgeSeconds || minAgeSeconds <= 0) return false;
  const age = now.getTime() - Date.parse(read.at);
  return Number.isFinite(age) && age >= 0 && age < minAgeSeconds * 1e3;
}
async function refreshDelivery(input) {
  const { projectRoot, run } = input;
  const now = input.now ?? (() => /* @__PURE__ */ new Date());
  const skip = (reason2) => ({
    refreshed: false,
    feature: null,
    reason: reason2,
    push: null,
    ci: null
  });
  const policy = loadGitWorkflow(projectRoot).config;
  if (!policy.enforcement.enabled) return skip("delivery policy is off");
  const branch = await readLine(run, "git", [
    "-C",
    projectRoot,
    "rev-parse",
    "--abbrev-ref",
    "HEAD"
  ]);
  if (!branch || branch === "HEAD") return skip("no branch");
  const feature = resolveDeliveryFeatureByBranch(projectRoot, branch);
  if (!feature) return skip("no change on this branch");
  const stored = readFeatureDelivery(projectRoot, feature).ci_read;
  if (readIsFresh(stored, input.minAgeSeconds, now())) {
    return storedOutcome(projectRoot, feature, false, "fresh");
  }
  const startedAt = now().toISOString();
  const result = await evaluateDelivery({ projectRoot, policy, run });
  if (!result.ran) return skip("no branch");
  saveDeliveryReading(projectRoot, result, now, { startedAt, keepOnGhFailure: true });
  const reason = ciReadOf(result).reason;
  return storedOutcome(projectRoot, feature, result.ghAvailable, reason);
}
function isBlockingFinding(finding) {
  return BLOCKING_CODES.has(finding.code);
}
function formatDeliverySummary(result, blocking = false) {
  if (result.findings.length === 0) {
    return "";
  }
  const lines = result.findings.map(
    (finding) => `> - ${blocking && isBlockingFinding(finding) ? "\u{1F534}" : "\u{1F7E1}"} ${finding.message}`
  );
  const lead = blocking ? `> \u{1F534} Needs your attention: this team's git workflow (agents: block) holds this turn until these are fixed:
` : `> Heads up \u2014 a delivery convention to tidy before you push (not blocking):
`;
  return `**\u25B8 paqad** \xB7 checked how this change would ship
` + lead + lines.join("\n");
}
async function runDeliveryCapability(projectRoot, seam, run) {
  if (seam !== "completion") {
    return NO_OP;
  }
  const loaded = loadGitWorkflow(projectRoot);
  const policy = loaded.config;
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
  const result = await evaluateDelivery({ projectRoot, policy, run, errors: loaded.errors });
  if (!result.ran) {
    return NO_OP;
  }
  if (loaded.valid) saveDeliveryReading(projectRoot, result, void 0, { startedAt });
  recordProjectEvent(
    projectRoot,
    DELIVERY_EVIDENCE_DOC_TYPE,
    {
      kind: "delivery-check",
      branch: result.branch,
      commit: result.commit,
      gh_available: result.ghAvailable,
      findings: result.findings
    },
    DELIVERY_EVIDENCE_SCHEMA_VERSION
  );
  const blocking = loaded.valid && policy.enforcement.agents === "block" && result.findings.some((finding) => isBlockingFinding(finding));
  const summary = formatDeliverySummary(result, blocking);
  return summary ? { ran: true, blocking, summary } : NO_OP;
}
var execaCommandRunner = async (command, args) => {
  const result = await execa(command, args, { reject: false, timeout: GH_TIMEOUT_MS });
  const missing = result.code === "ENOENT" || result.exitCode !== 0 && !result.timedOut && !isOnPath(command);
  return {
    stdout: result.stdout,
    stderr: result.stderr,
    exitCode: result.exitCode ?? 1,
    timedOut: result.timedOut,
    missing
  };
};

// src/planning/decision-selfarm.ts
init_esm_shims();
import { readFileSync as readFileSync4 } from "fs";
var ARMED_CATEGORIES = [
  { category: "create-vs-reuse", minConfidence: 0.92 },
  { category: "architecture-path", minConfidence: 0.9 }
];
var TTL_DAYS = 30;
var NO_OP2 = { ran: false, blocking: false, summary: "" };
function isTruthy(value) {
  return value !== void 0 && ["1", "true", "yes", "on"].includes(value.trim().toLowerCase());
}
function isSelfArmEnabled(projectRoot, env, opts = {}) {
  const envValue = env.PAQAD_DECISION_SELFARM;
  if (envValue !== void 0 && envValue !== "") {
    return isTruthy(envValue);
  }
  const configValue = readDotConfig(projectRoot).get("decision_selfarm");
  if (configValue !== void 0 && configValue !== "") {
    return isTruthy(configValue);
  }
  return opts.featureDevelopment === true;
}
function sessionOnFeatureDevelopment(projectRoot, sessionId) {
  if (!sessionId) return false;
  const state = readWorkflowState(projectRoot, resolveSessionId(projectRoot, sessionId));
  if (state.active && isFeatureDevelopmentRoute(state.active.workflow)) return true;
  return state.paused.some((entry) => isFeatureDevelopmentRoute(entry.workflow));
}
function lastUserPromptFromTranscript(transcriptText) {
  const lines = transcriptText.split("\n");
  for (let i = lines.length - 1; i >= 0; i--) {
    const line = lines[i].trim();
    if (!line) continue;
    let entry;
    try {
      entry = JSON.parse(line);
    } catch {
      continue;
    }
    const record = entry;
    const role = record.message?.role ?? record.role ?? record.type;
    if (role === "user") {
      const text = extractText(
        record.message?.content ?? record.content
      );
      if (text) return text;
    }
  }
  return "";
}
function extractText(content) {
  if (typeof content === "string") return content;
  if (Array.isArray(content)) {
    return content.map((part) => {
      const block = part;
      return block.type === "text" && typeof block.text === "string" ? block.text : "";
    }).join(" ").trim();
  }
  return "";
}
function selfArmDecision(input) {
  const armedFork = detectDecisionForks(input.promptText).find(
    (fork) => ARMED_CATEGORIES.some(
      (armed) => armed.category === fork.category && fork.confidence >= armed.minConfidence
    )
  );
  if (!armedFork) {
    return { minted: null, reason: "no-fork", category: null };
  }
  if (!input.sessionId) {
    return { minted: null, reason: "no-session", category: null };
  }
  const category = armedFork.category;
  const store = input.store ?? new DecisionStore(input.projectRoot);
  store.initialize();
  if (store.listPendingDecisionIds().length > 0) {
    return { minted: null, reason: "pending-exists", category };
  }
  const targetFile = input.targetPath || "src/unknown.ts";
  const options = decisionOptionsForCategory(input.projectRoot, category, targetFile).options;
  const question = decisionQuestionForCategory(category);
  const fingerprint2 = computeDecisionFingerprint({
    category,
    question,
    option_keys: options.map((option) => option.option_key),
    repo_state: { active_capabilities: ["coding"] }
  });
  if (store.findReusableDecision({ fingerprint: fingerprint2, category, options })) {
    return { minted: null, reason: "already-decided", category };
  }
  const now = input.now?.() ?? /* @__PURE__ */ new Date();
  const packet = {
    decision_id: store.nextDecisionId(),
    fingerprint: fingerprint2,
    category,
    question,
    context: contextForCategory(category, targetFile),
    options,
    confidence: armedFork.confidence,
    requested_by: "paqad",
    task_session_id: input.sessionId,
    created_at: now.toISOString(),
    status: "pending",
    ttl_until: new Date(now.getTime() + TTL_DAYS * 24 * 60 * 60 * 1e3).toISOString(),
    invalidation_watch: [targetFile]
  };
  try {
    store.writePending(packet);
    return { minted: packet.decision_id, reason: "minted", category };
  } catch {
    return { minted: null, reason: "write-failed", category };
  }
}
function contextForCategory(category, targetFile) {
  return category === "architecture-path" ? `This looks like a which-path choice that affects ${targetFile}. Pick the path before more is built on it.` : `This looks like a reuse-or-create choice that affects ${targetFile}. Pick the path before more is built on it.`;
}
function mintedSummary(category) {
  const headline = category === "architecture-path" ? `I hit a which-path choice that's yours to make` : `I hit a reuse-or-create choice that's yours to make`;
  return `**\u25B8 paqad** \xB7 ${headline}
> I paused and wrote it up so you can pick the path \u2014 answer the decision, then I'll continue.`;
}
function runDecisionSelfArm(input) {
  if (input.seam !== "pre-mutation") return NO_OP2;
  const featureDevelopment = sessionOnFeatureDevelopment(
    input.projectRoot,
    input.payload?.sessionId ?? input.env.CLAUDE_SESSION_ID ?? null
  );
  if (!isSelfArmEnabled(input.projectRoot, input.env, { featureDevelopment })) return NO_OP2;
  const transcriptPath = input.payload?.transcriptPath;
  const sessionId = input.payload?.sessionId ?? null;
  if (!transcriptPath || !sessionId) return NO_OP2;
  let promptText;
  try {
    const read = input.readTranscript ?? ((path) => readFileSync4(path, "utf8"));
    promptText = lastUserPromptFromTranscript(read(transcriptPath));
  } catch {
    return NO_OP2;
  }
  if (!promptText) return NO_OP2;
  const result = selfArmDecision({
    projectRoot: input.projectRoot,
    promptText,
    sessionId,
    targetPath: input.payload?.targetPath ?? null
  });
  return result.minted && result.category ? { ran: true, blocking: false, summary: mintedSummary(result.category) } : (
    /* v8 ignore stop */
    NO_OP2
  );
}

// src/spec/spec-change-guard.ts
init_esm_shims();
import { readFileSync as readFileSync10 } from "fs";
import { join as join12 } from "path";

// src/feature-evidence/projections.ts
init_esm_shims();

// src/feature-evidence/artifacts.ts
init_esm_shims();
import { mkdirSync as mkdirSync5, readFileSync as readFileSync8, renameSync as renameSync4, writeFileSync as writeFileSync5 } from "fs";
import { dirname as dirname5, join as join10 } from "path";

// src/visual-evidence/readiness.ts
init_esm_shims();

// src/site-map/store.ts
init_esm_shims();
var import_yaml = __toESM(require_dist(), 1);
import {
  existsSync as existsSync4,
  mkdirSync as mkdirSync2,
  readFileSync as readFileSync5,
  readdirSync as readdirSync3,
  renameSync as renameSync2,
  rmSync,
  writeFileSync as writeFileSync2
} from "fs";
import { dirname as dirname3, join as join7 } from "path";

// src/site-map/schema.ts
init_esm_shims();
var import_ajv2 = __toESM(require_ajv(), 1);

// src/validators/schemas/app-map.schema.json
var app_map_schema_default = {
  $schema: "http://json-schema.org/draft-07/schema#",
  $id: "paqad://schemas/app-map.json",
  title: "Site map \u2014 application behavioural map (app-map.yaml)",
  type: "object",
  additionalProperties: false,
  required: ["schema_version", "app", "surfaces"],
  properties: {
    schema_version: { type: "integer", const: 1 },
    app: { $ref: "#/$defs/app" },
    actors: { type: "array", items: { $ref: "#/$defs/actor" } },
    guards: { type: "array", items: { $ref: "#/$defs/guard" } },
    areas: { type: "array", items: { $ref: "#/$defs/area" } },
    surfaces: { type: "array", items: { $ref: "#/$defs/surface" } },
    journeys: { type: "array", items: { $ref: "#/$defs/journeyIndexEntry" } }
  },
  $defs: {
    appKind: {
      type: "string",
      enum: ["web", "api", "cli", "mobile", "desktop", "service", "llm-workflows"]
    },
    visibility: { type: "string", enum: ["frontstage", "backstage"] },
    derivation: { type: "string", enum: ["static", "convention", "agent", "human"] },
    confidence: { type: "string", enum: ["high", "medium", "low"] },
    trustTier: {
      type: "string",
      enum: ["unverified", "inferred", "proven-in-code", "proven-by-test", "human-confirmed"]
    },
    guardKind: {
      type: "string",
      enum: [
        "permission",
        "role",
        "feature-flag",
        "auth-state",
        "data-state",
        "capability",
        "environment"
      ]
    },
    guardRef: {
      oneOf: [
        { type: "string", minLength: 1 },
        { type: "array", items: { type: "string", minLength: 1 } }
      ]
    },
    evidenceItem: {
      type: "object",
      additionalProperties: false,
      required: ["file"],
      properties: {
        file: { type: "string", minLength: 1 },
        line: { type: "integer", minimum: 1 },
        note: { type: "string" }
      }
    },
    evidenceRef: {
      oneOf: [
        { $ref: "#/$defs/evidenceItem" },
        { type: "array", items: { $ref: "#/$defs/evidenceItem" } }
      ]
    },
    endsValue: { type: ["boolean", "string"] },
    ends: {
      type: "object",
      additionalProperties: false,
      properties: {
        success: { $ref: "#/$defs/endsValue" },
        failure: { $ref: "#/$defs/endsValue" },
        abandoned: { $ref: "#/$defs/endsValue" }
      }
    },
    app: {
      type: "object",
      additionalProperties: false,
      required: ["name", "kind"],
      properties: {
        name: { type: "string", minLength: 1 },
        kind: {
          oneOf: [
            { $ref: "#/$defs/appKind" },
            { type: "array", items: { $ref: "#/$defs/appKind" }, minItems: 1 }
          ]
        },
        frameworks: { type: "array", items: { type: "string" } },
        generated_from: { type: "string" },
        freshness: {
          type: "object",
          additionalProperties: false,
          required: ["anchors_total", "anchors_resolved", "anchors_broken"],
          properties: {
            anchors_total: { type: "integer", minimum: 0 },
            anchors_resolved: { type: "integer", minimum: 0 },
            anchors_broken: { type: "integer", minimum: 0 }
          }
        },
        language: {
          oneOf: [
            { type: "string", minLength: 1 },
            {
              type: "object",
              additionalProperties: false,
              required: ["default_locale"],
              properties: {
                default_locale: { type: "string", minLength: 1 },
                locales: { type: "array", items: { type: "string" } },
                catalogs: { type: "array", items: { type: "string" } },
                label_policy: { type: "string" }
              }
            }
          ]
        }
      }
    },
    actor: {
      type: "object",
      additionalProperties: false,
      required: ["id", "label"],
      properties: {
        id: { type: "string", minLength: 1 },
        label: { type: "string", minLength: 1 },
        satisfies: { type: "array", items: { type: "string" } },
        note: { type: "string" }
      }
    },
    guard: {
      type: "object",
      additionalProperties: false,
      required: ["id", "kind", "label"],
      properties: {
        id: { type: "string", minLength: 1 },
        kind: { $ref: "#/$defs/guardKind" },
        label: { type: "string", minLength: 1 },
        requires: { type: "string" },
        satisfy_via: { type: "string" },
        evidence: { $ref: "#/$defs/evidenceRef" },
        flag_key: { type: "string" },
        required_variant: { type: "string" },
        provider: { type: "string" },
        owner: { type: "string" },
        lifecycle: {
          type: "string",
          enum: ["new", "active", "launched", "inactive", "stale"]
        },
        introduced: { type: "string" },
        note: { type: "string" },
        trust: { $ref: "#/$defs/trustTier" }
      }
    },
    area: {
      type: "object",
      additionalProperties: false,
      required: ["id", "label"],
      properties: {
        id: { type: "string", minLength: 1 },
        label: { type: "string", minLength: 1 },
        visibility: { $ref: "#/$defs/visibility" },
        guard: { $ref: "#/$defs/guardRef" }
      }
    },
    surfaceEntry: {
      type: "object",
      additionalProperties: false,
      required: ["kind"],
      properties: {
        kind: { type: "string", minLength: 1 },
        value: { type: "string" },
        deep_link: { type: "boolean" }
      }
    },
    surfaceVariant: {
      type: "object",
      additionalProperties: false,
      required: ["flag", "variant"],
      properties: {
        flag: { type: "string", minLength: 1 },
        variant: { type: "string", minLength: 1 },
        adds: { type: "string" },
        changes: { type: "string" }
      }
    },
    handOff: {
      type: "object",
      additionalProperties: false,
      properties: {
        carries: { type: "string" },
        returns: { type: "boolean" }
      }
    },
    flowStep: {
      type: "object",
      additionalProperties: false,
      required: ["id"],
      properties: {
        id: { type: "string", minLength: 1 },
        label: { type: "string" },
        type: { type: "string" },
        visibility: { $ref: "#/$defs/visibility" },
        visible: { type: "string" },
        artifact: { type: "string" },
        presents: { type: "string" },
        decisions: { type: "array", items: { type: "string" } },
        resumable: { type: "boolean" },
        inputs: { type: "array", items: { type: "string" } },
        outputs: { type: "array", items: { type: "string" } }
      }
    },
    transition: {
      type: "object",
      additionalProperties: false,
      required: ["to", "trigger"],
      properties: {
        to: { type: "string", minLength: 1 },
        trigger: { type: "string", minLength: 1 },
        guard: { $ref: "#/$defs/guardRef" },
        guard_not: { type: "string" },
        note: { type: "string" },
        via: { type: "string" },
        carries: { type: "string" },
        visibility: { $ref: "#/$defs/visibility" },
        evidence: { $ref: "#/$defs/evidenceRef" },
        confidence: { $ref: "#/$defs/confidence" },
        trust: { $ref: "#/$defs/trustTier" },
        mode: { type: "string", enum: ["link", "call"] },
        guard_text: { type: "string" },
        guard_mode: { type: "string", enum: ["deterministic", "llm-judged"] },
        utterances: { type: "array", items: { type: "string" } },
        fallback: { type: "boolean" },
        routing_description: { type: "string" }
      }
    },
    surface: {
      type: "object",
      additionalProperties: false,
      required: ["id", "kind", "label"],
      properties: {
        id: { type: "string", minLength: 1 },
        kind: {
          type: "string",
          enum: [
            "page",
            "screen",
            "modal",
            "action",
            "api",
            "cli-command",
            "job",
            "webhook",
            "email",
            "external-system",
            "prompt-entry",
            "router",
            "llm-workflow",
            "step",
            "decision-pause",
            "handoff",
            "subflow",
            "terminal"
          ]
        },
        label: { type: "string", minLength: 1 },
        label_key: { type: "string" },
        label_source: { type: "string" },
        labels: { type: "object", additionalProperties: { type: "string" } },
        area: { type: "string" },
        entry: { $ref: "#/$defs/surfaceEntry" },
        page_type: { type: "string" },
        module: { type: "string" },
        guard: { $ref: "#/$defs/guardRef" },
        guard_not: { type: "string" },
        evidence: { $ref: "#/$defs/evidenceRef" },
        derivation: { $ref: "#/$defs/derivation" },
        confidence: { $ref: "#/$defs/confidence" },
        trust: { $ref: "#/$defs/trustTier" },
        visibility: { $ref: "#/$defs/visibility" },
        variants: { type: "array", items: { $ref: "#/$defs/surfaceVariant" } },
        variant_group: { type: "string" },
        variant_of: { type: "string" },
        status: { type: "string" },
        oracle: { type: "object", additionalProperties: { type: "string" } },
        hand_off: { $ref: "#/$defs/handOff" },
        ends: { $ref: "#/$defs/ends" },
        note: { type: "string" },
        actor: { type: "string" },
        intent: { type: "string" },
        routing_description: { type: "string" },
        utterances: { type: "array", items: { type: "string" } },
        steps: { type: "array", items: { $ref: "#/$defs/flowStep" } },
        transitions: { type: "array", items: { $ref: "#/$defs/transition" } }
      }
    },
    journeyIndexEntry: {
      type: "object",
      additionalProperties: false,
      required: ["id", "label"],
      properties: {
        id: { type: "string", minLength: 1 },
        label: { type: "string", minLength: 1 },
        actor: { type: "string" },
        priority: { type: "string" },
        status: { type: "string", enum: ["proposed", "confirmed", "locked"] },
        trust: { $ref: "#/$defs/trustTier" }
      }
    }
  }
};

// src/validators/schemas/journey.schema.json
var journey_schema_default = {
  $schema: "http://json-schema.org/draft-07/schema#",
  $id: "paqad://schemas/journey.json",
  title: "Site map \u2014 curated journey (journeys/<id>.journey.yaml)",
  type: "object",
  additionalProperties: false,
  required: ["schema_version", "id", "label", "actor", "goal", "entry", "steps", "status"],
  properties: {
    schema_version: { type: "integer", const: 1 },
    id: { type: "string", minLength: 1 },
    label: { type: "string", minLength: 1 },
    actor: { type: "string", minLength: 1 },
    on_behalf_of: { type: "string" },
    goal: { type: "string", minLength: 1 },
    entry: { type: "string", minLength: 1 },
    uses: { type: "array", items: { type: "string" } },
    steps: { type: "array", items: { $ref: "#/$defs/journeyStep" } },
    branches: { type: "array", items: { type: "object" } },
    ends: { $ref: "#/$defs/journeyEnds" },
    next_journeys: { type: "array", items: { type: "string" } },
    tests: {
      type: "array",
      items: {
        oneOf: [
          { type: "string" },
          { type: "null" },
          {
            type: "object",
            additionalProperties: false,
            properties: {
              spec: { type: ["string", "null"] },
              variant: { type: "string" }
            }
          }
        ]
      }
    },
    priority: { type: "string" },
    status: { type: "string", enum: ["proposed", "confirmed", "locked"] },
    derivation: { type: "string", enum: ["static", "convention", "agent", "human"] },
    evidence: {
      oneOf: [
        { $ref: "#/$defs/evidenceItem" },
        { type: "array", items: { $ref: "#/$defs/evidenceItem" } }
      ]
    }
  },
  $defs: {
    evidenceItem: {
      type: "object",
      additionalProperties: false,
      required: ["file"],
      properties: {
        file: { type: "string", minLength: 1 },
        line: { type: "integer", minimum: 1 },
        note: { type: "string" }
      }
    },
    journeyStep: {
      type: "object",
      additionalProperties: false,
      required: ["surface"],
      properties: {
        surface: { type: "string", minLength: 1 },
        action: { type: "string" },
        expect: { type: "string" },
        step: { type: "string" },
        type: { type: "string" },
        variant: {
          type: "object",
          additionalProperties: false,
          properties: {
            group: { type: "string" },
            when: { type: "string" }
          }
        }
      }
    },
    journeyEnds: {
      type: "object",
      additionalProperties: false,
      properties: {
        success: {},
        failure: {},
        abandoned: {}
      }
    }
  }
};

// src/validators/schemas/journey-capture.schema.json
var journey_capture_schema_default = {
  $schema: "http://json-schema.org/draft-07/schema#",
  $id: "paqad://schemas/journey-capture.json",
  title: "Site map \u2014 journey capture script (journeys/<id>.capture.yaml)",
  type: "object",
  additionalProperties: false,
  required: ["schema_version", "journey", "steps"],
  properties: {
    schema_version: { type: "integer", const: 1 },
    journey: { type: "string", minLength: 1 },
    setup: {
      type: "array",
      items: { $ref: "#/$defs/setupStep" }
    },
    steps: {
      type: "array",
      minItems: 1,
      items: { $ref: "#/$defs/captureStep" }
    }
  },
  $defs: {
    captureAction: {
      type: "object",
      additionalProperties: false,
      required: ["selector", "do"],
      properties: {
        selector: { type: "string", minLength: 1 },
        do: { type: "string", enum: ["click", "fill", "select", "press", "wait_for"] },
        value: { type: "string" }
      }
    },
    setupStep: {
      type: "object",
      additionalProperties: false,
      properties: {
        goto: { type: "string" },
        actions: { type: "array", items: { $ref: "#/$defs/captureAction" } }
      }
    },
    captureStep: {
      type: "object",
      additionalProperties: false,
      required: ["journey_step"],
      properties: {
        journey_step: { type: "integer", minimum: 1 },
        goto: { type: "string" },
        actions: { type: "array", items: { $ref: "#/$defs/captureAction" } },
        screenshot: { type: "boolean" }
      }
    }
  }
};

// src/validators/schemas/site-map-answers.schema.json
var site_map_answers_schema_default = {
  $schema: "http://json-schema.org/draft-07/schema#",
  $id: "paqad://schemas/site-map-answers.json",
  title: "Site-map creation answers (issue #466, Part A)",
  type: "object",
  additionalProperties: false,
  required: ["schema_version", "generated_by", "answers"],
  properties: {
    schema_version: { type: "integer", const: 1 },
    generated_by: { type: "string", const: "paqad-ai" },
    answers: {
      type: "array",
      items: {
        type: "object",
        additionalProperties: false,
        required: ["question_id", "category", "question", "answer", "decided_by", "anchors"],
        properties: {
          question_id: { type: "string", minLength: 1 },
          category: {
            type: "string",
            enum: [
              "app-kind",
              "actors-roles",
              "sensitive-surface",
              "journey-priority",
              "labels-language",
              "grouping",
              "tool-access",
              "journey-scope"
            ]
          },
          question: { type: "string", minLength: 1 },
          answer: { type: "string" },
          decided_by: { type: "string", enum: ["human", "default"] },
          anchors: { type: "array", items: { type: "string" } }
        }
      }
    }
  }
};

// src/site-map/schema.ts
var ajv2 = new import_ajv2.default({ allErrors: true, allowUnionTypes: true });
var compiledAppMap;
var compiledJourney;
var compiledJourneyCapture;
var compiledAnswers;
function appMapValidator() {
  if (!compiledAppMap) {
    compiledAppMap = ajv2.compile(app_map_schema_default);
  }
  return compiledAppMap;
}
function journeyValidator() {
  if (!compiledJourney) {
    compiledJourney = ajv2.compile(journey_schema_default);
  }
  return compiledJourney;
}
function journeyCaptureValidator() {
  if (!compiledJourneyCapture) {
    compiledJourneyCapture = ajv2.compile(journey_capture_schema_default);
  }
  return compiledJourneyCapture;
}
function answersValidator() {
  if (!compiledAnswers) {
    compiledAnswers = ajv2.compile(site_map_answers_schema_default);
  }
  return compiledAnswers;
}
function toValidation(validate, data) {
  if (validate(data)) {
    return { valid: true, errors: [] };
  }
  const rawErrors = validate.errors ?? [];
  const errors = rawErrors.map((error) => {
    const path = error.instancePath.length > 0 ? error.instancePath : "/";
    const message = error.message ?? "invalid";
    return `${path} ${message}`.trim();
  });
  return { valid: false, errors };
}
function validateAppMap(data) {
  return toValidation(appMapValidator(), data);
}
function validateJourney(data) {
  return toValidation(journeyValidator(), data);
}
function validateJourneyCapture(data) {
  return toValidation(journeyCaptureValidator(), data);
}
function validateSiteMapAnswers(data) {
  return toValidation(answersValidator(), data);
}

// src/site-map/store.ts
var SiteMapSchemaError = class extends Error {
  constructor(message, errors) {
    super(message);
    this.errors = errors;
    this.name = "SiteMapSchemaError";
  }
  errors;
};
var JOURNEY_SUFFIX = ".journey.yaml";
function canonicalAppMapPath(projectRoot) {
  return join7(projectRoot, PATHS.SITE_MAP_CANONICAL_APP_MAP);
}
function journeysDir(projectRoot) {
  return join7(projectRoot, PATHS.SITE_MAP_CANONICAL_JOURNEYS_DIR);
}
function journeyPath(projectRoot, id) {
  return join7(journeysDir(projectRoot), `${id}${JOURNEY_SUFFIX}`);
}
function writeYamlAtomic(target, data) {
  mkdirSync2(dirname3(target), { recursive: true });
  const tmp = `${target}.tmp`;
  writeFileSync2(tmp, import_yaml.default.stringify(data), "utf8");
  renameSync2(tmp, target);
  return target;
}
function readYaml(target) {
  try {
    return import_yaml.default.parse(readFileSync5(target, "utf8"));
  } catch {
    return null;
  }
}
function writeCanonicalSiteMap(projectRoot, map) {
  const result = validateAppMap(map);
  if (!result.valid) {
    throw new SiteMapSchemaError("canonical app-map failed schema validation", result.errors);
  }
  return writeYamlAtomic(canonicalAppMapPath(projectRoot), map);
}
function readCanonicalSiteMap(projectRoot) {
  const parsed = readYaml(canonicalAppMapPath(projectRoot));
  if (parsed === null) return null;
  return validateAppMap(parsed).valid ? parsed : null;
}
function writeJourney(projectRoot, journey) {
  const result = validateJourney(journey);
  if (!result.valid) {
    throw new SiteMapSchemaError(`journey "${journey.id}" failed schema validation`, result.errors);
  }
  return writeYamlAtomic(journeyPath(projectRoot, journey.id), journey);
}
function removeJourney(projectRoot, id) {
  const target = journeyPath(projectRoot, id);
  if (!existsSync4(target)) return false;
  rmSync(target, { force: true });
  return true;
}
function readJourneyAt(path) {
  const parsed = readYaml(path);
  if (parsed === null) return null;
  return validateJourney(parsed).valid ? parsed : null;
}
function listJourneyIdsIn(dir) {
  let names;
  try {
    names = readdirSync3(dir);
  } catch {
    return [];
  }
  return names.filter((name) => name.endsWith(JOURNEY_SUFFIX)).map((name) => name.slice(0, -JOURNEY_SUFFIX.length)).sort();
}
function readAllJourneysIn(dir) {
  const journeys = [];
  for (const id of listJourneyIdsIn(dir)) {
    const journey = readJourneyAt(join7(dir, `${id}${JOURNEY_SUFFIX}`));
    if (journey !== null) {
      journeys.push(journey);
    }
  }
  return journeys;
}
function readJourney(projectRoot, id) {
  return readJourneyAt(journeyPath(projectRoot, id));
}
function readAllJourneys(projectRoot) {
  return readAllJourneysIn(journeysDir(projectRoot));
}

// src/visual-evidence/capture-script.ts
init_esm_shims();
import { existsSync as existsSync5, readdirSync as readdirSync4 } from "fs";
var CAPTURE_SUFFIX = ".capture.yaml";
function hasValue(value) {
  return typeof value === "string" && value.length > 0;
}
function actionProblems(action, where) {
  const problems = [];
  const { do: verb, value } = action;
  if ((verb === "fill" || verb === "select" || verb === "press") && !hasValue(value)) {
    problems.push(`${where}: \`${verb}\` requires a value`);
  }
  if (verb === "wait_for" && value !== "visible" && value !== "hidden") {
    problems.push(`${where}: \`wait_for\` requires a value of "visible" or "hidden"`);
  }
  return problems;
}
function crossCheck(script, basename, journey) {
  const problems = [];
  if (basename !== script.journey) {
    problems.push(
      `journey "${script.journey}" must equal the sibling file id "${basename}" (${basename}${CAPTURE_SUFFIX})`
    );
  }
  if (!journey) {
    problems.push(`journey "${script.journey}" does not resolve to an existing journey`);
    return problems;
  }
  if (journey.status !== "confirmed") {
    problems.push(
      `journey "${script.journey}" is ${journey.status}, not confirmed \u2014 only confirmed journeys produce evidence`
    );
  }
  const stepCount = journey.steps.length;
  let previous = 0;
  for (const step of script.steps) {
    if (step.journey_step < 1 || step.journey_step > stepCount) {
      problems.push(
        `journey_step ${step.journey_step} does not exist in journey "${script.journey}" (${stepCount} steps)`
      );
    }
    if (step.journey_step < previous) {
      problems.push(
        `steps must be ordered ascending by journey_step (saw ${step.journey_step} after ${previous})`
      );
    }
    previous = step.journey_step;
    for (const [i, action] of (step.actions ?? []).entries()) {
      problems.push(...actionProblems(action, `step ${step.journey_step} action ${i + 1}`));
    }
  }
  for (const [i, setup] of (script.setup ?? []).entries()) {
    for (const [j, action] of (setup.actions ?? []).entries()) {
      problems.push(...actionProblems(action, `setup ${i + 1} action ${j + 1}`));
    }
  }
  return problems;
}
function listCaptureScriptIds(projectRoot) {
  const dir = journeysDir(projectRoot);
  if (!existsSync5(dir)) return [];
  return readdirSync4(dir).filter((name) => name.endsWith(CAPTURE_SUFFIX)).map((name) => name.slice(0, -CAPTURE_SUFFIX.length)).sort();
}
function loadCaptureScripts(projectRoot) {
  const scripts = [];
  const errors = [];
  const dir = journeysDir(projectRoot);
  for (const basename of listCaptureScriptIds(projectRoot)) {
    const file = `docs/site-map/journeys/${basename}${CAPTURE_SUFFIX}`;
    const parsed = readYaml(join(dir, `${basename}${CAPTURE_SUFFIX}`));
    if (parsed === null) {
      errors.push({ file, errors: ["file is missing or not valid YAML"] });
      continue;
    }
    const shape = validateJourneyCapture(parsed);
    if (!shape.valid) {
      errors.push({ file, errors: shape.errors });
      continue;
    }
    const script = parsed;
    const journey = readJourney(projectRoot, script.journey);
    const problems = crossCheck(script, basename, journey);
    if (problems.length > 0) {
      errors.push({ file, errors: problems });
      continue;
    }
    scripts.push({ file, script, journey });
  }
  return { scripts, errors };
}
function deriveCaption(journey, journeyStep) {
  const step = journey.steps[journeyStep - 1];
  const action = step?.action ?? "";
  const expect = step?.expect;
  return expect ? `${action}. ${expect}` : action;
}

// src/visual-evidence/provision.ts
init_esm_shims();
import { createRequire } from "module";
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
import { homedir } from "os";
import { join as join8 } from "path";
function resolveVeRuntimeDir() {
  return join8(homedir(), ".paqad-ai", "ve-runtime");
}
function provisionRecordPath(veRuntime) {
  return join8(veRuntime, "provision.json");
}
function browsersPath(veRuntime) {
  return join8(veRuntime, "browsers");
}
function resolvePlaywrightModulePath(veRuntime) {
  const req = createRequire(join8(veRuntime, "package.json"));
  return req.resolve("playwright");
}
function browserStatus(veRuntime = resolveVeRuntimeDir()) {
  let raw;
  try {
    raw = readFileSync6(provisionRecordPath(veRuntime), "utf8");
  } catch {
    return existsSync6(veRuntime) ? "broken" : "missing";
  }
  try {
    JSON.parse(raw);
    resolvePlaywrightModulePath(veRuntime);
    return "provisioned";
  } catch {
    return "broken";
  }
}
async function provisionBrowser(veRuntime = resolveVeRuntimeDir(), now = () => (/* @__PURE__ */ new Date()).toISOString()) {
  mkdirSync3(veRuntime, { recursive: true });
  const pkgPath = join8(veRuntime, "package.json");
  writeFileSync3(pkgPath, `${JSON.stringify({ private: true }, null, 2)}
`, "utf8");
  const env = { ...process.env, PLAYWRIGHT_BROWSERS_PATH: browsersPath(veRuntime) };
  await execa("npm", ["install", "playwright@^1", "--no-audit", "--no-fund"], {
    cwd: veRuntime,
    env
  });
  await execa("npx", ["playwright", "install", "chromium"], { cwd: veRuntime, env });
  const playwrightVersion = readInstalledPlaywrightVersion(veRuntime);
  const record = {
    schema_version: 1,
    playwright_version: playwrightVersion,
    provisioned_at: now()
  };
  writeFileSync3(provisionRecordPath(veRuntime), `${JSON.stringify(record, null, 2)}
`, "utf8");
  return record;
}
function readInstalledPlaywrightVersion(veRuntime) {
  const req = createRequire(join8(veRuntime, "package.json"));
  const pkg = JSON.parse(readFileSync6(req.resolve("playwright/package.json"), "utf8"));
  return pkg.version;
}
function veBrowsersPath(veRuntime = resolveVeRuntimeDir()) {
  return browsersPath(veRuntime);
}

// src/visual-evidence/trigger.ts
init_esm_shims();
function frontendGlobToRegExp(glob) {
  let out = "";
  let braceDepth = 0;
  for (let i = 0; i < glob.length; i += 1) {
    const ch = glob[i];
    const next = glob[i + 1];
    if (ch === "*" && next === "*") {
      if (glob[i + 2] === "/") {
        out += "(?:.*/)?";
        i += 2;
      } else {
        out += ".*";
        i += 1;
      }
      continue;
    }
    switch (ch) {
      case "*":
        out += "[^/]*";
        break;
      case "?":
        out += "[^/]";
        break;
      case "{":
        out += "(?:";
        braceDepth += 1;
        break;
      case "}":
        if (braceDepth > 0) {
          out += ")";
          braceDepth -= 1;
        } else {
          out += "\\}";
        }
        break;
      case ",":
        out += braceDepth > 0 ? "|" : "\\,";
        break;
      case ".":
      case "+":
      case "^":
      case "$":
      case "(":
      case ")":
      case "[":
      case "]":
      case "|":
      case "\\":
        out += `\\${ch}`;
        break;
      default:
        out += ch;
    }
  }
  return new RegExp(`^${out}$`);
}
function matchesFrontendGlob(file, glob) {
  return frontendGlobToRegExp(glob).test(file);
}
function toPosix(file) {
  return file.replace(/\\/g, "/");
}
function activeFrameworks(projectRoot) {
  const profile = readProjectProfile(projectRoot);
  if (!profile) return [];
  const frameworks = profile.stack_profile?.frameworks ?? [];
  const primary = getPrimaryStack({ stack_profile: profile.stack_profile });
  return [...new Set([...frameworks, primary].filter((name) => Boolean(name)))];
}
var PackRegistryEmptyError = class extends Error {
  runtimeRoot;
  frameworks;
  constructor(runtimeRoot, frameworks) {
    super(
      `paqad could not load its built-in stack packs (looked in ${runtimeRoot}) for ${frameworks.join(", ")}.`
    );
    this.name = "PackRegistryEmptyError";
    this.runtimeRoot = runtimeRoot;
    this.frameworks = frameworks;
  }
};
function packRegistryFaultDetail(runtimeRoot) {
  return `paqad could not load its built-in stack packs (looked in ${runtimeRoot}). This is an install fault, not a clean change.`;
}
var PACK_REGISTRY_FAULT_REMEDIATION = "run `paqad-ai doctor` and reinstall paqad-ai.";
function activeFrontendGlobs(projectRoot) {
  const frameworks = activeFrameworks(projectRoot);
  const packs = getPacksForFrameworks(frameworks, projectRoot);
  if (frameworks.length > 0 && packs.length === 0) {
    throw new PackRegistryEmptyError(getRuntimeRoot(), frameworks);
  }
  const out = [];
  for (const pack of packs) {
    for (const glob of pack.manifest.visual_evidence?.frontend_globs ?? []) {
      out.push({ pack: pack.manifest.name, glob });
    }
  }
  return out;
}
function frontendFilesOf(projectRoot, changedFiles) {
  const globs = activeFrontendGlobs(projectRoot);
  const matched = changedFiles.map(toPosix).filter((file) => globs.some(({ glob }) => matchesFrontendGlob(file, glob)));
  return [...new Set(matched)].sort();
}
function frontendTriggerOrFault(projectRoot, changedFiles) {
  try {
    const files = frontendFilesOf(projectRoot, changedFiles);
    return { triggered: files.length > 0, files, fault: null };
  } catch (error) {
    if (error instanceof PackRegistryEmptyError) {
      return { triggered: false, files: [], fault: { runtimeRoot: error.runtimeRoot } };
    }
    throw error;
  }
}
async function evaluateFrontendTrigger(projectRoot) {
  const change = await loadChangeEvidence(projectRoot);
  const changed = change.files.map(toPosix);
  const globs = activeFrontendGlobs(projectRoot);
  const matchedFiles = /* @__PURE__ */ new Set();
  const matchedGlobs = /* @__PURE__ */ new Set();
  const packs = /* @__PURE__ */ new Set();
  if (change.source !== "none") {
    for (const file of changed) {
      for (const { pack, glob } of globs) {
        if (matchesFrontendGlob(file, glob)) {
          matchedFiles.add(file);
          matchedGlobs.add(glob);
          packs.add(pack);
        }
      }
    }
  }
  return {
    triggered: matchedFiles.size > 0,
    matched_files: [...matchedFiles].sort(),
    matched_globs: [...matchedGlobs].sort(),
    packs: [...packs].sort(),
    changed_files: changed,
    source: change.source
  };
}

// src/visual-evidence/readiness.ts
function visualEvidenceReadiness(projectRoot) {
  const problems = [];
  if (!resolveFrameworkConfig(projectRoot).features.site_map) {
    problems.push({
      code: "site-map-off",
      detail: "visual_evidence is on but site_map is off \u2014 capture scripts derive from confirmed site-map journeys, so nothing can be captured.",
      remediation: "Turn on site_map (env PAQAD_SITE_MAP) and author a confirmed journey + capture script."
    });
  }
  if (!readAllJourneys(projectRoot).some((journey) => journey.status === "confirmed")) {
    problems.push({
      code: "no-confirmed-journey",
      detail: "visual_evidence is on but no confirmed journey exists under docs/site-map/journeys/ \u2014 only confirmed journeys produce evidence.",
      remediation: "Confirm a journey (paqad-ai sitemap journey confirm) so it can back a capture script."
    });
  }
  if (listCaptureScriptIds(projectRoot).length === 0) {
    problems.push({
      code: "no-capture-script",
      detail: "visual_evidence is on but no *.capture.yaml exists under docs/site-map/journeys/ \u2014 there is nothing to capture.",
      remediation: "Author a docs/site-map/journeys/<id>.capture.yaml for a confirmed journey (see docs/modules/visual-evidence)."
    });
  }
  if (browserStatus() !== "provisioned") {
    problems.push({
      code: "browser-not-provisioned",
      detail: "visual_evidence is on but the capture browser is not provisioned \u2014 a capture run would record playwright-not-provisioned.",
      remediation: "Run `paqad-ai visual-evidence setup` to provision Playwright + Chromium under ~/.paqad-ai/ve-runtime."
    });
  }
  return problems;
}
var READINESS_DECISION_TITLE = "Visual evidence is on, but I can't capture screenshots here yet";
var READINESS_WAIVE_OPTION = "waive";
function readinessToken(dirName) {
  return `[paqad-ve-readiness ${dirName}]`;
}
function visualEvidenceFlagOn(projectRoot) {
  if (!resolveFrameworkConfig(projectRoot).features.visual_evidence) return false;
  return readProjectProfile(projectRoot)?.active_capabilities?.includes("coding") ?? false;
}
function openVisualEvidenceReadinessPause(input) {
  const { projectRoot, dirName } = input;
  if (!visualEvidenceFlagOn(projectRoot)) return null;
  if (!frontendTriggerOrFault(projectRoot, [...input.files]).triggered) return null;
  const problems = visualEvidenceReadiness(projectRoot);
  if (problems.length === 0) return null;
  const token = readinessToken(dirName);
  if (readContractDecisions(projectRoot).some(({ packet }) => packet.context.includes(token))) {
    return null;
  }
  const reasons = problems.map((problem) => `- ${problem.detail}`).join("\n");
  const { id, reused } = createPendingDecision(projectRoot, {
    category: "workflow-or-tool",
    title: READINESS_DECISION_TITLE,
    context: `This change touches frontend files and visual evidence is on, but a capture run cannot work here yet:
${reasons}
Pick how this change gets its visual evidence. ${token}`,
    options: [
      { option_key: "setup", label: "Set it up now (site map journey, capture script, browser)" },
      {
        option_key: "attach",
        label: "Attach my own screenshots with paqad-ai visual-evidence attach"
      },
      { option_key: READINESS_WAIVE_OPTION, label: "Record a waiver for this change" }
    ],
    recommendation: "setup",
    change: featureChangeKey(dirName)
  });
  return reused ? null : id;
}
function visualAcRequiredFiles(projectRoot, files) {
  if (!visualEvidenceFlagOn(projectRoot)) return [];
  return frontendTriggerOrFault(projectRoot, [...new Set(files)]).files;
}
function findVisualEvidenceWaiver(projectRoot, dirName) {
  const token = readinessToken(dirName);
  for (const { packet, status } of readContractDecisions(projectRoot)) {
    if (status !== "resolved" || !packet.context.includes(token)) continue;
    if (packet.chosen === READINESS_WAIVE_OPTION) return packet.id;
  }
  return null;
}
function visualEvidenceWaiverHint(projectRoot, dirName) {
  const token = readinessToken(dirName);
  const pending = readContractDecisions(projectRoot).find(
    ({ packet, status }) => status === "pending" && packet.context.includes(token)
  );
  if (pending) {
    return `resolve the pending readiness decision with \`paqad-ai decision resolve ${pending.packet.id} ${READINESS_WAIVE_OPTION}\``;
  }
  return `create one with \`paqad-ai decision create --category workflow-or-tool --title "Visual evidence waiver" --context "<why no screenshots> ${token}" --option ${READINESS_WAIVE_OPTION}="Waive visual evidence" --option attach="Attach screenshots"\` and resolve it with \`paqad-ai decision resolve <D-id> ${READINESS_WAIVE_OPTION}\``;
}

// src/feature-evidence/lane-resize.ts
init_esm_shims();

// src/authoring/config.ts
init_esm_shims();
var RANGE_OF = {
  skill_orchestrator_enabled: "boolean",
  authoring_trigger_threshold: "share",
  authoring_trigger_min_runs: "count",
  skill_eval_min_gain_points: "count",
  skill_eval_max_token_multiple: "multiple",
  authoring_runs_per_arm: "count",
  authoring_max_iterations: "count",
  authoring_fact_max_age_days: "count",
  authoring_collision_jaccard: "share",
  authoring_description_budget_chars: "count",
  skill_body_max_tokens: "count",
  authoring_tool_result_max_tokens: "count",
  authoring_cache_hit_min: "share",
  authoring_regression_tolerance: "share",
  authoring_dark_window_days: "count",
  authoring_batch_mode: "batch",
  authoring_run_max_cost_usd: "cost",
  authoring_otel_endpoint: "endpoint",
  agent_orchestrator_enabled: "boolean",
  agent_pass_k_threshold: "share",
  agent_stage_pass_k_threshold: "share",
  agent_delegation_threshold: "share",
  agent_description_max_tokens: "count",
  agent_report_max_tokens: "count",
  agent_prompt_max_tokens: "count",
  agent_max_tools: "count",
  agent_max_cost_multiple: "multiple",
  agent_catalog_description_max_tokens: "count",
  agent_memory_max_lines: "count"
};
var ALLOWED_TEXT = {
  boolean: "true or false",
  share: "a number from 0 to 1",
  count: "a positive whole number",
  multiple: "a number of 1 or more",
  cost: "a number above 0",
  batch: AUTHORING_BATCH_MODES.join(" or "),
  endpoint: "empty or an http(s) URL"
};
var TRUTHY = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
var FALSY = /* @__PURE__ */ new Set(["0", "false", "no", "off"]);
var AUTHORING_CONFIG_KEYS = Object.keys(
  RANGE_OF
);
function defaultOf(key) {
  const spec = FRAMEWORK_CONFIG_SPECS.find(
    (entry) => entry.key === key && entry.section === AUTHORING_SECTION
  );
  if (spec?.default === void 0) {
    throw new Error(`authoring config key ${key} has no spec in FRAMEWORK_CONFIG_SPECS`);
  }
  return spec.default;
}
function isHttpUrl(raw) {
  try {
    const url = new URL(raw);
    return url.protocol === "http:" || url.protocol === "https:";
  } catch {
    return false;
  }
}
function parseValue(kind, raw) {
  const text = raw.trim();
  if (kind === "boolean") {
    const lower = text.toLowerCase();
    if (TRUTHY.has(lower)) return true;
    if (FALSY.has(lower)) return false;
    return void 0;
  }
  if (kind === "batch") {
    return AUTHORING_BATCH_MODES.includes(text) ? text : void 0;
  }
  if (kind === "endpoint") {
    return text === "" || isHttpUrl(text) ? text : void 0;
  }
  if (text === "") return void 0;
  const value = Number(text);
  if (!Number.isFinite(value)) return void 0;
  switch (kind) {
    case "share":
      return value >= 0 && value <= 1 ? value : void 0;
    case "count":
      return Number.isInteger(value) && value > 0 ? value : void 0;
    case "multiple":
      return value >= 1 ? value : void 0;
    case "cost":
      return value > 0 ? value : void 0;
  }
}
function authoringConfigProblems(projectRoot, env = process.env) {
  const map = layeredConfigMap(projectRoot, env);
  const problems = [];
  for (const key of AUTHORING_CONFIG_KEYS) {
    const raw = map.get(key);
    if (raw === void 0) continue;
    if (parseValue(RANGE_OF[key], raw) === void 0) {
      problems.push({ key, value: raw, allowed: ALLOWED_TEXT[RANGE_OF[key]] });
    }
  }
  return problems;
}
function readAuthoringConfig(projectRoot, env = process.env) {
  const map = layeredConfigMap(projectRoot, env);
  const config = {};
  for (const key of AUTHORING_CONFIG_KEYS) {
    const raw = map.get(key);
    const parsed = raw === void 0 ? void 0 : parseValue(RANGE_OF[key], raw);
    config[key] = parsed ?? defaultOf(key);
  }
  return config;
}
function skillOrchestratorEnabled(projectRoot, env = process.env) {
  return readAuthoringConfig(projectRoot, env).skill_orchestrator_enabled;
}
function agentOrchestratorEnabled(projectRoot, env = process.env) {
  return readAuthoringConfig(projectRoot, env).agent_orchestrator_enabled;
}

// src/feature-evidence/lane-resize.ts
var SMALL_CHANGE_MAX_FILES = 8;
var RISKY_DATABASE_IMPACTS = /* @__PURE__ */ new Set(["schema-change", "data-migration"]);
var RISKY_API_IMPACTS = /* @__PURE__ */ new Set(["breaking-change"]);
function planFiles(steps) {
  return [...new Set((steps ?? []).flatMap((step) => step.files ?? []))];
}
function sensitivePaths(projectRoot, files) {
  return files.filter((file) => resolveEffectiveLane(projectRoot, file, "graduated") === "full");
}
function isRiskyImpact(impact) {
  return impact === null || RISKY_DATABASE_IMPACTS.has(impact.database_impact) || RISKY_API_IMPACTS.has(impact.api_impact);
}
function editedFiles(changedFiles) {
  return [...new Set(changedFiles)].filter((file) => !/^\.paqad(\/|$)/.test(file));
}
function fileCount(count) {
  return `${count} file${count === 1 ? "" : "s"}`;
}
function planLaneResize(input) {
  if (!input.changesKnown) return null;
  const record = readFeatureRecord(input.projectRoot, input.dirName);
  if (record?.lane !== "full" || record.lane_source !== "classifier" || record.lane_resized) {
    return null;
  }
  const files = planFiles(input.steps);
  if (files.length > SMALL_CHANGE_MAX_FILES || sensitivePaths(input.projectRoot, files).length > 0 || isRiskyImpact(readPendingImpact(input.projectRoot, input.sessionId)) || editedFiles(input.changedFiles).length > 0) {
    return null;
  }
  const at = (input.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
  const written = updateFeatureRecord(
    input.projectRoot,
    input.dirName,
    {
      resizeLaneFromPlan: {
        reason: `the plan touches ${fileCount(files.length)}, none sensitive, no schema or breaking API change`,
        files: files.length,
        at
      }
    },
    input.now
  );
  return written?.lane_source === "plan" && written.lane_resized?.at === at ? written.lane_resized : null;
}
function raiseFacts(input) {
  const edited = editedFiles(input.changedFiles);
  if (edited.length > SMALL_CHANGE_MAX_FILES) {
    return { reason: `the change now touches ${fileCount(edited.length)}`, files: edited.length };
  }
  const sensitive = sensitivePaths(input.projectRoot, [
    .../* @__PURE__ */ new Set([...edited, ...input.plannedFiles])
  ]);
  return sensitive.length > 0 ? { reason: `the change touches a sensitive path: ${sensitive[0]}`, files: edited.length } : null;
}
function laneMayRise(projectRoot, dirName) {
  const record = readFeatureRecord(projectRoot, dirName);
  if (record?.lane !== "graduated" || record.lane_raised) return false;
  return record.lane_source === "plan" || record.lane_raised_for_authoring !== void 0;
}
function raiseLaneIfGrown(input) {
  if (!laneMayRise(input.projectRoot, input.dirName)) return null;
  const facts = raiseFacts(input);
  if (facts === null) return null;
  const at = (input.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
  const written = updateFeatureRecord(
    input.projectRoot,
    input.dirName,
    { raiseLaneFromChange: { ...facts, at } },
    input.now
  );
  return written?.lane_raised?.at === at ? written.lane_raised : null;
}
var AUTHORING_KIND_WORDS = {
  skill: { noun: "a skill", seat: "the Skill Orchestrator" },
  agent: { noun: "an agent", seat: "the Agent Orchestrator" }
};
function authoringRaiseReason(match) {
  const matches = typeof match === "string" ? [{ kind: "skill", path: match }] : match;
  if (matches.length === 1) {
    const words = AUTHORING_KIND_WORDS[matches[0].kind];
    return `the change creates or edits ${words.noun} (${matches[0].path}), so ${words.seat} sits at the High Table.`;
  }
  const parts = matches.map((entry) => `${AUTHORING_KIND_WORDS[entry.kind].noun} (${entry.path})`);
  const seats = matches.map((entry) => AUTHORING_KIND_WORDS[entry.kind].seat);
  return `the change creates or edits ${parts.join(" and ")}, so ${seats.join(" and ")} sit at the High Table.`;
}
function authoringMatches(projectRoot, files) {
  const skillsOn = skillOrchestratorEnabled(projectRoot);
  const agentsOn = agentOrchestratorEnabled(projectRoot);
  if (!skillsOn && !agentsOn) return [];
  const context = readSkillPathContext(projectRoot);
  const matches = [];
  const skill = skillsOn ? findSkillPaths(files, context.adapters, context)[0] : void 0;
  if (skill !== void 0) matches.push({ kind: "skill", path: skill });
  const agent = agentsOn ? findAgentPaths(files, context.adapters, context)[0] : void 0;
  if (agent !== void 0) matches.push({ kind: "agent", path: agent });
  return matches;
}
function authoringRaiseEnabled(projectRoot) {
  return skillOrchestratorEnabled(projectRoot) || agentOrchestratorEnabled(projectRoot);
}
function raiseLaneForAuthoring(input) {
  const record = readFeatureRecord(input.projectRoot, input.dirName);
  if (record?.lane !== "fast" || record.lane_raised_for_authoring !== void 0) return null;
  if (!authoringRaiseEnabled(input.projectRoot)) return null;
  const candidates = [.../* @__PURE__ */ new Set([...input.plannedFiles, ...editedFiles(input.changedFiles)])];
  const paths = authoringMatches(input.projectRoot, candidates);
  if (paths.length === 0) return null;
  const at = (input.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
  const written = updateFeatureRecord(
    input.projectRoot,
    input.dirName,
    {
      raiseLaneForAuthoring: {
        reason: authoringRaiseReason(paths),
        path: paths[0].path,
        paths,
        at
      }
    },
    input.now
  );
  return written?.lane_raised_for_authoring?.at === at ? written.lane_raised_for_authoring : null;
}

// src/feature-evidence/reuse.ts
init_esm_shims();

// src/framework-api/query.ts
init_esm_shims();

// src/framework-api/adapters/shared.ts
init_esm_shims();
import { relative as relative3 } from "path";
var DYNAMIC_MEMBER_SUFFIX = ".*";
function projectRelative(projectRoot, target) {
  return toPosixPath(relative3(projectRoot, target));
}
function dynamicRecord(container) {
  return {
    name: `${container}${DYNAMIC_MEMBER_SUFFIX}`,
    kind: "member",
    exists: true,
    deprecated: false,
    message: null,
    since: null,
    for_removal: false,
    provenance: "unknown-dynamic"
  };
}
var NAME_SEPARATORS = ["::", ".", "\\"];
function lastSeparator(name) {
  let at = -1;
  let width = 0;
  for (const separator of NAME_SEPARATORS) {
    const index = name.lastIndexOf(separator);
    if (index > at) {
      at = index;
      width = separator.length;
    }
  }
  return { at, width };
}
function lastNameSegment(name) {
  const { at, width } = lastSeparator(name);
  return at === -1 ? name : name.slice(at + width);
}
function containerOf(name) {
  const { at } = lastSeparator(name);
  return at === -1 ? null : name.slice(0, at);
}

// src/framework-api/query.ts
function findRecord(entry, symbol) {
  const exact = entry.symbols.find((record) => record.name === symbol);
  if (exact) {
    return exact;
  }
  const wanted = lastNameSegment(symbol);
  return entry.symbols.find(
    (record) => !record.name.endsWith(DYNAMIC_MEMBER_SUFFIX) && lastNameSegment(record.name) === wanted
  ) ?? null;
}
function coveredByDynamicContainer(entry, symbol) {
  const container = containerOf(symbol);
  if (container === null) {
    return false;
  }
  const wanted = lastNameSegment(container);
  return entry.symbols.some((record) => {
    if (!record.name.endsWith(DYNAMIC_MEMBER_SUFFIX)) {
      return false;
    }
    const recorded = record.name.slice(0, -DYNAMIC_MEMBER_SUFFIX.length);
    return recorded === container || lastNameSegment(recorded) === wanted;
  });
}
function nearestFrameworkSymbol(symbol, known2) {
  const wanted = lastNameSegment(symbol);
  const bound = Math.max(2, Math.floor(wanted.length / 4));
  let best = null;
  let bestDistance = bound + 1;
  for (const candidate of known2) {
    const distance = levenshtein(wanted, lastNameSegment(candidate), bound);
    if (distance < bestDistance) {
      best = candidate;
      bestDistance = distance;
    }
  }
  return best;
}
function queryFrameworkApi(index, packageName, symbol, version) {
  const forPackage = index.packages.filter((candidate) => candidate.package === packageName);
  const entry = (version === void 0 ? forPackage[0] : forPackage.find((candidate) => candidate.version === version) ?? forPackage[0]) ?? null;
  if (!entry) {
    return {
      package: packageName,
      symbol,
      verdict: "package-not-indexed",
      version: null,
      record: null,
      nearest: null
    };
  }
  const record = findRecord(entry, symbol);
  if (record) {
    return {
      package: packageName,
      symbol,
      verdict: record.provenance === "asserted" ? record.deprecated ? "deprecated" : "live" : "unknown-dynamic",
      version: entry.version,
      record,
      nearest: null
    };
  }
  if (coveredByDynamicContainer(entry, symbol)) {
    return {
      package: packageName,
      symbol,
      verdict: "unknown-dynamic",
      version: entry.version,
      record: null,
      nearest: null
    };
  }
  const known2 = entry.symbols.filter((candidate) => !candidate.name.endsWith(DYNAMIC_MEMBER_SUFFIX)).map((candidate) => candidate.name);
  return {
    package: packageName,
    symbol,
    verdict: "absent",
    version: entry.version,
    record: null,
    nearest: nearestFrameworkSymbol(symbol, known2)
  };
}

// src/framework-api/store.ts
init_esm_shims();
import { mkdirSync as mkdirSync4, readFileSync as readFileSync7, renameSync as renameSync3, writeFileSync as writeFileSync4 } from "fs";
import { dirname as dirname4, join as join9 } from "path";

// src/framework-api/schema.ts
init_esm_shims();
var import_ajv3 = __toESM(require_ajv(), 1);

// src/validators/schemas/framework-api.schema.json
var framework_api_schema_default = {
  $schema: "http://json-schema.org/draft-07/schema#",
  $id: "paqad://schemas/framework-api.json",
  title: "Installed framework-API index (issue #397)",
  type: "object",
  additionalProperties: false,
  required: ["schema_version", "header", "packages", "blocked"],
  properties: {
    schema_version: { type: "integer", const: 1 },
    header: {
      type: "object",
      additionalProperties: false,
      required: ["generated_at", "schema_version"],
      properties: {
        generated_at: { type: "string", minLength: 1 },
        schema_version: { type: "integer", const: 1 }
      }
    },
    packages: {
      type: "array",
      items: {
        type: "object",
        additionalProperties: false,
        required: [
          "package",
          "version",
          "ecosystem",
          "root",
          "content_hash",
          "sources",
          "symbols"
        ],
        properties: {
          package: { type: "string", minLength: 1 },
          version: { type: "string", minLength: 1 },
          ecosystem: { type: "string", minLength: 1 },
          root: { type: "string", minLength: 1 },
          content_hash: { type: "string", minLength: 1 },
          sources: { type: "array", items: { type: "string" } },
          symbols: {
            type: "array",
            items: {
              type: "object",
              additionalProperties: false,
              required: [
                "name",
                "kind",
                "exists",
                "deprecated",
                "message",
                "since",
                "for_removal",
                "provenance"
              ],
              properties: {
                name: { type: "string", minLength: 1 },
                kind: {
                  type: "string",
                  enum: [
                    "function",
                    "class",
                    "interface",
                    "type",
                    "variable",
                    "enum",
                    "namespace",
                    "member",
                    "unknown"
                  ]
                },
                exists: { type: "boolean" },
                deprecated: { type: "boolean" },
                message: { type: ["string", "null"] },
                since: { type: ["string", "null"] },
                for_removal: { type: "boolean" },
                provenance: {
                  type: "string",
                  enum: ["asserted", "unknown-dynamic", "doc-derived"]
                }
              }
            }
          }
        }
      }
    },
    blocked: {
      type: "array",
      items: {
        type: "object",
        additionalProperties: false,
        required: ["package", "ecosystem", "reason", "detail"],
        properties: {
          package: { type: "string", minLength: 1 },
          ecosystem: { type: "string", minLength: 1 },
          reason: {
            type: "string",
            enum: ["no-adapter", "not-installed", "no-types", "parser-unavailable"]
          },
          detail: { type: "string" }
        }
      }
    }
  }
};

// src/framework-api/schema.ts
var ajv3 = new import_ajv3.default({ allErrors: true, allowUnionTypes: true });
var compiled2;
function validator2() {
  if (!compiled2) {
    compiled2 = ajv3.compile(framework_api_schema_default);
  }
  return compiled2;
}
function validateFrameworkApiIndex(data) {
  const validate = validator2();
  if (validate(data)) {
    return { valid: true, errors: [] };
  }
  const rawErrors = validate.errors ?? [];
  const errors = rawErrors.map((error) => {
    const path = error.instancePath.length > 0 ? error.instancePath : "/";
    const message = error.message ?? "invalid";
    return `${path} ${message}`.trim();
  });
  return { valid: false, errors };
}

// src/framework-api/store.ts
function frameworkApiIndexPath(projectRoot) {
  return join9(projectRoot, PATHS.FRAMEWORK_API_INDEX);
}
function writeFrameworkApiIndex(projectRoot, index) {
  const target = frameworkApiIndexPath(projectRoot);
  mkdirSync4(dirname4(target), { recursive: true });
  const tmp = `${target}.tmp`;
  writeFileSync4(tmp, JSON.stringify(index, null, 2) + "\n", "utf8");
  renameSync3(tmp, target);
  return target;
}
function readFrameworkApiIndex(projectRoot) {
  try {
    const parsed = JSON.parse(readFileSync7(frameworkApiIndexPath(projectRoot), "utf8"));
    return validateFrameworkApiIndex(parsed).valid ? parsed : null;
  } catch {
    return null;
  }
}

// src/feature-evidence/reuse.ts
var CREATE_KEYWORDS = [
  "add helper",
  "add a helper",
  "add util",
  "add a util",
  "add component",
  "add a component",
  "create helper",
  "create a helper",
  "create util",
  "create a util",
  "create utility",
  "create component",
  "create a component",
  "new helper",
  "new util",
  "new component"
];
var EXPECTED_SHAPE = `"reuse": {
  "consulted": [{ "source": "index-query", "query": "date formatting", "hits": 2 }],
  "reusing": [{ "symbol": "formatIsoDate", "file": "src/utils/dates.ts", "how": "call as-is" }],
  "new_constructs": [{ "name": "formatRelativeDate", "justification": "no existing helper handles the relative form" }]
}`;
var INDEX_ABSENT_WARNING = "reuse claims unverified: index not built";
var SNAPSHOT_ABSENT_WARNING = "framework reuse claims unverified: stack snapshot not built";
var FRAMEWORK_API_INDEX_ABSENT_WARNING = "framework reuse claims unverified: framework-api index not built";
function validateReuseSection(input) {
  const shape = validateShape(input.reuse);
  if (shape.errors.length > 0) {
    return shape;
  }
  const reuse = input.reuse;
  const errors = [];
  const warnings = [];
  const provenance = {};
  checkDeclaredConstructs(reuse, input.steps, errors);
  checkFirstPartySymbols(input.projectRoot, reuse, errors, warnings);
  checkFrameworkClaims(input.projectRoot, reuse, errors, warnings);
  verifyFrameworkSymbols(input.projectRoot, reuse, errors, warnings, provenance);
  return { errors, warnings, provenance };
}
function frameworkClaimKey(packageName, symbol) {
  return `${packageName}::${symbol}`;
}
function verifyFrameworkSymbols(projectRoot, reuse, errors, warnings, provenance) {
  const claims = (reuse.reusing ?? []).filter(
    (claim) => claim.package !== void 0
  );
  if (claims.length === 0) {
    return;
  }
  const index = readFrameworkApiIndex(projectRoot);
  if (!index) {
    warnings.push(FRAMEWORK_API_INDEX_ABSENT_WARNING);
    return;
  }
  for (const claim of claims) {
    const result = queryFrameworkApi(index, claim.package, claim.symbol, claim.version);
    const key = frameworkClaimKey(claim.package, claim.symbol);
    switch (result.verdict) {
      case "live":
        provenance[key] = "asserted";
        break;
      case "deprecated":
        provenance[key] = "asserted";
        errors.push(deprecatedMessage(claim, result));
        break;
      case "absent":
        provenance[key] = "asserted";
        errors.push(
          `reuse.reusing "${claim.symbol}" does not exist in ${claim.package}@${result.version}` + (result.nearest === null ? "" : ` \u2014 did you mean "${result.nearest}"?`)
        );
        break;
      case "unknown-dynamic":
        provenance[key] = "unknown-dynamic";
        warnings.push(
          `reuse.reusing "${claim.symbol}" is provided dynamically by ${claim.package}@${result.version} \u2014 paqad could not verify it statically`
        );
        break;
      default:
        warnings.push(
          `reuse.reusing "${claim.symbol}" unverified: ${claim.package} is not in the framework-api index`
        );
    }
  }
}
function deprecatedMessage(claim, result) {
  const detail = result.record?.message ?? "no reason given";
  const since = result.record?.since === null ? "" : ` (deprecated since ${result.record?.since})`;
  const removal = result.record?.for_removal === true ? " It is slated for removal." : "";
  return `reuse.reusing "${claim.symbol}" is deprecated in ${claim.package}@${result.version}${since}: ${detail}${removal}`;
}
function validateShape(reuse) {
  if (reuse === void 0 || reuse === null) {
    return {
      errors: [
        `plan template is missing the required "reuse" section \u2014 record what you checked before building.
Expected shape:
${EXPECTED_SHAPE}`
      ],
      warnings: [],
      provenance: {}
    };
  }
  if (typeof reuse !== "object" || Array.isArray(reuse)) {
    return {
      errors: ['plan template "reuse" must be an object'],
      warnings: [],
      provenance: {}
    };
  }
  const candidate = reuse;
  const errors = [];
  if (!Array.isArray(candidate.consulted) || candidate.consulted.length === 0) {
    errors.push(
      'a plan must record what existing code was checked: plan template "reuse.consulted" must list at least one thing you checked (run `paqad-ai index query <name>` or read the Existing surface section)'
    );
  }
  if (candidate.reusing !== void 0 && !Array.isArray(candidate.reusing)) {
    errors.push('plan template "reuse.reusing" must be an array');
  }
  if (candidate.new_constructs !== void 0 && !Array.isArray(candidate.new_constructs)) {
    errors.push('plan template "reuse.new_constructs" must be an array');
  }
  return { errors, warnings: [], provenance: {} };
}
function matchedCreateKeyword(description) {
  const haystack = description.toLowerCase();
  return CREATE_KEYWORDS.find((keyword) => haystack.includes(keyword)) ?? null;
}
function checkDeclaredConstructs(reuse, steps, errors) {
  if ((reuse.new_constructs ?? []).length > 0) {
    return;
  }
  for (const step of steps) {
    const keyword = matchedCreateKeyword(step.description ?? "");
    if (keyword !== null) {
      errors.push(
        `plan step says "${keyword}" but "reuse.new_constructs" is empty \u2014 declare the new construct and justify it, or reword the step if nothing new is introduced`
      );
      return;
    }
  }
}
function checkFirstPartySymbols(projectRoot, reuse, errors, warnings) {
  const firstParty = (reuse.reusing ?? []).filter((claim) => claim.package === void 0);
  if (firstParty.length === 0) {
    return;
  }
  const index = readCodeKnowledgeIndex(projectRoot);
  if (!index) {
    warnings.push(INDEX_ABSENT_WARNING);
    return;
  }
  const known2 = index.symbols.map((symbol) => symbol.name);
  const knownSet = new Set(known2);
  for (const claim of firstParty) {
    if (knownSet.has(claim.symbol)) {
      continue;
    }
    const nearest = nearestSymbol(claim.symbol, known2);
    errors.push(
      nearest === null ? `reuse.reusing names "${claim.symbol}", which is not in the code-knowledge index \u2014 check the name, or declare it under new_constructs if you are creating it` : `reuse.reusing names "${claim.symbol}", which is not in the code-knowledge index \u2014 did you mean "${nearest}"?`
    );
  }
}
function nearestSymbol(symbol, known2) {
  const bound = Math.max(2, Math.floor(symbol.length / 4));
  let best = null;
  let bestDistance = bound + 1;
  for (const candidate of known2) {
    const distance = levenshtein(symbol, candidate, bound);
    if (distance < bestDistance) {
      best = candidate;
      bestDistance = distance;
    }
  }
  return best;
}
function checkFrameworkClaims(projectRoot, reuse, errors, warnings) {
  const frameworkClaims = (reuse.reusing ?? []).filter((claim) => claim.package !== void 0);
  if (frameworkClaims.length > 0) {
    checkFrameworkVersions(projectRoot, frameworkClaims, errors, warnings);
  }
  const frameworks = detectedFrameworks(projectRoot);
  if (frameworks.length === 0) {
    return;
  }
  for (const construct of reuse.new_constructs ?? []) {
    if (construct.framework_checked !== void 0) {
      continue;
    }
    if (justificationNamesFramework(construct.justification, frameworks)) {
      continue;
    }
    errors.push(
      `reuse.new_constructs "${construct.name}" must record the framework check \u2014 this project uses ${frameworks.join(", ")}. Add "framework_checked", or justify why no ${frameworks[0]} equivalent fits.`
    );
  }
}
function checkFrameworkVersions(projectRoot, claims, errors, warnings) {
  for (const claim of claims) {
    if (!claim.version) {
      errors.push(
        `reuse.reusing "${claim.symbol}" names package "${claim.package}" but sets no "version" \u2014 a framework claim must say which installed version it is made against`
      );
    }
  }
  const versioned = claims.filter((claim) => Boolean(claim.version));
  if (versioned.length === 0) {
    return;
  }
  const snapshot = readStackSnapshotSync(projectRoot);
  if (!snapshot) {
    warnings.push(SNAPSHOT_ABSENT_WARNING);
    return;
  }
  for (const claim of versioned) {
    const installed = snapshot.packages.find((pkg) => pkg.name === claim.package);
    if (!installed) {
      errors.push(
        `reuse.reusing "${claim.symbol}" names package "${claim.package}", which is not in the stack snapshot \u2014 check the package name`
      );
      continue;
    }
    if (installed.locked_version !== claim.version) {
      errors.push(
        `reuse.reusing "${claim.symbol}" claims ${claim.package}@${claim.version}; stack has ${installed.locked_version}`
      );
    }
  }
}
function detectedFrameworks(projectRoot) {
  const profile = readProjectProfile(projectRoot);
  const frameworks = profile?.stack_profile?.frameworks;
  return Array.isArray(frameworks) ? frameworks : [];
}
function justificationNamesFramework(justification, frameworks) {
  const haystack = justification.toLowerCase();
  return frameworks.some((framework) => haystack.includes(framework.toLowerCase()));
}
function reuseCounts(plan) {
  const reuse = plan?.reuse;
  if (!reuse) {
    return null;
  }
  return {
    reused: (reuse.reusing ?? []).length,
    newJustified: (reuse.new_constructs ?? []).length
  };
}

// src/feature-evidence/artifacts.ts
var ReuseDeclarationError = class extends Error {
  errors;
  constructor(errors) {
    super(errors.join("\n"));
    this.name = "ReuseDeclarationError";
    this.errors = errors;
  }
};
var NoActiveFeatureError = class extends Error {
  constructor() {
    super("No active feature \u2014 run `paqad-ai stage start planning` first.");
    this.name = "NoActiveFeatureError";
  }
};
function atomicWriteJson(absPath, value) {
  mkdirSync5(dirname5(absPath), { recursive: true });
  const tmp = `${absPath}.tmp`;
  writeFileSync5(tmp, `${JSON.stringify(value, null, 2)}
`, "utf8");
  renameSync4(tmp, absPath);
}
function atomicWriteText(absPath, text) {
  mkdirSync5(dirname5(absPath), { recursive: true });
  const tmp = `${absPath}.tmp`;
  writeFileSync5(tmp, text, "utf8");
  renameSync4(tmp, absPath);
}
function readJson(absPath) {
  try {
    return JSON.parse(readFileSync8(absPath, "utf8"));
  } catch {
    return null;
  }
}
function activeFeatureParts(projectRoot, sessionId) {
  const dirName = currentFeature(projectRoot, sessionId);
  if (!dirName) {
    throw new NoActiveFeatureError();
  }
  const parts = parseFeatureDirName(dirName);
  if (!parts) {
    throw new Error(`Active feature dir name is not parseable: ${JSON.stringify(dirName)}`);
  }
  return { dirName, parts };
}
function writeFeaturePlan(projectRoot, sessionId, input, options = {}) {
  const reuseCheck = validateReuseSection({
    projectRoot,
    reuse: input.reuse,
    steps: input.steps ?? []
  });
  if (reuseCheck.errors.length > 0) {
    throw new ReuseDeclarationError(reuseCheck.errors);
  }
  const reuse = applyComputedProvenance(input.reuse, reuseCheck.provenance);
  let { dirName, parts } = activeFeatureParts(projectRoot, sessionId);
  if (input.title !== void 0 && input.title.length > 0) {
    const backfilled = backfillFeatureSlug(projectRoot, dirName, input.title, input.now);
    if (backfilled.renamed) {
      dirName = backfilled.dirName;
      const renamedParts = parseFeatureDirName(dirName);
      if (renamedParts) {
        parts = renamedParts;
      }
    }
  }
  const record = buildPlanRecord({
    change: parts.ulid,
    session_id: sessionId,
    summary: input.summary,
    steps: input.steps,
    modules_touched: input.modules_touched,
    decisions: input.decisions,
    risks: input.risks,
    reuse,
    now: input.now
  });
  const errors = validatePlanRecord(record);
  if (errors.length > 0) {
    throw new Error(`Invalid plan.json: ${errors.join("; ")}`);
  }
  const rel = featureFilePath(dirName, "plan");
  atomicWriteJson(join10(projectRoot, rel), record);
  updateFeatureRecord(
    projectRoot,
    dirName,
    { title: input.title ?? parts.slug, slug: parts.slug, issue: parts.issue },
    input.now
  );
  const laneResized = planLaneResize({
    projectRoot,
    sessionId,
    dirName,
    steps: input.steps,
    changedFiles: options.changedFiles ?? [],
    changesKnown: options.changedFiles !== void 0 && options.gitReadable !== false,
    now: input.now
  });
  const laneRaisedForAuthoring = raiseLaneForAuthoring({
    projectRoot,
    dirName,
    plannedFiles: planFiles(input.steps),
    changedFiles: options.changedFiles ?? [],
    now: input.now
  });
  const laneRaised = laneResized ? null : raiseLaneIfGrown({
    projectRoot,
    dirName,
    plannedFiles: planFiles(input.steps),
    changedFiles: options.changedFiles ?? [],
    now: input.now
  });
  const armed = armDecisionFromPlan({
    projectRoot,
    changeKey: dirName,
    sessionId,
    constructs: (input.reuse?.new_constructs ?? []).map((construct) => ({
      name: construct.name,
      justification: construct.justification
    }))
  });
  const readinessDecision = openVisualEvidenceReadinessPause({
    projectRoot,
    dirName,
    files: [
      .../* @__PURE__ */ new Set([
        ...(input.steps ?? []).flatMap((step) => step.files ?? []),
        ...options.changedFiles ?? []
      ])
    ]
  });
  return {
    dirName,
    path: rel,
    record,
    warnings: [...reuseCheck.warnings, ...armed.warnings],
    armedDecisions: armed.minted,
    readinessDecision,
    laneResized,
    laneRaised,
    laneRaisedForAuthoring
  };
}
function applyComputedProvenance(reuse, computed) {
  if (reuse === void 0 || Object.keys(computed).length === 0) {
    return reuse;
  }
  return {
    ...reuse,
    reusing: (reuse.reusing ?? []).map((claim) => {
      if (claim.package === void 0) {
        return claim;
      }
      const verdict = computed[frameworkClaimKey(claim.package, claim.symbol)];
      return verdict === void 0 ? claim : { ...claim, provenance: verdict };
    })
  };
}
function readFeaturePlan(projectRoot, dirName) {
  return readJson(join10(projectRoot, featureFilePath(dirName, "plan")));
}
function writeFeatureSpecification(projectRoot, sessionId, spec, markdown) {
  const dirName = currentFeature(projectRoot, sessionId);
  if (!dirName) {
    throw new NoActiveFeatureError();
  }
  if (spec.frozen === null || spec.frozen === void 0) {
    throw new Error(`Refusing to persist unfrozen spec ${spec.spec_id}: freeze it first.`);
  }
  if (sha256Hex(markdown) !== spec.spec_hash) {
    throw new Error(
      `Refusing to persist spec ${spec.spec_id}: the source does not hash to its spec_hash.`
    );
  }
  const sessionForDocs = documentSessionId(projectRoot, dirName, sessionId);
  const header = buildTextHeader({
    docType: SPEC_SOURCE_DOC_TYPE,
    change: featureChangeKey(dirName),
    sessionId: sessionForDocs,
    schemaVersion: FEATURE_DOC_SCHEMA_VERSION,
    body: markdown
  });
  atomicWriteText(
    join10(projectRoot, featureFilePath(dirName, "specMd")),
    renderFrontMatter(header, markdown)
  );
  const body = { ...spec, spec_file: FEATURE_BUNDLE_FILES.specMd };
  delete body.schema_version;
  const record = stampFeatureDocument({
    projectRoot,
    dirName,
    docType: SPECIFICATION_DOC_TYPE,
    schemaVersion: FEATURE_DOC_SCHEMA_VERSION,
    sessionId: sessionForDocs,
    body
  });
  const rel = featureFilePath(dirName, "specification");
  atomicWriteJson(join10(projectRoot, rel), record);
  updateFeatureRecord(projectRoot, dirName, { spec_id: spec.spec_id });
  return { dirName, path: rel, record };
}
function readFeatureSpecification(projectRoot, dirName) {
  return readJson(join10(projectRoot, featureFilePath(dirName, "specification")));
}
function writeFeatureReview(projectRoot, sessionId, input) {
  const { dirName, parts } = activeFeatureParts(projectRoot, sessionId);
  const record = buildReviewRecord({
    change: parts.ulid,
    session_id: sessionId,
    summary: input.summary,
    verdict: input.verdict,
    findings: input.findings,
    checked: input.checked,
    rollback: input.rollback,
    now: input.now
  });
  const errors = validateReviewRecord(record);
  if (errors.length > 0) {
    throw new Error(`Invalid review.json: ${errors.join("; ")}`);
  }
  const rel = featureFilePath(dirName, "review");
  atomicWriteJson(join10(projectRoot, rel), record);
  return { dirName, path: rel, record };
}
function readFeatureReview(projectRoot, dirName) {
  return readJson(join10(projectRoot, featureFilePath(dirName, "review")));
}
var SKILL_RECORD_DOC_TYPE = "paqad.skill-record";
var SKILL_RECORD_SCHEMA_VERSION = 1;
var SKILL_RECORD_WRITER = "paqad-ai skill release";
function skillRecordFilename(name) {
  return `skill-${name}.json`;
}
function skillRecordPath(dirName, name) {
  return join10(featureDir(dirName), skillRecordFilename(name));
}
function readSkillRecord(projectRoot, dirName, name) {
  return readJson(
    join10(projectRoot, skillRecordPath(dirName, name))
  );
}
function sameSkillRecord(existing, body) {
  if (existing === null) return false;
  return JSON.stringify({ ...body }) === JSON.stringify({
    skill: existing.skill,
    status: existing.status,
    version: existing.version,
    released_path: existing.released_path,
    folder_hash: existing.folder_hash,
    evidence_path: existing.evidence_path,
    overrides: existing.overrides,
    labels: existing.labels
  });
}
function writeSkillRecord(projectRoot, sessionId, body, now) {
  const { dirName, parts } = activeFeatureParts(projectRoot, sessionId);
  const rel = skillRecordPath(dirName, body.skill);
  if (sameSkillRecord(readSkillRecord(projectRoot, dirName, body.skill), body)) {
    return { dirName, path: rel, written: false };
  }
  const record = buildDocumentEnvelope({
    docType: SKILL_RECORD_DOC_TYPE,
    change: parts.ulid,
    sessionId,
    schemaVersion: SKILL_RECORD_SCHEMA_VERSION,
    now,
    body: { ...body }
  });
  atomicWriteJson(join10(projectRoot, rel), record);
  return { dirName, path: rel, written: true };
}
var AGENT_RECORD_DOC_TYPE = "paqad.agent-record";
var AGENT_RECORD_SCHEMA_VERSION = 1;
var AGENT_RECORD_WRITER = "paqad-ai agent release";
function agentRecordFilename(name) {
  return `agent-${name}.json`;
}
function agentRecordPath(dirName, name) {
  return join10(featureDir(dirName), agentRecordFilename(name));
}
function readAgentRecord(projectRoot, dirName, name) {
  return readJson(
    join10(projectRoot, agentRecordPath(dirName, name))
  );
}
function sameAgentRecord(existing, body) {
  if (existing === null) return false;
  const pick = (record) => ({
    agent: record.agent,
    status: record.status,
    version: record.version,
    released_path: record.released_path,
    file_hash: record.file_hash,
    hook_scripts: record.hook_scripts,
    files: record.files,
    evidence_path: record.evidence_path,
    overrides: record.overrides,
    labels: record.labels
  });
  return JSON.stringify(pick(body)) === JSON.stringify(pick(existing));
}
function writeAgentRecord(projectRoot, sessionId, body, now) {
  const { dirName, parts } = activeFeatureParts(projectRoot, sessionId);
  const rel = agentRecordPath(dirName, body.agent);
  if (sameAgentRecord(readAgentRecord(projectRoot, dirName, body.agent), body)) {
    return { dirName, path: rel, written: false };
  }
  const record = buildDocumentEnvelope({
    docType: AGENT_RECORD_DOC_TYPE,
    change: parts.ulid,
    sessionId,
    schemaVersion: AGENT_RECORD_SCHEMA_VERSION,
    now,
    body: { ...body }
  });
  atomicWriteJson(join10(projectRoot, rel), record);
  return { dirName, path: rel, written: true };
}

// src/feature-evidence/projections.ts
function readAllFeatureStageRows(projectRoot) {
  return listFeatureDirs(projectRoot).flatMap(
    (dirName) => readUnitFile(projectRoot, featureFilePath(dirName, "stageEvidence"))
  );
}
function readAllFeatureRuleRuns(projectRoot) {
  return listFeatureDirs(projectRoot).flatMap(
    (dirName) => readUnitFile(projectRoot, featureFilePath(dirName, "ruleRun"))
  );
}
function readAllFeatureChangeMetrics(projectRoot) {
  return listFeatureDirs(projectRoot).flatMap(
    (dirName) => readUnitFile(projectRoot, featureFilePath(dirName, "changeMetrics"))
  );
}
function readAllFeatureRag(projectRoot) {
  return listFeatureDirs(projectRoot).flatMap(
    (dirName) => readUnitFile(projectRoot, featureFilePath(dirName, "rag"))
  );
}
function readFeatureChangeMetricsWindow(projectRoot, limit) {
  const rows = readAllFeatureChangeMetrics(projectRoot).slice().sort((a, b) => (rowRecordedAt(a) ?? "").localeCompare(rowRecordedAt(b) ?? ""));
  return limit >= rows.length ? rows : rows.slice(rows.length - limit);
}
function readAllFeatureEvidence(projectRoot) {
  return listFeatureDirs(projectRoot).flatMap(
    (dirName) => readFeatureEvidence(projectRoot, dirName)
  );
}
function readAllFeatureSpecificationEntries(projectRoot) {
  const entries = [];
  for (const dirName of listFeatureDirs(projectRoot)) {
    const spec = readFeatureSpecification(projectRoot, dirName);
    if (spec && spec.frozen !== null && spec.frozen !== void 0) {
      entries.push({ dirName, spec });
    }
  }
  return entries;
}

// src/spec-pipeline/metrics.ts
init_esm_shims();

// src/spec/feature-spec-builder.ts
init_esm_shims();
var FEATURE_SPEC_SCHEMA_VERSION = "1";
function buildFeatureSpec(options) {
  const index = extractObligationIndex({
    spec_file: options.spec_file,
    spec_markdown: options.spec_markdown,
    spec_review: options.spec_review ?? null,
    extracted_at: options.extracted_at
  });
  const behaviour = [];
  const criteria = [];
  let criterionSeq = 0;
  for (const obligation of index.obligations) {
    const description = stripLeadingId(obligation.description);
    if (obligation.category === "functional" || obligation.category === "non-functional") {
      behaviour.push(`${obligation.obligation_id}: ${description}`);
    } else if (obligation.category === "acceptance") {
      criterionSeq += 1;
      criteria.push(criterionFromObligation(obligation.obligation_id, description, criterionSeq));
    }
  }
  return {
    schema_version: FEATURE_SPEC_SCHEMA_VERSION,
    spec_id: options.spec_id,
    spec_file: options.spec_file,
    spec_hash: sha256Hex(options.spec_markdown),
    behaviour,
    acceptance_criteria: criteria,
    invariants: collectInvariants(options.spec_markdown, options.suggested_invariants ?? []),
    open_questions: extractOpenQuestions(options.spec_markdown),
    frozen: null,
    ...(() => {
      const nonGoals = extractNonGoals(options.spec_markdown);
      return nonGoals.length > 0 ? { non_goals: nonGoals } : {};
    })()
  };
}
function criterionFromObligation(obligationId, description, sequence) {
  const criterionId = /^AC-(?:TRACK-[A-Za-z0-9-]+|\d+)$/.test(obligationId) ? obligationId : `AC-${sequence}`;
  const { given, when, then } = parseGivenWhenThen(description);
  return {
    criterion_id: criterionId,
    given,
    when,
    then,
    proof_type: detectProofType(description),
    status: "uncovered",
    source: "planned",
    linked_requirement_ids: []
  };
}
function stripLeadingId(text) {
  return text.replace(/^(?:(?:FR|NFR|EC)-\d+(?:\.\d+)?|AC-(?:TRACK-[A-Za-z0-9-]+|\d+))\s*[:.)-]\s*/i, "").trim();
}
function parseGivenWhenThen(text) {
  const match = /given\s+(.*?),?\s+when\s+(.*?),?\s+then\s+(.*?)\.?$/i.exec(text.trim());
  if (match) {
    return { given: match[1].trim(), when: match[2].trim(), then: match[3].trim() };
  }
  return { given: "", when: "", then: text.trim() };
}
function detectProofType(text) {
  const match = /\(proof:\s*(automated|manual|visual)\)/i.exec(text);
  if (match) {
    return match[1].toLowerCase();
  }
  return "automated";
}
function collectInvariants(markdown, suggested) {
  const invariants = [];
  const seenStatements = /* @__PURE__ */ new Set();
  let sequence = 0;
  for (const authored of extractAuthoredInvariants(markdown)) {
    sequence += 1;
    seenStatements.add(authored.toLowerCase());
    invariants.push({
      invariant_id: `INV-${sequence}`,
      statement: authored,
      source: "authored",
      confirmed: false
    });
  }
  for (const suggestion of suggested) {
    const normalized = suggestion.statement.trim();
    if (normalized.length === 0 || seenStatements.has(normalized.toLowerCase())) {
      continue;
    }
    sequence += 1;
    seenStatements.add(normalized.toLowerCase());
    invariants.push({
      invariant_id: `INV-${sequence}`,
      statement: normalized,
      source: suggestion.source,
      ...suggestion.rule_id === void 0 ? {} : { rule_id: suggestion.rule_id },
      confirmed: false
    });
  }
  return invariants;
}
function extractAuthoredInvariants(markdown) {
  const statements = [];
  for (const line of splitLines(markdown)) {
    const match = /^\s*(?:[-*+]\s+)?(?:\*\*)?INV-\d+(?:\*\*)?\s*[:.)-]\s*(.+\S)\s*$/.exec(line);
    if (match) {
      statements.push(match[1].trim());
    }
  }
  return statements;
}
function extractOpenQuestions(markdown) {
  const questions = [];
  for (const line of splitLines(markdown)) {
    const match = /^\s*(?:[-*+]\s+)?(Q\d+)\s*[:.)-]\s*(.+\S)\s*$/.exec(line);
    if (match) {
      questions.push(`${match[1]}: ${match[2].trim()}`);
    }
  }
  return questions;
}
function extractNonGoals(markdown) {
  const nonGoals = [];
  let inSection = false;
  for (const line of splitLines(markdown)) {
    const heading = /^(#{1,6})\s+(.*\S)\s*$/.exec(line);
    if (heading) {
      inSection = /^non-?goals?$/i.test(heading[2].trim());
      continue;
    }
    if (!inSection) {
      continue;
    }
    const bullet = /^\s*[-*+]\s+(.+\S)\s*$/.exec(line);
    if (bullet) {
      nonGoals.push(bullet[1].trim());
    }
  }
  return nonGoals;
}

// src/spec/spec-freeze.ts
init_esm_shims();
function evaluateSpecFreeze(spec, specReview, options = {}) {
  const blockers = [];
  if (spec.behaviour.length === 0) {
    blockers.push("Spec has no behaviour statements.");
  }
  if (spec.acceptance_criteria.length === 0) {
    blockers.push("Spec has no acceptance criteria.");
  }
  if (spec.invariants.length === 0) {
    blockers.push("Spec has no invariants.");
  }
  for (const criterion of spec.acceptance_criteria) {
    if (!criterion.proof_type) {
      blockers.push(`Acceptance criterion ${criterion.criterion_id} has no proof_type.`);
    }
  }
  for (const invariant of spec.invariants) {
    if (!invariant.confirmed) {
      blockers.push(`Invariant ${invariant.invariant_id} is not human-confirmed.`);
    }
  }
  for (const question of spec.open_questions) {
    blockers.push(`Open question unresolved: ${question}`);
  }
  const visual = options.requireVisualAc;
  if (visual && !spec.acceptance_criteria.some((criterion) => criterion.proof_type === "visual")) {
    blockers.push(
      `Visual evidence is on and this change touches frontend files (${visual.files.join(", ")}), so at least one acceptance criterion needs (proof: visual).`
    );
  }
  if (specReview) {
    for (const defect of specReview.defects) {
      if (defect.severity === "critical" && defect.status !== "resolved") {
        blockers.push(`Critical spec-review defect open: ${defect.defect_id}`);
      }
    }
  }
  return { can_freeze: blockers.length === 0, blockers };
}
function freezeSpec(spec, input) {
  const evaluation = evaluateSpecFreeze(spec, input.spec_review);
  if (!evaluation.can_freeze) {
    throw new Error(`Cannot freeze spec ${spec.spec_id}: ${evaluation.blockers.join("; ")}`);
  }
  const frozen = {
    frozen_at: input.frozen_at,
    spec_hash: spec.spec_hash,
    signed_off_by: input.signed_off_by
  };
  const specReview = input.spec_review ? summarizeSpecReview(input.spec_review) : void 0;
  return {
    ...spec,
    frozen,
    ...specReview === void 0 ? {} : { spec_review: specReview },
    ...input.sections?.task === void 0 ? {} : { task: input.sections.task },
    ...input.sections?.grounding === void 0 ? {} : { grounding: input.sections.grounding },
    ...input.sections?.pipeline === void 0 ? {} : { pipeline: input.sections.pipeline },
    ...input.sections?.trace === void 0 ? {} : { trace: input.sections.trace }
  };
}
function summarizeSpecReview(report) {
  const open = report.defects.filter((defect) => defect.status !== "resolved");
  return {
    reviewed_at: report.metadata.reviewed_at,
    defect_count: open.length,
    by_severity: {
      critical: open.filter((defect) => defect.severity === "critical").length,
      major: open.filter((defect) => defect.severity === "major").length,
      minor: open.filter((defect) => defect.severity === "minor").length
    }
  };
}
function isFrozenSpecStale(spec, currentSpecHash) {
  return spec.frozen !== null && spec.frozen.spec_hash !== currentSpecHash;
}

// src/spec-pipeline/experts/assemble.ts
init_esm_shims();

// src/spec-pipeline/run-store.ts
init_esm_shims();
import { mkdirSync as mkdirSync6, readFileSync as readFileSync9, realpathSync, rmdirSync, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";

// src/spec-pipeline/experts/types.ts
init_esm_shims();
var MAX_EXPERT_QUESTIONS = 5;
var QA_ROLE = "qa-engineer";

// src/spec-pipeline/finish.ts
init_esm_shims();
function decideFinish(config, a5Live) {
  if (config.final_review === "strict") {
    return {
      outcome: "await-human-approval",
      reason: "final-review is required \u2014 a human must approve before freeze"
    };
  }
  if (!a5Live) {
    return {
      outcome: "non-blocking-review",
      reason: "the spec-vs-code check (A5) is not live \u2014 showing a non-blocking readable review instead of freezing silently"
    };
  }
  return {
    outcome: "freeze",
    reason: config.final_review === "warn" ? "A5 is live; final-review is advisory \u2014 showing the readable spec, then freezing" : "A5 is live \u2014 freezing automatically"
  };
}
function buildProvenance(config, a5Live, answerRefs, questions, experts, metrics) {
  return {
    pipeline_produced: true,
    answer_refs: answerRefs,
    questions,
    enforcement: config,
    a5_live: a5Live,
    outcome: decideFinish(config, a5Live).outcome,
    ...experts ? { experts } : {},
    ...metrics ? { metrics } : {}
  };
}
function frozenPipelineSection(finish) {
  const { provenance } = finish;
  const outcome = finish.outcome ?? provenance.outcome;
  const config = provenance.enforcement;
  return {
    produced: true,
    ...outcome === void 0 ? {} : { outcome },
    ...finish.reason === void 0 ? {} : { reason: finish.reason },
    ...provenance.a5_live === void 0 ? {} : { a5_live: provenance.a5_live },
    ...config === void 0 ? {} : {
      enforcement: {
        clarification: config.clarification,
        final_review: config.final_review,
        token_ceiling: config.token_ceiling,
        experts_enabled: config.experts_enabled,
        adoption: config.adoption,
        standing_experts: config.standing_experts,
        standing_experts_dropped: config.standing_experts_dropped
      }
    }
  };
}

// src/spec-pipeline/run-store.ts
var SPEC_PIPELINE_STAGING_DIR = ".paqad/tmp/spec-pipeline";
var STAGED_FILES = {
  grounding: "grounding.json",
  task: "task.json",
  trace: "trace.json",
  craft: "spec.md",
  finish: "finish.json",
  expertQuestions: "expert-questions.json",
  expertVerdicts: "expert-verdicts.json",
  inputs: "inputs.json"
};
function stagingDir(dirName) {
  return join(SPEC_PIPELINE_STAGING_DIR, featureChangeKey(dirName));
}
function stagedFilePath(dirName, file) {
  return join(stagingDir(dirName), STAGED_FILES[file]);
}
function readText(abs) {
  try {
    return readFileSync9(abs, "utf8");
  } catch {
    return null;
  }
}
function parseJson(raw) {
  try {
    return JSON.parse(raw);
  } catch {
    return void 0;
  }
}
function isRecord(value) {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}
function atomicWrite(abs, text) {
  mkdirSync6(dirname(abs), { recursive: true });
  const tmp = `${abs}.tmp-${process.pid}`;
  writeFileSync6(tmp, text, "utf8");
  renameWithRetrySync(tmp, abs);
}
function readStagedText(projectRoot, dirName, file) {
  return readText(join(projectRoot, stagedFilePath(dirName, file)));
}
function writeStagedText(projectRoot, dirName, file, text) {
  atomicWrite(join(projectRoot, stagedFilePath(dirName, file)), text);
}
function readStagedJson(projectRoot, dirName, file) {
  const raw = readStagedText(projectRoot, dirName, file);
  const parsed = raw === null ? void 0 : parseJson(raw);
  return parsed === void 0 ? null : parsed;
}
function writeStagedJson(projectRoot, dirName, file, value) {
  writeStagedText(projectRoot, dirName, file, `${JSON.stringify(value, null, 2)}
`);
}
function clearStaged(projectRoot, dirName, file) {
  rmSync2(join(projectRoot, stagedFilePath(dirName, file)), { force: true });
}
function readBundleBody(projectRoot, dirName, file) {
  const raw = readText(join(projectRoot, featureFilePath(dirName, file)));
  const parsed = raw === null ? void 0 : parseJson(raw);
  if (!isRecord(parsed)) return null;
  const body = { ...parsed };
  for (const key of ENVELOPE_HEADER_KEYS) delete body[key];
  return body;
}
function writeBundleBody(projectRoot, dirName, file, docType, body, options) {
  const doc = stampFeatureDocument({
    projectRoot,
    dirName,
    docType,
    schemaVersion: FEATURE_DOC_SCHEMA_VERSION,
    sessionId: options.sessionId,
    body,
    now: options.now
  });
  atomicWrite(
    join(projectRoot, featureFilePath(dirName, file)),
    `${JSON.stringify(doc, null, 2)}
`
  );
}
function writeRequest(projectRoot, dirName, text, options = {}) {
  const header = buildTextHeader({
    docType: REQUEST_DOC_TYPE,
    change: featureChangeKey(dirName),
    sessionId: documentSessionId(projectRoot, dirName, options.sessionId),
    schemaVersion: FEATURE_DOC_SCHEMA_VERSION,
    body: text,
    now: options.now
  });
  atomicWrite(
    join(projectRoot, featureFilePath(dirName, "request")),
    renderFrontMatter(header, text)
  );
}
function readRequest(projectRoot, dirName) {
  const raw = readText(join(projectRoot, featureFilePath(dirName, "request")));
  return raw === null ? "" : splitFrontMatter(raw).body;
}
function readClarification(projectRoot, dirName) {
  const body = readBundleBody(projectRoot, dirName, "clarification");
  if (body === null) return null;
  return {
    label: isRecord(body.label) ? body.label : null,
    questions: isRecord(body.questions) ? body.questions : null
  };
}
function writeClarification(projectRoot, dirName, patch, options) {
  const current = readClarification(projectRoot, dirName) ?? { label: null, questions: null };
  writeBundleBody(
    projectRoot,
    dirName,
    "clarification",
    CLARIFICATION_DOC_TYPE,
    { ...current, ...patch },
    options
  );
}
function writeLabel(projectRoot, dirName, label, options = {}) {
  writeClarification(
    projectRoot,
    dirName,
    {
      label: {
        value: label.label,
        signals: label.signals,
        question_budget: label.question_budget
      }
    },
    options
  );
}
function readLabel(projectRoot, dirName) {
  const section = readClarification(projectRoot, dirName)?.label;
  if (!section) return null;
  return {
    label: section.value,
    signals: section.signals,
    question_budget: section.question_budget
  };
}
function writeQuestions(projectRoot, dirName, questions, options = {}) {
  writeClarification(
    projectRoot,
    dirName,
    {
      questions: {
        asked: questions.questions,
        auto_answered: questions.auto_answered,
        deferred: questions.deferred_from_experts ?? [],
        counts: {
          asked: questions.asked,
          answered: questions.answered,
          auto_answered: questions.auto_answered.length,
          deferred: questions.deferred
        }
      }
    },
    options
  );
}
function readQuestions(projectRoot, dirName) {
  const section = readClarification(projectRoot, dirName)?.questions;
  if (!section) return null;
  return {
    questions: section.asked,
    auto_answered: section.auto_answered,
    asked: section.counts.asked,
    answered: section.counts.answered,
    deferred: section.counts.deferred,
    ...section.deferred.length > 0 ? { deferred_from_experts: section.deferred } : {}
  };
}
function clearClarificationSection(projectRoot, dirName, section, options = {}) {
  writeClarification(projectRoot, dirName, { [section]: null }, options);
}
function readExperts(projectRoot, dirName) {
  const body = readBundleBody(projectRoot, dirName, "experts");
  if (body === null || !Array.isArray(body.roster)) return null;
  return {
    roster: body.roster,
    findings: Array.isArray(body.findings) ? body.findings : null,
    synthesis: isRecord(body.synthesis) ? body.synthesis : null,
    ...Array.isArray(body.voice_warnings) ? { voice_warnings: body.voice_warnings } : {}
  };
}
function writeExperts(projectRoot, dirName, patch, options) {
  const current = readExperts(projectRoot, dirName) ?? {
    roster: [],
    findings: null,
    synthesis: null
  };
  writeBundleBody(
    projectRoot,
    dirName,
    "experts",
    EXPERTS_DOC_TYPE,
    { ...current, ...patch },
    options
  );
}
function writeExpertRoster(projectRoot, dirName, roster, options = {}) {
  writeExperts(projectRoot, dirName, { roster }, options);
}
function readExpertNeed(projectRoot, dirName) {
  const experts = readExperts(projectRoot, dirName);
  if (experts === null) return null;
  return {
    experts: experts.roster.map(({ role, reason, origin }) => ({
      role,
      reason,
      ...origin === void 0 ? {} : { origin }
    }))
  };
}
function expertNotesLockDir(projectRoot, dirName) {
  return join(projectRoot, PATHS.LOCKS_DIR, `experts-${featureChangeKey(dirName)}.lock`);
}
function warningBelongsTo(warning, role) {
  const prefix = `EX-${role}-`;
  return warning.id.startsWith(prefix) && /^\d+$/.test(warning.id.slice(prefix.length));
}
function inRosterOrder(findings, roster) {
  const seat = new Map(roster.map((entry, index) => [entry.role, index]));
  const rank = (role) => seat.get(role) ?? roster.length;
  return findings.map((finding, index) => ({ finding, index })).sort((a, b) => rank(a.finding.role) - rank(b.finding.role) || a.index - b.index).map(({ finding }) => finding);
}
function writeExpertNotes(projectRoot, dirName, artifact, options = {}) {
  withFileLockSync(
    expertNotesLockDir(projectRoot, dirName),
    () => mergeExpertNotesLocked(projectRoot, dirName, artifact, options)
  );
}
function mergeExpertNotesLocked(projectRoot, dirName, artifact, options) {
  const current = readExperts(projectRoot, dirName);
  const noteRoles = new Set(artifact.notes.map((note) => note.role));
  const tokenRoles = new Set(Object.keys(artifact.tokens));
  const roles = /* @__PURE__ */ new Set([...noteRoles, ...tokenRoles]);
  const kept = (current?.findings ?? []).filter((finding) => !roles.has(finding.role));
  const incoming = artifact.notes.flatMap(
    (note) => note.findings.map(({ id, ...finding }) => ({ id, role: note.role, ...finding }))
  );
  const roster = (current?.roster ?? []).map(
    (entry) => roles.has(entry.role) ? { ...entry, tokens_used: artifact.tokens[entry.role] ?? null } : entry
  );
  const touched = [...roles];
  const keptWarnings = (current?.voice_warnings ?? []).filter(
    (warning) => !touched.some((role) => warningBelongsTo(warning, role))
  );
  const voiceWarnings = artifact.voice_warnings === void 0 && current?.voice_warnings === void 0 ? void 0 : [...keptWarnings, ...artifact.voice_warnings ?? []];
  writeExperts(
    projectRoot,
    dirName,
    {
      roster,
      findings: inRosterOrder([...kept, ...incoming], roster),
      ...voiceWarnings === void 0 ? {} : { voice_warnings: voiceWarnings }
    },
    options
  );
  const questions = [
    ...readStagedExpertQuestions(projectRoot, dirName).filter((note) => !roles.has(note.role)),
    ...artifact.notes.filter((note) => (note.questions ?? []).length > 0).map((note) => ({ role: note.role, questions: note.questions }))
  ];
  if (questions.length > 0) {
    writeStagedJson(projectRoot, dirName, "expertQuestions", { notes: questions });
  } else {
    clearStaged(projectRoot, dirName, "expertQuestions");
  }
  const verdicts = artifact.notes.find((note) => note.verdicts !== void 0)?.verdicts;
  if (verdicts !== void 0) {
    writeStagedJson(projectRoot, dirName, "expertVerdicts", { role: QA_ROLE, verdicts });
  } else if (roles.has(QA_ROLE)) {
    clearStaged(projectRoot, dirName, "expertVerdicts");
  }
}
function readStagedQaVerdicts(projectRoot, dirName) {
  return readStagedJson(projectRoot, dirName, "expertVerdicts")?.verdicts ?? null;
}
function readStagedExpertQuestions(projectRoot, dirName) {
  return readStagedJson(projectRoot, dirName, "expertQuestions")?.notes ?? [];
}
function readExpertNotes(projectRoot, dirName) {
  const experts = readExperts(projectRoot, dirName);
  if (experts === null || experts.findings === null) return null;
  const byRole = /* @__PURE__ */ new Map();
  const noteFor = (role) => {
    const existing = byRole.get(role);
    if (existing) return existing;
    const created = { role, findings: [] };
    byRole.set(role, created);
    return created;
  };
  const superseded = new Set(
    experts.findings.flatMap(
      (finding) => (finding.merged_from ?? []).filter((id) => id !== finding.id)
    )
  );
  for (const { role, ...finding } of experts.findings) {
    if (superseded.has(finding.id)) continue;
    noteFor(role).findings.push(finding);
  }
  for (const { role, questions } of readStagedExpertQuestions(projectRoot, dirName)) {
    noteFor(role).questions = questions;
  }
  const verdicts = readStagedQaVerdicts(projectRoot, dirName);
  if (verdicts !== null) noteFor(QA_ROLE).verdicts = verdicts;
  const tokens = {};
  for (const entry of experts.roster) {
    if (entry.tokens_used !== null) tokens[entry.role] = entry.tokens_used;
  }
  return { notes: [...byRole.values()], tokens };
}
function writeExpertSynthesis(projectRoot, dirName, synthesis, options = {}) {
  writeExperts(projectRoot, dirName, { synthesis }, options);
}
function writeExpertFindings(projectRoot, dirName, findings, options = {}) {
  writeExperts(projectRoot, dirName, { findings }, options);
}
function readExpertSynthesis(projectRoot, dirName) {
  return readExperts(projectRoot, dirName)?.synthesis ?? null;
}
function clearExpertSynthesis(projectRoot, dirName, options = {}) {
  writeExperts(projectRoot, dirName, { synthesis: null }, options);
}
function readGrounding(projectRoot, dirName) {
  const staged = readStagedJson(projectRoot, dirName, "grounding");
  if (staged) return staged;
  const frozen = readBundleBody(projectRoot, dirName, "specification")?.grounding;
  if (!isRecord(frozen) || !Array.isArray(frozen.references)) return null;
  return { terms: [], ...frozen };
}
function readFrozenSpecificationBody(projectRoot, dirName) {
  return readBundleBody(projectRoot, dirName, "specification");
}
function readFreezeSections(projectRoot, dirName) {
  const finish = readStagedJson(projectRoot, dirName, "finish");
  if (!isRecord(finish) || !isRecord(finish.provenance)) return null;
  const task = readStagedJson(projectRoot, dirName, "task");
  const grounding = readStagedJson(projectRoot, dirName, "grounding");
  const trace = readStagedJson(projectRoot, dirName, "trace");
  return {
    ...isRecord(task) && typeof task.intent === "string" ? { task: { intent: task.intent, scope: isRecord(task.scope) ? task.scope : {} } } : {},
    ...isRecord(grounding) ? {
      grounding: {
        path: grounding.path,
        sparse: grounding.sparse,
        references: grounding.references,
        ...Array.isArray(grounding.vocabulary) ? { vocabulary: grounding.vocabulary } : {}
      }
    } : {},
    pipeline: withExpertFreezeSummary(frozenPipelineSection(finish), projectRoot, dirName),
    ...isRecord(trace) && Array.isArray(trace.entries) ? {
      trace: Object.fromEntries(
        trace.entries.map((entry) => [
          entry.id,
          entry.source
        ])
      )
    } : {}
  };
}
function withExpertFreezeSummary(pipeline, projectRoot, dirName) {
  const experts = readExperts(projectRoot, dirName);
  if (!experts || !experts.synthesis) return pipeline;
  const syn = experts.synthesis;
  const acceptedIds = new Set(syn.accepted);
  const findings = (experts.findings ?? []).filter((finding) => acceptedIds.has(finding.id)).map((finding) => ({
    id: finding.id,
    role: finding.role,
    kind: finding.kind ?? "requirement",
    severity: finding.severity ?? "should",
    target: finding.target,
    claim: finding.claim
  }));
  const renamed = (experts.findings ?? []).filter(
    (finding) => finding.renamed_from !== void 0
  ).length;
  return {
    ...pipeline,
    experts: {
      roles: experts.roster.map((entry) => entry.role),
      accepted: syn.accepted.length,
      declined: syn.declined.length,
      conflicts: syn.conflicts.length,
      auto_resolved: syn.auto_resolved?.length ?? 0,
      standing: experts.roster.filter((entry) => entry.origin === "standing").length,
      on_call: experts.roster.filter((entry) => entry.origin !== "standing").map((entry) => entry.role),
      renamed
    },
    ...findings.length > 0 ? { findings } : {}
  };
}
function isTmpPath(rel) {
  return rel.startsWith(".paqad/tmp/") && !rel.split("/").includes("..");
}
function clearFrozenRun(projectRoot, dirName, keep = []) {
  for (const rel of readRememberedInputs(projectRoot, dirName)) {
    if (isTmpPath(rel) && !keep.includes(rel)) rmSync2(join(projectRoot, rel), { force: true });
  }
  rmSync2(join(projectRoot, stagingDir(dirName)), { recursive: true, force: true });
  try {
    rmdirSync(join(projectRoot, SPEC_PIPELINE_STAGING_DIR));
  } catch {
  }
}
function rootForms(projectRoot) {
  const given = resolve(projectRoot);
  let real = given;
  try {
    real = resolve(realpathSync(projectRoot));
  } catch {
  }
  return [.../* @__PURE__ */ new Set([given, real])];
}
function tmpInputPath(projectRoot, file) {
  const abs = resolve(file);
  for (const base of rootForms(projectRoot)) {
    const rel = relative(base, abs);
    if (rel.startsWith(".paqad/tmp/")) return rel;
  }
  return null;
}
function rememberInput(projectRoot, dirName, file) {
  const rel = tmpInputPath(projectRoot, file);
  if (rel === null) return;
  const inputs = readRememberedInputs(projectRoot, dirName);
  if (inputs.includes(rel)) return;
  writeStagedJson(projectRoot, dirName, "inputs", { inputs: [...inputs, rel] });
}
function readRememberedInputs(projectRoot, dirName) {
  return readStagedJson(projectRoot, dirName, "inputs")?.inputs ?? [];
}
function appendSpecStepRow(projectRoot, dirName, input, options = {}) {
  return appendFeatureStageRow(
    projectRoot,
    documentSessionId(projectRoot, dirName, options.sessionId),
    dirName,
    {
      kind: SPEC_STEP_KIND,
      step: input.step,
      outcome: input.outcome,
      artifact_hash: input.artifactHash,
      ...input.tokens !== void 0 ? { tokens: input.tokens } : {}
    },
    options.now
  );
}
function readSpecStepRows(projectRoot, dirName) {
  return readFeatureStageUnit(projectRoot, dirName).filter((row) => row.kind === SPEC_STEP_KIND);
}
function appendSpecCorrectionRow(projectRoot, dirName, correction, options = {}) {
  return appendFeatureStageRow(
    projectRoot,
    documentSessionId(projectRoot, dirName, options.sessionId),
    dirName,
    {
      kind: SPEC_CORRECTION_KIND,
      spec_id: correction.spec_id,
      changed_sections: correction.changed_sections
    },
    options.now
  );
}
function readSpecCorrectionRows(projectRoot, dirName) {
  return readFeatureStageUnit(projectRoot, dirName).filter(
    (row) => row.kind === SPEC_CORRECTION_KIND
  );
}

// src/spec-pipeline/trace.ts
init_esm_shims();
var KINDS = ["FR", "NFR", "AC", "INV"];
function idNumber(id) {
  const m = /-(\d+)$/.exec(id);
  return m ? Number(m[1]) : 0;
}
function carryForwardIds(current, previous) {
  const priorBySource = /* @__PURE__ */ new Map();
  const maxByKind = /* @__PURE__ */ new Map();
  for (const kind of KINDS) maxByKind.set(kind, 0);
  for (const entry of previous?.entries ?? []) {
    priorBySource.set(`${entry.kind}\0${entry.source}`, entry.id);
    const n = idNumber(entry.id);
    if (n > (maxByKind.get(entry.kind) ?? 0)) {
      maxByKind.set(entry.kind, n);
    }
  }
  const entries = [];
  for (const req of current) {
    const key = `${req.kind}\0${req.source}`;
    const inherited = priorBySource.get(key);
    if (inherited !== void 0) {
      entries.push({ id: inherited, kind: req.kind, source: req.source });
      continue;
    }
    const next = (maxByKind.get(req.kind) ?? 0) + 1;
    maxByKind.set(req.kind, next);
    entries.push({ id: `${req.kind}-${next}`, kind: req.kind, source: req.source });
  }
  return { entries };
}
function validateTrace(trace) {
  const unsourced = trace.entries.filter((e) => typeof e.source !== "string" || e.source.trim().length === 0).map((e) => e.id);
  return { ok: unsourced.length === 0, unsourced };
}
function extractRequirementIds(markdown) {
  const ids = /* @__PURE__ */ new Set();
  const re = /\b((?:FR|NFR|AC|INV)-\d+)\b/g;
  let m;
  while ((m = re.exec(markdown)) !== null) ids.add(m[1]);
  return [...ids];
}
function validateCraftTrace(specMarkdown, trace, acceptedFindingIds = []) {
  const errors = [];
  const unsourced = validateTrace(trace);
  for (const id of unsourced.unsourced) {
    errors.push(
      `line "${id}" has no source; every requirement must trace to the task, an answer, or an expert finding`
    );
  }
  const tracedIds = new Set(trace.entries.map((entry) => entry.id));
  for (const id of extractRequirementIds(specMarkdown)) {
    if (!tracedIds.has(id)) {
      errors.push(
        `line "${id}" has no source; every requirement must trace to the task, an answer, or an expert finding`
      );
    }
  }
  const tracedSources = new Set(trace.entries.map((entry) => entry.source));
  const unreflected = acceptedFindingIds.filter((id) => !tracedSources.has(id));
  if (unreflected.length > 0) {
    errors.push(`accepted expert findings not reflected in the spec: ${unreflected.join(", ")}`);
  }
  return { ok: errors.length === 0, errors };
}
function writeTrace(projectRoot, dirName, trace) {
  writeStagedJson(projectRoot, dirName, "trace", trace);
}
function readTrace(projectRoot, dirName) {
  const parsed = readStagedJson(projectRoot, dirName, "trace");
  if (parsed !== null && Array.isArray(parsed.entries)) return parsed;
  const frozen = readFrozenSpecificationBody(projectRoot, dirName);
  const map = frozen?.trace;
  if (typeof map === "object" && map !== null && !Array.isArray(map)) {
    return traceFromMap(map);
  }
  const legacy = frozen?.provenance?.trace;
  return legacy !== void 0 && Array.isArray(legacy.entries) ? legacy : null;
}
function traceFromMap(map) {
  const entries = [];
  for (const [id, source] of Object.entries(map)) {
    const kind = kindOf(id);
    if (kind !== null && typeof source === "string") entries.push({ id, kind, source });
  }
  return { entries };
}
function kindOf(id) {
  const prefix = id.split("-")[0];
  return KINDS.includes(prefix) ? prefix : null;
}
function parseTraceArtifact(raw) {
  if (typeof raw !== "object" || raw === null) return null;
  const entries = raw.entries;
  if (!Array.isArray(entries)) return null;
  const out = [];
  for (const entry of entries) {
    if (typeof entry !== "object" || entry === null) return null;
    const { id, source } = entry;
    if (typeof id !== "string" || typeof source !== "string") return null;
    const kind = kindOf(id);
    if (kind === null) return null;
    out.push({ id, kind, source });
  }
  return { entries: out };
}

// src/spec-pipeline/experts/accounting.ts
init_esm_shims();
function buildExpertAccounting(input) {
  const notesByRole = /* @__PURE__ */ new Map();
  for (const note of input.notes) {
    notesByRole.set(note.role, note);
  }
  const experts = input.needs.map((need) => {
    const note = notesByRole.get(need.role);
    const findings = note?.findings ?? [];
    const changed = findings.some(
      (finding) => finding.id !== void 0 && input.tracedFindingIds.has(finding.id)
    );
    const empty = findings.length === 0 || findings.length === 1 && findings[0].kind === "non-goal";
    return {
      role: need.role,
      reason: need.reason,
      tokens: input.tokens[need.role] ?? 0,
      changed_spec: changed,
      ...need.origin === void 0 ? {} : { origin: need.origin },
      empty
    };
  });
  return {
    experts,
    total_tokens: experts.reduce((total, entry) => total + entry.tokens, 0),
    warnings: [...input.warnings ?? []]
  };
}

// src/spec-pipeline/experts/merge.ts
init_esm_shims();
function mergeExpertNotes(notes, voiceWarnings = []) {
  const byTarget = /* @__PURE__ */ new Map();
  const order = [];
  for (const note of notes) {
    for (const finding of note.findings) {
      const key = normalize(finding.target);
      if (!byTarget.has(key)) {
        byTarget.set(key, []);
        order.push(key);
      }
      byTarget.get(key).push({ role: note.role, finding });
    }
  }
  const findings = [];
  const conflicts = [];
  for (const key of order) {
    const group = collapseSameRole(byTarget.get(key));
    const distinctClaims = [...new Set(group.map((entry) => normalize(entry.finding.claim)))];
    if (distinctClaims.length <= 1) {
      findings.push(group[0].finding);
      continue;
    }
    conflicts.push({
      target: group[0].finding.target,
      roles: group.map((entry) => entry.role),
      claims: group.map((entry) => entry.finding.claim),
      finding_ids: group.map((entry) => entry.finding.id ?? ""),
      ...group.some((entry) => entry.finding.severity !== void 0) ? { severities: group.map((entry) => entry.finding.severity ?? null) } : {}
    });
  }
  const qa = notes.find((note) => note.verdicts !== void 0)?.verdicts;
  return {
    findings,
    conflicts,
    ...voiceWarnings.length > 0 ? { voice_warnings: [...voiceWarnings] } : {},
    ...qa === void 0 ? {} : { qa_verdicts: qa }
  };
}
function collapseSameRole(group) {
  const byRole = /* @__PURE__ */ new Map();
  for (const entry of group) {
    const list = byRole.get(entry.role);
    if (list) list.push(entry);
    else byRole.set(entry.role, [entry]);
  }
  return [...byRole.values()].map((entries) => {
    if (entries.length === 1) return entries[0];
    let kept = entries[0];
    for (const entry of entries.slice(1)) {
      if (entry.finding.claim.trim().length > kept.finding.claim.trim().length) kept = entry;
    }
    const mergedFrom = entries.flatMap((entry) => [entry.finding.id, ...entry.finding.merged_from ?? []]).filter((id) => typeof id === "string" && id.length > 0);
    const findings = entries.map((entry) => entry.finding);
    const severity = strongestSeverity(findings);
    const kind = mergedKind(findings);
    return {
      role: kept.role,
      finding: {
        ...kept.finding,
        ...severity ? { severity } : {},
        ...kind ? { kind } : {},
        merged_from: [...new Set(mergedFrom)]
      }
    };
  });
}
var SEVERITY_RANK = { must: 3, should: 2, could: 1 };
function strongestSeverity(findings) {
  let best;
  for (const finding of findings) {
    if (finding.severity === void 0) continue;
    if (best === void 0 || SEVERITY_RANK[finding.severity] > SEVERITY_RANK[best]) {
      best = finding.severity;
    }
  }
  return best;
}
function mergedKind(findings) {
  if (findings.some((finding) => finding.kind === "invariant")) return "invariant";
  return findings.find((finding) => finding.kind !== void 0)?.kind;
}
function normalize(text) {
  return text.trim().toLowerCase().replace(/\s+/g, " ");
}

// src/spec-pipeline/experts/need.ts
init_esm_shims();
function seatStandingExperts(detectorNeeds, standing) {
  const named = new Set(detectorNeeds.map((need) => need.role));
  const seated = detectorNeeds.map((need) => ({ ...need, origin: "detector" }));
  for (const role of standing) {
    if (named.has(role)) continue;
    named.add(role);
    seated.push({ role, reason: "standing expert (always at the table)", origin: "standing" });
  }
  return seated;
}
var SENSITIVE_PATH_EXPERT = "security-auditor";
var SKILL_PATH_EXPERT = "skill-orchestrator";
var AGENT_PATH_EXPERT = "agent-orchestrator";
function pathTriggeredSeats(input) {
  const agentPath = input.agentPath ?? null;
  return [
    {
      role: SENSITIVE_PATH_EXPERT,
      path: input.standingSits ? null : input.sensitivePath,
      reason: `a planned path is sensitive: ${input.sensitivePath}`
    },
    {
      role: SKILL_PATH_EXPERT,
      path: input.skillPath,
      reason: `${input.skillPathEdited ? "an edited" : "a planned"} file is a skill: ${input.skillPath}`
    },
    {
      role: AGENT_PATH_EXPERT,
      path: agentPath,
      reason: `${input.agentPathEdited ? "an edited" : "a planned"} file is an agent: ${agentPath}`
    }
  ];
}
function seatExpertsForLane(input) {
  const seated = seatStandingExperts(input.detectorNeeds, input.standingSits ? input.standing : []);
  for (const seat of input.pathSeats) {
    if (seat.path === null || seated.some((need) => need.role === seat.role)) continue;
    seated.push({ role: seat.role, reason: seat.reason, origin: "detector" });
  }
  return seated;
}
function fail(error) {
  return { ok: false, error };
}
function validateExpertNeed(raw) {
  const parsed = typeof raw === "string" ? parseJson2(raw) : raw;
  if (parsed === void 0) {
    return fail("expert-need artifact is not valid JSON");
  }
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
    return fail("expert-need artifact must be an object with an experts[] array");
  }
  const experts = parsed.experts;
  if (!Array.isArray(experts)) {
    return fail("expert-need artifact needs an experts[] array");
  }
  const seen = /* @__PURE__ */ new Set();
  const normalized = [];
  for (const [index, entry] of experts.entries()) {
    if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
      return fail(`experts[${index}] must be an object with role and reason`);
    }
    const { role, reason, origin } = entry;
    if (role === "chief-architect") {
      return fail('"chief-architect" is never picked: it runs automatically when any expert fires');
    }
    if (typeof role !== "string" || !isExpertRole(role)) {
      return fail(
        `experts[${index}].role "${String(role)}" is not an expert in the roster \u2014 the detector may not invent an expert (AC-8)`
      );
    }
    if (typeof reason !== "string" || reason.trim().length === 0) {
      return fail(`experts[${index}] ("${role}") needs a non-empty reason it fired (FR-7)`);
    }
    if (seen.has(role)) {
      return fail(`experts names "${role}" twice \u2014 one decision per expert`);
    }
    seen.add(role);
    normalized.push({
      role,
      reason: reason.trim(),
      origin: origin === "standing" ? "standing" : "detector"
    });
  }
  return { ok: true, artifact: { experts: normalized } };
}
function parseJson2(raw) {
  try {
    return JSON.parse(raw);
  } catch {
    return void 0;
  }
}

// src/spec-pipeline/experts/notes.ts
init_esm_shims();
import { existsSync as existsSync7 } from "fs";
import { join as join11 } from "path";

// src/code-knowledge/query.ts
init_esm_shims();
var TOP_CALLERS_LIMIT = 10;
function distinctSorted(values) {
  return [...new Set(values)].sort();
}
function symbolCard(index, symbol) {
  const callers = index.reference_edges.filter((edge) => edge.to === symbol.file && edge.symbol === symbol.name).map((edge) => edge.from);
  return {
    kind: "symbol",
    name: symbol.name,
    file: symbol.file,
    line: symbol.line,
    signature: symbol.signature,
    module_slug: symbol.module_slug,
    extraction_tier: symbol.extraction_tier,
    caller_count: symbol.caller_count,
    orphan: symbol.orphan,
    top_callers: distinctSorted(callers).slice(0, TOP_CALLERS_LIMIT)
  };
}
function fileCard(index, file) {
  const importers = index.import_edges.filter((edge) => edge.to === file.path).map((edge) => edge.from);
  const symbols = index.symbols.filter((symbol) => symbol.file === file.path).map((symbol) => ({ name: symbol.name, caller_count: symbol.caller_count }));
  return {
    kind: "file",
    path: file.path,
    caller_count: file.caller_count,
    orphan: file.orphan,
    entry_point: file.entry_point,
    importers: distinctSorted(importers).slice(0, TOP_CALLERS_LIMIT),
    symbols
  };
}
function queryCodeKnowledge(index, term) {
  const symbolMatches = index.symbols.filter((symbol) => symbol.name === term).map((symbol) => symbolCard(index, symbol));
  if (symbolMatches.length > 0) {
    return { term, matches: symbolMatches };
  }
  const fileMatch = index.files.find((file) => file.path === term);
  if (fileMatch) {
    return { term, matches: [fileCard(index, fileMatch)] };
  }
  return { term, matches: [] };
}

// src/spec-pipeline/plain-language.ts
init_esm_shims();
var JARGON_TERMS = /* @__PURE__ */ new Set([
  "endpoint",
  "backoff",
  "idempotent",
  "idempotency",
  "webhook",
  "mutex",
  "semaphore",
  "cron",
  "regex",
  "middleware",
  "polling",
  "debounce",
  "throttle",
  "websocket",
  "oauth",
  "jwt",
  "csrf",
  "cors",
  "orm",
  "sdk",
  "api",
  "sql",
  "nosql",
  "cache",
  "enum",
  "boolean",
  "null",
  "async",
  "thread",
  "kubernetes",
  "docker",
  "redis",
  "kafka"
]);
var JARGON_PHRASES = ["circuit breaker", "exponential backoff", "fail fast", "race condition"];
var HTTP_VERBS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
function looksTechnical(token) {
  if (/[a-z][A-Z]/.test(token)) return true;
  if (/_/.test(token)) return true;
  if (/[/\\]/.test(token)) return true;
  if (/\.[A-Za-z]{1,4}\b/.test(token)) return true;
  if (/\w\(/.test(token)) return true;
  if (/\d/.test(token) && /[A-Za-z]/.test(token)) return true;
  if (HTTP_VERBS.has(token)) return true;
  if (/^[1-5]\d\d$/.test(token)) return true;
  if (JARGON_TERMS.has(token.toLowerCase())) return true;
  return false;
}
function wordBag(text) {
  const bag = /* @__PURE__ */ new Set();
  for (const w of text.toLowerCase().split(/[^a-z0-9_]+/)) {
    if (w.length > 0) bag.add(w);
  }
  return bag;
}
function checkPlainLanguage(question, sources) {
  const haystack = [sources.prompt, ...sources.terms, ...sources.vocabulary ?? []].join(" ");
  const allowed = wordBag(haystack);
  const allowedLower = haystack.toLowerCase();
  const flagged = /* @__PURE__ */ new Set();
  const surfaces = [question.business_text, ...question.options];
  const combined = surfaces.join(" ");
  const combinedLower = combined.toLowerCase();
  for (const phrase of JARGON_PHRASES) {
    if (combinedLower.includes(phrase) && !allowedLower.includes(phrase)) {
      flagged.add(phrase);
    }
  }
  for (const surface of surfaces) {
    for (const rawToken of surface.split(/\s+/)) {
      const token = rawToken.replace(/^[^A-Za-z0-9_/\\.()]+|[^A-Za-z0-9_/\\.()]+$/g, "");
      if (token.length === 0) continue;
      if (!looksTechnical(token)) continue;
      if (allowed.has(token.toLowerCase())) continue;
      flagged.add(token);
    }
  }
  const flaggedList = [...flagged];
  return { ok: flaggedList.length === 0, flagged: flaggedList };
}

// src/spec-pipeline/experts/notes.ts
var FINDING_KINDS = [
  "requirement",
  "invariant",
  "acceptance",
  "risk",
  "non-goal"
];
var FINDING_SEVERITIES = ["must", "should", "could"];
function normalizeTarget(text) {
  return text.trim().toLowerCase().replace(/\s+/g, " ");
}
function closestWord(target, entries) {
  for (const entry of entries) {
    const term = normalizeTarget(entry.term);
    if (term.length < 3) continue;
    if (target.includes(term) || term.includes(target)) return entry;
  }
  for (const entry of entries) {
    const shared = normalizeTarget(entry.term).split(/[^a-z0-9]+/).filter((token) => token.length >= 3);
    if (shared.some((token) => target.includes(token))) return entry;
  }
  return null;
}
function checkFindingVoice(finding, sources) {
  const target = normalizeTarget(finding.target);
  if (target.startsWith("new ") || target === "this request") return { ok: true };
  const inVocabulary = sources.vocabulary.some((entry) => normalizeTarget(entry.term) === target);
  if (inVocabulary) return { ok: true };
  if (normalizeTarget(sources.requestText).includes(target)) return { ok: true };
  const stripped = finding.target.replace(/`/g, "").trim();
  if (stripped.length > 0 && existsSync7(join11(sources.projectRoot, stripped))) return { ok: true };
  if (sources.indexPresent) {
    const index = readCodeKnowledgeIndex(sources.projectRoot);
    if (index && queryCodeKnowledge(index, stripped).matches.length > 0) return { ok: true };
  }
  const business = closestWord(
    target,
    sources.vocabulary.filter((entry) => entry.kind !== "technical")
  );
  const technical = closestWord(
    target,
    sources.vocabulary.filter((entry) => entry.kind === "technical")
  );
  const parts = [];
  if (business) parts.push(`the docs say "${business.term}" (${business.kind})`);
  if (technical) parts.push(`the schema page names "${technical.term}"`);
  const closeness = parts.length > 0 ? parts.join(" and ") : "no close match";
  return {
    ok: false,
    hint: `target "${finding.target}" is not a name this project uses; ${closeness}`
  };
}
function fail2(error) {
  return { ok: false, error };
}
var QA_VERDICT_STATUSES = ["pass", "concerns", "fail"];
var QA_VERDICT_KEYS = ["spec_compliance", "quality"];
function checkVerdicts(role, raw, path, required) {
  if (role === QA_ROLE && raw === void 0 && !required) return null;
  if (role !== QA_ROLE) {
    return raw === void 0 ? null : `${path} ("${role}") may not carry verdicts; only ${QA_ROLE} gives them`;
  }
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
    return `${path} ("${QA_ROLE}") needs verdicts with spec_compliance and quality`;
  }
  const out = {};
  for (const key of QA_VERDICT_KEYS) {
    const verdict = raw[key];
    if (typeof verdict !== "object" || verdict === null || Array.isArray(verdict)) {
      return `${path} ("${QA_ROLE}") needs a ${key} verdict`;
    }
    const { status, reason } = verdict;
    if (!QA_VERDICT_STATUSES.includes(status)) {
      return `${path}.verdicts.${key} has an unknown status "${String(status)}" (want ${QA_VERDICT_STATUSES.join(" | ")})`;
    }
    if (typeof reason !== "string" || reason.trim().length === 0) {
      return `${path}.verdicts.${key} needs a non-empty reason`;
    }
    out[key] = { status, reason };
  }
  return out;
}
function validateExpertNotes(raw, sources, voiceSources, options = {}) {
  const parsed = typeof raw === "string" ? parseJson3(raw) : raw;
  if (parsed === void 0) return fail2("expert-notes artifact is not valid JSON");
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
    return fail2("expert-notes artifact must be an object with a notes[] array");
  }
  const obj = parsed;
  if (!Array.isArray(obj.notes)) return fail2("expert-notes artifact needs a notes[] array");
  const notes = [];
  const idCounters = /* @__PURE__ */ new Map();
  for (const [index, entry] of obj.notes.entries()) {
    if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
      return fail2(`notes[${index}] must be an object with role and findings`);
    }
    const { role, findings, questions, verdicts } = entry;
    if (typeof role !== "string" || !isExpertRole(role)) {
      return fail2(`notes[${index}].role "${String(role)}" is not an expert in the roster`);
    }
    const verdictCheck = checkVerdicts(
      role,
      verdicts,
      `notes[${index}]`,
      options.requireQaVerdicts !== false
    );
    if (typeof verdictCheck === "string") return fail2(verdictCheck);
    if (!Array.isArray(findings)) {
      return fail2(`notes[${index}] ("${role}") needs a findings[] array`);
    }
    const parsedFindings = [];
    for (const [fi, finding] of findings.entries()) {
      if (typeof finding !== "object" || finding === null || Array.isArray(finding)) {
        return fail2(`notes[${index}].findings[${fi}] must be an object with target and claim`);
      }
      const { target, claim, kind, severity, evidence } = finding;
      if (typeof target !== "string" || target.trim().length === 0) {
        return fail2(`notes[${index}].findings[${fi}] needs a non-empty target`);
      }
      if (typeof claim !== "string" || claim.trim().length === 0) {
        return fail2(`notes[${index}].findings[${fi}] needs a non-empty claim`);
      }
      if (kind !== void 0 && !FINDING_KINDS.includes(kind)) {
        return fail2(
          `notes[${index}].findings[${fi}] has an unknown kind "${String(kind)}" (want ${FINDING_KINDS.join(" | ")})`
        );
      }
      if (severity !== void 0 && !FINDING_SEVERITIES.includes(severity)) {
        return fail2(
          `notes[${index}].findings[${fi}] has an unknown severity "${String(severity)}" (want ${FINDING_SEVERITIES.join(" | ")})`
        );
      }
      if (evidence !== void 0 && typeof evidence !== "string") {
        return fail2(`notes[${index}].findings[${fi}] evidence must be a string when present`);
      }
      const seq = (idCounters.get(role) ?? 0) + 1;
      idCounters.set(role, seq);
      parsedFindings.push({
        id: `EX-${role}-${seq}`,
        target,
        claim,
        kind: kind ?? "requirement",
        severity: severity ?? "should",
        ...typeof evidence === "string" ? { evidence } : {}
      });
    }
    const parsedQuestions = [];
    if (questions !== void 0) {
      if (!Array.isArray(questions)) {
        return fail2(`notes[${index}] ("${role}") questions must be an array when present`);
      }
      for (const [qi, question] of questions.entries()) {
        const validated = validateQuestion(question, `notes[${index}].questions[${qi}]`, sources);
        if (!validated.ok) return fail2(validated.error);
        parsedQuestions.push(validated.question);
      }
    }
    notes.push({
      role,
      findings: parsedFindings,
      ...parsedQuestions.length > 0 ? { questions: parsedQuestions } : {},
      ...verdictCheck === null ? {} : { verdicts: verdictCheck }
    });
  }
  const tokens = {};
  if (obj.tokens !== void 0) {
    if (typeof obj.tokens !== "object" || obj.tokens === null || Array.isArray(obj.tokens)) {
      return fail2('expert-notes "tokens" must be an object of role -> number');
    }
    for (const [role, value] of Object.entries(obj.tokens)) {
      if (!isExpertRole(role)) return fail2(`tokens names "${role}", which is not an expert role`);
      if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
        return fail2(`tokens["${role}"] must be a non-negative number`);
      }
      tokens[role] = value;
    }
  }
  let voice_warnings;
  if (voiceSources) {
    const warnings = [];
    for (const note of notes) {
      for (const finding of note.findings) {
        const result = checkFindingVoice(finding, voiceSources);
        if (!result.ok) {
          warnings.push({ id: finding.id, target: finding.target, hint: result.hint });
        }
      }
    }
    voice_warnings = warnings;
  }
  return {
    ok: true,
    artifact: { notes, tokens, ...voice_warnings === void 0 ? {} : { voice_warnings } }
  };
}
function validateQuestion(raw, path, sources) {
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
    return { ok: false, error: `${path} must be a question object` };
  }
  const q = raw;
  if (typeof q.business_text !== "string" || q.business_text.trim().length === 0) {
    return { ok: false, error: `${path} needs a non-empty business_text` };
  }
  if (typeof q.why_it_matters !== "string" || q.why_it_matters.trim().length === 0) {
    return { ok: false, error: `${path} needs a non-empty why_it_matters` };
  }
  if (!Array.isArray(q.options) || q.options.length === 0 || !q.options.every((o) => typeof o === "string" && o.trim().length > 0)) {
    return { ok: false, error: `${path} needs a non-empty options[] of strings` };
  }
  if (q.grounded_in !== null && typeof q.grounded_in !== "string") {
    return { ok: false, error: `${path} grounded_in must be a string or null` };
  }
  const question = {
    business_text: q.business_text,
    why_it_matters: q.why_it_matters,
    options: q.options,
    grounded_in: q.grounded_in ?? null,
    ...typeof q.technical_note === "string" ? { technical_note: q.technical_note } : {}
  };
  if (sources) {
    const plain = checkPlainLanguage(question, sources);
    if (!plain.ok) {
      return {
        ok: false,
        error: `${path} is not plain language \u2014 flagged: ${plain.flagged.join(", ")}`
      };
    }
  }
  return { ok: true, question };
}
function parseJson3(raw) {
  try {
    return JSON.parse(raw);
  } catch {
    return void 0;
  }
}

// src/spec-pipeline/experts/slice.ts
init_esm_shims();
function ceilingWarning(sum, ceiling, count) {
  return `expert slices need ${sum} tokens but the run ceiling is ${ceiling}; scaled ${count} expert${count === 1 ? "" : "s"} to fit \u2014 none dropped`;
}
function planExpertSlices(roles, ceiling) {
  if (roles.length === 0) {
    return { slices: [], warnings: [] };
  }
  const budgets = roles.map((role) => ({ role, budget: expertBudget(role) }));
  const sum = budgets.reduce((total, entry) => total + entry.budget, 0);
  if (sum <= ceiling) {
    return {
      slices: budgets.map(({ role, budget }) => ({
        role,
        budget,
        granted: budget,
        clamped: false
      })),
      warnings: []
    };
  }
  const slices = budgets.map(({ role, budget }) => {
    const granted = Math.max(1, Math.floor(budget / sum * ceiling));
    return { role, budget, granted, clamped: granted < budget };
  });
  return { slices, warnings: [ceilingWarning(sum, ceiling, roles.length)] };
}

// src/spec-pipeline/experts/assemble.ts
function assembleExpertRun(projectRoot, dirName, ceiling) {
  const rawNeed = readExpertNeed(projectRoot, dirName);
  if (rawNeed === null) return null;
  const need = validateExpertNeed(rawNeed);
  if (!need.ok || !need.artifact) return null;
  const roles = need.artifact.experts.map((expert) => expert.role);
  const slicePlan = planExpertSlices(roles, ceiling);
  const rawNotes = readExpertNotes(projectRoot, dirName);
  const notes = rawNotes === null ? { ok: true, artifact: { notes: [], tokens: {} } } : validateExpertNotes(rawNotes, void 0, void 0, { requireQaVerdicts: false });
  const notesArtifact = notes.ok && notes.artifact ? notes.artifact : { notes: [], tokens: {} };
  const merged = mergeExpertNotes(notesArtifact.notes);
  const trace = readTrace(projectRoot, dirName);
  const tracedFindingIds = new Set((trace?.entries ?? []).map((entry) => entry.source));
  const accounting = buildExpertAccounting({
    needs: need.artifact.experts,
    notes: notesArtifact.notes,
    tokens: notesArtifact.tokens,
    tracedFindingIds,
    warnings: slicePlan.warnings
  });
  return { accounting, conflicts: merged.conflicts };
}

// src/spec-pipeline/metrics.ts
function numberField(value) {
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
}
function buildRunMetrics(projectRoot, dirName, config, a5Live, a5Verdict) {
  const grounding = readStagedJson(
    projectRoot,
    dirName,
    "grounding"
  );
  const label = readLabel(projectRoot, dirName);
  const questionsArtifact = readQuestions(projectRoot, dirName);
  const task = readStagedJson(projectRoot, dirName, "task");
  const trace = readStagedJson(projectRoot, dirName, "trace");
  const spec = readSpec(projectRoot, dirName);
  const questions = {
    asked: questionsArtifact?.asked ?? 0,
    answered: questionsArtifact?.answered ?? 0,
    auto_answered: questionsArtifact?.auto_answered?.length ?? 0,
    deferred: questionsArtifact?.deferred ?? 0
  };
  const tokensByStep = {};
  const setStep = (step, value) => {
    if (value !== void 0) tokensByStep[step] = value;
  };
  const questionsRow = readSpecStepRows(projectRoot, dirName).filter((row) => row.step === "questions" && row.outcome === "complete").at(-1);
  setStep("questions", numberField(questionsRow?.tokens));
  setStep("task", numberField(task?.tokens));
  setStep("craft", numberField(trace?.tokens));
  const tiersByStep = {};
  const need = readNeed(projectRoot, dirName);
  const expertCount = need?.experts.length ?? 0;
  const ceilingWarnings = need ? planExpertSlices(
    need.experts.map((expert) => expert.role),
    config.token_ceiling
  ).warnings : [];
  if (need) {
    const notes = readExpertNotes(projectRoot, dirName);
    for (const [role, value] of Object.entries(notes?.tokens ?? {})) {
      const n = numberField(value);
      if (n !== void 0) tokensByStep[role] = n;
    }
    const synthesis = readExpertSynthesis(projectRoot, dirName);
    if (synthesis) {
      setStep("experts", numberField(synthesis.tokens));
      tiersByStep.experts = "reasoning";
    }
  }
  const freezeChecksFired = spec ? evaluateSpecFreeze(spec.spec).blockers : [];
  return {
    grounding_sparse: grounding?.sparse ?? false,
    grounding_path: grounding?.path === "rag" ? "rag" : "docs-fallback",
    label: label?.label ?? "clear",
    signal_count: label?.signals.length ?? 0,
    questions,
    expert_count: expertCount,
    spec_words: spec ? spec.words : 0,
    tokens_by_step: tokensByStep,
    tiers_by_step: tiersByStep,
    ceiling_warnings: ceilingWarnings,
    a5_live: a5Live,
    a5_verdict: a5Verdict,
    freeze_checks_fired: freezeChecksFired
  };
}
function readNeed(projectRoot, dirName) {
  const raw = readExpertNeed(projectRoot, dirName);
  if (raw === null) return null;
  const validated = validateExpertNeed(raw);
  return validated.ok && validated.artifact ? validated.artifact : null;
}
function readSpec(projectRoot, dirName) {
  const markdown = readStagedText(projectRoot, dirName, "craft");
  if (markdown === null) return null;
  const words = markdown.split(/\s+/).filter((token) => token.length > 0).length;
  try {
    return {
      spec: buildFeatureSpec({ spec_id: "metrics", spec_file: "spec.md", spec_markdown: markdown }),
      words
    };
  } catch {
    return null;
  }
}
function recordSpecCorrection(projectRoot, dirName, correction) {
  appendSpecCorrectionRow(
    projectRoot,
    dirName,
    { spec_id: correction.spec_id, changed_sections: correction.changed_sections },
    { now: () => new Date(correction.at) }
  );
}
function readSpecCorrections(projectRoot, dirName) {
  return readSpecCorrectionRows(projectRoot, dirName).map((row) => ({
    spec_id: String(row.spec_id),
    changed_sections: row.changed_sections,
    at: String(row.recorded_at)
  }));
}
function pipelineRun(projectRoot, dirName) {
  if (readStagedJson(projectRoot, dirName, "finish")?.provenance) {
    return { legacy: null };
  }
  const frozen = readFrozenSpecificationBody(projectRoot, dirName);
  if (frozen?.pipeline?.produced === true) {
    return { legacy: null };
  }
  const legacy = frozen?.provenance;
  return legacy?.pipeline_produced === true ? { legacy } : null;
}
function stepTokens(projectRoot, dirName) {
  const tokens = /* @__PURE__ */ new Map();
  for (const row of readSpecStepRows(projectRoot, dirName)) {
    const n = numberField(row.tokens);
    if (row.outcome === "redone") tokens.delete(String(row.step));
    else if (row.outcome === "complete" && n !== void 0) tokens.set(String(row.step), n);
  }
  return tokens;
}
function aggregateSpecPipelineMetrics(projectRoot, dirNames) {
  const aggregate = {
    runs: 0,
    experts_fired: {},
    changed_spec_rate: {},
    tokens_by_role: {},
    tokens_by_step: {},
    conflicts: 0,
    auto_resolved: 0,
    corrections_by_section: {},
    label_distribution: {},
    grounding_sparse_runs: 0,
    tier_by_role: {},
    empty_by_role: {},
    voice_warnings_by_role: {},
    renames: 0
  };
  for (const dirName of dirNames) {
    for (const correction of readSpecCorrections(projectRoot, dirName)) {
      for (const section of correction.changed_sections) {
        aggregate.corrections_by_section[section] = (aggregate.corrections_by_section[section] ?? 0) + 1;
      }
    }
    const run = pipelineRun(projectRoot, dirName);
    if (run === null) continue;
    aggregate.runs += 1;
    const experts = assembleExpertRun(projectRoot, dirName, Number.MAX_SAFE_INTEGER);
    const fired = (role) => {
      aggregate.experts_fired[role] = (aggregate.experts_fired[role] ?? 0) + 1;
      const rate = aggregate.changed_spec_rate[role] ??= { fired: 0, changed: 0 };
      rate.fired += 1;
      return rate;
    };
    if (experts) {
      for (const expert of experts.accounting.experts) {
        const rate = fired(expert.role);
        if (expert.changed_spec) rate.changed += 1;
        aggregate.tokens_by_role[expert.role] = (aggregate.tokens_by_role[expert.role] ?? 0) + expert.tokens;
        if (expert.origin === "standing") aggregate.tier_by_role[expert.role] = "standing";
        else if (aggregate.tier_by_role[expert.role] === void 0)
          aggregate.tier_by_role[expert.role] = "on-call";
        const emptyRate = aggregate.empty_by_role[expert.role] ??= { fired: 0, empty: 0 };
        emptyRate.fired += 1;
        if (expert.empty) emptyRate.empty += 1;
      }
      aggregate.conflicts += experts.conflicts.length;
      for (const warning of readExperts(projectRoot, dirName)?.voice_warnings ?? []) {
        const role = roleOfFindingId(warning.id);
        if (role) {
          aggregate.voice_warnings_by_role[role] = (aggregate.voice_warnings_by_role[role] ?? 0) + 1;
        }
      }
    } else if (run.legacy?.experts) {
      for (const role of run.legacy.experts.roles) fired(role);
      aggregate.conflicts += run.legacy.experts.conflicts;
    }
    const label = readLabel(projectRoot, dirName)?.label ?? run.legacy?.label;
    if (label !== void 0) {
      aggregate.label_distribution[label] = (aggregate.label_distribution[label] ?? 0) + 1;
    }
    const sparse = readGrounding(projectRoot, dirName)?.sparse ?? run.legacy?.grounding?.sparse;
    if (sparse === true) aggregate.grounding_sparse_runs += 1;
    const synthesis = readExpertSynthesis(projectRoot, dirName);
    const tokens = stepTokens(projectRoot, dirName);
    const synthesisTokens = numberField(synthesis?.tokens);
    if (!tokens.has("experts") && synthesisTokens !== void 0) {
      tokens.set("experts", synthesisTokens);
    }
    for (const [step, value] of tokens) {
      aggregate.tokens_by_step[step] = (aggregate.tokens_by_step[step] ?? 0) + value;
    }
    aggregate.auto_resolved += synthesis?.auto_resolved?.length ?? 0;
    aggregate.renames += synthesis?.renames?.length ?? 0;
  }
  return aggregate;
}
function roleOfFindingId(id) {
  const match = /^EX-(.+)-\d+$/.exec(id);
  return match ? match[1] : null;
}
function listRunDirs(projectRoot) {
  return listFeatureDirs(projectRoot);
}

// src/spec/spec-decisions.ts
init_esm_shims();
var DEFAULT_REQUESTED_BY = "spec-stage";
function buildSpecChangePacket(input) {
  return buildPacket("spec.change", input, {
    question: "The goal changed mid-build. Update the frozen spec and re-freeze, or keep it as-is?",
    options: [
      {
        option_key: "update-and-refreeze",
        label: "Take the new goal",
        one_line_preview: `If you pick this, we will update ${input.spec_file} to the new goal and re-freeze it.`,
        trade_off: "You give up: the first frozen goal, so the new one becomes the bar."
      },
      {
        option_key: "keep-current-spec",
        label: "Keep the current spec",
        one_line_preview: "If you pick this, we will keep building to the spec already frozen.",
        trade_off: "You give up: capturing the new goal, so the change waits."
      }
    ],
    recommendation: "update-and-refreeze",
    recommendation_reason: "A real goal change must update the spec and re-freeze before work continues."
  });
}
function buildPacket(category, input, shape) {
  const ttlDays = DECISION_CATEGORY_DEFAULTS[category].ttl_days;
  const ttlUntil = new Date(
    new Date(input.created_at).getTime() + ttlDays * 24 * 60 * 60 * 1e3
  ).toISOString();
  const fingerprint2 = `sha256:${sha256Hex(`${category}:${input.spec_id}:${input.detail}`)}`;
  const packet = {
    decision_id: input.decision_id,
    fingerprint: fingerprint2,
    category,
    question: shape.question,
    context: `Spec ${input.spec_id} (${input.spec_file}). ${input.detail}`,
    options: shape.options.map((option) => ({ ...option, evidence: {} })),
    confidence: 0.5,
    requested_by: input.requested_by ?? DEFAULT_REQUESTED_BY,
    task_session_id: input.task_session_id,
    linked_requirements: [],
    created_at: input.created_at,
    status: "pending",
    ttl_until: ttlUntil,
    invalidation_watch: [input.spec_file]
  };
  if (shape.recommendation !== null) {
    packet.recommendation = shape.recommendation;
    packet.recommendation_reason = shape.recommendation_reason;
  }
  return packet;
}

// src/spec/spec-change-guard.ts
function diffSpecSections(frozen, current) {
  const changed = [];
  const differs = (a, b) => JSON.stringify(a) !== JSON.stringify(b);
  if (differs(frozen.behaviour, current.behaviour)) changed.push("behaviour");
  if (differs(frozen.acceptance_criteria, current.acceptance_criteria)) {
    changed.push("acceptance_criteria");
  }
  if (differs(frozen.invariants, current.invariants)) changed.push("invariants");
  if (differs(frozen.non_goals ?? [], current.non_goals ?? [])) changed.push("non_goals");
  return changed;
}
function pipelineProduced(spec) {
  return spec.pipeline?.produced === true || spec.provenance?.pipeline_produced === true;
}
var NO_OP3 = { ran: false, blocking: false, summary: "" };
function watchedSpec(dirName, spec) {
  return spec.spec_file === FEATURE_BUNDLE_FILES.specMd ? { spec, dirName, source: featureFilePath(dirName, "specMd"), bundled: true } : { spec, dirName, source: spec.spec_file, bundled: false };
}
var STALE_DETAIL = "The frozen spec source changed since it was frozen \u2014 the goal may have moved. Confirm the new goal before more is built on the old one.";
function mintedSummary2(specId) {
  return `**\u25B8 paqad** \xB7 the frozen spec ${specId} changed mid-build \u2014 that's a goal call that's yours
> I paused and wrote it up so you can update-and-refreeze or hold the line \u2014 answer it, then I'll continue.`;
}
function runSpecChangeGuard(input) {
  if (input.seam !== void 0 && input.seam !== "pre-mutation") return NO_OP3;
  if (!input.sessionId) return NO_OP3;
  const specs = input.frozenSpecs ? input.frozenSpecs.map((spec) => ({
    spec,
    dirName: null,
    source: spec.spec_file,
    bundled: false
  })) : readAllFeatureSpecificationEntries(input.projectRoot).map(
    (entry) => watchedSpec(entry.dirName, entry.spec)
  );
  if (specs.length === 0) return NO_OP3;
  const store = input.store ?? new DecisionStore(input.projectRoot);
  store.initialize();
  if (store.listPendingDecisionIds().length > 0) return NO_OP3;
  const readMarkdown = input.readMarkdown ?? ((specFile) => readFileSync10(join12(input.projectRoot, specFile), "utf8"));
  const now = input.now?.() ?? /* @__PURE__ */ new Date();
  for (const { spec, dirName, source, bundled } of specs) {
    let currentMarkdown;
    try {
      const text = readMarkdown(source);
      currentMarkdown = bundled ? splitFrontMatter(text).body : text;
    } catch {
      continue;
    }
    if (!isFrozenSpecStale(spec, sha256Hex(currentMarkdown))) continue;
    const changedSections = diffSpecSections(
      spec,
      buildFeatureSpec({
        spec_id: spec.spec_id,
        spec_file: spec.spec_file,
        spec_markdown: currentMarkdown
      })
    );
    if (dirName !== null && pipelineProduced(spec)) {
      recordSpecCorrection(input.projectRoot, dirName, {
        spec_id: spec.spec_id,
        changed_sections: changedSections,
        at: now.toISOString()
      });
    }
    const packet = buildSpecChangePacket({
      decision_id: store.nextDecisionId(),
      spec_id: spec.spec_id,
      spec_file: source,
      detail: changedSections.length > 0 ? `${STALE_DETAIL} Changed sections: ${changedSections.join(", ")}.` : STALE_DETAIL,
      task_session_id: input.sessionId,
      created_at: now.toISOString()
    });
    if (store.findReusableDecision(packet)) continue;
    try {
      store.writePending(packet);
      return { ran: true, blocking: false, summary: mintedSummary2(spec.spec_id) };
    } catch {
      return NO_OP3;
    }
  }
  return NO_OP3;
}

// src/rule-scripts/edit-result.ts
init_esm_shims();
import {
  closeSync,
  constants,
  lstatSync,
  mkdirSync as mkdirSync7,
  mkdtempSync,
  openSync,
  readdirSync as readdirSync5,
  readFileSync as readFileSync11,
  realpathSync as realpathSync2,
  rmSync as rmSync3,
  symlinkSync,
  writeFileSync as writeFileSync7
} from "fs";
import { tmpdir } from "os";
import { join as join13, sep } from "path";

// src/rule-scripts/edited-content.ts
init_esm_shims();
var EDIT_KEYS = /* @__PURE__ */ new Set(["file_path", "old_string", "new_string", "replace_all"]);
var WRITE_KEYS = /* @__PURE__ */ new Set(["file_path", "content"]);
var PATCH_BEGIN = "*** Begin Patch";
var PATCH_END = "*** End Patch";
var PATCH_UPDATE = "*** Update File: ";
function asObject(input) {
  return typeof input === "object" && input !== null && !Array.isArray(input) ? input : null;
}
function onlyKeys(input, allowed) {
  return Object.keys(input).every((key) => allowed.has(key));
}
function applyEdit(input, current) {
  const { old_string: before, new_string: after, replace_all: replaceAll } = input;
  if (!onlyKeys(input, EDIT_KEYS)) return null;
  if (typeof before !== "string" || typeof after !== "string") return null;
  if (before === "" || before === after) return null;
  if (replaceAll !== void 0 && typeof replaceAll !== "boolean") return null;
  const parts = current.split(before);
  if (parts.length < 2) return null;
  if (parts.length > 2 && replaceAll !== true) return null;
  return parts.join(after);
}
function applyWrite(input) {
  if (!onlyKeys(input, WRITE_KEYS)) return null;
  return typeof input.content === "string" ? input.content : null;
}
function patchText(input) {
  const object = asObject(input);
  const values = object === null ? [input] : Object.values(object);
  const patches = values.filter(
    (value) => typeof value === "string" && value.includes(PATCH_BEGIN)
  );
  return patches.length === 1 ? patches[0] : null;
}
function readChunks(body) {
  const chunks = [];
  let open = null;
  for (const line of body) {
    if (line === "@@") {
      open = { before: [], after: [] };
      chunks.push(open);
      continue;
    }
    const mark = line[0];
    if (mark !== " " && mark !== "-" && mark !== "+") return null;
    if (open === null) {
      open = { before: [], after: [] };
      chunks.push(open);
    }
    const text = line.slice(1);
    if (mark !== "+") open.before.push(text);
    if (mark !== "-") open.after.push(text);
  }
  return chunks.length > 0 && chunks.every((chunk) => chunk.before.length > 0) ? chunks : null;
}
function parsePatch(text) {
  const lines = text.split("\n");
  if (lines.at(-1) === "") lines.pop();
  if (lines[0] !== PATCH_BEGIN || lines.at(-1) !== PATCH_END) return null;
  if (!lines[1]?.startsWith(PATCH_UPDATE)) return null;
  return readChunks(lines.slice(2, -1));
}
function matchesAt(lines, wanted, at) {
  return wanted.every((line, offset) => lines[at + offset] === line);
}
function onlyMatch(lines, wanted) {
  const found = [];
  for (let at = 0; at + wanted.length <= lines.length; at += 1) {
    if (matchesAt(lines, wanted, at)) found.push(at);
  }
  return found.length === 1 ? found[0] : null;
}
function applyPatch(input, current) {
  const text = patchText(input);
  const chunks = text === null ? null : parsePatch(text);
  if (chunks === null) return null;
  const lines = current.split("\n");
  if (lines.at(-1) === "") lines.pop();
  let cursor = 0;
  for (const chunk of chunks) {
    const at = onlyMatch(lines, chunk.before);
    if (at === null || at < cursor) return null;
    lines.splice(at, chunk.before.length, ...chunk.after);
    cursor = at + chunk.after.length;
  }
  return `${lines.join("\n")}
`;
}
function editedContent(toolName, toolInput, current) {
  if (toolName === "apply_patch") return applyPatch(toolInput, current);
  const input = asObject(toolInput);
  if (input === null) return null;
  if (toolName === "Edit") return applyEdit(input, current);
  if (toolName === "Write") return applyWrite(input);
  return null;
}

// src/rule-scripts/edit-result.ts
var RULE_SOURCES_PREFIX = "docs/instructions/rules/";
var MAX_GROWN_RESULT_BYTES = 1024 * 1024;
var REWRITE_NOT_THROUGH_LINK = constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW;
var NOT_A_CANDIDATE = { allowed: false, reason: null };
function refuse(reason) {
  return { allowed: false, reason };
}
function spelledAsOnDisk(projectRoot, rel) {
  let dir = projectRoot;
  return rel.split("/").every((segment) => {
    const found = readdirSync5(dir).includes(segment);
    dir = join13(dir, segment);
    return found;
  });
}
function isRegularFileInProject(projectRoot, rel) {
  const abs = join13(projectRoot, rel);
  return lstatSync(abs).isFile() && realpathSync2(abs) === join13(realpathSync2(projectRoot), rel) && spelledAsOnDisk(projectRoot, rel);
}
function resultSizeAllowed(current, after) {
  const size = Buffer.byteLength(after);
  return size <= MAX_GROWN_RESULT_BYTES || size <= Buffer.byteLength(current);
}
function readEditedTarget(input, rel) {
  if (!isRegularFileInProject(input.projectRoot, rel)) {
    return { reason: "the target is not a plain file inside the project" };
  }
  const current = readFileSync11(join13(input.projectRoot, rel), "utf8");
  const after = editedContent(input.toolName, input.toolInput, current);
  if (after === null) return { reason: "the result of this edit could not be worked out" };
  if (!resultSizeAllowed(current, after)) return { reason: "the result of this edit is too large" };
  return { target: { rel, current, after } };
}
function rerunScripts(projectRoot) {
  const scripts = [];
  for (const rule of loadRuleScriptMap(projectRoot)?.rules ?? []) {
    for (const script of rule.scripts) {
      if (script.kind !== "deterministic" || script.scope !== "changed-files") continue;
      const abs = join13(projectRoot, script.path);
      let source;
      try {
        source = readFileSync11(abs, "utf8");
      } catch {
        return null;
      }
      const { header } = parseScriptHeader(source);
      if (missingBinaries(header?.requires?.binaries).length > 0) return null;
      scripts.push({
        ruleId: rule.id,
        path: script.path,
        abs,
        perFile: header?.per_file === true
      });
    }
  }
  return scripts;
}
function buildOverlay(projectRoot, rel, overlay) {
  const segments = rel.split("/");
  let from = projectRoot;
  let to = overlay;
  segments.forEach((segment, index) => {
    for (const name of readdirSync5(from)) {
      if (name !== segment) symlinkSync(join13(from, name), join13(to, name));
    }
    from = join13(from, segment);
    to = join13(to, segment);
    if (index < segments.length - 1) mkdirSync7(to);
  });
}
function rewriteNotThroughLink(path, content) {
  const file = openSync(path, REWRITE_NOT_THROUGH_LINK);
  try {
    writeFileSync7(file, content, "utf8");
  } finally {
    closeSync(file);
  }
}
async function scanOverlay(overlay, scripts, target, changedFiles, input) {
  const runs = await runPool(
    scripts,
    input.concurrency,
    (script) => executeRuleScriptAsync(
      script.abs,
      { projectRoot: overlay, files: script.perFile ? [target] : [...changedFiles] },
      { signal: input.signal }
    )
  );
  if (runs.some((run) => !run.ok || !run.report)) return null;
  return runs.flatMap(
    (run, index) => (run.report?.findings ?? []).map((finding) => ({
      ...finding,
      script: scripts[index].path,
      rule_id: scripts[index].ruleId
    }))
  );
}
function counts(keys) {
  const tally = /* @__PURE__ */ new Map();
  for (const key of keys) tally.set(key, (tally.get(key) ?? 0) + 1);
  return tally;
}
function sameCounts(left, right) {
  const a = counts(left);
  const b = counts(right);
  return a.size === b.size && [...a].every(([key, count]) => b.get(key) === count);
}
function exactKey(finding) {
  return JSON.stringify([
    finding.script,
    finding.rule_id,
    finding.file,
    finding.line ?? null,
    finding.message,
    finding.severity
  ]);
}
function comparableKey(finding, target, content) {
  const onTarget = finding.file === target.rel;
  const where = onTarget && finding.line !== void 0 ? content.split("\n")[finding.line - 1] ?? "" : finding.line ?? null;
  return JSON.stringify([
    finding.script,
    finding.rule_id,
    finding.file,
    where,
    finding.message,
    finding.severity
  ]);
}
function matchesRealRun(baseline, scripts, target, violations) {
  const byPath = new Map(scripts.map((script) => [script.path, script]));
  const rerun = violations.filter((violation) => {
    const script = byPath.get(violation.script);
    return script !== void 0 && (!script.perFile || violation.file === target);
  });
  return sameCounts(baseline.map(exactKey), rerun.map(exactKey));
}
function compareResults(baseline, after, target) {
  if (after.some((finding) => finding.file === target.rel && finding.line === void 0)) {
    return { reason: "a finding left in the target has no line, so it cannot be told apart" };
  }
  const before = counts(baseline.map((finding) => comparableKey(finding, target, target.current)));
  const result = counts(after.map((finding) => comparableKey(finding, target, target.after)));
  for (const [key, count] of result) {
    if (count > (before.get(key) ?? 0)) return { reason: "this edit adds a finding" };
  }
  const onTarget = (finding) => finding.file === target.rel;
  const removed = baseline.filter(onTarget).length - after.filter(onTarget).length;
  return removed > 0 ? { removed } : { reason: "this edit removes no finding from its target" };
}
function removedFindings(baseline, after, target, violations) {
  const left = counts(after.map((finding) => comparableKey(finding, target, target.after)));
  const removed = /* @__PURE__ */ new Set();
  for (const finding of baseline) {
    const key = comparableKey(finding, target, target.current);
    const remaining = left.get(key) ?? 0;
    if (remaining > 0) left.set(key, remaining - 1);
    else if (finding.file === target.rel) removed.add(exactKey(finding));
  }
  return violations.filter((violation) => removed.has(exactKey(violation)));
}
async function compareOnOverlay(input, target, scripts, changedFiles) {
  const overlay = realpathSync2(mkdtempSync(join13(tmpdir(), "paqad-edit-result-")));
  try {
    buildOverlay(input.projectRoot, target.rel, overlay);
    const overlayTarget = join13(overlay, target.rel);
    writeFileSync7(overlayTarget, target.current, { encoding: "utf8", flag: "wx" });
    const baseline = await scanOverlay(overlay, scripts, target.rel, changedFiles, input);
    if (baseline === null) return refuse("a rule script gave no answer");
    if (!matchesRealRun(baseline, scripts, target.rel, input.violations)) {
      return refuse("the comparison did not reproduce the findings of the real run");
    }
    rewriteNotThroughLink(overlayTarget, target.after);
    const after = await scanOverlay(overlay, scripts, target.rel, changedFiles, input);
    if (after === null) return refuse("a rule script gave no answer");
    const compared = compareResults(baseline, after, target);
    if ("reason" in compared) return refuse(compared.reason);
    return {
      allowed: true,
      target: target.rel,
      removed: removedFindings(baseline, after, target, input.violations),
      remaining: input.violations.length - compared.removed
    };
  } finally {
    rmSync3(overlay, { recursive: true, force: true });
  }
}
async function judgeEditResult(input) {
  const rels = projectRelativeTargets(input.projectRoot, [...input.targets]);
  const rel = rels[0];
  if (input.targets.length !== 1 || rel === void 0) return NOT_A_CANDIDATE;
  if (sep !== "/" || rel.includes("\\")) return NOT_A_CANDIDATE;
  if (!input.violations.some((violation) => violation.file === rel)) return NOT_A_CANDIDATE;
  if (rel.startsWith(RULE_SOURCES_PREFIX)) return refuse("the target is a rule file");
  const read = readEditedTarget(input, rel);
  if ("reason" in read) return refuse(read.reason);
  const scripts = rerunScripts(input.projectRoot);
  if (scripts === null) return refuse("a rule script could not run");
  const evidence = await loadChangeEvidence(input.projectRoot);
  const changedFiles = Array.from(/* @__PURE__ */ new Set([...evidence.files, rel]));
  return compareOnOverlay(input, read.target, scripts, changedFiles);
}

// src/rule-scripts/pending-marker.ts
init_esm_shims();
import { existsSync as existsSync8, rmSync as rmSync4 } from "fs";
import { join as join14 } from "path";
var WITNESS_FILE = ".pending-pre-mutation.json";
function pendingPreMutationPaths(projectRoot, sessionId) {
  const safe = safeSessionId(sessionId);
  return [
    join14(projectRoot, PATHS.RULE_SCRIPTS_PENDING_DIR, `${safe}.json`),
    join14(projectRoot, sessionLedgerDir(STAGE_EVIDENCE_DOC_TYPE, safe), WITNESS_FILE)
  ];
}
function writePendingPreMutation(projectRoot, marker) {
  const body = `${JSON.stringify(marker, null, 2)}
`;
  for (const path of pendingPreMutationPaths(projectRoot, marker.session_id)) {
    writeFileAtomic(path, body);
  }
}
function hasPendingPreMutation(projectRoot, sessionId) {
  return pendingPreMutationPaths(projectRoot, sessionId).some((path) => existsSync8(path));
}
function clearPendingPreMutation(projectRoot, sessionId) {
  for (const path of pendingPreMutationPaths(projectRoot, sessionId)) {
    rmSync4(path, { force: true });
  }
}

// src/hld/gate.ts
init_esm_shims();

// src/hld/applies.ts
init_esm_shims();
function designSignOffApplies(config, lane) {
  return config.enabled && lane !== null && config.lanes.includes(lane);
}
function designSignOffWaits(config, lane) {
  return designSignOffApplies(config, lane) && config.wait_for_approval;
}
function loadDesignSignOff(projectRoot) {
  return loadGitWorkflow(projectRoot).config.design_sign_off;
}
function changeLane(projectRoot, sessionId, dirName) {
  return resolveEffectiveLane(
    projectRoot,
    void 0,
    foldFeature(projectRoot, sessionId, dirName).lane
  );
}
function freezeNudge(projectRoot, sessionId, dirName) {
  if (!designSignOffApplies(
    loadDesignSignOff(projectRoot),
    changeLane(projectRoot, sessionId, dirName)
  )) {
    return null;
  }
  return readHld(projectRoot, dirName)?.state === "changes_requested" ? FREEZE_NEXT_V2_LINE : FREEZE_NEXT_LINE;
}

// src/hld/gate.ts
var OPEN_STATES = /* @__PURE__ */ new Set(["approved", "skipped", "not_posted"]);
function designSignOffBlock(projectRoot, dirName, lane) {
  const config = loadDesignSignOff(projectRoot);
  if (!designSignOffWaits(config, lane)) return null;
  const record = readHld(projectRoot, dirName);
  if (record === null) return GATE_NO_DESIGN_LINE;
  if (OPEN_STATES.has(record.state)) return null;
  return gateBlockLine(record.ref, config.allow_skip_with_reason);
}

// src/feature-evidence/rules-loaded.ts
init_esm_shims();
import { mkdirSync as mkdirSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "fs";
import { dirname as dirname6, join as join16 } from "path";

// src/context/rule-context.ts
init_esm_shims();
import { createHash } from "crypto";
import { existsSync as existsSync9 } from "fs";
import { join as join15 } from "path";

// src/context/rule-manifest.ts
init_esm_shims();
var SCRIPT_GLYPH = "\u2699";
var DEFAULT_SUMMARY_CHARS = 140;
function scriptedSourcePaths(map) {
  const paths = /* @__PURE__ */ new Set();
  if (!map) return paths;
  for (const rule of map.rules ?? []) {
    if ((rule.scripts?.length ?? 0) > 0) {
      paths.add(rule.source);
    }
  }
  return paths;
}
function oneLineSummary(summary, maxChars) {
  const flattened = summary.replace(/`/g, "").replace(/\s+/g, " ").replace(/^[-*]\s*/, "").trim();
  if (flattened.length <= maxChars) return flattened;
  return `${flattened.slice(0, maxChars - 1).trimEnd()}\u2026`;
}
function renderTrigger(pattern) {
  return `\`${pattern.replace(/`/g, "").replace(/\s+/g, "")}\``;
}
function manifestLine(rule, scripted, maxChars) {
  const triggers = rule.trigger_patterns.length ? rule.trigger_patterns.map(renderTrigger).join(" ") : "`**`";
  const script = scripted.has(rule.source_path) ? ` ${SCRIPT_GLYPH}` : "";
  const summary = oneLineSummary(rule.summary, maxChars);
  return `- **${rule.rule_id}** ${rule.title} \xB7 ${rule.severity} \xB7 triggers: ${triggers}${script}${summary ? ` \u2014 ${summary}` : ""}`;
}
function generateRuleManifest(store, options = {}) {
  const scripted = options.scriptedSourcePaths ?? /* @__PURE__ */ new Set();
  const maxChars = options.maxSummaryChars ?? DEFAULT_SUMMARY_CHARS;
  const rules = store.rules ?? [];
  const header = `## paqad rule manifest \u2014 ${rules.length} ${rules.length === 1 ? "rule" : "rules"}`;
  const intro = `> Every rule that governs this repo. This is the index; the full text of the rules that apply to your changed files is below under "Loaded rule text" \u2014 read it, then run \`paqad-ai rules load\` to record that you did (required for a code change). ${SCRIPT_GLYPH} marks a script-enforced rule: paqad enforces it whether or not its text is loaded.`;
  if (rules.length === 0) {
    return `${header}
${intro}

_No rules compiled yet._
`;
  }
  const lines = rules.map((rule) => manifestLine(rule, scripted, maxChars));
  return `${header}
${intro}

${lines.join("\n")}
`;
}

// src/context/rule-context.ts
var STALE_LOCK_MS = 10 * 60 * 1e3;
var RULES_MISSING_FALLBACK_MARKER = "> \u26A0\uFE0F No compiled rules in this artifact \u2014 load `docs/instructions/rules/` in full before relying on rules.";
var DECISION_PAUSE_REMINDER = "## Decision pause is active\n> Before a create-vs-reuse, shared-abstraction, architecture-path, component-reuse, workflow-or-tool, or ux-pattern choice, write a decision packet and pause for the human (`paqad-ai decision create \u2026`) \u2014 do not decide it unilaterally.";
function selectTriggeredRules(rules, changedPaths) {
  const alwaysLoad = [];
  const triggered = [];
  for (const rule of rules) {
    if (isAlwaysLoadRule(rule)) {
      alwaysLoad.push(rule);
    } else if (changedPaths.length > 0 && ruleTriggersMatch(rule, changedPaths)) {
      triggered.push(rule);
    }
  }
  return { alwaysLoad, triggered };
}
function ruleTextBlock(rule) {
  const body = (rule.raw_text ?? rule.summary).trim();
  return `### ${rule.rule_id} \xB7 ${rule.title}
${body}`;
}
function composeLoadedRuleText(rules) {
  return rules.map(ruleTextBlock).join("\n\n");
}
function hashRuleText(text) {
  return createHash("sha256").update(text).digest("hex");
}
function computeRuleApplicability(rules, changedPaths) {
  const { alwaysLoad, triggered } = selectTriggeredRules(rules, changedPaths);
  const applicable = [
    ...alwaysLoad.map((rule) => ({
      rule_id: rule.rule_id,
      title: rule.title,
      always_load: true,
      matched_paths: []
    })),
    ...triggered.map((rule) => ({
      rule_id: rule.rule_id,
      title: rule.title,
      always_load: false,
      matched_paths: changedPaths.filter((path) => ruleTriggersMatch(rule, [path]))
    }))
  ];
  const loadedRuleText = composeLoadedRuleText([...alwaysLoad, ...triggered]);
  return {
    applicable,
    loadedRuleText,
    ruleTextHash: hashRuleText(loadedRuleText),
    ruleSourcePaths: [...alwaysLoad, ...triggered].map((rule) => rule.source_path)
  };
}
async function changeOwnPaths(projectRoot, sessionId, scope) {
  const since = scope.dirName ? readChangeStartPoint(projectRoot, scope.dirName) : void 0;
  const evidence = await loadChangeEvidence(projectRoot, since ? { since } : {});
  const files = evidence.files.filter((filePath) => !isPaqadArtifactPath(filePath));
  return sessionId === null ? files : subtractUnchangedBaselineFiles(projectRoot, sessionId, files);
}
async function resolveRuleApplicabilityForChange(projectRoot, sessionId = null, scope = {}) {
  const store = await readCompiledRules(projectRoot);
  const changedPaths = await changeOwnPaths(projectRoot, sessionId, scope);
  const rules = store?.rules ?? [];
  if (rules.length === 0) {
    return {
      applicable: [],
      loadedRuleText: "",
      ruleTextHash: hashRuleText(""),
      ruleSourcePaths: [],
      changedPaths,
      hasStore: false
    };
  }
  return { ...computeRuleApplicability(rules, changedPaths), changedPaths, hasStore: true };
}
function composeRuleContext(store, options = {}) {
  const changedPaths = options.changedPaths ?? [];
  const manifest = generateRuleManifest(store, { scriptedSourcePaths: options.scriptedPaths });
  const { alwaysLoad, triggered } = selectTriggeredRules(store.rules ?? [], changedPaths);
  const loaded = [...alwaysLoad, ...triggered];
  if (loaded.length === 0) {
    return manifest;
  }
  const heading = triggered.length > 0 ? `## Loaded rule text \u2014 ${loaded.length} rules apply to the files in play` : `## Loaded rule text \u2014 ${loaded.length} always-on rules`;
  const blocks = composeLoadedRuleText(loaded);
  return `${manifest}
${heading}

${blocks}
`;
}
async function writeRuleContext(projectRoot, options = {}) {
  const loadRules = options.loadRules ?? true;
  const store = loadRules ? await readCompiledRules(projectRoot) : null;
  const existingSurfaceSection = loadRules ? options.existingSurfaceSection?.trim() ?? "" : "";
  const memorySection = options.memorySection?.trim() ?? "";
  const retrievalSection = options.retrievalSection?.trim() ?? "";
  const driftSection = options.driftSection?.trim() ?? "";
  if (!store && !existingSurfaceSection && !memorySection && !retrievalSection && !driftSection)
    return null;
  let markdown = "";
  if (store) {
    const changedPaths = (await loadChangeEvidence(projectRoot)).files;
    const scriptedPaths = scriptedSourcePaths(loadRuleScriptMap(projectRoot));
    markdown = composeRuleContext(store, { changedPaths, scriptedPaths });
    if ((store.rules?.length ?? 0) > 0) {
      markdown = `${markdown}
${DECISION_PAUSE_REMINDER}
`;
    }
  }
  for (const section of [existingSurfaceSection, memorySection, retrievalSection, driftSection]) {
    if (section) {
      markdown = markdown ? `${markdown}
${section}
` : `${section}
`;
    }
  }
  const hasRules = (store?.rules?.length ?? 0) > 0;
  if (loadRules && !hasRules) {
    markdown = `${RULES_MISSING_FALLBACK_MARKER}

${markdown}`;
  }
  const target = join15(projectRoot, PATHS.CONTEXT_SESSION_ARTIFACT);
  await atomicWriteFile(target, markdown);
  return target;
}
async function ensureCompiledRulesCurrent(projectRoot) {
  if (!existsSync9(join15(projectRoot, PATHS.RULES_DIR))) {
    return null;
  }
  try {
    if (await isCompiledRulesStale(projectRoot)) {
      await writeCompiledRules(projectRoot, await compileRules(projectRoot));
    }
    return null;
  } catch (error) {
    return error instanceof Error ? error : new Error(String(error));
  }
}
function compileFailureReason(error) {
  return error.message.replace(/\.+$/u, "");
}
async function refreshRuleContext(projectRoot, options = {}) {
  const lockDir = join15(projectRoot, PATHS.LOCKS_DIR, "rule-context.lock");
  const lock = tryAcquireLock(lockDir, { staleLockMs: STALE_LOCK_MS });
  if (!lock.acquired) {
    return null;
  }
  try {
    const failure = await ensureCompiledRulesCurrent(projectRoot);
    if (failure !== null) options.onCompileFailure?.(failure);
    return await writeRuleContext(projectRoot, options);
  } finally {
    releaseLock(lockDir);
  }
}

// src/feature-evidence/rules-loaded.ts
var RULES_LOADED_DOC_TYPE = "paqad.rules-loaded";
var RULES_LOADED_SCHEMA_VERSION = 2;
function atomicWriteJson2(absPath, value) {
  mkdirSync8(dirname6(absPath), { recursive: true });
  const tmp = `${absPath}.tmp`;
  writeFileSync8(tmp, `${JSON.stringify(value, null, 2)}
`, "utf8");
  renameWithRetrySync(tmp, absPath);
}
function buildRulesLoadedRecord(dirName, sessionId, input) {
  return buildDocumentEnvelope({
    docType: RULES_LOADED_DOC_TYPE,
    change: featureChangeKey(dirName),
    sessionId,
    schemaVersion: RULES_LOADED_SCHEMA_VERSION,
    now: input.now,
    body: {
      changed_files: [...input.changedPaths],
      applicable_rules: input.applicable,
      rule_text_hash: input.ruleTextHash,
      ...input.ruleSourceHashes ? { rule_source_hashes: { ...input.ruleSourceHashes } } : {},
      artifact: PATHS.CONTEXT_SESSION_ARTIFACT
    }
  });
}
function writeRulesLoaded(projectRoot, sessionId, input) {
  const dirName = currentFeature(projectRoot, sessionId);
  if (!dirName) {
    return null;
  }
  const record = buildRulesLoadedRecord(dirName, sessionId, input);
  atomicWriteJson2(join16(projectRoot, featureFilePath(dirName, "rulesLoaded")), record);
  return record;
}
function readRulesLoaded(projectRoot, dirName) {
  try {
    return JSON.parse(
      readFileSync12(join16(projectRoot, featureFilePath(dirName, "rulesLoaded")), "utf8")
    );
  } catch {
    return null;
  }
}
function coverageGaps(record, currentApplicableIds) {
  const recorded = new Set((record?.applicable_rules ?? []).map((rule) => rule.rule_id));
  return currentApplicableIds.filter((id) => !recorded.has(id));
}
function currentSourceHash(projectRoot, sourcePath) {
  try {
    return hashRuleText(readFileSync12(join16(projectRoot, sourcePath), "utf8"));
  } catch {
    return null;
  }
}
function hashRuleSources(projectRoot, sourcePaths) {
  const hashes = {};
  for (const sourcePath of sourcePaths) {
    const hash = currentSourceHash(projectRoot, sourcePath);
    if (hash !== null) {
      hashes[sourcePath] = hash;
    }
  }
  return hashes;
}
function changedLoadedRuleSources(projectRoot, record) {
  const recorded = record.rule_source_hashes;
  if (!recorded || typeof recorded !== "object") {
    return [];
  }
  return Object.entries(recorded).filter(([sourcePath, hash]) => currentSourceHash(projectRoot, sourcePath) !== hash).map(([sourcePath]) => sourcePath).sort();
}

// src/stage-evidence/mode.ts
init_esm_shims();
var STAGES_MODES = ["off", "warn", "strict"];
var DEFAULT_STAGES_MODE = "strict";
function resolveStagesMode(projectRoot, env = process.env) {
  return resolveFlooredMode(
    {
      team: readConfigsDir(projectRoot).merged.get("stages_mode"),
      local: readDotConfig(projectRoot).get("stages_mode"),
      env: envConfigValue(env, "stages_mode")
    },
    STAGES_MODES,
    DEFAULT_STAGES_MODE
  );
}

// src/stage-evidence/scope.ts
init_esm_shims();
function toRelativePosix(targetPath, projectRoot) {
  const rel = projectRoot && isAbsolute(targetPath) ? relative(projectRoot, targetPath) : targetPath;
  return rel.replace(/\\/g, "/").replace(/^\.\//, "");
}
function isFrameworkInternalPath(targetPath, projectRoot) {
  const p = toRelativePosix(targetPath, projectRoot);
  return p === "" || p === ".paqad" || p.startsWith(".paqad/") || p.startsWith("../");
}
function isDocumentationPath(targetPath, projectRoot) {
  const p = toRelativePosix(targetPath, projectRoot);
  if (p === "docs" || p.startsWith("docs/")) return true;
  if (/\.(md|mdx|markdown|mdown|rst|adoc)$/i.test(p)) return true;
  if (/^(readme|changelog|contributing|license|licence|notice|authors)(\.|$)/i.test(p)) return true;
  return false;
}
var HOST_AGENT_CONFIG_DIRS = [
  ".claude",
  ".codex",
  ".gemini",
  ".junie",
  ".cursor",
  ".windsurf",
  ".continue",
  ".aider",
  ".aiassistant",
  ".ai"
];
var HOST_AGENT_CONFIG_FILES = [".windsurfrules"];
function isHostAgentConfigPath(targetPath, projectRoot) {
  const p = toRelativePosix(targetPath, projectRoot);
  if (HOST_AGENT_CONFIG_FILES.includes(p)) {
    return true;
  }
  return HOST_AGENT_CONFIG_DIRS.some((dir) => p === dir || p.startsWith(`${dir}/`));
}
function isFeatureDevEdit(targetPath, projectRoot) {
  if (!targetPath) return true;
  if (isFrameworkInternalPath(targetPath, projectRoot)) return false;
  if (isHostAgentConfigPath(targetPath, projectRoot)) return false;
  if (isDocumentationPath(targetPath, projectRoot)) return false;
  return true;
}
function changeIsFeatureDev(files, projectRoot) {
  return files.some((file) => isFeatureDevEdit(file, projectRoot));
}

// src/kernel/compat.ts
init_esm_shims();
function evaluateCapabilityCompat(projectRoot, descriptor) {
  const blessed = readCapabilityVersions(projectRoot, descriptor.id);
  if (blessed === null) {
    return "unversioned";
  }
  if (blessed.policy > descriptor.policySchemaVersion || blessed.record > descriptor.recordSchemaVersion) {
    return "project-ahead";
  }
  if (blessed.policy < descriptor.policySchemaVersion || blessed.record < descriptor.recordSchemaVersion) {
    return "project-behind";
  }
  return "current";
}
function isRefusedByCompat(compat) {
  return compat === "project-ahead";
}

// src/kernel/capability.ts
var NO_OP4 = { ran: false, blocking: false, summary: "" };
var RULE_COMPLIANCE_MODES = ["off", "warn", "strict"];
var DEFAULT_RULE_COMPLIANCE = "warn";
function resolveRuleComplianceMode(projectRoot, env = process.env) {
  return resolveFlooredMode(
    {
      team: readConfigsDir(projectRoot).merged.get("rule_compliance"),
      local: readDotConfig(projectRoot).get("rule_compliance"),
      env: envConfigValue(env, "rule_compliance")
    },
    RULE_COMPLIANCE_MODES,
    DEFAULT_RULE_COMPLIANCE
  );
}
function verifyRuleScriptsIntegrity(projectRoot) {
  const digest = computeRuleScriptsDigest(projectRoot);
  if (digest === null) {
    return "ok";
  }
  const locked = readCapabilityDigest(projectRoot, "rule-scripts");
  if (locked === null) {
    return "unverified";
  }
  return locked === digest ? "ok" : "tampered";
}
function formatTamperSummary(mode) {
  const blocking = mode === "strict";
  const verb = blocking ? "Needs your attention" : "Heads up";
  const glyph = blocking ? "\u{1F534}" : "\u{1F7E1}";
  const tail = blocking ? "" : " (warn mode, not blocking)";
  return `**\u25B8 paqad** \xB7 scripted-rule bindings changed outside the engine
> ${glyph} ${verb} \u2014 your rule-script bindings were edited outside paqad, so I can't trust this check; a binding may have been weakened. Re-bless them with \`analyze rules\` / \`generate rule scripts\`, then commit the updated lock.${tail}`;
}
function formatNoneArmedSummary() {
  return `**\u25B8 paqad** \xB7 scripted rules: \u26AA none armed
> No rule-scripts are registered for this repo, so I ran no scripted enforcement. Your checks passed on the toolchain (format/tests/build), not on the team's rule-scripts \u2014 arm them with \`generate rule scripts\` to enforce the rules deterministically.`;
}
function formatUnverifiedSummary() {
  return `**\u25B8 paqad** \xB7 scripted-rule bindings not yet attested
> \u{1F7E1} Heads up \u2014 your rule-script bindings have no integrity lock yet, so I can't attest they are unchanged. Re-run \`generate rule scripts\` to bless them. (advisory, not blocking)`;
}
function formatCompatRefusalSummary() {
  return `**\u25B8 paqad** \xB7 scripted-rule enforcement paused \u2014 framework update pending
> \u{1F7E1} Heads up \u2014 this project's rule-script bindings were blessed by a newer paqad than the one installed here, so I'm holding off enforcing them rather than risk misreading a newer format. The framework self-heals on update, then I'll resume. (advisory, not blocking)`;
}
var RULE_GATE_BUDGET_CONTEXT = "paqad: the blocking rule checks did not finish in time for this edit. They will run again at the end of this turn and block there if anything fails.";
var RULE_GATE_REMOVAL_CONTEXT = "paqad: this edit removes a rule finding from its file and adds none, so it passed and was recorded. Other findings in this change still block at the end of the turn.";
var PRE_EDIT_FILTER = {
  kinds: ["deterministic"],
  scopes: ["changed-files"]
};
function formatPendingUnwritableSummary() {
  return `**\u25B8 paqad** \xB7 blocking rule checks could not be deferred
> \u{1F534} Needs your attention \u2014 the blocking rule checks did not finish in time for this edit, and I could not record that they still have to run at the end of this turn, so I stopped the edit. Check that \`.paqad/scripts/rules/.cache/\` is writable, then re-try the edit.`;
}
function payloadSessionId(projectRoot, env, payload) {
  return resolveSessionId(projectRoot, payload?.sessionId ?? env.CLAUDE_SESSION_ID ?? null);
}
function payloadTargets(payload) {
  return payload?.targetPaths ?? (payload?.targetPath ? [payload.targetPath] : []);
}
async function withinGateBudget(context, gate, work) {
  const now = context.now ?? Date.now;
  const remaining = (context.startedAt ?? now()) + gate.budgetMs - now();
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), Math.max(0, remaining));
  if (remaining <= 0) controller.abort();
  try {
    return await work(controller.signal);
  } finally {
    clearTimeout(timer);
  }
}
function enforceBeforeEdit(context, mode, gate) {
  return withinGateBudget(
    context,
    gate,
    (signal) => enforceRuleScripts({
      projectRoot: context.projectRoot,
      mode,
      scope: "whole-tree",
      ...gate.preEdit === "blocking-only" ? { ...PRE_EDIT_FILTER, useFileCache: true, editTargets: payloadTargets(context.payload) } : {},
      concurrency: gate.concurrency,
      signal
    })
  );
}
function recordAllowedEdit(context, verdict) {
  const { projectRoot, env, payload } = context;
  const sessionId = payloadSessionId(projectRoot, env, payload);
  const removed = verdict.removed.length;
  const row = appendRuleRun(projectRoot, sessionId, {
    kind: "edit-allowed",
    counts: { removed, remaining: verdict.remaining },
    blocking: false,
    note: `edit to ${verdict.target} removes ${removed} rule finding(s) and adds none`,
    findings: verdict.removed.map((violation) => ({
      rule_id: violation.rule_id,
      file: violation.file,
      line: violation.line ?? null,
      message: violation.message,
      severity: violation.severity
    }))
  });
  if (row === null && currentFeature(projectRoot, sessionId)) return false;
  appendHooksLog(
    projectRoot,
    `capability-gate rule-scripts edit-allowed session=${sessionId} file=${verdict.target} removed=${removed} remaining=${verdict.remaining}`
  );
  return true;
}
function refusalForFailedComparison(error) {
  const code = error?.code;
  if (code === "ENOENT" || code === "EEXIST") return { allowed: false, reason: null };
  return { allowed: false, reason: `the comparison failed (${code ?? String(error)})` };
}
async function editOnlyRemovesFindings(context, gate, result) {
  if (gate.preEdit !== "blocking-only") return false;
  const { projectRoot, payload } = context;
  const verdict = await withinGateBudget(
    context,
    gate,
    (signal) => judgeEditResult({
      projectRoot,
      targets: payloadTargets(payload),
      toolName: payload?.toolName,
      toolInput: payload?.toolInput,
      violations: result.violations,
      concurrency: gate.concurrency,
      signal
    })
  ).catch(refusalForFailedComparison);
  if (verdict.allowed) return recordAllowedEdit(context, verdict);
  if (verdict.reason !== null) {
    appendHooksLog(projectRoot, `capability-gate rule-scripts edit-refused ${verdict.reason}`);
  }
  return false;
}
function deferToTurnEnd(context) {
  const { projectRoot, env, payload } = context;
  const sessionId = payloadSessionId(projectRoot, env, payload);
  const files = payload?.targetPaths?.length ?? (payload?.targetPath ? 1 : 0);
  try {
    writePendingPreMutation(projectRoot, {
      session_id: sessionId,
      change: currentFeature(projectRoot, sessionId) ?? null,
      written_at: new Date((context.now ?? Date.now)()).toISOString(),
      files
    });
  } catch (error) {
    const code = error?.code ?? "unknown";
    appendHooksLog(
      projectRoot,
      `capability-gate rule-scripts pending-unwritable ${code} session=${sessionId} files=${files}`
    );
    return { ran: true, blocking: true, summary: formatPendingUnwritableSummary() };
  }
  appendHooksLog(
    projectRoot,
    `capability-gate rule-scripts budget-exhausted session=${sessionId} files=${files}`
  );
  return { ran: true, blocking: false, summary: "", context: RULE_GATE_BUDGET_CONTEXT };
}
function payloadFeatureDev(payload, projectRoot) {
  if (payload?.targetPaths && payload.targetPaths.length > 0) {
    return changeIsFeatureDev(payload.targetPaths, projectRoot);
  }
  return isFeatureDevEdit(payload?.targetPath, projectRoot);
}
var ruleScriptsCapability = {
  id: "rule-scripts",
  async evaluate(context) {
    const { projectRoot, seam, env, payload } = context;
    if (seam === "pre-mutation" && !payloadFeatureDev(payload, projectRoot)) {
      return NO_OP4;
    }
    const pendingSession = seam === "completion" ? payloadSessionId(projectRoot, env, payload) : null;
    const pending = pendingSession !== null && hasPendingPreMutation(projectRoot, pendingSession);
    if (seam === "completion" && !pending && !classifyCompletionEnforcement(
      projectRoot,
      payload?.sessionId ?? env.CLAUDE_SESSION_ID ?? null
    ).enforce) {
      return NO_OP4;
    }
    const mode = resolveRuleComplianceMode(projectRoot, env);
    if (mode === "off") {
      return NO_OP4;
    }
    if (isRefusedByCompat(evaluateCapabilityCompat(projectRoot, getCapability("rule-scripts")))) {
      return { ran: true, blocking: false, summary: formatCompatRefusalSummary() };
    }
    const integrity = verifyRuleScriptsIntegrity(projectRoot);
    if (integrity === "tampered") {
      return { ran: true, blocking: mode === "strict", summary: formatTamperSummary(mode) };
    }
    const result = seam === "pre-mutation" ? await enforceBeforeEdit(context, mode, resolveRuleGateConfig(projectRoot, env)) : await enforceRuleScripts({ projectRoot, mode, scope: "changed-files" });
    if (result.aborted) {
      return deferToTurnEnd(context);
    }
    if (pending && pendingSession !== null) {
      clearPendingPreMutation(projectRoot, pendingSession);
    }
    if (!result.ran) {
      if (seam === "completion" && result.armed === 0) {
        return { ran: true, blocking: false, summary: formatNoneArmedSummary() };
      }
      return NO_OP4;
    }
    if (result.violations.length === 0) {
      return integrity === "unverified" ? { ran: true, blocking: false, summary: formatUnverifiedSummary() } : NO_OP4;
    }
    if (seam === "pre-mutation" && result.blocking && await editOnlyRemovesFindings(context, resolveRuleGateConfig(projectRoot, env), result)) {
      return { ran: true, blocking: false, summary: "", context: RULE_GATE_REMOVAL_CONTEXT };
    }
    return {
      ran: true,
      blocking: result.blocking,
      summary: result.summary,
      violations: result.violations
    };
  }
};
function formatMissingStageSummary(stage, mode) {
  const blocking = mode === "strict";
  const verb = blocking ? "Needs your attention" : "Heads up";
  const glyph = blocking ? "\u{1F534}" : "\u{1F7E1}";
  const tail = blocking ? "" : " (warn mode, not blocking)";
  const endMarker = isArtifactBearingStage(stage) ? `\`paqad:stage ${stage} end -- <artifact-path>\` (a real, non-empty file \u2014 e.g. a plan/spec/findings file)` : `\`paqad:stage ${stage} end\``;
  const endCli = isArtifactBearingStage(stage) ? `\`paqad-ai stage end ${stage} --artifact <artifact-path>\`` : `\`paqad-ai stage end ${stage}\``;
  return `**\u25B8 paqad** \xB7 run ${stage} before you change code
> ${glyph} ${verb} \u2014 the feature-development workflow needs the **${stage}** stage recorded before this edit. Mark it: emit \`paqad:stage ${stage} start\` and ${endMarker} each on its own line (parsed before the next edit, so they clear this block in the same turn), or run \`paqad-ai stage start ${stage}\` then ${endCli}. Set stage_tracking: warn or off in .paqad/configs/feature-development/workflow.yaml to adopt the workflow before enforcing.${tail}`;
}
function sweepSameTurnMarkers(projectRoot, transcriptPath, sessionId, adapter) {
  if (!transcriptPath) return "";
  let transcriptText;
  try {
    transcriptText = readFileSync13(transcriptPath, "utf8");
  } catch {
    return "";
  }
  const recorded = parseAndRecordMarkers({ projectRoot, transcriptText, sessionId, adapter });
  return markerBatchNarration(recorded);
}
function bundleFileWriter(filename) {
  if (filename === SCREENSHOTS_DIR || filename.startsWith(`${SCREENSHOTS_DIR}/`)) {
    return "paqad-ai visual-evidence run (or visual-evidence attach)";
  }
  return bundleWriterFor(filename);
}
function formatBundleWriteSummary(relPath, writer) {
  const remedy = writer ? `This file is written by \`${writer}\`. Run that verb and hand it your content instead.` : `No verb writes this file, so it does not belong in the bundle. Keep your notes outside \`.paqad/ledger/feature-evidence/\`.`;
  return `**\u25B8 paqad** \xB7 bundle files are written by paqad, not by hand
> \u{1F534} Needs your attention \u2014 \`${relPath}\` sits inside a feature evidence bundle, and every file there is created by a paqad verb so the record can be trusted. ${remedy}`;
}
function normalizedPayloadTargets(payload, projectRoot) {
  const targets = payload?.targetPaths && payload.targetPaths.length > 0 ? payload.targetPaths : payload?.targetPath ? [payload.targetPath] : [];
  const rels = [];
  for (const target of targets) {
    try {
      rels.push(normalizeArtifactPath(projectRoot, target));
    } catch {
      continue;
    }
  }
  return rels;
}
function firstBundleTarget(targets) {
  for (const rel of targets) {
    const classified = classifyBundlePath(rel);
    if (classified) return { rel, filename: classified.filename };
  }
  return null;
}
function firstChecksRecordTarget(targets) {
  return targets.find((rel) => rel === PATHS.CHECKS_DIR || rel.startsWith(`${PATHS.CHECKS_DIR}/`)) ?? null;
}
function formatChecksRecordWriteSummary(relPath) {
  return `**\u25B8 paqad** \xB7 checks records are written by paqad, not by hand
> \u{1F534} Needs your attention \u2014 \`${relPath}\` is part of the checks record (the run history, the run snapshots and the last report). Only \`paqad-ai checks run\` writes it, so a green result there can be trusted. Run \`paqad-ai checks run\` instead.`;
}
function firstGitWorkflowTarget(targets) {
  const dir = PATHS.GIT_WORKFLOW_DIR;
  return targets.find((rel) => rel === dir || rel.startsWith(`${dir}/`)) ?? null;
}
function formatGitWorkflowWriteSummary(relPath) {
  return `**\u25B8 paqad** \xB7 the git workflow rules are changed by the git-workflow skill
> \u{1F534} Needs your attention: \`${relPath}\` holds this team's git workflow rules. Only the git-workflow skill changes them, so every change is checked, explained and recorded. Use the git-workflow skill instead.`;
}
var stagesCapability = {
  id: "stages",
  async evaluate({ projectRoot, seam, env, payload }) {
    if (seam !== "pre-mutation") return NO_OP4;
    const targets = normalizedPayloadTargets(payload, projectRoot);
    const workflowTarget = firstGitWorkflowTarget(targets);
    if (workflowTarget) {
      return { ran: true, blocking: true, summary: formatGitWorkflowWriteSummary(workflowTarget) };
    }
    const checksTarget = firstChecksRecordTarget(targets);
    if (checksTarget) {
      return { ran: true, blocking: true, summary: formatChecksRecordWriteSummary(checksTarget) };
    }
    const bundleTarget = firstBundleTarget(targets);
    if (bundleTarget) {
      return {
        ran: true,
        blocking: true,
        summary: formatBundleWriteSummary(
          bundleTarget.rel,
          bundleFileWriter(bundleTarget.filename)
        )
      };
    }
    const writeDecision = stageAgentWriteDecision({
      projectRoot,
      agentType: payload?.agentType,
      toolName: payload?.toolName,
      targets
    });
    if (writeDecision.blocked) {
      return { ran: true, blocking: true, summary: formatStageAgentWriteSummary(writeDecision) };
    }
    const unresolvedAgent = unresolvedStageAgent(payload?.agentType);
    if (unresolvedAgent !== null) {
      return { ran: true, blocking: true, summary: formatUnresolvedStageSummary(unresolvedAgent) };
    }
    const mode = resolveStagesMode(projectRoot, env);
    if (mode === "off") return NO_OP4;
    if (!payloadFeatureDev(payload, projectRoot)) return NO_OP4;
    const blocking = mode === "strict";
    const sessionId = resolveSessionId(
      projectRoot,
      payload?.sessionId ?? env.CLAUDE_SESSION_ID ?? null
    );
    const narration = sweepSameTurnMarkers(
      projectRoot,
      payload?.transcriptPath,
      sessionId,
      payload?.adapter
    );
    const dirName = currentFeature(projectRoot, sessionId);
    const prefix = PRE_CODE_STAGES;
    if (!dirName) {
      const firstRequired = prefix[0] ?? "planning";
      return {
        ran: true,
        blocking,
        summary: formatMissingStageSummary(firstRequired, mode),
        narration
      };
    }
    const fold = foldFeature(projectRoot, sessionId, dirName);
    const byStage = new Map(fold.stages.map((stage) => [stage.stage, stage]));
    const effectiveLane = resolveEffectiveLane(projectRoot, payload?.targetPath, fold.lane);
    const requiredStages = requiredPreCodeStages(effectiveLane);
    for (const stage of requiredStages) {
      const folded = byStage.get(stage);
      const hasPair = Boolean(folded?.started_at && folded?.ended_at);
      const hasArtifact = !isArtifactBearingStage(stage) || Boolean(folded?.artifact_digest);
      if (!hasPair || !hasArtifact) {
        return { ran: true, blocking, summary: formatMissingStageSummary(stage, mode), narration };
      }
    }
    const signOff = designSignOffBlock(projectRoot, dirName, effectiveLane);
    if (signOff !== null) return { ran: true, blocking, summary: signOff, narration };
    return { ran: true, blocking: false, summary: "", narration };
  }
};
function formatRulesNotLoadedSummary() {
  return `**\u25B8 paqad** \xB7 rules not loaded for this change
> \u{1F534} Needs your attention \u2014 you are editing code but I have no record that the applicable project rules were loaded. Read them and record it: run \`paqad-ai rules load\` (it prints the rules that apply to your changed files and writes the evidence), then re-try the edit.`;
}
function formatRulesChangedSummary(changed) {
  return `**\u25B8 paqad** \xB7 rules changed since you loaded them
> \u{1F534} Needs your attention \u2014 the text of ${changed.length} rule${changed.length === 1 ? "" : "s"} you loaded for this change has changed since: ${changed.join(", ")}. Read the new text: run \`paqad-ai rules load\`, then re-try the edit.`;
}
var rulesLoadedCapability = {
  id: "rules-loaded",
  async evaluate({ projectRoot, seam, env, payload }) {
    if (seam !== "pre-mutation") return NO_OP4;
    if (!payloadFeatureDev(payload, projectRoot)) return NO_OP4;
    const store = await readCompiledRules(projectRoot);
    if (!store || (store.rules?.length ?? 0) === 0) return NO_OP4;
    const sessionId = resolveSessionId(
      projectRoot,
      payload?.sessionId ?? env.CLAUDE_SESSION_ID ?? null
    );
    const dirName = currentFeature(projectRoot, sessionId);
    if (!dirName) return NO_OP4;
    const record = readRulesLoaded(projectRoot, dirName);
    if (!record) {
      return { ran: true, blocking: true, summary: formatRulesNotLoadedSummary() };
    }
    const changed = changedLoadedRuleSources(projectRoot, record);
    if (changed.length === 0) return NO_OP4;
    return { ran: true, blocking: true, summary: formatRulesChangedSummary(changed) };
  }
};
var deliveryCapability = {
  id: "delivery",
  evaluate({ projectRoot, seam }) {
    return runDeliveryCapability(projectRoot, seam, execaCommandRunner);
  }
};
var decisionPauseSelfArmCapability = {
  id: "decision-pause",
  async evaluate({ projectRoot, seam, env, payload }) {
    const selfArm = runDecisionSelfArm({ projectRoot, seam, env, payload });
    if (selfArm.ran) return selfArm;
    return runSpecChangeGuard({ projectRoot, seam, sessionId: payload?.sessionId ?? null });
  }
};
var CAPABILITY_IMPLS = /* @__PURE__ */ new Map([
  [ruleScriptsCapability.id, ruleScriptsCapability],
  [stagesCapability.id, stagesCapability],
  [rulesLoadedCapability.id, rulesLoadedCapability],
  [deliveryCapability.id, deliveryCapability],
  [decisionPauseSelfArmCapability.id, decisionPauseSelfArmCapability]
]);

// src/hud/last-block.ts
init_esm_shims();
import { randomBytes } from "crypto";
import { readFileSync as readFileSync14, rmSync as rmSync5 } from "fs";
var LAST_BLOCK_DIFF_MAX_BYTES = 2048;
function asRecord(value) {
  return typeof value === "object" && value !== null ? value : null;
}
function prefixed(text, mark) {
  return typeof text === "string" && text !== "" ? text.split(/\r?\n/).map((line) => `${mark} ${line}`) : [];
}
function editLines(edit) {
  if (!edit) return [];
  return [...prefixed(edit.old_string, "-"), ...prefixed(edit.new_string, "+")];
}
function truncateUtf8(text, maxBytes) {
  if (Buffer.byteLength(text, "utf8") <= maxBytes) return text;
  let out = "";
  let used = 0;
  for (const char of text) {
    const size = Buffer.byteLength(char, "utf8");
    if (used + size > maxBytes) break;
    out += char;
    used += size;
  }
  return out;
}
function shortDiff(toolInput, maxBytes = LAST_BLOCK_DIFF_MAX_BYTES) {
  const input = asRecord(toolInput);
  if (!input) return "";
  const edits = Array.isArray(input.edits) ? input.edits.map(asRecord) : [input];
  const lines = [
    ...edits.flatMap(editLines),
    ...prefixed(input.content, "+"),
    ...prefixed(input.new_source, "+")
  ];
  return truncateUtf8(lines.join("\n"), maxBytes);
}
function writeRuleStopRecord(input) {
  const record = {
    rule: input.rule,
    title: input.title ?? null,
    file: input.file,
    tool_use_id: input.toolUseId ?? null,
    managed: input.managed,
    sole: input.sole === true,
    at: (input.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
    diff: shortDiff(input.toolInput),
    token: input.token ?? randomBytes(16).toString("hex"),
    used: false,
    allowed_once: false,
    session: input.sessionId
  };
  writeFileAtomic(
    hudLastBlockPath(input.projectRoot, input.sessionId),
    `${JSON.stringify(record, null, 2)}
`
  );
  return record;
}
function str(value) {
  return typeof value === "string" && value !== "" ? value : null;
}
function parseRuleStopRecord(value) {
  const raw = asRecord(value);
  const rule = str(raw?.rule);
  const file = str(raw?.file);
  const at = str(raw?.at);
  const token = str(raw?.token);
  const session = str(raw?.session);
  if (!raw || !rule || !file || !at || !token || !session) return null;
  return {
    rule,
    title: str(raw.title),
    file,
    tool_use_id: str(raw.tool_use_id),
    managed: raw.managed === true,
    sole: raw.sole === true,
    at,
    diff: typeof raw.diff === "string" ? raw.diff : "",
    token,
    used: raw.used === true,
    allowed_once: raw.allowed_once === true,
    session
  };
}
function readRuleStopRecord(projectRoot, sessionId) {
  let parsed;
  try {
    parsed = JSON.parse(readFileSync14(hudLastBlockPath(projectRoot, sessionId), "utf8"));
  } catch {
    return null;
  }
  const record = parseRuleStopRecord(parsed);
  return record && record.session === sessionId ? record : null;
}
function writeRecord(projectRoot, record) {
  writeFileAtomic(
    hudLastBlockPath(projectRoot, record.session),
    `${JSON.stringify(record, null, 2)}
`
  );
}
function markRuleStopAllowedOnce(projectRoot, record) {
  writeRecord(projectRoot, { ...record, used: true, allowed_once: true });
}
function takeAllowedOnce(projectRoot, sessionId, call) {
  const record = readRuleStopRecord(projectRoot, sessionId);
  if (!record?.allowed_once || !call.toolUseId || record.tool_use_id !== call.toolUseId) {
    return false;
  }
  if (record.rule !== call.rule) return false;
  const recordFile = toProjectRelativePath(projectRoot, record.file);
  const callFile = toProjectRelativePath(projectRoot, call.file);
  if (recordFile === null || callFile === null || recordFile !== callFile) return false;
  writeRecord(projectRoot, { ...record, allowed_once: false });
  return true;
}
function clearRuleStopRecord(projectRoot, sessionId) {
  rmSync5(hudLastBlockPath(projectRoot, sessionId), { force: true });
}

export {
  COMPLIANCE_SCHEMA_VERSION,
  SPEC_REVIEW_SCHEMA_VERSION,
  DEFAULT_OBLIGATION_INDEX_PATH,
  slugifySpec,
  specIndexPath,
  specReportPath,
  specReviewPath,
  reviewSpecification,
  attachSpecDefectsToObligations,
  compareDefects,
  extractObligationIndex,
  planExpertSlices,
  MAX_EXPERT_QUESTIONS,
  decideFinish,
  buildProvenance,
  SPEC_PIPELINE_STAGING_DIR,
  readStagedText,
  writeStagedText,
  readStagedJson,
  clearStaged,
  writeRequest,
  readRequest,
  writeLabel,
  readLabel,
  writeQuestions,
  readQuestions,
  clearClarificationSection,
  readExperts,
  writeExpertRoster,
  readExpertNeed,
  writeExpertNotes,
  readStagedExpertQuestions,
  readExpertNotes,
  writeExpertSynthesis,
  writeExpertFindings,
  readExpertSynthesis,
  clearExpertSynthesis,
  readGrounding,
  readFreezeSections,
  clearFrozenRun,
  rememberInput,
  appendSpecStepRow,
  appendSpecCorrectionRow,
  validateSiteMapAnswers,
  SiteMapSchemaError,
  canonicalAppMapPath,
  writeYamlAtomic,
  readYaml,
  writeCanonicalSiteMap,
  readCanonicalSiteMap,
  writeJourney,
  removeJourney,
  readJourney,
  readAllJourneys,
  loadCaptureScripts,
  deriveCaption,
  resolveVeRuntimeDir,
  resolvePlaywrightModulePath,
  browserStatus,
  provisionBrowser,
  veBrowsersPath,
  PackRegistryEmptyError,
  packRegistryFaultDetail,
  PACK_REGISTRY_FAULT_REMEDIATION,
  frontendTriggerOrFault,
  evaluateFrontendTrigger,
  visualEvidenceReadiness,
  visualEvidenceFlagOn,
  visualAcRequiredFiles,
  findVisualEvidenceWaiver,
  visualEvidenceWaiverHint,
  isManagedEnv,
  isFrameworkEnabled,
  isFrameworkEnabledForRoot,
  isFrameworkDisabledForRoot,
  setFrameworkEnabled,
  buildFeatureSpec,
  sessionWroteInEpisode,
  editedDevelopmentThisTurn,
  changePastSpecification,
  classifyCompletionEnforcement,
  DELIVERY_EVIDENCE_DOC_TYPE,
  renderDelivery,
  GIT_WORKFLOW_PATH,
  requireTicket,
  matchTicketSource,
  pickBranchType,
  resolveBase,
  isConfiguredBase,
  planBranch,
  currentBranch,
  styleVerdict,
  commitStep,
  placeholdersIn2 as placeholdersIn,
  missingHeadings,
  linkTicket,
  uniqueInOrder,
  MISSING_LABEL_HINT,
  branchFinding,
  commitFindings,
  ticketInBranch,
  refreshDelivery,
  execaCommandRunner,
  validateCodeKnowledgeIndex,
  writeCodeKnowledgeIndex,
  readCodeKnowledgeIndex,
  applyResolvedDecisions,
  armDecisionFromDuplicationFinding,
  assembleDecisionEvidence,
  countFileReferences,
  defaultSimilarityFor,
  buildDecisionPacket,
  computePacketConfidence,
  decisionQuestionForCategory,
  decisionOptionsForCategory,
  selectViableDecisionOptions,
  authoringConfigProblems,
  readAuthoringConfig,
  skillOrchestratorEnabled,
  agentOrchestratorEnabled,
  planFiles,
  sensitivePaths,
  editedFiles,
  laneMayRise,
  raiseLaneIfGrown,
  authoringRaiseEnabled,
  raiseLaneForAuthoring,
  projectRelative,
  dynamicRecord,
  queryFrameworkApi,
  validateFrameworkApiIndex,
  writeFrameworkApiIndex,
  readFrameworkApiIndex,
  reuseCounts,
  ReuseDeclarationError,
  NoActiveFeatureError,
  writeFeaturePlan,
  readFeaturePlan,
  writeFeatureSpecification,
  readFeatureSpecification,
  writeFeatureReview,
  readFeatureReview,
  SKILL_RECORD_WRITER,
  skillRecordFilename,
  readSkillRecord,
  writeSkillRecord,
  AGENT_RECORD_WRITER,
  agentRecordFilename,
  readAgentRecord,
  writeAgentRecord,
  readAllFeatureStageRows,
  readAllFeatureRuleRuns,
  readAllFeatureChangeMetrics,
  readAllFeatureRag,
  readFeatureChangeMetricsWindow,
  readAllFeatureEvidence,
  evaluateSpecFreeze,
  freezeSpec,
  carryForwardIds,
  validateCraftTrace,
  writeTrace,
  readTrace,
  parseTraceArtifact,
  mergeExpertNotes,
  strongestSeverity,
  mergedKind,
  pathTriggeredSeats,
  seatExpertsForLane,
  validateExpertNeed,
  queryCodeKnowledge,
  validateExpertNotes,
  validateQuestion,
  assembleExpertRun,
  buildRunMetrics,
  aggregateSpecPipelineMetrics,
  listRunDirs,
  buildSpecChangePacket,
  designSignOffApplies,
  loadDesignSignOff,
  changeLane,
  freezeNudge,
  resolveRuleApplicabilityForChange,
  writeRuleContext,
  ensureCompiledRulesCurrent,
  compileFailureReason,
  refreshRuleContext,
  writeRulesLoaded,
  readRulesLoaded,
  coverageGaps,
  hashRuleSources,
  resolveStagesMode,
  changeIsFeatureDev,
  resolveRuleComplianceMode,
  CAPABILITY_IMPLS,
  writeRuleStopRecord,
  readRuleStopRecord,
  markRuleStopAllowedOnce,
  takeAllowedOnce,
  clearRuleStopRecord
};