{"version":3,"file":"common-CQVJccPS.mjs","names":[],"sources":["../src/eslint/rules/common.ts"],"sourcesContent":["/**\n * Shared `createRule` factory for the published `@nhtio/adk` ESLint rules.\n *\n * @remarks\n * Wraps `ESLintUtils.RuleCreator` so every rule gets a consistent `meta.docs.url` pointing at the\n * published rule reference. Each rule module imports this and default-exports `createRule({...})`.\n *\n * `@typescript-eslint/utils` is an OPTIONAL peer dependency of `@nhtio/adk` — it is only needed by\n * consumers who import `@nhtio/adk/eslint`. The main library never imports this module.\n */\n\nimport { ESLintUtils } from '@typescript-eslint/utils'\n\nimport type { TSESTree } from '@typescript-eslint/utils'\n\n/**\n * Rule factory for `@nhtio/adk` ESLint rules. The `name` passed to each rule becomes the slug in the\n * generated documentation URL.\n */\nexport const createRule = ESLintUtils.RuleCreator(\n  (name) => `https://adk.nht.io/eslint/rules/${name}`\n)\n\n/** Any function-like AST node (arrow, function expression, or declaration). */\nexport type FunctionNode =\n  | TSESTree.FunctionDeclaration\n  | TSESTree.FunctionExpression\n  | TSESTree.ArrowFunctionExpression\n\nexport const isFunctionNode = (node: TSESTree.Node): node is FunctionNode =>\n  node.type === 'FunctionDeclaration' ||\n  node.type === 'FunctionExpression' ||\n  node.type === 'ArrowFunctionExpression'\n\n// Provider SDK constructor names whose presence inside a handler/middleware indicates a primary\n// model call. Conservative, well-known set — keeps false positives low.\nconst LLM_CTOR_NAMES = new Set([\n  'OpenAI',\n  'AzureOpenAI',\n  'Anthropic',\n  'AnthropicBedrock',\n  'AnthropicVertex',\n  'GoogleGenerativeAI',\n  'GoogleGenAI',\n  'Mistral',\n  'CohereClient',\n  'CohereClientV2',\n  'Groq',\n])\n\n// Method-name tails that indicate a chat/completion/generation call on a provider client, e.g.\n// `client.chat.completions.create(...)`, `client.messages.create(...)`, `model.generateContent(...)`.\nconst LLM_METHOD_NAMES = new Set(['generateContent', 'generateContentStream', 'generateMessage'])\n\nconst memberPropertyName = (node: TSESTree.MemberExpression): string | undefined =>\n  node.property.type === 'Identifier' ? node.property.name : undefined\n\n/**\n * Heuristic: does this call/new expression look like a primary LLM invocation? Detects known\n * provider SDK constructors and `…create()` calls whose receiver chain passes through\n * `chat`/`completions`/`messages`/`responses`, plus a small set of generate* methods. Deliberately\n * conservative — the goal is to catch the obvious footgun, not to be exhaustive.\n */\nexport const isLlmCall = (node: TSESTree.Node): boolean => {\n  if (node.type === 'NewExpression') {\n    return node.callee.type === 'Identifier' && LLM_CTOR_NAMES.has(node.callee.name)\n  }\n  if (node.type === 'CallExpression' && node.callee.type === 'MemberExpression') {\n    const method = memberPropertyName(node.callee)\n    if (method && LLM_METHOD_NAMES.has(method)) return true\n    // `<chain>.create()` where the chain passes through a provider sub-resource.\n    if (method === 'create') {\n      let cur: TSESTree.Node = node.callee.object\n      while (cur.type === 'MemberExpression') {\n        const seg = memberPropertyName(cur)\n        if (seg === 'completions' || seg === 'messages' || seg === 'responses') return true\n        cur = cur.object\n      }\n    }\n  }\n  return false\n}\n\n/**\n * Walks the descendants of a function body (NOT crossing into nested function scopes) and invokes\n * `visit` for every node, so a rule can scan a handler/middleware body for a pattern while ignoring\n * inner closures (e.g. a `new TurnRunner` sub-agent's own callbacks).\n */\nexport const walkBodySkippingNestedFunctions = (\n  body: TSESTree.Node,\n  visit: (node: TSESTree.Node) => void\n): void => {\n  const recurse = (node: TSESTree.Node): void => {\n    visit(node)\n    for (const key of Object.keys(node)) {\n      if (key === 'parent') continue\n      const value = (node as unknown as Record<string, unknown>)[key]\n      const children = Array.isArray(value) ? value : [value]\n      for (const child of children) {\n        if (!child || typeof child !== 'object') continue\n        const childNode = child as TSESTree.Node\n        if (typeof childNode.type !== 'string') continue\n        // Do not descend into nested function scopes — their calls belong to a different context.\n        if (isFunctionNode(childNode)) continue\n        recurse(childNode)\n      }\n    }\n  }\n  recurse(body)\n}\n\n// A sub-agent is run through one of two blessed entry points: constructing a scoped\n// `new TurnRunner(...)`, or the lower-level static `DispatchRunner.dispatch(...)` (its constructor\n// is token-gated private, so `dispatch()` is the real entry point). Either one inside a tool handler\n// means \"this tool is deliberately a sub-agent,\" which is the documented exception to\n// no-model-in-tool-handler.\nconst isSubAgentEntry = (node: TSESTree.Node): boolean => {\n  // new TurnRunner(...)\n  if (\n    node.type === 'NewExpression' &&\n    node.callee.type === 'Identifier' &&\n    node.callee.name === 'TurnRunner'\n  ) {\n    return true\n  }\n  // DispatchRunner.dispatch(...)\n  if (\n    node.type === 'CallExpression' &&\n    node.callee.type === 'MemberExpression' &&\n    node.callee.property.type === 'Identifier' &&\n    node.callee.property.name === 'dispatch' &&\n    node.callee.object.type === 'Identifier' &&\n    node.callee.object.name === 'DispatchRunner'\n  ) {\n    return true\n  }\n  return false\n}\n\n/**\n * True when the function body runs a scoped sub-agent — either `new TurnRunner(...)` or\n * `DispatchRunner.dispatch(...)` — the documented escape hatch that exempts a tool handler from\n * {@link isLlmCall} flagging.\n */\nexport const runsSubAgent = (body: TSESTree.Node): boolean => {\n  let found = false\n  const recurse = (node: TSESTree.Node): void => {\n    if (found) return\n    if (isSubAgentEntry(node)) {\n      found = true\n      return\n    }\n    for (const key of Object.keys(node)) {\n      if (key === 'parent') continue\n      const value = (node as unknown as Record<string, unknown>)[key]\n      const children = Array.isArray(value) ? value : [value]\n      for (const child of children) {\n        if (!child || typeof child !== 'object') continue\n        const childNode = child as TSESTree.Node\n        if (typeof childNode.type !== 'string') continue\n        recurse(childNode)\n      }\n    }\n  }\n  recurse(body)\n  return found\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAmBA,IAAa,aAAa,YAAY,aACnC,SAAS,mCAAmC,MAC/C;AAQA,IAAa,kBAAkB,SAC7B,KAAK,SAAS,yBACd,KAAK,SAAS,wBACd,KAAK,SAAS;AAIhB,IAAM,iBAAiB,IAAI,IAAI;CAC7B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAID,IAAM,mBAAmB,IAAI,IAAI;CAAC;CAAmB;CAAyB;AAAiB,CAAC;AAEhG,IAAM,sBAAsB,SAC1B,KAAK,SAAS,SAAS,eAAe,KAAK,SAAS,OAAO,KAAA;;;;;;;AAQ7D,IAAa,aAAa,SAAiC;CACzD,IAAI,KAAK,SAAS,iBAChB,OAAO,KAAK,OAAO,SAAS,gBAAgB,eAAe,IAAI,KAAK,OAAO,IAAI;CAEjF,IAAI,KAAK,SAAS,oBAAoB,KAAK,OAAO,SAAS,oBAAoB;EAC7E,MAAM,SAAS,mBAAmB,KAAK,MAAM;EAC7C,IAAI,UAAU,iBAAiB,IAAI,MAAM,GAAG,OAAO;EAEnD,IAAI,WAAW,UAAU;GACvB,IAAI,MAAqB,KAAK,OAAO;GACrC,OAAO,IAAI,SAAS,oBAAoB;IACtC,MAAM,MAAM,mBAAmB,GAAG;IAClC,IAAI,QAAQ,iBAAiB,QAAQ,cAAc,QAAQ,aAAa,OAAO;IAC/E,MAAM,IAAI;GACZ;EACF;CACF;CACA,OAAO;AACT;;;;;;AAOA,IAAa,mCACX,MACA,UACS;CACT,MAAM,WAAW,SAA8B;EAC7C,MAAM,IAAI;EACV,KAAK,MAAM,OAAO,OAAO,KAAK,IAAI,GAAG;GACnC,IAAI,QAAQ,UAAU;GACtB,MAAM,QAAS,KAA4C;GAC3D,MAAM,WAAW,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;GACtD,KAAK,MAAM,SAAS,UAAU;IAC5B,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU;IACzC,MAAM,YAAY;IAClB,IAAI,OAAO,UAAU,SAAS,UAAU;IAExC,IAAI,eAAe,SAAS,GAAG;IAC/B,QAAQ,SAAS;GACnB;EACF;CACF;CACA,QAAQ,IAAI;AACd;AAOA,IAAM,mBAAmB,SAAiC;CAExD,IACE,KAAK,SAAS,mBACd,KAAK,OAAO,SAAS,gBACrB,KAAK,OAAO,SAAS,cAErB,OAAO;CAGT,IACE,KAAK,SAAS,oBACd,KAAK,OAAO,SAAS,sBACrB,KAAK,OAAO,SAAS,SAAS,gBAC9B,KAAK,OAAO,SAAS,SAAS,cAC9B,KAAK,OAAO,OAAO,SAAS,gBAC5B,KAAK,OAAO,OAAO,SAAS,kBAE5B,OAAO;CAET,OAAO;AACT;;;;;;AAOA,IAAa,gBAAgB,SAAiC;CAC5D,IAAI,QAAQ;CACZ,MAAM,WAAW,SAA8B;EAC7C,IAAI,OAAO;EACX,IAAI,gBAAgB,IAAI,GAAG;GACzB,QAAQ;GACR;EACF;EACA,KAAK,MAAM,OAAO,OAAO,KAAK,IAAI,GAAG;GACnC,IAAI,QAAQ,UAAU;GACtB,MAAM,QAAS,KAA4C;GAC3D,MAAM,WAAW,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;GACtD,KAAK,MAAM,SAAS,UAAU;IAC5B,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU;IACzC,MAAM,YAAY;IAClB,IAAI,OAAO,UAAU,SAAS,UAAU;IACxC,QAAQ,SAAS;GACnB;EACF;CACF;CACA,QAAQ,IAAI;CACZ,OAAO;AACT"}