{
  "version": 3,
  "sources": ["../../../src/hooks/ts/pre-tool-use/route-edits-to-worker.ts", "../../../src/hooks/ts/lib/edit-gate.ts"],
  "sourcesContent": ["#!/usr/bin/env node\n\n/**\n * route-edits-to-worker.ts \u2014 PreToolUse (Edit|MultiEdit|Write)\n *\n * The outer orchestrator session does not edit code files; code changes go\n * through `pai worker run`. Allowed through silently:\n *   - worker sessions (the runner sets PAI_WORKER=1)\n *   - paths outside any git work tree\n *   - the exempt scratch prefixes (.claude incl. memory dirs, the local\n *     notes/tasks/scratchpad dirs, the OS temp dir)\n *   - prose: notes, session logs, handovers \u2014 see lib/edit-gate.ts isProseEdit\n * Everything else inside a git work tree is blocked with the worker command.\n *\n * This file is only I/O: read stdin, resolve the path, ask lib/edit-gate for\n * the decision, print it. The decision itself lives in lib/edit-gate.ts and\n * is tested there.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { dirname, join, resolve, sep } from \"node:path\";\nimport { homedir, tmpdir } from \"node:os\";\nimport { decideEditGate, type EditGateContext } from \"../lib/edit-gate.js\";\n\nfunction isUnder(p: string, prefix: string): boolean {\n  return p === prefix || p.startsWith(prefix + sep);\n}\n\n// A .git entry in any ancestor marks a git work tree (.git is a directory in\n// a normal repo, a file in a linked worktree \u2014 existsSync covers both).\nfunction inGitWorkTree(p: string): boolean {\n  let dir = resolve(p);\n  for (;;) {\n    if (existsSync(join(dir, \".git\"))) return true;\n    const parent = dirname(dir);\n    if (parent === dir) return false;\n    dir = parent;\n  }\n}\n\nfunction exemptPrefixes(): string[] {\n  const home = homedir();\n  return [\n    join(home, \".claude\"),\n    join(home, \"colibri\", \"Notes\"),\n    join(home, \"colibri\", \"tasks\"),\n    join(home, \"colibri\", \"scratchpad\"),\n    tmpdir(),\n    \"/tmp\",\n  ];\n}\n\nfunction allow(): void {\n  process.exit(0);\n}\n\nfunction block(reason: string): void {\n  process.stdout.write(JSON.stringify({ decision: \"block\", reason }) + \"\\n\");\n  process.exit(0);\n}\n\nasync function main(): Promise<void> {\n  let text = \"\";\n  try {\n    for await (const chunk of process.stdin) text += chunk;\n  } catch {\n    return allow();\n  }\n  if (!text.trim()) return allow();\n\n  let input: { cwd?: string; tool_input?: { file_path?: string } };\n  try {\n    input = JSON.parse(text);\n  } catch {\n    return allow();\n  }\n\n  const filePath =\n    input.tool_input && typeof input.tool_input.file_path === \"string\" ? input.tool_input.file_path : \"\";\n  if (!filePath) return allow();\n\n  const abs = resolve(input.cwd ?? process.cwd(), filePath);\n\n  const ctx: EditGateContext = {\n    filePath: abs,\n    isPaiWorker: process.env.PAI_WORKER === \"1\",\n    isExemptPrefix: exemptPrefixes().some((p) => isUnder(abs, p)),\n    isGitWorkTree: inGitWorkTree(abs),\n  };\n\n  const decision = decideEditGate(ctx);\n  if (decision.decision === \"block\") return block(decision.reason);\n  return allow();\n}\n\nmain().catch(() => process.exit(0));\n", "/**\n * edit-gate.ts \u2014 the decision behind the PreToolUse edit-routing gate.\n *\n * Pure: no stdin, no filesystem, no process.env read of its own. The hook\n * entry (pre-tool-use/route-edits-to-worker.ts) resolves paths and checks the\n * filesystem/env, then hands this module the already-computed facts to\n * decide on \u2014 same split as lib/agent-gate.ts.\n */\n\nimport { basename, extname } from \"node:path\";\n\nexport interface EditGateContext {\n  /** Absolute, already-resolved target path. */\n  filePath: string;\n  isPaiWorker: boolean;\n  isExemptPrefix: boolean;\n  isGitWorkTree: boolean;\n}\n\nexport type EditGateDecision = { decision: \"allow\" } | { decision: \"block\"; reason: string };\n\n/** Extensions that are prose regardless of which directory they live in. */\nconst PROSE_EXTENSIONS = new Set([\".md\", \".markdown\", \".txt\"]);\n\n/**\n * True for notes, session logs, handovers and other prose the outer session\n * writes directly instead of routing through a worker \u2014 a `Notes`/`notes`\n * directory segment anywhere in the path, or a prose file extension.\n * Extension check runs first: a source file that happens to live under a\n * `notes/` directory is still code, but `src/foo.md` is still prose.\n */\nexport function isProseEdit(filePath: string): boolean {\n  const ext = extname(filePath).toLowerCase();\n  if (PROSE_EXTENSIONS.has(ext)) return true;\n  return filePath.split(/[\\\\/]/).some((segment) => segment === \"Notes\" || segment === \"notes\");\n}\n\nexport function decideEditGate(ctx: EditGateContext): EditGateDecision {\n  if (ctx.isPaiWorker) return { decision: \"allow\" };\n  if (ctx.isExemptPrefix) return { decision: \"allow\" };\n  if (isProseEdit(ctx.filePath)) return { decision: \"allow\" };\n  if (!ctx.isGitWorkTree) return { decision: \"allow\" };\n\n  const label = basename(ctx.filePath).slice(0, 60) || \"edit\";\n  return {\n    decision: \"block\",\n    reason:\n      `main session does not edit code: run a worker instead \u2014 ` +\n      `pai worker run --label \"${label}\" --class implement --output-format json -p <spec> (target: ${ctx.filePath})`,\n  };\n}\n"],
  "mappings": ";;;;;;AAmBA,SAAS,kBAAkB;AAC3B,SAAS,SAAS,MAAM,SAAS,WAAW;AAC5C,SAAS,SAAS,cAAc;;;ACZhC,SAAS,UAAU,eAAe;AAalC,IAAM,mBAAmB,oBAAI,IAAI,CAAC,OAAO,aAAa,MAAM,CAAC;AAStD,SAAS,YAAY,UAA2B;AACrD,QAAM,MAAM,QAAQ,QAAQ,EAAE,YAAY;AAC1C,MAAI,iBAAiB,IAAI,GAAG,EAAG,QAAO;AACtC,SAAO,SAAS,MAAM,OAAO,EAAE,KAAK,CAAC,YAAY,YAAY,WAAW,YAAY,OAAO;AAC7F;AAEO,SAAS,eAAe,KAAwC;AACrE,MAAI,IAAI,YAAa,QAAO,EAAE,UAAU,QAAQ;AAChD,MAAI,IAAI,eAAgB,QAAO,EAAE,UAAU,QAAQ;AACnD,MAAI,YAAY,IAAI,QAAQ,EAAG,QAAO,EAAE,UAAU,QAAQ;AAC1D,MAAI,CAAC,IAAI,cAAe,QAAO,EAAE,UAAU,QAAQ;AAEnD,QAAM,QAAQ,SAAS,IAAI,QAAQ,EAAE,MAAM,GAAG,EAAE,KAAK;AACrD,SAAO;AAAA,IACL,UAAU;AAAA,IACV,QACE,wFAC2B,KAAK,+DAA+D,IAAI,QAAQ;AAAA,EAC/G;AACF;;;AD1BA,SAAS,QAAQ,GAAW,QAAyB;AACnD,SAAO,MAAM,UAAU,EAAE,WAAW,SAAS,GAAG;AAClD;AAIA,SAAS,cAAc,GAAoB;AACzC,MAAI,MAAM,QAAQ,CAAC;AACnB,aAAS;AACP,QAAI,WAAW,KAAK,KAAK,MAAM,CAAC,EAAG,QAAO;AAC1C,UAAM,SAAS,QAAQ,GAAG;AAC1B,QAAI,WAAW,IAAK,QAAO;AAC3B,UAAM;AAAA,EACR;AACF;AAEA,SAAS,iBAA2B;AAClC,QAAM,OAAO,QAAQ;AACrB,SAAO;AAAA,IACL,KAAK,MAAM,SAAS;AAAA,IACpB,KAAK,MAAM,WAAW,OAAO;AAAA,IAC7B,KAAK,MAAM,WAAW,OAAO;AAAA,IAC7B,KAAK,MAAM,WAAW,YAAY;AAAA,IAClC,OAAO;AAAA,IACP;AAAA,EACF;AACF;AAEA,SAAS,QAAc;AACrB,UAAQ,KAAK,CAAC;AAChB;AAEA,SAAS,MAAM,QAAsB;AACnC,UAAQ,OAAO,MAAM,KAAK,UAAU,EAAE,UAAU,SAAS,OAAO,CAAC,IAAI,IAAI;AACzE,UAAQ,KAAK,CAAC;AAChB;AAEA,eAAe,OAAsB;AACnC,MAAI,OAAO;AACX,MAAI;AACF,qBAAiB,SAAS,QAAQ,MAAO,SAAQ;AAAA,EACnD,QAAQ;AACN,WAAO,MAAM;AAAA,EACf;AACA,MAAI,CAAC,KAAK,KAAK,EAAG,QAAO,MAAM;AAE/B,MAAI;AACJ,MAAI;AACF,YAAQ,KAAK,MAAM,IAAI;AAAA,EACzB,QAAQ;AACN,WAAO,MAAM;AAAA,EACf;AAEA,QAAM,WACJ,MAAM,cAAc,OAAO,MAAM,WAAW,cAAc,WAAW,MAAM,WAAW,YAAY;AACpG,MAAI,CAAC,SAAU,QAAO,MAAM;AAE5B,QAAM,MAAM,QAAQ,MAAM,OAAO,QAAQ,IAAI,GAAG,QAAQ;AAExD,QAAM,MAAuB;AAAA,IAC3B,UAAU;AAAA,IACV,aAAa,QAAQ,IAAI,eAAe;AAAA,IACxC,gBAAgB,eAAe,EAAE,KAAK,CAAC,MAAM,QAAQ,KAAK,CAAC,CAAC;AAAA,IAC5D,eAAe,cAAc,GAAG;AAAA,EAClC;AAEA,QAAM,WAAW,eAAe,GAAG;AACnC,MAAI,SAAS,aAAa,QAAS,QAAO,MAAM,SAAS,MAAM;AAC/D,SAAO,MAAM;AACf;AAEA,KAAK,EAAE,MAAM,MAAM,QAAQ,KAAK,CAAC,CAAC;",
  "names": []
}
