{"version":3,"file":"detect-Bf2z-oKB.mjs","names":[],"sources":["../src/cli/commands/detect.ts"],"sourcesContent":["/**\n * Project detection logic for PAI.\n *\n * detectProject(cwd) — given a filesystem path, returns the best matching\n * project from the registry:\n *   1. Exact path match\n *   2. Longest parent match (project whose root_path is an ancestor of cwd)\n *\n * Exported for use by the CLI `pai project detect` command and the MCP\n * `project_detect` tool.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport { resolve } from \"node:path\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface DetectedProject {\n  id: number;\n  slug: string;\n  display_name: string;\n  root_path: string;\n  encoded_dir: string;\n  type: string;\n  status: string;\n  session_count: number;\n  last_session_date: string | null;\n  match_type: \"exact\" | \"parent\";\n  /** Only set when match_type is 'parent' — the portion of cwd below root_path */\n  relative_path: string | null;\n}\n\ninterface ProjectRow {\n  id: number;\n  slug: string;\n  display_name: string;\n  root_path: string;\n  encoded_dir: string;\n  type: string;\n  status: string;\n}\n\n// ---------------------------------------------------------------------------\n// Core detection function\n// ---------------------------------------------------------------------------\n\n/**\n * Detect which registered project a filesystem path belongs to.\n *\n * @param db   Open registry database\n * @param cwd  Absolute path to detect (defaults to process.cwd())\n * @returns    The best matching project, or null if no match\n */\nexport function detectProject(\n  db: Database,\n  cwd?: string\n): DetectedProject | null {\n  const target = resolve(cwd ?? process.cwd());\n\n  // Load all active projects ordered by root_path length descending\n  // so the longest (most specific) match wins in a linear scan.\n  const projects = db\n    .prepare(\n      `SELECT id, slug, display_name, root_path, encoded_dir, type, status\n       FROM projects\n       WHERE status != 'archived'\n       ORDER BY LENGTH(root_path) DESC`\n    )\n    .all() as ProjectRow[];\n\n  let matched: ProjectRow | null = null;\n  let matchType: \"exact\" | \"parent\" = \"exact\";\n\n  for (const p of projects) {\n    const root = resolve(p.root_path);\n    if (target === root) {\n      matched = p;\n      matchType = \"exact\";\n      break;\n    }\n    if (!matched && target.startsWith(root + \"/\")) {\n      matched = p;\n      matchType = \"parent\";\n      // Keep scanning — a longer root_path match might exist (but shouldn't\n      // since we sorted by length desc). Safety break anyway once found.\n      break;\n    }\n  }\n\n  if (!matched) return null;\n\n  // Enrich with session stats\n  const sessionStats = db\n    .prepare(\n      `SELECT COUNT(*) AS cnt, MAX(date) AS last_date\n       FROM sessions WHERE project_id = ?`\n    )\n    .get(matched.id) as { cnt: number; last_date: string | null };\n\n  const relative =\n    matchType === \"parent\"\n      ? target.slice(resolve(matched.root_path).length + 1)\n      : null;\n\n  return {\n    id: matched.id,\n    slug: matched.slug,\n    display_name: matched.display_name,\n    root_path: matched.root_path,\n    encoded_dir: matched.encoded_dir,\n    type: matched.type,\n    status: matched.status,\n    session_count: sessionStats.cnt,\n    last_session_date: sessionStats.last_date,\n    match_type: matchType,\n    relative_path: relative,\n  };\n}\n\n// ---------------------------------------------------------------------------\n// Format helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Format a DetectedProject for human-readable CLI output.\n */\nexport function formatDetection(d: DetectedProject): string {\n  const lines: string[] = [\n    `slug:         ${d.slug}`,\n    `display_name: ${d.display_name}`,\n    `root_path:    ${d.root_path}`,\n    `type:         ${d.type}`,\n    `status:       ${d.status}`,\n    `match:        ${d.match_type}${d.relative_path ? ` (+${d.relative_path})` : \"\"}`,\n    `sessions:     ${d.session_count}`,\n  ];\n  if (d.last_session_date) {\n    lines.push(`last_session: ${d.last_session_date}`);\n  }\n  return lines.join(\"\\n\");\n}\n\n/**\n * Format a DetectedProject as JSON for machine consumption.\n */\nexport function formatDetectionJson(d: DetectedProject): string {\n  return JSON.stringify(\n    {\n      slug: d.slug,\n      display_name: d.display_name,\n      root_path: d.root_path,\n      encoded_dir: d.encoded_dir,\n      type: d.type,\n      status: d.status,\n      match_type: d.match_type,\n      relative_path: d.relative_path,\n      session_count: d.session_count,\n      last_session_date: d.last_session_date,\n    },\n    null,\n    2\n  );\n}\n"],"mappings":";;;;;;;;;;AAuDA,SAAgB,cACd,IACA,KACwB;CACxB,MAAM,SAAS,QAAQ,OAAO,QAAQ,KAAK,CAAC;CAI5C,MAAM,WAAW,GACd,QACC;;;wCAID,CACA,KAAK;CAER,IAAI,UAA6B;CACjC,IAAI,YAAgC;AAEpC,MAAK,MAAM,KAAK,UAAU;EACxB,MAAM,OAAO,QAAQ,EAAE,UAAU;AACjC,MAAI,WAAW,MAAM;AACnB,aAAU;AACV,eAAY;AACZ;;AAEF,MAAI,CAAC,WAAW,OAAO,WAAW,OAAO,IAAI,EAAE;AAC7C,aAAU;AACV,eAAY;AAGZ;;;AAIJ,KAAI,CAAC,QAAS,QAAO;CAGrB,MAAM,eAAe,GAClB,QACC;2CAED,CACA,IAAI,QAAQ,GAAG;CAElB,MAAM,WACJ,cAAc,WACV,OAAO,MAAM,QAAQ,QAAQ,UAAU,CAAC,SAAS,EAAE,GACnD;AAEN,QAAO;EACL,IAAI,QAAQ;EACZ,MAAM,QAAQ;EACd,cAAc,QAAQ;EACtB,WAAW,QAAQ;EACnB,aAAa,QAAQ;EACrB,MAAM,QAAQ;EACd,QAAQ,QAAQ;EAChB,eAAe,aAAa;EAC5B,mBAAmB,aAAa;EAChC,YAAY;EACZ,eAAe;EAChB;;;;;AAUH,SAAgB,gBAAgB,GAA4B;CAC1D,MAAM,QAAkB;EACtB,iBAAiB,EAAE;EACnB,iBAAiB,EAAE;EACnB,iBAAiB,EAAE;EACnB,iBAAiB,EAAE;EACnB,iBAAiB,EAAE;EACnB,iBAAiB,EAAE,aAAa,EAAE,gBAAgB,MAAM,EAAE,cAAc,KAAK;EAC7E,iBAAiB,EAAE;EACpB;AACD,KAAI,EAAE,kBACJ,OAAM,KAAK,iBAAiB,EAAE,oBAAoB;AAEpD,QAAO,MAAM,KAAK,KAAK;;;;;AAMzB,SAAgB,oBAAoB,GAA4B;AAC9D,QAAO,KAAK,UACV;EACE,MAAM,EAAE;EACR,cAAc,EAAE;EAChB,WAAW,EAAE;EACb,aAAa,EAAE;EACf,MAAM,EAAE;EACR,QAAQ,EAAE;EACV,YAAY,EAAE;EACd,eAAe,EAAE;EACjB,eAAe,EAAE;EACjB,mBAAmB,EAAE;EACtB,EACD,MACA,EACD"}