{"version":3,"file":"index.cjs","names":["fs","path","buildSkillCatalog","counter","buildPersonaPrompt","priceMcpToolSchemas","globalDoomConfigDirectory","BaseCommand"],"sources":["../../../../src/cli/commands/explain/index.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { buildPersonaPrompt, globalDoomConfigDirectory } from '@agimon-ai/doompi-config';\nimport { type McpServerCost, priceMcpToolSchemas } from '@agimon-ai/doompi-core/mcp-schema-cost';\nimport { buildSkillCatalog, counter } from '@agimon-ai/doompi-skill/catalog';\n\nimport type { HarnessContext } from '../../../builders/cli/harnessContext';\nimport type { HarnessOptions } from '../../../composition/types/harness';\nimport { BaseCommand } from '../baseCommand';\n\n/**\n * What the selection costs before the first prompt, in tokens.\n *\n * Every figure is an estimate of what the provider will be sent, not a billed\n * total: skills are counted from files on disk and MCP tools from the schemas\n * their servers advertise, both with the same tokenizer.\n */\nexport interface MatrixCost {\n  /** The always-on skill block Pi puts in the system prompt. */\n  skillPromptTokens: number;\n  /** Skill bodies on disk, read only when a skill is invoked. */\n  skillBodyTokens: number;\n  /** The persona prompt, when a profile selected one. */\n  personaTokens: number;\n  /** Always-on tool schemas, per configured MCP server. */\n  mcpServers: McpServerCost[];\n  mcpToolTokens: number;\n}\n\nexport interface MatrixExplanation {\n  majorMode: string;\n  profile?: string;\n  persona?: string;\n  domains: string[];\n  plugins: string[];\n  layers: string[];\n  hookGroups: string[];\n  hooksEnabled: boolean;\n  skillDirectories: string[];\n  skillCount: number;\n  agentCount: number;\n  agentDirectories: string[];\n  mcpConfigPath?: string;\n  mcpServers: string[];\n  mcpProxyUpstreams?: string[];\n  mcpFiltered: boolean;\n  cost?: MatrixCost;\n}\n\nfunction readMcpServers(configPath: string): Record<string, unknown> {\n  const config = JSON.parse(fs.readFileSync(configPath, 'utf8')) as { mcpServers?: Record<string, unknown> };\n  return config.mcpServers ?? {};\n}\n\n/**\n * Renders the resolved matrix for --explain. Composition spans three config\n * files, so this answers \"why is that loaded\" in one command instead of a\n * manual trace through domains, major modes, and plugin subsets.\n */\nexport function explainMatrix(explanation: MatrixExplanation): string {\n  const section = (label: string, values: string[], empty = '(none)'): string =>\n    `${label}:\\n${values.length === 0 ? `  ${empty}` : values.map((value) => `  ${value}`).join('\\n')}\\n`;\n\n  return [\n    `doompi resolved matrix\\n\\n`,\n    `major mode: ${explanation.majorMode}\\n\\n`,\n    section('profile', explanation.profile ? [explanation.profile] : [], '(none)'),\n    '\\n',\n    section('persona', explanation.persona ? [explanation.persona] : [], '(none)'),\n    '\\n',\n    section('domains (content)', explanation.domains),\n    '\\n',\n    section('layers (behavior, from the major mode)', explanation.layers, 'core only'),\n    '\\n',\n    section(\n      'hook groups from layers',\n      explanation.hookGroups,\n      explanation.hooksEnabled ? '(core groups only)' : '(hooks disabled)',\n    ),\n    '\\n',\n    section(\n      'plugins',\n      explanation.plugins.map((directory) => path.basename(directory)),\n    ),\n    '\\n',\n    section(\n      'skill paths',\n      explanation.skillDirectories.map((directory) => path.relative(process.cwd(), directory)),\n    ),\n    '\\n',\n    section(\n      explanation.mcpFiltered ? 'mcp servers (filtered by domain allowlist)' : 'mcp servers (unfiltered)',\n      explanation.mcpServers,\n      '(mcp disabled)',\n    ),\n    '\\n',\n    explanation.mcpProxyUpstreams\n      ? section('mcp proxy upstreams (filtered)', explanation.mcpProxyUpstreams)\n      : 'mcp proxy upstreams:\\n  (unfiltered, every upstream in mcp-config.yaml)\\n',\n    '\\n',\n    `skills: ${explanation.skillCount} (from ${explanation.skillDirectories.length} paths)\\n`,\n    `agents: ${explanation.agentCount}\\n`,\n    `mcp config: ${explanation.mcpConfigPath ?? '(disabled)'}\\n`,\n    explanation.cost ? renderCost(explanation.cost) : '',\n  ].join('');\n}\n\nconst TOKEN_COLUMN = 9;\n\nfunction tokenLine(label: string, tokens: number, note = ''): string {\n  const row = `  ${label.padEnd(16)}${tokens.toLocaleString('en-US').padStart(TOKEN_COLUMN)}`;\n  return `${note ? `${row}  ${note}` : row}\\n`;\n}\n\n/**\n * The bill, rendered last so the sections above keep their order.\n *\n * Skill and persona figures are derived from files on disk and MCP figures\n * from a cached handshake, so two runs of the same selection print the same\n * numbers and a reader can reproduce them without trusting this output.\n */\nfunction renderCost(cost: MatrixCost): string {\n  const startup = cost.skillPromptTokens + cost.personaTokens + cost.mcpToolTokens;\n  const unpriced = cost.mcpServers.filter((server) => server.unavailable);\n  return [\n    '\\ncontext cost (tokens)\\n',\n    tokenLine('skills prompt', cost.skillPromptTokens, 'always on'),\n    tokenLine('persona', cost.personaTokens, 'always on'),\n    tokenLine('mcp tools', cost.mcpToolTokens, mcpNote(cost.mcpServers)),\n    tokenLine('startup total', startup),\n    tokenLine('skill bodies', cost.skillBodyTokens, 'read on demand'),\n    ...cost.mcpServers.filter((server) => !server.unavailable).map(serverLine),\n    unpriced.length > 0\n      ? `\\n  Not priced: ${unpriced.map((server) => `${server.name} (${server.unavailable})`).join(', ')}\\n`\n      : '',\n    '\\n  Excludes skills contributed by extensions, which register after startup.\\n',\n  ].join('');\n}\n\nfunction mcpNote(servers: McpServerCost[]): string {\n  const priced = servers.filter((server) => !server.unavailable);\n  if (priced.length === 0) return 'always on';\n  const tools = priced.reduce((total, server) => total + server.toolCount, 0);\n  return `always on, ${tools} tools from ${priced.length} server${priced.length === 1 ? '' : 's'}`;\n}\n\nfunction serverLine(server: McpServerCost): string {\n  return tokenLine(`  ${server.name}`, server.tokens, server.cached ? 'cached' : 'measured now');\n}\n\n/** Prints the fully resolved matrix instead of launching Pi. */\nasync function costExplain(context: HarnessContext): Promise<MatrixCost | undefined> {\n  const { options, resources } = context;\n  try {\n    const [catalog, countTokens] = await Promise.all([\n      buildSkillCatalog({\n        repoRoot: options.repoRoot,\n        activeSkillDirectories: resources.skillDirectories,\n        extensionSources: [],\n      }),\n      counter(),\n    ]);\n    const persona = context.personaDirectory\n      ? buildPersonaPrompt(context.personaRoot ?? options.repoRoot, context.personaDirectory)\n      : undefined;\n    const mcp = resources.mcpConfigPath\n      ? await priceMcpToolSchemas({\n          configPath: resources.mcpConfigPath,\n          homeDoomDirectory: globalDoomConfigDirectory(),\n          cwd: options.repoRoot,\n          environment: context.environment,\n          countTokens,\n        })\n      : { servers: [], totalTokens: 0 };\n    return {\n      skillPromptTokens: catalog.promptTokens,\n      skillBodyTokens: catalog.bodyTokens,\n      personaTokens: persona ? countTokens(persona) : 0,\n      mcpServers: mcp.servers,\n      mcpToolTokens: mcp.totalTokens,\n    };\n  } catch (error) {\n    // Pricing reads the skill files, so an unreadable one must not take the\n    // whole explanation down: the resolved matrix is the point, the bill is\n    // the extra. Reported rather than swallowed.\n    const detail = error instanceof Error ? error.message : String(error);\n    process.stderr.write(`[doompi] ${'explain'}: could not price the selection: ${detail}\\n`);\n    return undefined;\n  }\n}\n\nexport async function runExplain(context: HarnessContext): Promise<number> {\n  const { options, resources, plugins, mcpAllowlist } = context;\n  const cost = await costExplain(context);\n\n  process.stdout.write(\n    explainMatrix({\n      cost,\n      majorMode: options.majorMode,\n      profile: context.profile,\n      persona: context.personaDirectory,\n      domains: options.domains,\n      plugins: plugins.map((entry) => entry.directory),\n      layers: context.selectedLayers,\n      hookGroups: context.hookGroups,\n      hooksEnabled: options.hooks,\n      skillDirectories: resources.skillDirectories,\n      skillCount: resources.skillCount,\n      agentCount: resources.agentCount,\n      agentDirectories: resources.agentDirectories,\n      mcpConfigPath: resources.mcpConfigPath,\n      // Read back from the generated config so this reports what Pi will\n      // actually load, not what the allowlist intended.\n      mcpServers: resources.mcpConfigPath ? Object.keys(readMcpServers(resources.mcpConfigPath)) : [],\n      mcpProxyUpstreams: mcpAllowlist?.proxy,\n      mcpFiltered: Boolean(mcpAllowlist),\n    }),\n  );\n  return 0;\n}\n\n/** Compatibility API. Executables call the command function directly. */\nexport class ExplainCommand extends BaseCommand {\n  readonly name = 'explain';\n\n  matches(options: HarnessOptions): boolean {\n    return options.explain;\n  }\n\n  async execute(context: HarnessContext): Promise<number> {\n    return runExplain(context);\n  }\n}\n"],"mappings":"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"}