get-tbd 0.1.26 → 0.1.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.mjs","names":["BUILD_VERSION","parseYaml","execFileAsync","normalizePath","addCommand","removeCommand"],"sources":["../src/cli/lib/version.ts","../src/cli/lib/context.ts","../src/cli/lib/output.ts","../src/cli/lib/errors.ts","../src/cli/lib/base-command.ts","../src/utils/file-utils.ts","../src/utils/gitignore-utils.ts","../src/cli/lib/prefix-detection.ts","../src/utils/time-utils.ts","../src/file/git.ts","../src/cli/commands/init.ts","../src/file/storage.ts","../src/lib/priority.ts","../src/lib/project-paths.ts","../src/cli/commands/create.ts","../src/cli/lib/limit-utils.ts","../src/cli/lib/data-context.ts","../src/lib/status.ts","../src/lib/truncate.ts","../src/cli/lib/issue-format.ts","../src/cli/lib/tree-view.ts","../src/lib/spec-matching.ts","../src/cli/commands/list.ts","../src/cli/commands/show.ts","../src/cli/commands/update.ts","../src/cli/commands/close.ts","../src/cli/commands/reopen.ts","../src/cli/commands/ready.ts","../src/cli/commands/blocked.ts","../src/cli/commands/stale.ts","../src/cli/commands/label.ts","../src/cli/commands/dep.ts","../src/lib/sync-summary.ts","../src/file/github-fetch.ts","../src/file/doc-sync.ts","../src/file/workspace.ts","../src/cli/commands/sync.ts","../src/lib/format-utils.ts","../src/cli/commands/search.ts","../src/cli/lib/sections.ts","../src/lib/integration-paths.ts","../src/cli/commands/status.ts","../src/cli/commands/stats.ts","../src/cli/lib/diagnostics.ts","../src/cli/commands/doctor.ts","../src/cli/commands/config.ts","../src/cli/commands/attic.ts","../src/cli/commands/import.ts","../src/cli/commands/docs.ts","../src/cli/commands/closing.ts","../src/cli/commands/design.ts","../src/cli/commands/readme.ts","../src/cli/commands/uninstall.ts","../src/file/doc-cache.ts","../src/cli/commands/prime.ts","../src/cli/commands/skill.ts","../src/cli/lib/doc-prompts.ts","../src/file/doc-add.ts","../src/cli/commands/shortcut.ts","../src/cli/lib/doc-command-handler.ts","../src/cli/commands/guidelines.ts","../src/cli/commands/template.ts","../src/cli/commands/setup.ts","../src/cli/commands/save.ts","../src/cli/commands/workspace.ts","../src/cli/cli.ts"],"sourcesContent":["/**\n * CLI version detection\n *\n * Priority:\n * 1. Build-time injected __TBD_VERSION__ (production builds)\n * 2. TBD_DEV_VERSION env var (dev mode, set by pnpm tbd script)\n * 3. package.json version (fallback)\n *\n * No git dependency at runtime - git version is computed at build time\n * or by the dev script wrapper.\n */\n\nimport { createRequire } from 'node:module';\n\nimport { VERSION as BUILD_VERSION } from '../../index.js';\n\n/**\n * Get the CLI version.\n */\nfunction getVersion(): string {\n // 1. Build-time injected version (production)\n if (BUILD_VERSION !== 'development') {\n return BUILD_VERSION;\n }\n\n // 2. Dev mode env var (set by pnpm tbd script)\n if (process.env.TBD_DEV_VERSION) {\n return process.env.TBD_DEV_VERSION;\n }\n\n // 3. Fallback to package.json version\n const require = createRequire(import.meta.url);\n const pkg = require('../../../package.json') as { version: string };\n return pkg.version;\n}\n\n/**\n * CLI version - use this instead of importing VERSION directly from index.ts\n */\nexport const VERSION = getVersion();\n","/**\n * Command context and global options management.\n *\n * See: research-modern-typescript-cli-patterns.md#9-global-options\n */\n\nimport type { Command } from 'commander';\n\n/**\n * Output format options.\n */\nexport type OutputFormat = 'text' | 'json';\n\n/**\n * Color output options.\n */\nexport type ColorOption = 'auto' | 'always' | 'never';\n\n/**\n * Global command context extracted from Commander options.\n */\nexport interface CommandContext {\n dryRun: boolean;\n verbose: boolean;\n quiet: boolean;\n json: boolean;\n color: ColorOption;\n sync: boolean;\n /** Debug mode: shows internal IDs alongside public IDs */\n debug: boolean;\n}\n\n/**\n * Extract command context from a Commander command.\n * Handles inheritance of global options through the command hierarchy.\n */\nexport function getCommandContext(command: Command): CommandContext {\n const opts = command.optsWithGlobals();\n\n return {\n dryRun: opts.dryRun ?? false,\n verbose: opts.verbose ?? false,\n quiet: opts.quiet ?? false,\n json: opts.json ?? false,\n color: (opts.color as ColorOption) ?? 'auto',\n sync: opts.sync !== false, // --no-sync sets this to false\n debug: opts.debug ?? false,\n };\n}\n\n/**\n * Determine if output should be colorized based on options and environment.\n */\nexport function shouldColorize(colorOption: ColorOption): boolean {\n // NO_COLOR takes precedence (unless --color=always explicitly set)\n if (process.env.NO_COLOR && colorOption !== 'always') {\n return false;\n }\n if (colorOption === 'always') {\n return true;\n }\n if (colorOption === 'never') {\n return false;\n }\n return process.stdout.isTTY ?? false;\n}\n\n/**\n * Check if we should use interactive output features (colors, pagination).\n * Returns true when: TTY output, not JSON mode, not quiet mode.\n *\n * This is specifically for human-readable output formatting. Agents typically\n * capture output via pipes (isTTY=false) or use --json mode, so they get\n * clean plain text without ANSI codes or pagination.\n */\nexport function shouldUseInteractiveOutput(ctx: CommandContext): boolean {\n return !ctx.json && !ctx.quiet && shouldColorize(ctx.color) && process.stdout.isTTY === true;\n}\n","/**\n * OutputManager for dual-mode output (text + JSON).\n *\n * See: research-modern-typescript-cli-patterns.md#4-dual-output-mode-text--json\n */\n\nimport pc from 'picocolors';\nimport type { Command } from 'commander';\nimport { marked } from 'marked';\nimport { markedTerminal } from 'marked-terminal';\nimport { spawn } from 'node:child_process';\n\nimport type { CommandContext, ColorOption } from './context.js';\nimport { shouldColorize } from './context.js';\nimport { PAGINATION_LINE_THRESHOLD } from '../../lib/settings.js';\nimport { parseMarkdown } from '../../utils/markdown-utils.js';\nimport type { OperationLogger } from '../../lib/types.js';\n\n/**\n * Standard icons for CLI output. Use these constants instead of hardcoded characters.\n *\n * Message icons (prefix messages with these):\n * - SUCCESS_ICON: ✓ for completed operations\n * - ERROR_ICON: ✗ for failures\n * - WARN_ICON: ⚠ for warnings\n * - NOTICE_ICON: • for notices/bullets\n *\n * Status icons (show issue status):\n * - OPEN_ICON: ○ for open issues\n * - IN_PROGRESS_ICON: ◐ for in-progress issues\n * - BLOCKED_ICON: ● for blocked issues\n * - CLOSED_ICON: ✓ for closed issues (same as SUCCESS_ICON)\n */\n\n/**\n * Format a section heading - ALL CAPS for consistent CLI output.\n * Per arch-cli-interface-design-system.md, section headings should be\n * ALL CAPS, bold, followed by blank line before content.\n *\n * @param text - The heading text to format\n * @returns Uppercase heading string\n */\nexport function formatHeading(text: string): string {\n return text.toUpperCase();\n}\n\nexport const ICONS = {\n // Message icons\n SUCCESS: '✓', // U+2713\n ERROR: '✗', // U+2717\n WARN: '⚠', // U+26A0\n NOTICE: '•', // U+2022\n\n // Status icons\n OPEN: '○', // U+25CB\n IN_PROGRESS: '◐', // U+25D0\n BLOCKED: '●', // U+25CF\n CLOSED: '✓', // U+2713 (same as SUCCESS)\n DEFERRED: '○', // U+25CB (same as OPEN)\n} as const;\n\n/**\n * Pre-parse argv to determine color setting before Commander parses options.\n * This is needed because help output happens before full option parsing.\n */\nexport function getColorOptionFromArgv(): ColorOption {\n const colorArg = process.argv.find((arg) => arg.startsWith('--color='));\n if (colorArg) {\n const value = colorArg.split('=')[1];\n if (value === 'always' || value === 'never' || value === 'auto') {\n return value;\n }\n }\n // Check for --color followed by value\n const colorIdx = process.argv.indexOf('--color');\n if (colorIdx !== -1 && process.argv[colorIdx + 1]) {\n const value = process.argv[colorIdx + 1];\n if (value === 'always' || value === 'never' || value === 'auto') {\n return value;\n }\n }\n return 'auto';\n}\n\n/**\n * Maximum width for help text and formatted output. We cap at 88 characters\n * for readability, but use narrower if the terminal is smaller.\n */\nexport const MAX_HELP_WIDTH = 88;\n\n/**\n * Get terminal width capped at MAX_HELP_WIDTH.\n * Use this for all formatted CLI output to ensure consistent width handling.\n */\nexport function getTerminalWidth(): number {\n return Math.min(MAX_HELP_WIDTH, process.stdout.columns ?? 80);\n}\n\n/**\n * Create colored help configuration for Commander.js.\n * Uses Commander's built-in configureHelp() style functions (requires v14+).\n *\n * @param colorOption - Color option to determine if colors should be enabled\n * @returns Help configuration object for program.configureHelp()\n */\nexport function createColoredHelpConfig(colorOption: ColorOption = 'auto') {\n const colors = pc.createColors(shouldColorize(colorOption));\n\n return {\n helpWidth: getTerminalWidth(),\n styleTitle: (str: string) => colors.bold(colors.cyan(str)),\n styleCommandText: (str: string) => colors.green(str),\n styleOptionText: (str: string) => colors.yellow(str),\n showGlobalOptions: true,\n };\n}\n\n/**\n * Create the help epilog text with color.\n * Includes \"Getting Started\" section and prominent agent guidance per spec.\n *\n * @param colorOption - Color option to determine if colors should be enabled\n * @returns Colored epilog string\n */\nexport function createHelpEpilog(colorOption: ColorOption = 'auto'): string {\n const colors = pc.createColors(shouldColorize(colorOption));\n const lines = [\n colors.bold(colors.yellow('IMPORTANT:')),\n ` Agents unfamiliar with tbd should run ${colors.green('`tbd prime`')} for full workflow context.`,\n '',\n colors.bold('Getting Started:'),\n ` ${colors.green('npm install -g get-tbd@latest && tbd setup --auto --prefix=<name>')}`,\n '',\n ' This initializes tbd and configures your coding agents automatically.',\n ` To refresh setup (idempotent, safe anytime): ${colors.green('`tbd setup --auto`')}`,\n ` For interactive setup: ${colors.dim('`tbd setup --interactive`')}`,\n '',\n colors.blue('For more on tbd, see: https://github.com/jlevy/tbd'),\n ];\n return lines.join('\\n');\n}\n\n/**\n * Configure Commander.js with colored help text.\n * Call this on the program before adding commands.\n */\nexport function configureColoredHelp(program: Command): Command {\n const colorOption = getColorOptionFromArgv();\n return program.configureHelp(createColoredHelpConfig(colorOption));\n}\n\n/**\n * Color utilities with conditional colorization.\n *\n * Uses picocolors' createColors() for manual color support control,\n * which is the recommended approach per picocolors documentation.\n * This allows --color=always to work even when stdout is not a TTY.\n */\nexport function createColors(colorOption: ColorOption) {\n const enabled = shouldColorize(colorOption);\n\n // Use picocolors' createColors() for proper manual control\n // This overrides picocolors' automatic TTY detection\n const colors = pc.createColors(enabled);\n\n return {\n // Status colors\n success: colors.green,\n error: colors.red,\n warn: colors.yellow,\n info: colors.blue,\n\n // Text formatting\n bold: colors.bold,\n dim: colors.dim,\n italic: colors.italic,\n underline: colors.underline,\n\n // Semantic colors\n id: colors.cyan,\n label: colors.magenta,\n path: colors.blue,\n };\n}\n\n/**\n * Render Markdown to colorized terminal output.\n *\n * Uses marked-terminal for colorized output when colors are enabled,\n * falls back to plain Markdown when colors are disabled or piped.\n * Respects the --color option and TTY detection.\n *\n * @param content - Markdown string to render\n * @param colorOption - Color option to determine if colors should be enabled\n * @returns Rendered string (colorized or plain)\n */\nexport function renderMarkdown(content: string, colorOption: ColorOption = 'auto'): string {\n const useColors = shouldColorize(colorOption);\n\n if (!useColors) {\n // Return plain markdown when colors are disabled\n return content;\n }\n\n // Configure marked with terminal renderer for this parse\n // Note: @types/marked-terminal is outdated; markedTerminal returns MarkedExtension in v7+\n // but types still claim it returns TerminalRenderer. Cast to work around this.\n marked.use(\n markedTerminal({\n width: getTerminalWidth(),\n reflowText: true,\n }) as unknown as Parameters<typeof marked.use>[0],\n );\n\n // marked.parse returns string with sync renderer\n return marked.parse(content) as string;\n}\n\n/**\n * Render YAML frontmatter with custom styling.\n * Keys are dimmed, values are bold.\n *\n * @param frontmatter - Raw YAML frontmatter string (without --- delimiters)\n * @returns Styled frontmatter string\n */\nfunction renderYamlFrontmatter(frontmatter: string): string {\n const lines = frontmatter.split('\\n');\n const styledLines = lines.map((line) => {\n // Match YAML key: value pattern\n const match = /^(\\s*)([^:]+:)(.*)$/.exec(line);\n if (match) {\n const [, indent, key, value] = match;\n // Key (including colon) is dim, value is bold\n return indent + pc.dim(key) + pc.bold(value);\n }\n // Lines without key: pattern (e.g., continuation lines) stay as-is but bold\n return pc.bold(line);\n });\n return styledLines.join('\\n');\n}\n\n/**\n * Render markdown with proper YAML frontmatter handling.\n *\n * Separates YAML frontmatter from markdown body and renders them appropriately:\n * - Frontmatter keys are dimmed, values are bold (no indentation)\n * - Body is rendered as regular markdown\n *\n * Works with or without frontmatter - if no frontmatter exists, renders as plain markdown.\n *\n * @param content - Markdown string (possibly with YAML frontmatter) to render\n * @param colorOption - Color option to determine if colors should be enabled\n * @returns Rendered string (colorized or plain)\n */\nexport function renderMarkdownWithFrontmatter(\n content: string,\n colorOption: ColorOption = 'auto',\n): string {\n const useColors = shouldColorize(colorOption);\n\n if (!useColors) {\n // Return plain markdown when colors are disabled\n return content;\n }\n\n const { frontmatter, body } = parseMarkdown(content);\n\n let result = '';\n\n // Render frontmatter with custom YAML styling if present\n if (frontmatter !== null && frontmatter.length > 0) {\n result += pc.dim('---') + '\\n';\n result += renderYamlFrontmatter(frontmatter) + '\\n';\n result += pc.dim('---') + '\\n\\n';\n }\n\n // Render body as markdown\n if (body) {\n result += renderMarkdown(body, colorOption);\n }\n\n return result;\n}\n\n/**\n * Output content with pagination if it exceeds threshold and TTY is interactive.\n * Uses PAGER env var or falls back to 'less -R' (supports colors).\n *\n * When output is piped or not a TTY, outputs directly without pagination.\n * This ensures agents and scripts get clean output.\n *\n * @param content - Content to output\n * @param hasColors - If true, content has ANSI colors (use -R flag for less)\n * @returns Promise that resolves when output is complete\n */\nexport async function paginateOutput(content: string, hasColors = false): Promise<void> {\n const lines = content.split('\\n').length;\n\n // Don't paginate short content or non-TTY\n if (lines < PAGINATION_LINE_THRESHOLD || !process.stdout.isTTY) {\n console.log(content);\n return;\n }\n\n const pager = process.env.PAGER ?? (hasColors ? 'less -R' : 'less');\n const [cmd, ...args] = pager.split(' ');\n\n return new Promise((resolve) => {\n const child = spawn(cmd!, args, {\n stdio: ['pipe', 'inherit', 'inherit'],\n });\n\n // Handle EPIPE error when user quits pager (e.g., pressing 'q' in less).\n // This is expected behavior - the pager closes stdin when the user exits early.\n child.stdin.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EPIPE') {\n // User quit the pager early - this is fine, not an error\n return;\n }\n // For other errors, log only in debug scenarios\n // (but don't throw - let the process exit cleanly)\n });\n\n child.stdin.write(content);\n child.stdin.end();\n\n child.on('close', () => {\n resolve();\n });\n child.on('error', () => {\n // Fall back to direct output if pager fails (e.g., less not installed)\n console.log(content);\n resolve();\n });\n });\n}\n\n/**\n * Spinner interface for progress indication.\n */\nexport interface Spinner {\n message(msg: string): void;\n stop(msg?: string): void;\n}\n\n/**\n * No-op spinner for non-TTY or quiet mode.\n */\nconst noopSpinner: Spinner = {\n message: () => {},\n stop: () => {},\n};\n\n/**\n * OutputManager handles all CLI output with format switching.\n */\nexport class OutputManager {\n private ctx: CommandContext;\n private colors: ReturnType<typeof createColors>;\n\n constructor(ctx: CommandContext) {\n this.ctx = ctx;\n this.colors = createColors(ctx.color);\n }\n\n /**\n * Output structured data - always goes to stdout.\n * In JSON mode, outputs JSON. In text mode, calls the formatter.\n */\n data<T>(data: T, textFormatter?: (data: T) => void): void {\n if (this.ctx.json) {\n console.log(JSON.stringify(data, null, 2));\n } else if (textFormatter) {\n textFormatter(data);\n }\n }\n\n /**\n * Output success message - text mode only, stdout.\n * Suppressed by --quiet and --json.\n */\n success(message: string): void {\n if (!this.ctx.json && !this.ctx.quiet) {\n console.log(this.colors.success(`${ICONS.SUCCESS} ${message}`));\n }\n }\n\n /**\n * Output notice message - noteworthy events during normal operation.\n * Blue bullet, shown at default level. Suppressed by --quiet and --json.\n */\n notice(message: string): void {\n if (!this.ctx.json && !this.ctx.quiet) {\n console.log(this.colors.info(`${ICONS.NOTICE} ${message}`));\n }\n }\n\n /**\n * Output info message - operational progress.\n * Requires --verbose or --debug. Suppressed by --json.\n */\n info(message: string): void {\n if (!this.ctx.json && (this.ctx.verbose || this.ctx.debug)) {\n console.error(this.colors.dim(message));\n }\n }\n\n /**\n * Output warning - issues that didn't stop operation.\n * Yellow warning icon, stderr. Suppressed by --quiet.\n */\n warn(message: string): void {\n if (this.ctx.json) {\n console.error(JSON.stringify({ warning: message }));\n } else if (!this.ctx.quiet) {\n console.error(this.colors.warn(`${ICONS.WARN} ${message}`));\n }\n }\n\n /**\n * Output error - failures that stop operation.\n * Red X icon, always shown (even in --quiet), stderr.\n */\n error(message: string, err?: Error): void {\n if (this.ctx.json) {\n console.error(JSON.stringify({ error: message, details: err?.message }));\n } else {\n console.error(this.colors.error(`${ICONS.ERROR} ${message}`));\n if (this.ctx.verbose && err?.stack) {\n console.error(this.colors.dim(err.stack));\n }\n }\n }\n\n /**\n * Output command being executed - shows external commands.\n * Requires --verbose or --debug. Suppressed by --json.\n */\n command(cmd: string, args?: string[]): void {\n if (!this.ctx.json && (this.ctx.verbose || this.ctx.debug)) {\n const fullCmd = args ? `${cmd} ${args.join(' ')}` : cmd;\n console.error(this.colors.dim(`> ${fullCmd}`));\n }\n }\n\n /**\n * Output debug message - internal state for troubleshooting.\n * Requires --debug only (not --verbose). Suppressed by --json.\n */\n debug(message: string): void {\n if (this.ctx.debug && !this.ctx.json) {\n console.error(this.colors.dim(`[debug] ${message}`));\n }\n }\n\n /**\n * Output dry-run indication.\n */\n dryRun(message: string, details?: object): void {\n if (this.ctx.json) {\n console.log(JSON.stringify({ dryRun: true, action: message, ...details }));\n } else {\n console.log(this.colors.warn(`[DRY-RUN] ${message}`));\n if (details && (this.ctx.verbose || this.ctx.debug)) {\n console.log(this.colors.dim(JSON.stringify(details, null, 2)));\n }\n }\n }\n\n /**\n * Output a table with headers and rows.\n * Headers are dimmed. Rows are formatted with consistent column widths.\n * Suppressed in JSON mode.\n *\n * @param headers - Array of column headers with widths\n * @param rows - Array of row data arrays (each row = array of strings)\n */\n table(\n headers: { label: string; width: number }[],\n rows: (string | { value: string; color?: (s: string) => string })[][],\n ): void {\n if (this.ctx.json) return;\n\n // Output header row\n const headerLine = headers.map((h) => h.label.padEnd(h.width)).join('');\n console.log(this.colors.dim(headerLine));\n\n // Output data rows\n for (const row of rows) {\n const cells = row.map((cell, i) => {\n const width = headers[i]?.width ?? 0;\n if (typeof cell === 'string') {\n return cell.padEnd(width);\n }\n // Cell with custom color\n const paddedValue = cell.value.padEnd(width);\n return cell.color ? cell.color(paddedValue) : paddedValue;\n });\n console.log(cells.join(''));\n }\n }\n\n /**\n * Output a bulleted list.\n * Uses NOTICE icon (•) as bullet. Suppressed in JSON mode.\n *\n * @param items - Array of items to list\n * @param options - Optional indent level (default 0)\n */\n list(items: string[], options?: { indent?: number }): void {\n if (this.ctx.json) return;\n\n const indent = ' '.repeat(options?.indent ?? 0);\n for (const item of items) {\n console.log(`${indent}${ICONS.NOTICE} ${item}`);\n }\n }\n\n /**\n * Output a count summary in standard format.\n * Format: \"N item(s)\" with dim color. Suppressed in JSON mode.\n *\n * @param count - The count to display\n * @param singular - Singular form of the item (e.g., \"issue\")\n * @param plural - Optional plural form (defaults to singular + \"s\")\n */\n count(count: number, singular: string, plural?: string): void {\n if (this.ctx.json) return;\n\n const pluralForm = plural ?? `${singular}s`;\n const label = count === 1 ? singular : pluralForm;\n console.log(this.colors.dim(`${count} ${label}`));\n }\n\n /**\n * Create a spinner for progress indication.\n * Returns no-op in JSON/quiet mode or non-TTY.\n */\n spinner(message: string): Spinner {\n // Never show spinners in JSON mode, quiet mode, or non-TTY\n if (this.ctx.json || this.ctx.quiet || !process.stderr.isTTY) {\n return noopSpinner;\n }\n\n // Simple inline spinner (no external dependency for now)\n let frame = 0;\n const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];\n let currentMessage = message;\n\n const spinnerColor = this.colors.info;\n const write = () => {\n process.stderr.write(`\\r${spinnerColor(frames[frame] ?? '⠋')} ${currentMessage}`);\n frame = (frame + 1) % frames.length;\n };\n\n write();\n const interval = setInterval(write, 80);\n\n return {\n message: (msg: string) => {\n currentMessage = msg;\n },\n stop: (msg?: string) => {\n clearInterval(interval);\n process.stderr.write('\\r' + ' '.repeat(currentMessage.length + 3) + '\\r');\n if (msg) {\n console.error(msg);\n }\n },\n };\n }\n\n /**\n * Create an OperationLogger wired to this OutputManager and a spinner.\n *\n * Eliminates the boilerplate of manually wiring spinner.message, output.info,\n * output.warn, and output.debug in every command that calls a core function.\n */\n logger(spinner: Spinner): OperationLogger {\n return {\n progress: (msg) => {\n spinner.message(msg);\n },\n info: (msg) => {\n this.info(msg);\n },\n warn: (msg) => {\n this.warn(msg);\n },\n debug: (msg) => {\n this.debug(msg);\n },\n };\n }\n\n /**\n * Get colors instance for direct use.\n */\n getColors() {\n return this.colors;\n }\n\n /**\n * Check if quiet mode is enabled.\n */\n isQuiet(): boolean {\n return this.ctx.quiet;\n }\n}\n\n// ============================================================================\n// Component Helper Functions\n// ============================================================================\n\n/**\n * Format command header with version.\n * Used at start of orientation commands (status, doctor, stats).\n *\n * @example formatCommandHeader('tbd', '0.1.9', colors) → \"tbd v0.1.9\" (bold name)\n */\nexport function formatCommandHeader(\n name: string,\n version: string,\n colors: ReturnType<typeof createColors>,\n): string {\n return `${colors.bold(name)} v${version}`;\n}\n\n/**\n * Format key-value line with dim key.\n * Used for configuration display.\n *\n * @example formatKeyValue('Sync branch', 'tbd-sync', colors) → \"Sync branch: tbd-sync\"\n */\nexport function formatKeyValue(\n key: string,\n value: string,\n colors: ReturnType<typeof createColors>,\n): string {\n return `${colors.dim(key + ':')} ${value}`;\n}\n\n/**\n * Format aligned statistic block.\n * Returns array of formatted lines with aligned values.\n *\n * @param stats - Array of {label, value} pairs\n * @param colors - Color functions (unused but kept for consistency)\n * @returns Array of formatted lines\n *\n * @example\n * formatStatBlock([{label: 'Ready', value: 12}, {label: 'In progress', value: 4}], colors)\n * → [' Ready: 12', ' In progress: 4']\n */\nexport function formatStatBlock(\n stats: { label: string; value: number | string }[],\n _colors: ReturnType<typeof createColors>,\n): string[] {\n const maxLabelLen = Math.max(...stats.map((s) => s.label.length));\n\n return stats.map((stat) => {\n const padding = ' '.repeat(maxLabelLen - stat.label.length + 1);\n return ` ${stat.label}:${padding}${stat.value}`;\n });\n}\n\n/**\n * Format multi-line warning block.\n * Returns array of lines for a warning with headline, details, and suggestion.\n *\n * @param headline - Warning headline (shown with ⚠ icon)\n * @param details - Detail lines\n * @param suggestion - Optional suggestion with command (bolded)\n * @param colors - Color functions\n * @returns Array of formatted lines\n */\nexport function formatWarningBlock(\n headline: string,\n details: string[],\n suggestion: { text: string; command: string } | undefined,\n colors: ReturnType<typeof createColors>,\n): string[] {\n const lines: string[] = [];\n\n // Headline with warning icon\n lines.push(`${colors.warn(ICONS.WARN)} ${headline}`);\n\n // Detail lines\n for (const detail of details) {\n lines.push(detail);\n }\n\n // Suggestion with bolded command\n if (suggestion) {\n lines.push(`${suggestion.text} ${colors.bold(suggestion.command)}`);\n }\n\n return lines;\n}\n\n/**\n * Format footer with command suggestions.\n * Returns a formatted string like \"Use 'tbd stats' for statistics, 'tbd doctor' for health checks.\"\n *\n * @param suggestions - Array of {command, description} pairs\n * @param colors - Color functions\n * @returns Formatted footer string\n */\nexport function formatFooter(\n suggestions: { command: string; description: string }[],\n colors: ReturnType<typeof createColors>,\n): string {\n if (suggestions.length === 0) {\n return '';\n }\n\n const parts = suggestions.map((s) => `${colors.bold(`'${s.command}'`)} for ${s.description}`);\n\n if (parts.length === 1) {\n return `Use ${parts[0]}.`;\n }\n\n return `Use ${parts.join(', ')}.`;\n}\n","/**\n * CLI error types and helpers for structured error handling.\n *\n * See: research-modern-typescript-cli-patterns.md#3-base-command-pattern\n */\n\nimport { findTbdRoot } from '../../file/config.js';\n\n/**\n * Find and return the tbd repository root, starting from the given directory.\n * Walks up the directory tree to find .tbd/.\n *\n * @param cwd - Working directory to start from (defaults to process.cwd())\n * @returns The tbd repository root path\n * @throws NotInitializedError if tbd is not initialized in any parent directory\n */\nexport async function requireInit(cwd: string = process.cwd()): Promise<string> {\n const tbdRoot = await findTbdRoot(cwd);\n if (!tbdRoot) {\n throw new NotInitializedError();\n }\n return tbdRoot;\n}\n\n/**\n * Base CLI error. Thrown for operational errors that should exit\n * with a specific code but don't need stack traces.\n */\nexport class CLIError extends Error {\n constructor(\n message: string,\n public exitCode = 1,\n ) {\n super(message);\n this.name = 'CLIError';\n }\n}\n\n/**\n * Validation error for usage/argument issues.\n * Exit code 2 follows Unix convention.\n */\nexport class ValidationError extends CLIError {\n constructor(message: string) {\n super(message, 2);\n this.name = 'ValidationError';\n }\n}\n\n/**\n * Not initialized error - tbd repository not found.\n * Uses the stable error message defined in design spec §5.6.\n */\nexport class NotInitializedError extends CLIError {\n constructor(message = \"Not a tbd repository (run 'tbd setup --auto --prefix=<name>' first)\") {\n super(message, 1);\n this.name = 'NotInitializedError';\n }\n}\n\n/**\n * Entity not found error (issue, config, etc.).\n */\nexport class NotFoundError extends CLIError {\n constructor(entityType: string, id: string) {\n super(`${entityType} not found: ${id}`, 1);\n this.name = 'NotFoundError';\n }\n}\n\n/**\n * Sync/conflict error.\n */\nexport class SyncError extends CLIError {\n constructor(message: string) {\n super(message, 1);\n this.name = 'SyncError';\n }\n}\n\n/**\n * Worktree missing error - the data-sync-worktree directory doesn't exist.\n * This indicates the worktree was never created or was deleted.\n * See: tbd-design.md §2.3.6 Worktree Error Classes\n */\nexport class WorktreeMissingError extends CLIError {\n constructor(\n message = \"Worktree not found at .tbd/data-sync-worktree/. Run 'tbd doctor --fix' to repair.\",\n ) {\n super(message, 1);\n this.name = 'WorktreeMissingError';\n }\n}\n\n/**\n * Worktree corrupted error - the worktree exists but is invalid.\n * This can occur when the .git file is missing or points to an invalid location.\n * See: tbd-design.md §2.3.6 Worktree Error Classes\n */\nexport class WorktreeCorruptedError extends CLIError {\n constructor(\n message = \"Worktree at .tbd/data-sync-worktree/ is corrupted. Run 'tbd doctor --fix' to repair.\",\n ) {\n super(message, 1);\n this.name = 'WorktreeCorruptedError';\n }\n}\n\n/**\n * Sync branch error - issues with the tbd-sync branch.\n * This can indicate the branch is missing, orphaned, or has diverged.\n * See: tbd-design.md §2.3.6 Worktree Error Classes\n */\nexport class SyncBranchError extends CLIError {\n constructor(message: string) {\n super(message, 1);\n this.name = 'SyncBranchError';\n }\n}\n\n/**\n * Classification of sync errors for auto-save/retry decisions.\n * - 'permanent': Error indicates push is blocked (e.g., 403, protected branch).\n * Auto-save to outbox is appropriate.\n * - 'transient': Error is likely temporary (e.g., network timeout).\n * User should retry.\n * - 'unknown': Cannot determine error type.\n * Treat conservatively (suggest retry, mention save option).\n */\nexport type SyncErrorType = 'permanent' | 'transient' | 'unknown';\n\n/**\n * Classify a sync error to determine appropriate recovery action.\n *\n * Used by `tbd sync` to decide whether to:\n * - Auto-save to outbox (permanent failure - push is blocked)\n * - Suggest retry (transient failure - might work next time)\n * - Offer both options (unknown - let user decide)\n *\n * @param error - Error message or Error object from git push\n * @returns Classification of the error type\n */\nexport function classifySyncError(error: Error | string): SyncErrorType {\n const msg = typeof error === 'string' ? error : error.message;\n const lower = msg.toLowerCase();\n\n // Permanent indicators - push is blocked by policy/permissions\n const permanentPatterns = [\n /403/, // HTTP 403 Forbidden\n /forbidden/,\n /permission denied/,\n /401/, // HTTP 401 Unauthorized\n /unauthorized/,\n /protected branch/,\n /remote rejected/,\n /pre-receive hook declined/,\n /push declined/,\n /not allowed to push/,\n ];\n\n for (const pattern of permanentPatterns) {\n if (pattern.test(lower)) return 'permanent';\n }\n\n // Transient indicators - likely temporary, should retry\n const transientPatterns = [\n /timeout/,\n /timed out/,\n /connection refused/,\n /connection reset/,\n /network/,\n /dns/,\n /5\\d\\d/, // HTTP 5xx server errors\n /server error/,\n /temporarily/,\n /try again/,\n /could not resolve/,\n /no route to host/,\n /connection closed/,\n ];\n\n for (const pattern of transientPatterns) {\n if (pattern.test(lower)) return 'transient';\n }\n\n return 'unknown';\n}\n","/**\n * Base command class for CLI handlers.\n *\n * See: research-modern-typescript-cli-patterns.md#3-base-command-pattern\n */\n\nimport type { Command } from 'commander';\n\nimport type { CommandContext, OutputFormat } from './context.js';\nimport { getCommandContext } from './context.js';\nimport { OutputManager } from './output.js';\nimport { CLIError } from './errors.js';\n\n/**\n * Base class for all CLI command handlers.\n * Provides common functionality for context, output, and error handling.\n */\nexport abstract class BaseCommand {\n protected ctx: CommandContext;\n protected output: OutputManager;\n\n constructor(command: Command) {\n this.ctx = getCommandContext(command);\n this.output = new OutputManager(this.ctx);\n }\n\n /**\n * Execute an async action with error handling.\n * Catches errors and formats them consistently.\n * Preserves original error as `cause` for debugging.\n */\n protected async execute<T>(action: () => Promise<T>, errorMessage: string): Promise<T> {\n try {\n return await action();\n } catch (error) {\n if (error instanceof CLIError) {\n this.output.error(error.message);\n throw error;\n }\n const originalError = error instanceof Error ? error : undefined;\n const detail = originalError?.message;\n const fullMessage =\n detail && detail !== errorMessage ? `${errorMessage}: ${detail}` : errorMessage;\n this.output.error(fullMessage, originalError);\n const wrapped = new CLIError(fullMessage);\n if (originalError) {\n wrapped.cause = originalError;\n }\n throw wrapped;\n }\n }\n\n /**\n * Check if dry-run mode is enabled and log the action.\n * Returns true if in dry-run mode (caller should skip the actual action).\n */\n protected checkDryRun(message: string, details?: object): boolean {\n if (this.ctx.dryRun) {\n this.output.dryRun(message, details);\n return true;\n }\n return false;\n }\n\n /**\n * Abstract method that subclasses must implement.\n * Signature varies by command (positional args + options object).\n */\n abstract run(...args: unknown[]): Promise<void>;\n}\n\n/**\n * Helper to get output format from context.\n */\nexport function getOutputFormat(ctx: CommandContext): OutputFormat {\n return ctx.json ? 'json' : 'text';\n}\n","/**\n * File system utility functions.\n */\n\nimport { access } from 'node:fs/promises';\n\n/**\n * Check if a path exists on the filesystem.\n */\nexport async function pathExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n","/**\n * Gitignore file utilities for idempotent pattern management.\n */\n\nimport { readFile } from 'node:fs/promises';\nimport { writeFile } from 'atomically';\nimport { pathExists } from './file-utils.js';\n\n/**\n * Check if a pattern exists in gitignore content.\n * Matches exact lines, normalizing trailing slashes for directories.\n */\nexport function hasGitignorePattern(content: string, pattern: string): boolean {\n const normalizedPattern = pattern.replace(/\\/+$/, '');\n const lines = content.split('\\n');\n\n for (const line of lines) {\n const trimmed = line.trim();\n // Skip comments and empty lines\n if (trimmed === '' || trimmed.startsWith('#')) continue;\n\n const normalizedLine = trimmed.replace(/\\/+$/, '');\n if (normalizedLine === normalizedPattern) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Ensure patterns exist in a .gitignore file.\n * Creates file if missing. Appends only missing patterns.\n * Always uses atomic write.\n *\n * @param gitignorePath - Path to .gitignore file\n * @param patterns - Patterns to ensure exist (can include comments)\n * @param header - Optional header comment for new patterns section\n */\nexport async function ensureGitignorePatterns(\n gitignorePath: string,\n patterns: string[],\n header?: string,\n): Promise<{ added: string[]; skipped: string[]; created: boolean }> {\n // Read existing content or empty string\n let content = '';\n let created = false;\n\n if (await pathExists(gitignorePath)) {\n content = await readFile(gitignorePath, 'utf-8');\n } else {\n created = true;\n }\n\n // Group patterns into entries: each entry is leading comments/blanks followed by\n // one or more actual patterns. Comments are only included if their associated\n // pattern(s) are new, preventing orphaned comment duplication on upgrades.\n const entries: { preamble: string[]; patterns: string[] }[] = [];\n let currentPreamble: string[] = [];\n\n for (const pattern of patterns) {\n const trimmed = pattern.trim();\n if (trimmed === '' || trimmed.startsWith('#')) {\n currentPreamble.push(pattern);\n } else {\n entries.push({ preamble: currentPreamble, patterns: [trimmed] });\n currentPreamble = [];\n }\n }\n // Trailing comments/blanks (no associated pattern) form their own entry\n if (currentPreamble.length > 0) {\n entries.push({ preamble: currentPreamble, patterns: [] });\n }\n\n // Determine which entries have new patterns to add\n const added: string[] = [];\n const skipped: string[] = [];\n const linesToAppend: string[] = [];\n\n for (const entry of entries) {\n const newPatterns = entry.patterns.filter((p) => !hasGitignorePattern(content, p));\n const existingPatterns = entry.patterns.filter((p) => hasGitignorePattern(content, p));\n skipped.push(...existingPatterns);\n\n if (newPatterns.length > 0) {\n added.push(...newPatterns);\n // Include preamble comments only when their associated pattern is new\n linesToAppend.push(...entry.preamble, ...newPatterns);\n }\n }\n\n // If nothing new to add, return early\n if (added.length === 0) {\n return { added: [], skipped, created: false };\n }\n\n // Build new content\n let newContent = content;\n\n // Ensure content ends with newline before appending\n if (newContent && !newContent.endsWith('\\n')) {\n newContent += '\\n';\n }\n\n // Add blank line separator if file has existing content\n if (newContent && !newContent.endsWith('\\n\\n')) {\n newContent += '\\n';\n }\n\n // Add header if provided\n if (header) {\n newContent += header + '\\n';\n }\n\n // Add only entries with new patterns\n newContent += linesToAppend.join('\\n') + '\\n';\n\n // Atomic write\n await writeFile(gitignorePath, newContent);\n\n return { added, skipped, created };\n}\n","/**\n * Prefix validation and beads prefix extraction module.\n *\n * Provides functions to validate prefixes and extract prefix from beads config.\n * Used by setup commands to validate user-provided prefixes and migrate from beads.\n */\n\nimport { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { parse as parseYaml } from 'yaml';\n\n/** Maximum length for a valid prefix */\nconst MAX_PREFIX_LENGTH = 20;\n\n/** Minimum length for a valid prefix */\nconst MIN_PREFIX_LENGTH = 1;\n\n/** Recommended minimum length */\nconst RECOMMENDED_MIN_LENGTH = 2;\n\n/** Recommended maximum length */\nconst RECOMMENDED_MAX_LENGTH = 8;\n\n/**\n * Normalize a prefix string.\n * - Lowercases\n * - Removes invalid characters (keeps alphanumeric, dot, underscore)\n * - Truncates to max length\n */\nexport function normalizePrefix(s: string): string {\n if (!s) return '';\n\n // Lowercase and remove invalid characters (keep alphanumeric, dot, underscore)\n const normalized = s.toLowerCase().replace(/[^a-z0-9._]/g, '');\n\n // Truncate to max length\n return normalized.slice(0, MAX_PREFIX_LENGTH);\n}\n\n/**\n * Check if a prefix is valid (hard rules, always enforced).\n * - Must be 1-20 characters\n * - Must start with a letter (a-z)\n * - Must end with alphanumeric (a-z0-9)\n * - Middle characters can be alphanumeric, dot, or underscore\n * - No dashes allowed (breaks ID syntax)\n */\nexport function isValidPrefix(s: string): boolean {\n if (!s) return false;\n if (s.length < MIN_PREFIX_LENGTH || s.length > MAX_PREFIX_LENGTH) return false;\n\n // First char must be a letter\n if (!/^[a-z]/.test(s)) return false;\n\n // Last char must be alphanumeric (for length > 1)\n if (s.length > 1 && !/[a-z0-9]$/.test(s)) return false;\n\n // All chars must be alphanumeric, dot, or underscore (no dashes!)\n return /^[a-z][a-z0-9._]*$/.test(s);\n}\n\n/**\n * Check if a prefix follows recommended format (soft rules).\n * - Must be 2-8 characters\n * - Must be alphabetic only (a-z)\n *\n * Prefixes that don't match this can still be used with --force.\n */\nexport function isRecommendedPrefix(s: string): boolean {\n if (!s) return false;\n if (s.length < RECOMMENDED_MIN_LENGTH || s.length > RECOMMENDED_MAX_LENGTH) return false;\n return /^[a-z]+$/.test(s);\n}\n\n/**\n * Get prefix from existing beads config.\n *\n * Looks for .beads/config.yaml and extracts display.id_prefix\n *\n * @param cwd Current working directory\n * @returns The beads prefix, or null if not found\n */\nexport async function getBeadsPrefix(cwd: string): Promise<string | null> {\n try {\n const configPath = join(cwd, '.beads', 'config.yaml');\n const content = await readFile(configPath, 'utf-8');\n const config = parseYaml(content) as Record<string, unknown>;\n\n const display = config?.display as Record<string, unknown> | undefined;\n const prefix = display?.id_prefix;\n\n if (typeof prefix === 'string' && isValidPrefix(prefix)) {\n return prefix;\n }\n\n return null;\n } catch {\n return null;\n }\n}\n","/**\n * Time utilities for tbd.\n *\n * All timestamps should be ISO 8601 UTC format with Z suffix.\n * Use these functions instead of raw Date calls for consistency.\n */\n\n/**\n * Get current time as ISO 8601 UTC string.\n * Use this when recording timestamps for new/updated issues.\n */\nexport function now(): string {\n return new Date().toISOString();\n}\n\n/**\n * Get current time as Date object.\n * Use this when you need date arithmetic or comparisons.\n */\nexport function nowDate(): Date {\n return new Date();\n}\n\n/**\n * Parse a timestamp string to Date object.\n * Returns null if the timestamp is invalid.\n */\nexport function parseDate(timestamp: string | undefined | null): Date | null {\n if (!timestamp) return null;\n try {\n const date = new Date(timestamp);\n if (isNaN(date.getTime())) return null;\n return date;\n } catch {\n return null;\n }\n}\n\n/**\n * Normalize any timestamp to ISO 8601 UTC format with Z suffix.\n * Handles various formats including timezone offsets like -08:00.\n * Returns null if the timestamp is invalid or missing.\n */\nexport function normalizeTimestamp(timestamp: string | undefined | null): string | null {\n const date = parseDate(timestamp);\n return date?.toISOString() ?? null;\n}\n\n/**\n * Check if a timestamp string is valid.\n */\nexport function isValidTimestamp(timestamp: string | undefined | null): boolean {\n return parseDate(timestamp) !== null;\n}\n\n/**\n * Get current time as a filename-safe timestamp.\n * Replaces colons with dashes and removes milliseconds for filesystem compatibility.\n */\nexport function nowFilenameTimestamp(): string {\n return new Date()\n .toISOString()\n .replace(/:/g, '-')\n .replace(/\\.\\d+Z$/, 'Z');\n}\n","/**\n * Git utilities for sync operations.\n *\n * Provides:\n * - Isolated index operations (protect user's staging area)\n * - Field-level merge algorithm\n * - Push retry with exponential backoff\n *\n * See: tbd-design.md §3.3 Sync Operations\n */\n\nimport { execFile } from 'node:child_process';\nimport { mkdir } from 'node:fs/promises';\nimport { promisify } from 'node:util';\nimport { join } from 'node:path';\n\nimport { writeFile } from 'atomically';\n\nimport type { Issue } from '../lib/types.js';\nimport { now, nowFilenameTimestamp } from '../utils/time-utils.js';\n\nconst execFileAsync = promisify(execFile);\n\n/**\n * Maximum buffer size for git command output.\n *\n * Node.js child_process.execFile() defaults to 1MB (1024 * 1024 bytes).\n * When exceeded, the child process is terminated with \"stdout maxBuffer length exceeded\".\n * Git commands like push/fetch with verbose output or diff on large changesets can exceed 1MB.\n *\n * See: https://nodejs.org/api/child_process.html#child_processexecfilefile-args-options-callback\n */\nconst GIT_MAX_BUFFER = 50 * 1024 * 1024; // 50MB\n\n/**\n * Execute a git command and return stdout.\n * Uses execFile for security - prevents shell injection attacks.\n */\nexport async function git(...args: string[]): Promise<string> {\n const { stdout } = await execFileAsync('git', args, { maxBuffer: GIT_MAX_BUFFER });\n return stdout.trim();\n}\n\n// =============================================================================\n// Git Version Detection\n// See: plan spec §3.4 Git Integration Architecture\n// =============================================================================\n\n/**\n * Minimum Git version required.\n * Git 2.42 (August 2023) introduced `git worktree add --orphan` which tbd requires.\n */\nexport const MIN_GIT_VERSION = '2.42.0';\n\n/**\n * Parsed Git version information.\n */\n/**\n * Find the git repository root directory.\n * Uses `git rev-parse --show-toplevel` which returns the absolute path.\n *\n * @param cwd - Directory to start from (default: process.cwd())\n * @returns Absolute path to the git root, or null if not in a git repo\n */\nexport async function findGitRoot(cwd?: string): Promise<string | null> {\n try {\n const args = ['rev-parse', '--show-toplevel'];\n if (cwd) {\n args.unshift('-C', cwd);\n }\n return await git(...args);\n } catch {\n return null;\n }\n}\n\n/**\n * Check if the current directory is inside a git repository.\n */\nexport async function isInGitRepo(cwd?: string): Promise<boolean> {\n try {\n const args = ['rev-parse', '--is-inside-work-tree'];\n if (cwd) {\n args.unshift('-C', cwd);\n }\n const result = await git(...args);\n return result === 'true';\n } catch {\n return false;\n }\n}\n\nexport interface GitVersion {\n major: number;\n minor: number;\n patch: number;\n raw: string;\n}\n\n/**\n * Get the installed Git version.\n *\n * @returns Parsed version information\n * @throws Error if git is not installed or version cannot be parsed\n */\nexport async function getGitVersion(): Promise<GitVersion> {\n const versionOutput = await git('--version');\n // Output format: \"git version 2.42.0\" or \"git version 2.42.0.windows.1\"\n const versionRegex = /git version (\\d+)\\.(\\d+)\\.(\\d+)/;\n const match = versionRegex.exec(versionOutput);\n\n const major = match?.[1];\n const minor = match?.[2];\n const patch = match?.[3];\n\n if (!major || !minor || !patch) {\n throw new Error(`Unable to parse git version from: ${versionOutput}`);\n }\n\n return {\n major: parseInt(major, 10),\n minor: parseInt(minor, 10),\n patch: parseInt(patch, 10),\n raw: versionOutput,\n };\n}\n\n/**\n * Compare two version strings.\n *\n * @returns -1 if a < b, 0 if a === b, 1 if a > b\n */\nexport function compareVersions(a: GitVersion, b: string): number {\n const parts = b.split('.');\n const bMajor = parseInt(parts[0] ?? '0', 10);\n const bMinor = parseInt(parts[1] ?? '0', 10);\n const bPatch = parseInt(parts[2] ?? '0', 10);\n\n if (a.major !== bMajor) return a.major < bMajor ? -1 : 1;\n if (a.minor !== bMinor) return a.minor < bMinor ? -1 : 1;\n if (a.patch !== bPatch) return a.patch < bPatch ? -1 : 1;\n return 0;\n}\n\n/**\n * Check if the installed Git version meets minimum requirements.\n *\n * @returns Object with version info and whether it meets requirements\n * @throws Error with upgrade instructions if Git version is too old\n */\nexport async function checkGitVersion(): Promise<{\n version: GitVersion;\n supported: boolean;\n}> {\n const version = await getGitVersion();\n const supported = compareVersions(version, MIN_GIT_VERSION) >= 0;\n return { version, supported };\n}\n\n/**\n * Require minimum Git version, throwing an error if not met.\n */\nexport async function requireGitVersion(): Promise<GitVersion> {\n const { version, supported } = await checkGitVersion();\n if (!supported) {\n throw new Error(getUpgradeInstructions(version));\n }\n return version;\n}\n\n/**\n * Get platform-specific upgrade instructions.\n * Points to official documentation rather than detailed commands for easier maintenance.\n */\nfunction getUpgradeInstructions(currentVersion: GitVersion): string {\n const platform = process.platform;\n const versionStr = `${currentVersion.major}.${currentVersion.minor}.${currentVersion.patch}`;\n\n let upgradeUrl: string;\n switch (platform) {\n case 'darwin':\n upgradeUrl = 'https://git-scm.com/download/mac';\n break;\n case 'linux':\n upgradeUrl = 'https://git-scm.com/download/linux';\n break;\n case 'win32':\n upgradeUrl = 'https://git-scm.com/download/win';\n break;\n default:\n upgradeUrl = 'https://git-scm.com/downloads';\n }\n\n return `Git ${versionStr} detected. Git ${MIN_GIT_VERSION}+ required for tbd.\\nUpgrade: ${upgradeUrl}`;\n}\n\n/**\n * Execute a git command with isolated index.\n * This protects the user's staging area during sync operations.\n *\n * See: tbd-design.md §3.3.2 Writing to Sync Branch\n */\nexport async function withIsolatedIndex<T>(fn: () => Promise<T>): Promise<T> {\n const gitDir = await git('rev-parse', '--git-dir');\n const isolatedIndex = join(gitDir, 'tbd-index');\n const originalIndex = process.env.GIT_INDEX_FILE;\n\n try {\n process.env.GIT_INDEX_FILE = isolatedIndex;\n return await fn();\n } finally {\n if (originalIndex) {\n process.env.GIT_INDEX_FILE = originalIndex;\n } else {\n delete process.env.GIT_INDEX_FILE;\n }\n }\n}\n\n/**\n * Commit changes to sync branch using isolated index.\n */\nexport async function commitToSyncBranch(\n syncBranch: string,\n message: string,\n files: string[],\n): Promise<string> {\n return withIsolatedIndex(async () => {\n // Try to read existing tree from sync branch\n try {\n await git('read-tree', syncBranch);\n } catch {\n // Branch doesn't exist - start fresh\n }\n\n // Add changed files to index\n for (const file of files) {\n await git('add', file);\n }\n\n // Write tree object\n const tree = await git('write-tree');\n\n // Get parent commit if exists\n let parent: string | null = null;\n try {\n parent = await git('rev-parse', syncBranch);\n } catch {\n // No parent - orphan commit\n }\n\n // Create commit\n // Note: With execFile, we pass the message directly without shell quoting\n const commitArgs = ['commit-tree', tree, '-m', message];\n if (parent) {\n commitArgs.push('-p', parent);\n }\n\n const commit = await git(...commitArgs);\n\n // Update branch ref\n await git('update-ref', `refs/heads/${syncBranch}`, commit);\n\n return commit;\n });\n}\n\n/**\n * Field-level merge strategy types.\n */\ntype MergeStrategy = 'lww' | 'union' | 'max' | 'immutable';\n\n/**\n * Field-level merge strategies for Issue fields.\n * See: tbd-design.md §3.5 Merge Rules\n */\nconst FIELD_STRATEGIES: Record<keyof Issue, MergeStrategy> = {\n // Immutable - never change after creation\n type: 'immutable',\n id: 'immutable',\n created_at: 'immutable',\n created_by: 'immutable',\n\n // LWW (Last-Write-Wins) - compare updated_at\n version: 'max',\n kind: 'lww',\n title: 'lww',\n description: 'lww',\n notes: 'lww',\n status: 'lww',\n priority: 'lww',\n assignee: 'lww',\n parent_id: 'lww',\n child_order_hints: 'lww',\n updated_at: 'max',\n closed_at: 'lww',\n close_reason: 'lww',\n due_date: 'lww',\n deferred_until: 'lww',\n spec_path: 'lww',\n\n // Union - combine arrays, deduplicate\n labels: 'union',\n dependencies: 'union',\n\n // Extensions - LWW for whole object\n extensions: 'lww',\n};\n\n/**\n * Conflict entry for attic storage.\n */\nexport interface ConflictEntry {\n issue_id: string;\n field: string;\n timestamp: string;\n lost_value: unknown;\n winner_value: unknown;\n local_version: number;\n remote_version: number;\n resolution: 'lww' | 'union' | 'manual';\n}\n\n/**\n * Merge result with merged issue and any conflicts.\n */\nexport interface MergeResult {\n merged: Issue;\n conflicts: ConflictEntry[];\n}\n\n/**\n * Fields that are metadata-only and should be ignored when checking\n * for substantive changes between issues. These fields change on every\n * merge operation and don't represent meaningful content changes.\n */\nconst METADATA_ONLY_FIELDS: ReadonlySet<keyof Issue> = new Set(['version', 'updated_at']);\n\n/**\n * Check if two issues are substantively equal, ignoring metadata fields\n * (version, updated_at) that change on every merge.\n *\n * This prevents trivial timestamp/version bumps from being treated as\n * real changes during outbox saves and sync operations.\n */\nexport function issuesSubstantivelyEqual(a: Issue, b: Issue): boolean {\n for (const key of Object.keys(FIELD_STRATEGIES) as (keyof Issue)[]) {\n if (METADATA_ONLY_FIELDS.has(key)) continue;\n if (!deepEqual(a[key], b[key])) return false;\n }\n return true;\n}\n\n/**\n * Deep equality check for values.\n */\nexport function deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a === null || b === null) return a === b;\n if (typeof a !== typeof b) return false;\n\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n return a.every((item, i) => deepEqual(item, b[i]));\n }\n\n if (typeof a === 'object' && typeof b === 'object') {\n const aKeys = Object.keys(a);\n const bKeys = Object.keys(b);\n if (aKeys.length !== bKeys.length) return false;\n return aKeys.every((key) =>\n deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]),\n );\n }\n\n return false;\n}\n\n/**\n * Union arrays with deduplication.\n */\nfunction unionArrays<T>(a: T[], b: T[]): T[] {\n const result = [...a];\n for (const item of b) {\n if (!result.some((existing) => deepEqual(existing, item))) {\n result.push(item);\n }\n }\n return result;\n}\n\n/**\n * Create an attic entry for a conflict.\n */\nfunction createConflictEntry(\n issueId: string,\n field: string,\n lostValue: unknown,\n winnerValue: unknown,\n localVersion: number,\n remoteVersion: number,\n resolution: 'lww' | 'union' | 'manual',\n): ConflictEntry {\n const timestamp = nowFilenameTimestamp();\n\n return {\n issue_id: issueId,\n field,\n timestamp,\n lost_value: lostValue,\n winner_value: winnerValue,\n local_version: localVersion,\n remote_version: remoteVersion,\n resolution,\n };\n}\n\n/**\n * Three-way merge algorithm for issues.\n * See: tbd-design.md §3.4 Conflict Detection and Resolution\n *\n * @param base - Common ancestor (null if new issue)\n * @param local - Local version\n * @param remote - Remote version\n */\nexport function mergeIssues(base: Issue | null, local: Issue, remote: Issue): MergeResult {\n const conflicts: ConflictEntry[] = [];\n\n // If no base, check if these are versions of the same issue or independent creations\n if (!base) {\n const localTime = new Date(local.created_at).getTime();\n const remoteTime = new Date(remote.created_at).getTime();\n\n // Same created_at means same original creation - these are versions of the same issue\n // Use field-by-field merge instead of whole_issue conflict\n if (localTime === remoteTime) {\n // Use the one with the lower version as a synthetic base\n // This forces field-by-field comparison\n base = local.version <= remote.version ? local : remote;\n // Fall through to field-by-field merge below\n } else {\n // Different creation times - truly independent issues\n // Use whole_issue conflict (original behavior)\n if (localTime < remoteTime) {\n // Local was created first - it wins\n if (!deepEqual(local, remote)) {\n conflicts.push(\n createConflictEntry(\n remote.id,\n 'whole_issue',\n remote,\n local,\n remote.version,\n local.version,\n 'lww',\n ),\n );\n }\n return { merged: local, conflicts };\n } else {\n // Remote was created first - it wins\n if (!deepEqual(local, remote)) {\n conflicts.push(\n createConflictEntry(\n local.id,\n 'whole_issue',\n local,\n remote,\n local.version,\n remote.version,\n 'lww',\n ),\n );\n }\n return { merged: remote, conflicts };\n }\n }\n }\n\n // Field-by-field merge\n const merged = { ...base } as Issue;\n\n for (const [field, strategy] of Object.entries(FIELD_STRATEGIES)) {\n const key = field as keyof Issue;\n const localVal = local[key];\n const remoteVal = remote[key];\n const baseVal = base[key];\n\n // Skip if both unchanged from base\n if (deepEqual(localVal, baseVal) && deepEqual(remoteVal, baseVal)) {\n continue;\n }\n\n // Only one changed - take changed value\n if (deepEqual(localVal, baseVal)) {\n (merged as Record<string, unknown>)[key] = remoteVal;\n continue;\n }\n if (deepEqual(remoteVal, baseVal)) {\n (merged as Record<string, unknown>)[key] = localVal;\n continue;\n }\n\n // Both changed - apply strategy\n switch (strategy) {\n case 'immutable':\n // Keep base value (shouldn't change)\n break;\n\n case 'lww': {\n // Only create conflicts when values actually differ (data is discarded)\n // See: tbd-design.md §3.5 \"Attic entries are created only when a merge strategy discards data\"\n if (deepEqual(localVal, remoteVal)) {\n // Values are identical - no conflict, use either one\n (merged as Record<string, unknown>)[key] = localVal;\n break;\n }\n\n // Values differ - apply LWW based on updated_at timestamps\n const localTime = new Date(local.updated_at).getTime();\n const remoteTime = new Date(remote.updated_at).getTime();\n\n if (localTime >= remoteTime) {\n (merged as Record<string, unknown>)[key] = localVal;\n conflicts.push(\n createConflictEntry(\n local.id,\n field,\n remoteVal,\n localVal,\n local.version,\n remote.version,\n 'lww',\n ),\n );\n } else {\n (merged as Record<string, unknown>)[key] = remoteVal;\n conflicts.push(\n createConflictEntry(\n local.id,\n field,\n localVal,\n remoteVal,\n local.version,\n remote.version,\n 'lww',\n ),\n );\n }\n break;\n }\n\n case 'union':\n // Combine arrays and deduplicate\n (merged as Record<string, unknown>)[key] = unionArrays(\n localVal as unknown[],\n remoteVal as unknown[],\n );\n break;\n\n case 'max':\n // Take maximum value\n (merged as Record<string, unknown>)[key] = Math.max(\n localVal as number,\n remoteVal as number,\n );\n break;\n }\n }\n\n // Check if the merge produced any substantive changes compared to the\n // highest-versioned input. If not, return that input as-is to avoid\n // gratuitous version/timestamp bumps that cause bulk outbox saves.\n const latest = local.version >= remote.version ? local : remote;\n if (issuesSubstantivelyEqual(merged, latest)) {\n // No substantive change - return the latest version without bumping\n return { merged: { ...latest }, conflicts };\n }\n\n // Actual substantive changes from merge - bump version\n merged.version = Math.max(local.version, remote.version) + 1;\n merged.updated_at = now();\n\n return { merged, conflicts };\n}\n\n/**\n * Maximum retry attempts for push operations.\n */\nconst MAX_PUSH_RETRIES = 3;\n\n/**\n * Check if error is a non-fast-forward rejection.\n */\nfunction isNonFastForward(error: unknown): boolean {\n const msg = error instanceof Error ? error.message : String(error);\n return (\n msg.includes('non-fast-forward') || msg.includes('fetch first') || msg.includes('rejected')\n );\n}\n\n/**\n * Push result with retry information.\n */\nexport interface PushResult {\n success: boolean;\n attempt: number;\n conflicts?: ConflictEntry[];\n error?: string;\n}\n\n/**\n * Push with retry and merge on conflict.\n * See: tbd-design.md §3.3.3 Sync Algorithm\n *\n * @param syncBranch - The sync branch name\n * @param remote - The remote name\n * @param onMergeNeeded - Callback to merge remote changes\n * @param baseDir - Repository root directory (uses process.cwd() if not provided)\n */\nexport async function pushWithRetry(\n syncBranch: string,\n remote: string,\n onMergeNeeded: () => Promise<ConflictEntry[]>,\n baseDir?: string,\n): Promise<PushResult> {\n // Use explicit refspec to avoid ambiguity with tags or other refs\n const refspec = `refs/heads/${syncBranch}:refs/heads/${syncBranch}`;\n // Build -C prefix args when baseDir is provided\n const dirArgs = baseDir ? ['-C', baseDir] : [];\n\n for (let attempt = 1; attempt <= MAX_PUSH_RETRIES; attempt++) {\n try {\n // Try to push\n await git(...dirArgs, 'push', remote, refspec);\n return { success: true, attempt };\n } catch (error) {\n if (!isNonFastForward(error)) {\n // Unrecoverable error\n return {\n success: false,\n attempt,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n\n if (attempt === MAX_PUSH_RETRIES) {\n return {\n success: false,\n attempt,\n error: `Push failed after ${MAX_PUSH_RETRIES} attempts. Remote has conflicting changes.`,\n };\n }\n\n // Fetch and merge remote changes\n await git(...dirArgs, 'fetch', remote, syncBranch);\n const conflicts = await onMergeNeeded();\n\n if (conflicts.length > 0) {\n // Return conflicts but continue trying\n return { success: false, attempt, conflicts };\n }\n\n // Loop to retry push\n }\n }\n\n return { success: false, attempt: MAX_PUSH_RETRIES, error: 'Unexpected error in push retry' };\n}\n\n/**\n * Get the current branch name.\n */\nexport async function getCurrentBranch(): Promise<string> {\n return git('rev-parse', '--abbrev-ref', 'HEAD');\n}\n\n/**\n * Check if a branch exists locally.\n */\nexport async function branchExists(branch: string): Promise<boolean> {\n try {\n await git('rev-parse', '--verify', `refs/heads/${branch}`);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Check if a remote branch exists.\n */\nexport async function remoteBranchExists(remote: string, branch: string): Promise<boolean> {\n try {\n await git('ls-remote', '--exit-code', remote, `refs/heads/${branch}`);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Get the remote URL.\n */\nexport async function getRemoteUrl(remote: string): Promise<string | null> {\n try {\n return await git('remote', 'get-url', remote);\n } catch {\n return null;\n }\n}\n\n// =============================================================================\n// Worktree Management\n// See: tbd-design.md §2.3 Hidden Worktree Model\n// =============================================================================\n\nimport { access, rm, cp } from 'node:fs/promises';\nimport {\n WORKTREE_DIR,\n WORKTREE_DIR_NAME,\n TBD_DIR,\n DATA_SYNC_DIR_NAME,\n SYNC_BRANCH,\n} from '../lib/paths.js';\n\n/**\n * Check if the hidden worktree exists and is valid.\n */\nexport async function worktreeExists(baseDir: string): Promise<boolean> {\n const worktreePath = join(baseDir, WORKTREE_DIR);\n try {\n await access(worktreePath);\n // Also verify it's a valid git worktree by checking for .git file\n await access(join(worktreePath, '.git'));\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Worktree health status values.\n * See: tbd-design.md §2.3.4 Worktree Health States\n */\nexport type WorktreeStatus = 'valid' | 'missing' | 'prunable' | 'corrupted';\n\n/**\n * Worktree health status.\n */\nexport interface WorktreeHealth {\n /** Whether the worktree directory exists on disk */\n exists: boolean;\n /** Whether the worktree is valid and functional */\n valid: boolean;\n /** Detailed status: valid, missing, prunable, or corrupted */\n status: WorktreeStatus;\n /** The branch checked out in the worktree */\n branch: string | null;\n /** The commit HEAD points to */\n commit: string | null;\n /** Error message if status is not 'valid' */\n error?: string;\n}\n\n/**\n * Check worktree health and return status.\n * See: tbd-design.md §2.3 Worktree Lifecycle\n * See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md §3\n */\nexport async function checkWorktreeHealth(baseDir: string): Promise<WorktreeHealth> {\n const worktreePath = join(baseDir, WORKTREE_DIR);\n\n // First check if git reports the worktree as prunable\n // This catches the case where worktree directory was deleted but git still tracks it\n try {\n const worktreeList = await git('-C', baseDir, 'worktree', 'list', '--porcelain');\n\n // Check if our worktree path appears in the list as prunable\n const lines = worktreeList.split('\\n');\n let foundWorktree = false;\n let isPrunable = false;\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n // Check if this entry is for our worktree path\n if (line?.startsWith('worktree ') && line.includes(WORKTREE_DIR_NAME)) {\n foundWorktree = true;\n // Look for prunable marker in subsequent lines until next worktree entry\n for (let j = i + 1; j < lines.length && !lines[j]?.startsWith('worktree '); j++) {\n if (lines[j]?.startsWith('prunable')) {\n isPrunable = true;\n break;\n }\n }\n break;\n }\n }\n\n if (isPrunable) {\n return {\n exists: false,\n valid: false,\n status: 'prunable',\n branch: null,\n commit: null,\n error: 'Worktree directory was deleted but git still tracks it. Run: git worktree prune',\n };\n }\n\n // If git doesn't know about the worktree, check if directory exists\n if (!foundWorktree) {\n try {\n await access(worktreePath);\n // Directory exists but git doesn't know about it - corrupted\n return {\n exists: true,\n valid: false,\n status: 'corrupted',\n branch: null,\n commit: null,\n error: 'Worktree directory exists but is not registered with git',\n };\n } catch {\n // Directory doesn't exist and git doesn't know about it - missing\n return {\n exists: false,\n valid: false,\n status: 'missing',\n branch: null,\n commit: null,\n };\n }\n }\n } catch {\n // git worktree list failed - likely not in a git repo\n // Fall through to directory-based checks\n }\n\n // Check if worktree directory exists\n try {\n await access(worktreePath);\n } catch {\n return {\n exists: false,\n valid: false,\n status: 'missing',\n branch: null,\n commit: null,\n };\n }\n\n // Check if it's a valid git worktree\n try {\n await access(join(worktreePath, '.git'));\n } catch {\n return {\n exists: true,\n valid: false,\n status: 'corrupted',\n branch: null,\n commit: null,\n error: 'Worktree directory exists but is not a valid git worktree (missing .git)',\n };\n }\n\n // Get current commit and branch info\n try {\n const commit = await git('-C', worktreePath, 'rev-parse', 'HEAD');\n let branch: string | null = null;\n\n try {\n // Check if we're on detached HEAD pointing to tbd-sync\n const refName = await git('-C', worktreePath, 'symbolic-ref', '-q', 'HEAD');\n branch = refName.replace('refs/heads/', '');\n } catch {\n // Detached HEAD - expected state\n branch = null;\n }\n\n return { exists: true, valid: true, status: 'valid', branch, commit };\n } catch (error) {\n return {\n exists: true,\n valid: false,\n status: 'corrupted',\n branch: null,\n commit: null,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\n/**\n * Initialize the hidden worktree for the tbd-sync branch.\n * Follows the decision tree from tbd-design.md §2.3.\n *\n * @param baseDir - The base directory of the repository\n * @param remote - The remote name (default: 'origin')\n * @param syncBranch - The sync branch name (default: 'tbd-sync')\n * @returns Path to the worktree or error message\n */\nexport async function initWorktree(\n baseDir: string,\n remote = 'origin',\n syncBranch: string = SYNC_BRANCH,\n): Promise<{ success: boolean; path?: string; created?: boolean; error?: string }> {\n const worktreePath = join(baseDir, WORKTREE_DIR);\n\n // Check if worktree already exists and is valid\n if (await worktreeExists(baseDir)) {\n return { success: true, path: worktreePath, created: false };\n }\n\n // Remove any stale worktree directory\n try {\n await rm(worktreePath, { recursive: true, force: true });\n } catch {\n // Ignore errors - directory might not exist\n }\n\n try {\n // Check if local branch exists\n const localExists = await branchExists(syncBranch);\n if (localExists) {\n // Create worktree on local branch (no --detach, so commits update the branch)\n // Note: Don't use --detach here - we want commits to update tbd-sync branch\n await git('-C', baseDir, 'worktree', 'add', worktreePath, syncBranch);\n return { success: true, path: worktreePath, created: true };\n }\n\n // Check if remote branch exists\n const remoteExists = await remoteBranchExists(remote, syncBranch);\n if (remoteExists) {\n // Fetch and create worktree from remote branch with local tracking branch\n await git('-C', baseDir, 'fetch', remote, syncBranch);\n // Use -b to create local branch tracking remote, not --detach\n // This ensures commits update the local branch which can then be pushed\n await git(\n '-C',\n baseDir,\n 'worktree',\n 'add',\n '-b',\n syncBranch,\n worktreePath,\n `${remote}/${syncBranch}`,\n );\n return { success: true, path: worktreePath, created: true };\n }\n\n // No branch exists - create orphan worktree (requires Git 2.42+)\n // Syntax: git worktree add --orphan -b <branch> <path>\n await requireGitVersion();\n await git('-C', baseDir, 'worktree', 'add', '--orphan', '-b', syncBranch, worktreePath);\n\n // Initialize the data-sync directory structure in the worktree\n const dataSyncPath = join(worktreePath, TBD_DIR, DATA_SYNC_DIR_NAME);\n await mkdir(join(dataSyncPath, 'issues'), { recursive: true });\n await mkdir(join(dataSyncPath, 'mappings'), { recursive: true });\n await mkdir(join(dataSyncPath, 'attic', 'conflicts'), { recursive: true });\n\n // Create initial commit in worktree\n await writeFile(join(dataSyncPath, 'meta.yml'), 'schema_version: 1\\n');\n await writeFile(join(dataSyncPath, 'issues', '.gitkeep'), '');\n await writeFile(join(dataSyncPath, 'mappings', '.gitkeep'), '');\n\n // Add .gitattributes for merge=union on ids.yml so concurrent additions\n // (both sides add non-overlapping keys) merge cleanly instead of conflicting.\n // This must be inside the worktree — .gitattributes on the main branch has\n // no effect on merges happening on the tbd-sync branch.\n await writeFile(join(dataSyncPath, 'mappings', '.gitattributes'), 'ids.yml merge=union\\n');\n\n // Stage and commit the initial structure\n // Use --no-verify to bypass parent repo hooks (lefthook, husky, etc.)\n await git('-C', worktreePath, 'add', '.');\n await git('-C', worktreePath, 'commit', '--no-verify', '-m', 'Initialize tbd-sync branch');\n\n return { success: true, path: worktreePath, created: true };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\n/**\n * Update the hidden worktree to latest sync branch state.\n * Called after sync operations to ensure worktree reflects current state.\n *\n * @param baseDir - The base directory of the repository\n * @param remote - The remote name (default: 'origin')\n * @param syncBranch - The sync branch name (default: 'tbd-sync')\n */\nexport async function updateWorktree(\n baseDir: string,\n remote = 'origin',\n syncBranch: string = SYNC_BRANCH,\n): Promise<{ success: boolean; error?: string }> {\n const worktreePath = join(baseDir, WORKTREE_DIR);\n\n // Ensure worktree exists\n if (!(await worktreeExists(baseDir))) {\n const initResult = await initWorktree(baseDir, remote, syncBranch);\n if (!initResult.success) {\n return { success: false, error: initResult.error };\n }\n }\n\n try {\n // Fetch latest from remote\n try {\n await git('-C', baseDir, 'fetch', remote, syncBranch);\n } catch {\n // Remote fetch may fail if offline - that's ok\n }\n\n // Get the latest commit on the sync branch\n let targetCommit: string;\n try {\n // Try local branch first\n targetCommit = await git('-C', baseDir, 'rev-parse', `refs/heads/${syncBranch}`);\n } catch {\n try {\n // Fall back to remote tracking branch\n targetCommit = await git(\n '-C',\n baseDir,\n 'rev-parse',\n `refs/remotes/${remote}/${syncBranch}`,\n );\n } catch {\n // No remote either - worktree is already at latest\n return { success: true };\n }\n }\n\n // Update worktree to that commit (detached HEAD)\n await git('-C', worktreePath, 'checkout', '--detach', targetCommit);\n\n return { success: true };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\n// =============================================================================\n// Branch Health Checks\n// See: tbd-design.md §2.3 Hidden Worktree Model, plan spec §4b\n// =============================================================================\n\n/**\n * Local branch health status.\n */\nexport interface LocalBranchHealth {\n exists: boolean;\n orphaned: boolean;\n head?: string;\n}\n\n/**\n * Check local sync branch health.\n * See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md §4b\n *\n * @param syncBranch - The sync branch name (default: 'tbd-sync')\n * @returns Health status indicating if branch exists and has commits\n */\nexport async function checkLocalBranchHealth(\n syncBranch: string = SYNC_BRANCH,\n): Promise<LocalBranchHealth> {\n try {\n const head = await git('rev-parse', `refs/heads/${syncBranch}`);\n return { exists: true, orphaned: false, head: head.trim() };\n } catch {\n // Check if branch ref exists but is orphaned (no commits)\n try {\n await git('show-ref', '--verify', `refs/heads/${syncBranch}`);\n return { exists: true, orphaned: true };\n } catch {\n return { exists: false, orphaned: false };\n }\n }\n}\n\n/**\n * Remote branch health status.\n */\nexport interface RemoteBranchHealth {\n exists: boolean;\n diverged: boolean;\n head?: string;\n}\n\n/**\n * Check remote sync branch health.\n * See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md §4b\n *\n * @param remote - The remote name (default: 'origin')\n * @param syncBranch - The sync branch name (default: 'tbd-sync')\n * @returns Health status indicating if remote branch exists and divergence state\n */\nexport async function checkRemoteBranchHealth(\n remote = 'origin',\n syncBranch: string = SYNC_BRANCH,\n): Promise<RemoteBranchHealth> {\n try {\n await git('fetch', remote, syncBranch);\n const head = await git('rev-parse', `refs/remotes/${remote}/${syncBranch}`);\n const remoteHead = head.trim();\n\n // Check for divergence (only if local branch exists)\n let diverged = false;\n try {\n const mergeBase = await git('merge-base', syncBranch, `${remote}/${syncBranch}`);\n const localHead = await git('rev-parse', syncBranch);\n\n // Diverged if merge-base is neither local nor remote HEAD\n diverged = mergeBase.trim() !== localHead.trim() && mergeBase.trim() !== remoteHead;\n } catch {\n // Local branch doesn't exist - can't be diverged\n diverged = false;\n }\n\n return { exists: true, diverged, head: remoteHead };\n } catch {\n return { exists: false, diverged: false };\n }\n}\n\n/**\n * Sync consistency status.\n */\nexport interface SyncConsistency {\n /** Worktree HEAD commit SHA */\n worktreeHead: string;\n /** Local branch HEAD commit SHA */\n localHead: string;\n /** Remote branch HEAD commit SHA */\n remoteHead: string;\n /** Whether worktree HEAD matches local branch HEAD */\n worktreeMatchesLocal: boolean;\n /** Number of commits local is ahead of remote */\n localAhead: number;\n /** Number of commits local is behind remote */\n localBehind: number;\n}\n\n/**\n * Check consistency between worktree, local branch, and remote.\n * See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md §4b\n *\n * @param baseDir - The base directory of the repository\n * @param syncBranch - The sync branch name (default: 'tbd-sync')\n * @param remote - The remote name (default: 'origin')\n * @returns Consistency status with HEAD comparisons and ahead/behind counts\n */\nexport async function checkSyncConsistency(\n baseDir: string,\n syncBranch: string = SYNC_BRANCH,\n remote = 'origin',\n): Promise<SyncConsistency> {\n const worktreePath = join(baseDir, WORKTREE_DIR);\n\n // Get worktree HEAD\n const worktreeHead = await git('-C', worktreePath, 'rev-parse', 'HEAD').catch(() => '');\n\n // Get local branch HEAD\n const localHead = await git('-C', baseDir, 'rev-parse', syncBranch).catch(() => '');\n\n // Get remote branch HEAD\n const remoteHead = await git('-C', baseDir, 'rev-parse', `${remote}/${syncBranch}`).catch(\n () => '',\n );\n\n // Calculate ahead/behind counts\n let localAhead = 0;\n let localBehind = 0;\n\n if (localHead && remoteHead) {\n try {\n const aheadOutput = await git(\n '-C',\n baseDir,\n 'rev-list',\n '--count',\n `${remote}/${syncBranch}..${syncBranch}`,\n );\n localAhead = parseInt(aheadOutput.trim(), 10) || 0;\n } catch {\n // Ignore errors\n }\n\n try {\n const behindOutput = await git(\n '-C',\n baseDir,\n 'rev-list',\n '--count',\n `${syncBranch}..${remote}/${syncBranch}`,\n );\n localBehind = parseInt(behindOutput.trim(), 10) || 0;\n } catch {\n // Ignore errors\n }\n }\n\n return {\n worktreeHead: worktreeHead.trim(),\n localHead: localHead.trim(),\n remoteHead: remoteHead.trim(),\n worktreeMatchesLocal: worktreeHead.trim() === localHead.trim(),\n localAhead,\n localBehind,\n };\n}\n\n/**\n * Count issues on a remote sync branch without creating a worktree.\n * Used by doctor to show accurate statistics on fresh clones.\n *\n * @param remote - The remote name (default: 'origin')\n * @param syncBranch - The sync branch name (default: 'tbd-sync')\n * @returns Number of issue files on the remote branch, or null if branch doesn't exist\n */\nexport async function countRemoteIssues(\n remote = 'origin',\n syncBranch: string = SYNC_BRANCH,\n): Promise<number | null> {\n try {\n // Fetch the remote branch first\n await git('fetch', remote, syncBranch);\n\n // List all files in the remote branch\n const remoteBranch = `${remote}/${syncBranch}`;\n const output = await git('ls-tree', '-r', '--name-only', remoteBranch);\n\n // Count issue files in the issues directory\n // Uses path constants to avoid hardcoded paths\n const issuesDir = `${TBD_DIR}/${DATA_SYNC_DIR_NAME}/issues/`;\n const lines = output.split('\\n').filter(Boolean);\n const issueCount = lines.filter(\n (line) => line.startsWith(issuesDir) && line.endsWith('.md'),\n ).length;\n\n return issueCount;\n } catch {\n // Remote branch doesn't exist or fetch failed\n return null;\n }\n}\n\n/**\n * Remove the hidden worktree.\n * Used by doctor --fix when worktree is corrupted.\n */\nexport async function removeWorktree(\n baseDir: string,\n): Promise<{ success: boolean; error?: string }> {\n const worktreePath = join(baseDir, WORKTREE_DIR);\n\n try {\n // First try to properly remove via git\n try {\n await git('-C', baseDir, 'worktree', 'remove', worktreePath, '--force');\n } catch {\n // If git worktree remove fails, just delete the directory\n await rm(worktreePath, { recursive: true, force: true });\n }\n\n // Prune stale worktree references\n await git('-C', baseDir, 'worktree', 'prune');\n\n return { success: true };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\n/**\n * Repair an unhealthy worktree.\n *\n * Follows decision tree from spec Appendix E:\n * - PRUNABLE: git worktree prune, then recreate\n * - CORRUPTED: backup to .tbd/backups/, remove, then recreate\n * - MISSING: just create\n *\n * See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md\n *\n * @param baseDir - The base directory of the repository\n * @param status - Current worktree health status\n * @param remote - The remote name (default: 'origin')\n * @param syncBranch - The sync branch name (default: 'tbd-sync')\n */\nexport async function repairWorktree(\n baseDir: string,\n status: 'missing' | 'prunable' | 'corrupted',\n remote = 'origin',\n syncBranch: string = SYNC_BRANCH,\n): Promise<{ success: boolean; path?: string; backedUp?: string; error?: string }> {\n const worktreePath = join(baseDir, WORKTREE_DIR);\n\n try {\n // Always prune stale worktree entries first for missing and prunable states\n // This ensures git's worktree list is clean before creating a new worktree\n if (status === 'missing' || status === 'prunable') {\n await git('-C', baseDir, 'worktree', 'prune');\n }\n\n // Handle corrupted status: backup before removal\n if (status === 'corrupted') {\n const backupsDir = join(baseDir, TBD_DIR, 'backups');\n await mkdir(backupsDir, { recursive: true });\n\n const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);\n const backupPath = join(backupsDir, `corrupted-worktree-backup-${timestamp}`);\n\n // Copy corrupted worktree to backup before removal\n try {\n await cp(worktreePath, backupPath, { recursive: true });\n } catch {\n // If copy fails, the directory might not exist or be accessible\n // Continue with repair anyway\n }\n\n // Remove the corrupted worktree\n await rm(worktreePath, { recursive: true, force: true });\n await git('-C', baseDir, 'worktree', 'prune');\n\n // Initialize fresh worktree\n const result = await initWorktree(baseDir, remote, syncBranch);\n return { ...result, backedUp: backupPath };\n }\n\n // For missing or prunable (after prune), just initialize\n const result = await initWorktree(baseDir, remote, syncBranch);\n return result;\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\n/**\n * Ensure worktree is attached to sync branch, not detached HEAD.\n * Old tbd versions (pre-v0.1.9) created worktrees with --detach flag.\n * This repairs them automatically.\n *\n * @param worktreePath - Path to the worktree directory\n * @returns true if worktree was detached and repaired, false if already attached\n */\nexport async function ensureWorktreeAttached(worktreePath: string): Promise<boolean> {\n try {\n const currentBranch = await git('-C', worktreePath, 'branch', '--show-current').catch(() => '');\n\n if (!currentBranch) {\n // Detached HEAD - re-attach to sync branch\n // This is a one-time repair for repos created with old tbd versions\n await git('-C', worktreePath, 'checkout', SYNC_BRANCH);\n return true; // Was detached, now repaired\n }\n\n return false; // Already attached\n } catch (error) {\n // If we can't check/fix, that's a problem but don't fail the operation\n console.warn('Warning: Could not check worktree HEAD status:', error);\n return false;\n }\n}\n\n/**\n * Migrate data from wrong location (.tbd/data-sync/) to worktree.\n *\n * Used when data was incorrectly written to the direct path instead of the worktree.\n * Per spec Appendix E:\n * 1. Backup to .tbd/backups/\n * 2. Copy issues/mappings from .tbd/data-sync/ to worktree\n * 3. Commit in worktree\n * 4. Optionally remove wrong location data\n *\n * See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md\n *\n * @param baseDir - The base directory of the repository\n * @param removeSource - Whether to remove data from wrong location after migration\n */\nexport async function migrateDataToWorktree(\n baseDir: string,\n removeSource = false,\n): Promise<{\n success: boolean;\n migratedCount: number;\n backupPath?: string;\n error?: string;\n}> {\n const wrongPath = join(baseDir, TBD_DIR, DATA_SYNC_DIR_NAME);\n const correctPath = join(baseDir, WORKTREE_DIR, TBD_DIR, DATA_SYNC_DIR_NAME);\n const worktreePath = join(baseDir, WORKTREE_DIR);\n\n try {\n // Ensure worktree is attached to sync branch (repair old tbd repos)\n await ensureWorktreeAttached(worktreePath);\n // Check if there's data in the wrong location\n const wrongIssuesPath = join(wrongPath, 'issues');\n const wrongMappingsPath = join(wrongPath, 'mappings');\n\n let issueFiles: string[] = [];\n let mappingFiles: string[] = [];\n\n try {\n const { readdir } = await import('node:fs/promises');\n issueFiles = await readdir(wrongIssuesPath).catch(() => []);\n mappingFiles = await readdir(wrongMappingsPath).catch(() => []);\n } catch {\n // Directory doesn't exist\n }\n\n // Filter out .gitkeep files\n issueFiles = issueFiles.filter((f) => f !== '.gitkeep');\n mappingFiles = mappingFiles.filter((f) => f !== '.gitkeep');\n\n if (issueFiles.length === 0 && mappingFiles.length === 0) {\n return { success: true, migratedCount: 0 };\n }\n\n // Step 1: Backup to .tbd/backups/\n const backupsDir = join(baseDir, TBD_DIR, 'backups');\n await mkdir(backupsDir, { recursive: true });\n\n const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);\n const backupPath = join(backupsDir, `data-sync-backup-${timestamp}`);\n\n await cp(wrongPath, backupPath, { recursive: true });\n\n // Step 2: Copy issues and mappings to worktree\n const correctIssuesPath = join(correctPath, 'issues');\n const correctMappingsPath = join(correctPath, 'mappings');\n\n await mkdir(correctIssuesPath, { recursive: true });\n await mkdir(correctMappingsPath, { recursive: true });\n\n for (const file of issueFiles) {\n await cp(join(wrongIssuesPath, file), join(correctIssuesPath, file));\n }\n\n // Merge ID mappings instead of overwriting — ids.yml is append-only, so a\n // raw cp would destroy existing entries in the worktree. Import and use the\n // merge utilities so both source and destination entries are preserved.\n for (const file of mappingFiles) {\n if (file === 'ids.yml') {\n const { readFile } = await import('node:fs/promises');\n const { loadIdMapping, mergeIdMappings, saveIdMapping, resolveIdMappingConflicts } =\n await import('./id-mapping.js');\n const sourceContent = await readFile(join(wrongMappingsPath, file), 'utf-8');\n const sourceMapping = resolveIdMappingConflicts(sourceContent);\n\n let targetMapping;\n try {\n const targetContent = await readFile(join(correctMappingsPath, file), 'utf-8');\n targetMapping = resolveIdMappingConflicts(targetContent);\n } catch {\n targetMapping = await loadIdMapping(correctPath);\n }\n\n const merged = mergeIdMappings(targetMapping, sourceMapping);\n await saveIdMapping(correctPath, merged);\n } else {\n await cp(join(wrongMappingsPath, file), join(correctMappingsPath, file));\n }\n }\n\n // Step 3: Commit in worktree (if there are changes)\n // Use --no-verify to bypass parent repo hooks (lefthook, husky, etc.)\n const totalFiles = issueFiles.length + mappingFiles.length;\n await git('-C', worktreePath, 'add', '-A');\n\n // Check if there are staged changes before committing\n const hasChanges = await git('-C', worktreePath, 'diff', '--cached', '--quiet')\n .then(() => false)\n .catch(() => true);\n\n if (hasChanges) {\n await git(\n '-C',\n worktreePath,\n 'commit',\n '--no-verify',\n '-m',\n `tbd: migrate ${totalFiles} file(s) from incorrect location`,\n );\n }\n // If no changes, files were already migrated - that's fine\n\n // Step 4: Optionally remove wrong location data\n if (removeSource) {\n // Remove issue and mapping files, but keep directory structure\n for (const file of issueFiles) {\n await rm(join(wrongIssuesPath, file));\n }\n for (const file of mappingFiles) {\n await rm(join(wrongMappingsPath, file));\n }\n }\n\n return {\n success: true,\n migratedCount: totalFiles,\n backupPath,\n };\n } catch (error) {\n return {\n success: false,\n migratedCount: 0,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n","/**\n * `tbd init` - Initialize tbd in a repository.\n *\n * See: tbd-design.md §4.3 Initialization\n */\n\nimport { Command } from 'commander';\nimport { mkdir, stat } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { ensureGitignorePatterns } from '../../utils/gitignore-utils.js';\nimport { CLIError, ValidationError } from '../lib/errors.js';\nimport { isValidPrefix, isRecommendedPrefix } from '../lib/prefix-detection.js';\nimport { VERSION } from '../lib/version.js';\nimport { initConfig } from '../../file/config.js';\nimport {\n TBD_DIR,\n WORKTREE_DIR_NAME,\n DATA_SYNC_DIR_NAME,\n SYNC_BRANCH,\n TBD_SHORTCUTS_SYSTEM,\n TBD_SHORTCUTS_STANDARD,\n TBD_GUIDELINES_DIR,\n TBD_TEMPLATES_DIR,\n TBD_DOCS_DIR,\n} from '../../lib/paths.js';\nimport {\n initWorktree,\n checkGitVersion,\n checkWorktreeHealth,\n findGitRoot,\n isInGitRepo,\n MIN_GIT_VERSION,\n} from '../../file/git.js';\n\ninterface InitOptions {\n prefix?: string;\n force?: boolean;\n syncBranch?: string;\n remote?: string;\n}\n\nclass InitHandler extends BaseCommand {\n async run(options: InitOptions): Promise<void> {\n // Require git repository and resolve to git root\n const inGitRepo = await isInGitRepo();\n if (!inGitRepo) {\n throw new CLIError('Not a git repository. Run `git init` first.');\n }\n\n const gitRoot = await findGitRoot();\n if (!gitRoot) {\n throw new CLIError('Could not determine git repository root.');\n }\n\n // Use git root as the working directory (ensures .tbd/ is always at repo root)\n const cwd = gitRoot;\n\n // Check if already initialized\n try {\n await stat(join(cwd, TBD_DIR));\n throw new CLIError('tbd is already initialized in this directory');\n } catch (error) {\n // Not initialized - continue (unless it's our CLIError)\n if (error instanceof CLIError) throw error;\n }\n\n // Validate prefix is provided\n if (!options.prefix) {\n throw new ValidationError(\n 'The --prefix option is required\\n\\n' +\n 'Usage: tbd init --prefix=<name>\\n\\n' +\n 'The prefix is used for display IDs (e.g., proj-a7k2, myapp-b3m9)\\n' +\n 'Choose a short 2-8 letter prefix for your project (e.g., tbd, myp, proj).\\n\\n' +\n 'For full setup with integrations: tbd setup --auto --prefix=<name>',\n );\n }\n\n const prefix = options.prefix;\n\n // Hard validation: always enforced\n if (!isValidPrefix(prefix)) {\n throw new ValidationError(\n 'Invalid prefix format.\\n' +\n 'Prefix must be 1-20 lowercase characters:\\n' +\n ' - Must start with a letter (a-z)\\n' +\n ' - Must end with alphanumeric (a-z, 0-9)\\n' +\n ' - Middle characters can include dots (.) and underscores (_)\\n' +\n ' - No dashes allowed (breaks ID syntax)\\n\\n' +\n 'Example:\\n' +\n ' tbd init --prefix=tbd',\n );\n }\n\n // Soft validation: recommended format (2-8 alphabetic)\n if (!isRecommendedPrefix(prefix) && !options.force) {\n throw new ValidationError(\n `Prefix \"${prefix}\" is not recommended.\\n` +\n 'Recommended prefixes are 2-8 alphabetic characters (e.g., \"tbd\", \"myp\", \"proj\").\\n\\n' +\n 'If you really want to use this prefix, add --force to override.\\n\\n' +\n 'Example:\\n' +\n ` tbd init --prefix=${prefix} --force`,\n );\n }\n\n if (this.checkDryRun('Would initialize tbd repository', options)) {\n return;\n }\n\n await this.execute(async () => {\n // 1. Create .tbd/ directory with config.yml\n // Note: options.prefix is validated to be non-null above\n await initConfig(cwd, VERSION, options.prefix!);\n this.output.debug(`Created ${TBD_DIR}/config.yml with prefix '${options.prefix}'`);\n\n // 2. Create .tbd/.gitignore (idempotent)\n // Per spec: Must ignore docs/, data-sync-worktree/, and data-sync/\n await ensureGitignorePatterns(join(cwd, TBD_DIR, '.gitignore'), [\n '# Installed documentation (regenerated on setup)',\n 'docs/',\n '',\n '# Hidden worktree for tbd-sync branch',\n `${WORKTREE_DIR_NAME}/`,\n '',\n '# Data sync directory (only exists in worktree)',\n `${DATA_SYNC_DIR_NAME}/`,\n '',\n '# Local state',\n 'state.yml',\n '',\n '# Migration backups (local only, not synced)',\n 'backups/',\n '',\n '# Temporary files',\n '*.tmp',\n '*.temp',\n '',\n '# workspaces/ stores state (including outbox) committed to the working branch',\n '!workspaces/',\n ]);\n this.output.debug(`Created ${TBD_DIR}/.gitignore`);\n\n // 3. Create docs directories for shortcuts, guidelines, and templates\n await mkdir(join(cwd, TBD_SHORTCUTS_SYSTEM), { recursive: true });\n await mkdir(join(cwd, TBD_SHORTCUTS_STANDARD), { recursive: true });\n await mkdir(join(cwd, TBD_GUIDELINES_DIR), { recursive: true });\n await mkdir(join(cwd, TBD_TEMPLATES_DIR), { recursive: true });\n this.output.debug(`Created ${TBD_DOCS_DIR}/ directories`);\n\n // 4. Initialize the hidden worktree for tbd-sync branch\n // This creates .tbd/data-sync-worktree/ with the sync branch checkout\n const remote = options.remote ?? 'origin';\n const syncBranch = options.syncBranch ?? SYNC_BRANCH;\n\n // Check Git version before attempting worktree creation\n // Git 2.42+ is required for --orphan worktree support\n try {\n const { version, supported } = await checkGitVersion();\n if (!supported) {\n const versionStr = `${version.major}.${version.minor}.${version.patch}`;\n throw new CLIError(\n `Git ${versionStr} detected. Git ${MIN_GIT_VERSION}+ is required for tbd.\\n\\n` +\n `tbd requires Git 2.42+ for orphan worktree support.\\n` +\n `Please upgrade Git: https://git-scm.com/downloads`,\n );\n }\n this.output.debug(`Git version ${version.major}.${version.minor}.${version.patch} OK`);\n } catch (error) {\n // If git is not installed at all, let worktree init handle it\n if (error instanceof CLIError) throw error;\n this.output.debug(`Git version check skipped: ${(error as Error).message}`);\n }\n\n const worktreeResult = await initWorktree(cwd, remote, syncBranch);\n\n if (worktreeResult.success) {\n if (worktreeResult.created) {\n this.output.debug(`Created hidden worktree at ${TBD_DIR}/${WORKTREE_DIR_NAME}/`);\n } else {\n this.output.debug(`Worktree already exists at ${TBD_DIR}/${WORKTREE_DIR_NAME}/`);\n }\n\n // Verify worktree health after creation (prevents silent failures)\n const health = await checkWorktreeHealth(cwd);\n if (!health.valid) {\n this.output.warn(\n `Worktree created but failed verification (status: ${health.status}). ` +\n `Run 'tbd doctor' to diagnose.`,\n );\n }\n } else {\n // Worktree creation failed - this is ok if not in a git repo\n // Log warning but don't fail init (supports non-git usage)\n this.output.debug(`Note: Worktree not created (${worktreeResult.error})`);\n }\n }, 'Failed to initialize tbd');\n\n this.output.data({ initialized: true, version: VERSION, prefix: options.prefix }, () => {\n this.output.success(`Initialized tbd repository (prefix: ${options.prefix})`);\n // Only show next steps if not in quiet mode\n if (!this.output.isQuiet()) {\n console.log('');\n console.log('Next steps:');\n console.log(' git add .tbd/ && git commit -m \"Initialize tbd\"');\n console.log(' tbd setup --auto # Optional: configure agent integrations');\n }\n });\n }\n}\n\nexport const initCommand = new Command('init')\n .description('Initialize tbd in a git repository')\n .option('--prefix <name>', 'Project prefix for display IDs (2-8 alphabetic recommended)')\n .option('--force', 'Allow non-recommended prefix format')\n .option('--sync-branch <name>', 'Sync branch name (default: tbd-sync)')\n .option('--remote <name>', 'Remote name (default: origin)')\n .action(async (options, command) => {\n const handler = new InitHandler(command);\n await handler.run(options);\n });\n","/**\n * Storage layer for issue files.\n *\n * Provides atomic file operations and issue CRUD operations.\n * All operations work on the hidden worktree at .tbd/data-sync/issues/.\n *\n * See: tbd-design.md §3.2 Storage Layer\n */\n\nimport { readFile, unlink, readdir } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { writeFile } from 'atomically';\n\nimport type { Issue } from '../lib/types.js';\nimport { parseIssue, serializeIssue } from './parser.js';\n\n/**\n * Get the path to an issue file.\n */\nfunction getIssuePath(baseDir: string, id: string): string {\n return join(baseDir, 'issues', `${id}.md`);\n}\n\n/**\n * Read an issue from the worktree.\n * @throws If the issue file doesn't exist or is invalid.\n */\nexport async function readIssue(baseDir: string, id: string): Promise<Issue> {\n const filePath = getIssuePath(baseDir, id);\n const content = await readFile(filePath, 'utf-8');\n return parseIssue(content);\n}\n\n/**\n * Write an issue to the worktree.\n * Uses atomic write to prevent corruption.\n */\nexport async function writeIssue(baseDir: string, issue: Issue): Promise<void> {\n const filePath = getIssuePath(baseDir, issue.id);\n const content = serializeIssue(issue);\n await writeFile(filePath, content);\n}\n\n/**\n * List all issues in the worktree.\n * Returns empty array if issues directory doesn't exist.\n *\n * Uses parallel file reading for better performance with many issues.\n */\nexport async function listIssues(baseDir: string): Promise<Issue[]> {\n const issuesDir = join(baseDir, 'issues');\n\n let files: string[];\n try {\n files = await readdir(issuesDir);\n } catch {\n // Directory doesn't exist - return empty\n return [];\n }\n\n // Filter to only .md files\n const mdFiles = files.filter((f) => f.endsWith('.md'));\n\n // Read files in batches to avoid EMFILE (too many open files) on large repos.\n // Unbounded Promise.all on thousands of files exceeds typical fd limits (1024-4096).\n const BATCH_SIZE = 200;\n const issues: Issue[] = [];\n\n for (let i = 0; i < mdFiles.length; i += BATCH_SIZE) {\n const batch = mdFiles.slice(i, i + BATCH_SIZE);\n const fileContents = await Promise.all(\n batch.map(async (file) => {\n const filePath = join(issuesDir, file);\n try {\n const content = await readFile(filePath, 'utf-8');\n return { file, content };\n } catch {\n return { file, content: null };\n }\n }),\n );\n\n for (const { file, content } of fileContents) {\n if (content === null) continue;\n try {\n const issue = parseIssue(content);\n issues.push(issue);\n } catch (error) {\n // Skip invalid files with a warning\n console.warn(`Skipping invalid issue file: ${file}`, error);\n }\n }\n }\n\n return issues;\n}\n\n/**\n * Delete an issue from the worktree.\n * Does not throw if issue doesn't exist.\n */\nexport async function deleteIssue(baseDir: string, id: string): Promise<void> {\n const filePath = getIssuePath(baseDir, id);\n try {\n await unlink(filePath);\n } catch (error) {\n // Ignore ENOENT (file doesn't exist)\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {\n throw error;\n }\n }\n}\n","/**\n * Priority formatting and parsing utilities.\n *\n * Priority values range from 0 (critical) to 4 (backlog).\n * Display format uses \"P\" prefix: P0, P1, P2, P3, P4.\n */\n\nimport type { createColors } from '../cli/lib/output.js';\n\n/**\n * Valid priority values.\n */\nexport const MIN_PRIORITY = 0;\nexport const MAX_PRIORITY = 4;\n\n/**\n * Format a priority number for display.\n * Always uses \"P\" prefix format.\n *\n * @example formatPriority(0) → \"P0\"\n * @example formatPriority(2) → \"P2\"\n */\nexport function formatPriority(priority: number): string {\n return `P${priority}`;\n}\n\n/**\n * Parse a priority string to a number.\n * Accepts both numeric (\"1\") and prefixed (\"P1\") formats.\n * Case-insensitive for prefixed format.\n *\n * @example parsePriority(\"P1\") → 1\n * @example parsePriority(\"p0\") → 0\n * @example parsePriority(\"2\") → 2\n * @example parsePriority(\"invalid\") → undefined\n *\n * @returns The priority number, or undefined if invalid.\n */\nexport function parsePriority(input: string): number | undefined {\n const trimmed = input.trim().toUpperCase();\n if (!trimmed) return undefined;\n\n let numStr: string;\n if (trimmed.startsWith('P')) {\n // Prefixed format: P0, P1, etc.\n if (trimmed.length !== 2) return undefined;\n numStr = trimmed.slice(1);\n } else {\n // Numeric format: 0, 1, etc.\n numStr = trimmed;\n }\n\n const num = parseInt(numStr, 10);\n if (isNaN(num) || num < MIN_PRIORITY || num > MAX_PRIORITY) {\n return undefined;\n }\n\n return num;\n}\n\n/**\n * Get the color function for a priority value.\n *\n * - P0 (critical): red\n * - P1 (high): yellow\n * - P2-P4: no color (identity function)\n *\n * @param priority - The priority number (0-4)\n * @param colors - The colors object from createColors()\n * @returns A function that applies the appropriate color\n */\nexport function getPriorityColor(\n priority: number,\n colors: ReturnType<typeof createColors>,\n): (s: string) => string {\n switch (priority) {\n case 0:\n return colors.error;\n case 1:\n return colors.warn;\n default:\n return (s) => s;\n }\n}\n","/**\n * Generic project path utilities for handling user-provided paths.\n *\n * This module provides reusable functions for resolving, validating, and\n * normalizing paths relative to a project root. It is designed to be\n * general-purpose and not tied to any specific use case (e.g., specs).\n *\n * Key features:\n * - Resolves absolute, relative, and subdirectory paths to project-relative paths\n * - Validates that paths stay within project boundaries\n * - Normalizes paths (removes ./, converts backslashes, etc.)\n * - Validates file existence\n */\n\nimport { resolve, relative, isAbsolute, normalize, sep } from 'node:path';\nimport { access, stat } from 'node:fs/promises';\n\n/**\n * Result of resolving a path relative to the project root.\n */\nexport interface ResolvedProjectPath {\n /** Path relative to project root (always uses forward slashes) */\n relativePath: string;\n /** Absolute path to the file */\n absolutePath: string;\n}\n\n/**\n * Error thrown when a path operation fails.\n * The message is user-friendly and can be displayed directly.\n */\nexport class ProjectPathError extends Error {\n constructor(\n message: string,\n public readonly code: 'OUTSIDE_PROJECT' | 'NOT_FOUND' | 'NOT_A_FILE',\n ) {\n super(message);\n this.name = 'ProjectPathError';\n }\n}\n\n/**\n * Converts a ProjectPathError to a user-friendly error message for CLI display.\n * Returns the error message if it's a ProjectPathError, otherwise re-throws.\n */\nexport function getPathErrorMessage(error: unknown): string {\n if (error instanceof ProjectPathError) {\n return error.message;\n }\n throw error;\n}\n\n/**\n * Normalizes a path by:\n * - Removing leading ./\n * - Converting backslashes to forward slashes (Windows compatibility)\n * - Removing redundant slashes\n * - Resolving . and .. components\n *\n * @param inputPath - The path to normalize\n * @returns Normalized path string\n */\nexport function normalizePath(inputPath: string): string {\n if (!inputPath) {\n return '';\n }\n\n // First, convert all backslashes to forward slashes (Windows compatibility)\n // This must happen BEFORE normalize() since backslashes aren't path separators on Linux\n let normalized = inputPath.replace(/\\\\/g, '/');\n\n // Use Node's normalize to handle . and .. components\n // (normalize on Linux won't touch forward slashes)\n normalized = normalize(normalized);\n\n // Ensure we still have forward slashes after normalize (in case of mixed separators)\n normalized = normalized.split(sep).join('/');\n\n // Remove leading ./\n while (normalized.startsWith('./')) {\n normalized = normalized.slice(2);\n }\n\n // Remove trailing slash (unless it's just \"/\")\n if (normalized.length > 1 && normalized.endsWith('/')) {\n normalized = normalized.slice(0, -1);\n }\n\n // Handle edge case where normalize returns '.'\n if (normalized === '.') {\n return '';\n }\n\n return normalized;\n}\n\n/**\n * Checks if an absolute path is within the project root.\n *\n * @param absolutePath - The absolute path to check\n * @param projectRoot - The project root directory\n * @returns true if the path is within or at the project root\n */\nexport function isPathWithinProject(absolutePath: string, projectRoot: string): boolean {\n // Normalize both paths for consistent comparison\n const normalizedPath = resolve(absolutePath);\n const normalizedRoot = resolve(projectRoot);\n\n // The path is within the project if it starts with the project root\n // We need to handle the case where the path IS the project root\n // or is a subdirectory of it\n if (normalizedPath === normalizedRoot) {\n return true;\n }\n\n // Check if path starts with root + separator\n // This prevents false positives like /project-backup matching /project\n return normalizedPath.startsWith(normalizedRoot + sep);\n}\n\n/**\n * Resolves any path (absolute, relative, or from subdirectory) to a project-relative path.\n *\n * Resolution rules:\n * 1. Absolute paths within project → convert to relative\n * 2. Absolute paths outside project → error\n * 3. Relative paths from subdirectory → resolve to project root\n * 4. Already project-relative → pass through with normalization\n * 5. Path escaping project (../../) → error\n *\n * @param inputPath - The path provided by the user (can be absolute or relative)\n * @param projectRoot - The project root directory (parent of .tbd/)\n * @param cwd - Current working directory (where command was run)\n * @returns Resolved paths object with both relative and absolute paths\n * @throws ProjectPathError if path is outside project\n */\nexport function resolveProjectPath(\n inputPath: string,\n projectRoot: string,\n cwd: string,\n): ResolvedProjectPath {\n // Normalize inputs\n const normalizedProjectRoot = resolve(projectRoot);\n const normalizedCwd = resolve(cwd);\n\n let absolutePath: string;\n\n if (isAbsolute(inputPath)) {\n // Input is already absolute\n absolutePath = resolve(inputPath);\n } else {\n // Input is relative - resolve from current working directory\n absolutePath = resolve(normalizedCwd, inputPath);\n }\n\n // Check if path is within project\n if (!isPathWithinProject(absolutePath, normalizedProjectRoot)) {\n throw new ProjectPathError(`Path is outside project root: ${inputPath}`, 'OUTSIDE_PROJECT');\n }\n\n // Calculate relative path from project root\n const relativePath = relative(normalizedProjectRoot, absolutePath);\n\n // Normalize the relative path (remove ./, convert separators, etc.)\n const normalizedRelative = normalizePath(relativePath);\n\n return {\n relativePath: normalizedRelative,\n absolutePath,\n };\n}\n\n/**\n * Validates that a file exists at the resolved path.\n *\n * @param resolvedPath - Path already resolved via resolveProjectPath\n * @returns true if file exists\n * @throws ProjectPathError if file does not exist or is not a file\n */\nexport async function validateFileExists(resolvedPath: ResolvedProjectPath): Promise<boolean> {\n try {\n await access(resolvedPath.absolutePath);\n } catch {\n throw new ProjectPathError(`File not found: ${resolvedPath.relativePath}`, 'NOT_FOUND');\n }\n\n // Also verify it's a file, not a directory\n try {\n const stats = await stat(resolvedPath.absolutePath);\n if (!stats.isFile()) {\n throw new ProjectPathError(`Path is not a file: ${resolvedPath.relativePath}`, 'NOT_A_FILE');\n }\n } catch (error) {\n if (error instanceof ProjectPathError) {\n throw error;\n }\n throw new ProjectPathError(`File not found: ${resolvedPath.relativePath}`, 'NOT_FOUND');\n }\n\n return true;\n}\n\n/**\n * Convenience function that resolves and validates a path in one call.\n *\n * @param inputPath - The path provided by the user\n * @param projectRoot - The project root directory\n * @param cwd - Current working directory\n * @returns Resolved and validated path\n * @throws ProjectPathError if path is outside project or file doesn't exist\n */\nexport async function resolveAndValidatePath(\n inputPath: string,\n projectRoot: string,\n cwd: string,\n): Promise<ResolvedProjectPath> {\n const resolved = resolveProjectPath(inputPath, projectRoot, cwd);\n await validateFileExists(resolved);\n return resolved;\n}\n","/**\n * `tbd create` - Create a new issue.\n *\n * See: tbd-design.md §4.4 Create\n */\n\nimport { Command } from 'commander';\nimport { readFile } from 'node:fs/promises';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { requireInit, ValidationError, CLIError } from '../lib/errors.js';\nimport type { Issue, IssueKindType, PriorityType } from '../../lib/types.js';\nimport { generateInternalId, extractUlidFromInternalId } from '../../lib/ids.js';\nimport { readIssue, writeIssue } from '../../file/storage.js';\nimport {\n loadIdMapping,\n saveIdMapping,\n generateUniqueShortId,\n addIdMapping,\n resolveToInternalId,\n} from '../../file/id-mapping.js';\nimport { IssueKind } from '../../lib/schemas.js';\nimport { parsePriority } from '../../lib/priority.js';\nimport { resolveDataSyncDir } from '../../lib/paths.js';\nimport { now } from '../../utils/time-utils.js';\nimport { readConfig } from '../../file/config.js';\nimport { resolveAndValidatePath, getPathErrorMessage } from '../../lib/project-paths.js';\n\ninterface CreateOptions {\n fromFile?: string;\n type?: string;\n priority?: string;\n description?: string;\n file?: string;\n assignee?: string;\n due?: string;\n defer?: string;\n parent?: string;\n label?: string[];\n spec?: string;\n}\n\nclass CreateHandler extends BaseCommand {\n async run(title: string | undefined, options: CreateOptions): Promise<void> {\n const tbdRoot = await requireInit();\n\n // Validate title is provided (unless --from-file)\n if (!title && !options.fromFile) {\n throw new ValidationError('Title is required. Use: tbd create \"Issue title\"');\n }\n\n // Parse and validate options\n const kind = this.parseKind(options.type ?? 'task');\n const priority = this.validatePriority(options.priority ?? '2');\n\n // Read description from file if specified\n let description = options.description;\n if (options.file) {\n try {\n description = await readFile(options.file, 'utf-8');\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new CLIError(`Failed to read description from file '${options.file}': ${message}`);\n }\n }\n\n // Validate and normalize spec path if provided\n let specPath: string | undefined;\n if (options.spec) {\n try {\n const resolved = await resolveAndValidatePath(options.spec, tbdRoot, process.cwd());\n specPath = resolved.relativePath;\n } catch (error) {\n throw new ValidationError(getPathErrorMessage(error));\n }\n }\n\n if (\n this.checkDryRun('Would create issue', { title, kind, priority, spec: specPath, ...options })\n ) {\n return;\n }\n\n const timestamp = now();\n const id = generateInternalId();\n const ulid = extractUlidFromInternalId(id);\n\n let shortId: string;\n let prefix: string;\n let issue: Issue;\n await this.execute(async () => {\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n\n // Read config for display prefix\n const config = await readConfig(tbdRoot);\n prefix = config.display.id_prefix;\n\n // Load mapping, generate unique short ID, and save\n const mapping = await loadIdMapping(dataSyncDir);\n shortId = generateUniqueShortId(mapping);\n addIdMapping(mapping, ulid, shortId);\n\n // Resolve parent ID if provided (convert display ID to internal ID)\n let parentId: string | undefined;\n if (options.parent) {\n try {\n parentId = resolveToInternalId(options.parent, mapping);\n } catch {\n throw new ValidationError(`Invalid parent ID: ${options.parent}`);\n }\n }\n\n // Inherit spec_path from parent if not explicitly provided\n if (!specPath && parentId) {\n const parentIssue = await readIssue(dataSyncDir, parentId);\n if (parentIssue.spec_path) {\n specPath = parentIssue.spec_path;\n }\n }\n\n issue = {\n type: 'is',\n id,\n version: 1,\n title: title!,\n kind,\n status: 'open',\n priority,\n labels: options.label ?? [],\n dependencies: [],\n created_at: timestamp,\n updated_at: timestamp,\n description: description ?? undefined,\n assignee: options.assignee ?? undefined,\n due_date: options.due ?? undefined,\n deferred_until: options.defer ?? undefined,\n parent_id: parentId,\n spec_path: specPath,\n };\n\n // Write both the issue and the mapping\n await writeIssue(dataSyncDir, issue);\n await saveIdMapping(dataSyncDir, mapping);\n\n // When creating with a parent, append child to parent's child_order_hints\n if (parentId) {\n try {\n const parentIssue = await readIssue(dataSyncDir, parentId);\n const hints = parentIssue.child_order_hints ?? [];\n\n // Only append if not already in hints (shouldn't happen for new issue, but safe)\n if (!hints.includes(id)) {\n parentIssue.child_order_hints = [...hints, id];\n parentIssue.version += 1;\n parentIssue.updated_at = timestamp;\n await writeIssue(dataSyncDir, parentIssue);\n }\n } catch {\n // Parent not found or other error - skip order hint update\n }\n }\n }, 'Failed to create issue');\n\n // Output with display ID (prefix + short ID)\n const displayId = `${prefix!}-${shortId!}`;\n this.output.data({ id: displayId, internalId: id, title }, () => {\n this.output.success(`Created ${displayId}: ${title}`);\n });\n }\n\n private parseKind(value: string): IssueKindType {\n const result = IssueKind.safeParse(value);\n if (!result.success) {\n throw new ValidationError(`Invalid type: ${value}. Must be: bug, feature, task, epic, chore`);\n }\n return result.data;\n }\n\n private validatePriority(value: string): PriorityType {\n // Use shared parsePriority which accepts both \"P1\" and \"1\" formats\n const num = parsePriority(value);\n if (num === undefined) {\n throw new ValidationError(`Invalid priority: ${value}. Use P0-P4 or 0-4.`);\n }\n return num;\n }\n}\n\nexport const createCommand = new Command('create')\n .description('Create a new issue')\n .argument('[title]', 'Issue title')\n .option('--from-file <path>', 'Create from YAML+Markdown file')\n .option('-t, --type <type>', 'Issue type: bug, feature, task, epic, chore', 'task')\n .option('-p, --priority <0-4>', 'Priority (0=critical, 4=lowest)', '2')\n .option('-d, --description <text>', 'Description')\n .option('-f, --file <path>', 'Read description from file')\n .option('--assignee <name>', 'Assignee')\n .option('--due <date>', 'Due date (ISO8601)')\n .option('--defer <date>', 'Defer until date (ISO8601)')\n .option('--parent <id>', 'Parent issue ID')\n .option('--spec <path>', 'Link to spec document (relative path)')\n .option('-l, --label <label>', 'Add label (repeatable)', (val, prev: string[] = []) => [\n ...prev,\n val,\n ])\n .action(async (title, options, command) => {\n const handler = new CreateHandler(command);\n await handler.run(title, options);\n });\n","/**\n * Utility for parsing limit options in CLI commands.\n */\n\n/**\n * Parse a limit option and apply it to an array.\n *\n * @param items - Array to limit\n * @param limitOption - String limit option from CLI (may be undefined)\n * @returns Limited array (or original if no valid limit)\n */\nexport function applyLimit<T>(items: T[], limitOption: string | undefined): T[] {\n if (!limitOption) {\n return items;\n }\n const limit = parseInt(limitOption, 10);\n if (isNaN(limit) || limit <= 0) {\n return items;\n }\n return items.slice(0, limit);\n}\n","/**\n * Shared data context for tbd commands.\n *\n * Provides a single point to load common data needed by most commands:\n * - dataSyncDir: the path to the data sync directory\n * - mapping: the ID mapping (ULID to short ID)\n * - config: the project configuration\n * - prefix: the display prefix (from config.display.id_prefix)\n *\n * This eliminates the repetitive pattern of loading these individually in each command.\n *\n * For unified CLI + data context with helper methods, use FullCommandContext and\n * loadFullContext() which adds displayId() and other conveniences.\n */\n\nimport type { Command } from 'commander';\nimport type { IdMapping } from '../../file/id-mapping.js';\nimport { loadIdMapping, resolveToInternalId } from '../../file/id-mapping.js';\nimport { readConfig } from '../../file/config.js';\nimport type { Config } from '../../lib/types.js';\nimport { resolveDataSyncDir } from '../../lib/paths.js';\nimport { formatDisplayId, formatDebugId } from '../../lib/ids.js';\nimport type { CommandContext } from './context.js';\nimport { getCommandContext } from './context.js';\nimport { requireInit, NotFoundError } from './errors.js';\n\n/**\n * Data context containing commonly needed data for tbd commands.\n */\nexport interface TbdDataContext {\n /** Path to the data sync directory */\n dataSyncDir: string;\n /** ID mapping (ULID to short ID and vice versa) */\n mapping: IdMapping;\n /** Project configuration */\n config: Config;\n /** Display prefix from config (convenience accessor) */\n prefix: string;\n}\n\n/**\n * Full command context combining CLI options with data context.\n * Provides unified access to all command needs with helper methods.\n */\nexport interface FullCommandContext extends TbdDataContext {\n /** CLI options (dryRun, verbose, json, debug, etc.) */\n cli: CommandContext;\n /**\n * Format an internal issue ID for display.\n * Automatically respects debug mode to show full internal ID.\n */\n displayId(internalId: string): string;\n /**\n * Resolve user input ID to internal ID.\n * @throws NotFoundError if the ID cannot be resolved\n */\n resolveId(inputId: string): string;\n}\n\n/**\n * Load all common data context needed by tbd commands.\n *\n * This loads:\n * - dataSyncDir from resolveDataSyncDir()\n * - mapping from loadIdMapping()\n * - config from readConfig()\n * - prefix from config.display.id_prefix\n *\n * Call this once at the start of a command handler instead of\n * loading each piece separately.\n *\n * @param tbdRoot - The tbd repository root directory (from requireInit or findTbdRoot)\n * @throws Error if any of the resources fail to load\n */\nexport async function loadDataContext(tbdRoot: string): Promise<TbdDataContext> {\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n const [mapping, config] = await Promise.all([loadIdMapping(dataSyncDir), readConfig(tbdRoot)]);\n\n return {\n dataSyncDir,\n mapping,\n config,\n prefix: config.display.id_prefix,\n };\n}\n\n/**\n * Load unified command context with CLI options, data, and helper methods.\n *\n * This is the recommended way to initialize command context. It:\n * 1. Checks that tbd is initialized (calls requireInit)\n * 2. Loads data context (dataSyncDir, mapping, config, prefix)\n * 3. Extracts CLI context from Commander\n * 4. Provides helper methods like displayId() and resolveId()\n *\n * Usage:\n * ```ts\n * class MyHandler extends BaseCommand {\n * async run(id: string): Promise<void> {\n * const ctx = await loadFullContext(this.command);\n * const internalId = ctx.resolveId(id);\n * const issue = await readIssue(ctx.dataSyncDir, internalId);\n * console.log(ctx.displayId(issue.id));\n * }\n * }\n * ```\n *\n * @param command - The Commander command instance\n * @throws Error if tbd is not initialized or resources fail to load\n */\nexport async function loadFullContext(command: Command): Promise<FullCommandContext> {\n const tbdRoot = await requireInit();\n\n const cli = getCommandContext(command);\n const dataCtx = await loadDataContext(tbdRoot);\n\n return {\n ...dataCtx,\n cli,\n displayId(internalId: string): string {\n return cli.debug\n ? formatDebugId(internalId, dataCtx.mapping, dataCtx.prefix)\n : formatDisplayId(internalId, dataCtx.mapping, dataCtx.prefix);\n },\n resolveId(inputId: string): string {\n try {\n return resolveToInternalId(inputId, dataCtx.mapping);\n } catch {\n throw new NotFoundError('Issue', inputId);\n }\n },\n };\n}\n","/**\n * Status formatting utilities.\n *\n * Status values: open, in_progress, blocked, deferred, closed.\n */\n\nimport { ICONS, type createColors } from '../cli/lib/output.js';\nimport type { IssueStatusType } from './types.js';\n\n/**\n * Get the icon for a status value.\n *\n * - open: ○ (empty circle)\n * - in_progress: ◐ (half-filled circle)\n * - blocked: ● (filled circle)\n * - deferred: ○ (empty circle, same as open)\n * - closed: ✓ (checkmark)\n */\nexport function getStatusIcon(status: IssueStatusType): string {\n switch (status) {\n case 'open':\n return ICONS.OPEN;\n case 'in_progress':\n return ICONS.IN_PROGRESS;\n case 'blocked':\n return ICONS.BLOCKED;\n case 'deferred':\n return ICONS.DEFERRED;\n case 'closed':\n return ICONS.CLOSED;\n default:\n return '';\n }\n}\n\n/**\n * Format a status for display with icon prefix.\n * Format: \"icon status\" (e.g., \"○ open\", \"◐ in_progress\")\n *\n * @example formatStatus('open') → \"○ open\"\n * @example formatStatus('blocked') → \"● blocked\"\n */\nexport function formatStatus(status: IssueStatusType): string {\n const icon = getStatusIcon(status);\n return `${icon} ${status}`;\n}\n\n/**\n * Get the color function for a status value.\n *\n * - open: blue (info)\n * - in_progress: green (success)\n * - blocked: red (error)\n * - deferred: dim\n * - closed: dim\n *\n * @param status - The status value\n * @param colors - The colors object from createColors()\n * @returns A function that applies the appropriate color\n */\nexport function getStatusColor(\n status: IssueStatusType,\n colors: ReturnType<typeof createColors>,\n): (s: string) => string {\n switch (status) {\n case 'open':\n return colors.info;\n case 'in_progress':\n return colors.success;\n case 'blocked':\n return colors.error;\n case 'deferred':\n return colors.dim;\n case 'closed':\n return colors.dim;\n default:\n return (s) => s;\n }\n}\n","/**\n * Text truncation utilities for CLI output.\n *\n * Provides consistent truncation with Unicode ellipsis character.\n */\n\n/**\n * Unicode ellipsis character (U+2026). Use this instead of '...'.\n */\nexport const ELLIPSIS = '…';\n\n/**\n * Options for truncate function.\n */\nexport interface TruncateOptions {\n /** If true, truncate at last word boundary before maxLength. */\n wordBoundary?: boolean;\n}\n\n/**\n * Truncate text to maxLength, appending ellipsis if truncated.\n *\n * @param text - Text to truncate\n * @param maxLength - Maximum length including ellipsis\n * @param options - Truncation options\n * @returns Truncated text with ellipsis, or original if short enough\n */\nexport function truncate(text: string, maxLength: number, options?: TruncateOptions): string {\n if (maxLength <= 0) {\n return '';\n }\n\n if (text.length <= maxLength) {\n return text;\n }\n\n if (maxLength === 1) {\n return ELLIPSIS;\n }\n\n const truncatedLength = maxLength - 1; // Reserve space for ellipsis\n\n if (options?.wordBoundary) {\n // Find last space within the truncation limit\n const lastSpace = text.lastIndexOf(' ', truncatedLength);\n if (lastSpace > 0) {\n return text.slice(0, lastSpace) + ELLIPSIS;\n }\n }\n\n // Character-level truncation\n return text.slice(0, truncatedLength) + ELLIPSIS;\n}\n\n/**\n * Truncate text from the middle, preserving start and end.\n * Useful for paths and IDs where both prefix and suffix are meaningful.\n *\n * @param text - Text to truncate\n * @param maxLength - Maximum length including ellipsis\n * @returns Truncated text with ellipsis in middle, or original if short enough\n */\nexport function truncateMiddle(text: string, maxLength: number): string {\n if (maxLength <= 0) {\n return '';\n }\n\n if (text.length <= maxLength) {\n return text;\n }\n\n if (maxLength === 1) {\n return ELLIPSIS;\n }\n\n if (maxLength === 2) {\n // Only room for one char + ellipsis\n return text[0] + ELLIPSIS;\n }\n\n // Calculate how many characters to keep on each side\n // Subtract 1 for the ellipsis\n const availableChars = maxLength - 1;\n const endLength = Math.floor(availableChars / 2);\n const startLength = availableChars - endLength;\n\n const start = text.slice(0, startLength);\n const end = text.slice(text.length - endLength);\n\n return start + ELLIPSIS + end;\n}\n","/**\n * Issue formatting utilities for consistent CLI output.\n *\n * Provides standardized formatting for issue display across all commands.\n */\n\nimport { formatPriority, getPriorityColor } from '../../lib/priority.js';\nimport { getStatusIcon, getStatusColor } from '../../lib/status.js';\nimport { truncate, ELLIPSIS } from '../../lib/truncate.js';\nimport type { createColors } from './output.js';\nimport type { IssueKindType, IssueStatusType } from '../../lib/types.js';\n\n/**\n * Column width constants for issue tables.\n */\nexport const ISSUE_COLUMNS = {\n ID: 12,\n PRIORITY: 5,\n STATUS: 16,\n ASSIGNEE: 10,\n} as const;\n\n/**\n * Issue data structure for formatting.\n */\nexport interface IssueForDisplay {\n id: string;\n priority: number;\n status: IssueStatusType;\n kind: IssueKindType;\n title: string;\n description?: string;\n labels?: string[];\n assignee?: string;\n}\n\n/**\n * Format a kind in brackets.\n *\n * @example formatKind('bug') → \"[bug]\"\n * @example formatKind('feature') → \"[feature]\"\n */\nexport function formatKind(kind: IssueKindType): string {\n return `[${kind}]`;\n}\n\n/**\n * Format a standard issue line for table display.\n *\n * Format: {ID} {PRI} {STATUS} [kind] {TITLE}\n *\n * @example \"bd-a1b2 P0 ● blocked [bug] Fix authentication timeout\"\n */\nexport function formatIssueLine(\n issue: IssueForDisplay,\n colors: ReturnType<typeof createColors>,\n): string {\n const idCol = colors.id(issue.id.padEnd(ISSUE_COLUMNS.ID));\n const priCol = getPriorityColor(\n issue.priority,\n colors,\n )(formatPriority(issue.priority).padEnd(ISSUE_COLUMNS.PRIORITY));\n const statusText = `${getStatusIcon(issue.status)} ${issue.status}`;\n const statusCol = getStatusColor(issue.status, colors)(statusText.padEnd(ISSUE_COLUMNS.STATUS));\n const kindPrefix = colors.dim(formatKind(issue.kind));\n\n return `${idCol}${priCol}${statusCol}${kindPrefix} ${issue.title}`;\n}\n\n/**\n * Format an extended issue line with assignee column.\n *\n * Format: {ID} {PRI} {STATUS} {ASSIGNEE} [kind] {TITLE}\n */\nexport function formatIssueLineExtended(\n issue: IssueForDisplay,\n colors: ReturnType<typeof createColors>,\n): string {\n const idCol = colors.id(issue.id.padEnd(ISSUE_COLUMNS.ID));\n const priCol = getPriorityColor(\n issue.priority,\n colors,\n )(formatPriority(issue.priority).padEnd(ISSUE_COLUMNS.PRIORITY));\n const statusText = `${getStatusIcon(issue.status)} ${issue.status}`;\n const statusCol = getStatusColor(issue.status, colors)(statusText.padEnd(ISSUE_COLUMNS.STATUS));\n const assigneeText = issue.assignee ? `@${issue.assignee}` : '-';\n const assigneeCol = assigneeText.padEnd(ISSUE_COLUMNS.ASSIGNEE);\n const kindPrefix = colors.dim(formatKind(issue.kind));\n\n return `${idCol}${priCol}${statusCol}${assigneeCol}${kindPrefix} ${issue.title}`;\n}\n\n/**\n * Format an issue line with labels.\n *\n * Format: {ID} {PRI} {STATUS} [kind] {TITLE} [labels]\n */\nexport function formatIssueWithLabels(\n issue: IssueForDisplay,\n colors: ReturnType<typeof createColors>,\n): string {\n const baseLine = formatIssueLine(issue, colors);\n\n if (!issue.labels || issue.labels.length === 0) {\n return baseLine;\n }\n\n const labelsText = colors.label(`[${issue.labels.join(', ')}]`);\n return `${baseLine} ${labelsText}`;\n}\n\n/**\n * Format a compact issue reference.\n *\n * Format: {ID} {STATUS_ICON} {TITLE}\n * (No kind shown)\n *\n * @example \"bd-a1b2 ● Fix authentication timeout\"\n */\nexport function formatIssueCompact(\n issue: IssueForDisplay,\n colors: ReturnType<typeof createColors>,\n): string {\n const icon = getStatusIcon(issue.status);\n return `${colors.id(issue.id)} ${icon} ${issue.title}`;\n}\n\n/**\n * Format an inline issue mention.\n *\n * Format: {ID} ({TITLE})\n * (No kind shown)\n *\n * @example \"bd-a1b2 (Fix authentication timeout)\"\n */\nexport function formatIssueInline(issue: IssueForDisplay): string {\n return `${issue.id} (${issue.title})`;\n}\n\n/**\n * Format the table header row for issue listings.\n */\nexport function formatIssueHeader(colors: ReturnType<typeof createColors>): string {\n const idHeader = 'ID'.padEnd(ISSUE_COLUMNS.ID);\n const priHeader = 'PRI'.padEnd(ISSUE_COLUMNS.PRIORITY);\n const statusHeader = 'STATUS'.padEnd(ISSUE_COLUMNS.STATUS);\n const titleHeader = 'TITLE';\n\n return colors.dim(`${idHeader}${priHeader}${statusHeader}${titleHeader}`);\n}\n\n/**\n * Format the extended table header row with assignee column.\n */\nexport function formatIssueHeaderExtended(colors: ReturnType<typeof createColors>): string {\n const idHeader = 'ID'.padEnd(ISSUE_COLUMNS.ID);\n const priHeader = 'PRI'.padEnd(ISSUE_COLUMNS.PRIORITY);\n const statusHeader = 'STATUS'.padEnd(ISSUE_COLUMNS.STATUS);\n const assigneeHeader = 'ASSIGNEE'.padEnd(ISSUE_COLUMNS.ASSIGNEE);\n const titleHeader = 'TITLE';\n\n return colors.dim(`${idHeader}${priHeader}${statusHeader}${assigneeHeader}${titleHeader}`);\n}\n\n/**\n * Format an issue with long format (includes description on second line).\n *\n * Description is indented 6 spaces, dim color, max 2 lines.\n */\nexport function formatIssueLong(\n issue: IssueForDisplay,\n colors: ReturnType<typeof createColors>,\n maxWidth = 80,\n): string {\n const firstLine = formatIssueLine(issue, colors);\n\n if (!issue.description) {\n return firstLine;\n }\n\n const descLines = wrapDescription(issue.description, 6, 2, maxWidth);\n if (!descLines) {\n return firstLine;\n }\n\n return `${firstLine}\\n${colors.dim(descLines)}`;\n}\n\n/**\n * Word-wrap description text with indentation.\n *\n * @param text - The text to wrap\n * @param indent - Number of spaces to indent each line\n * @param maxLines - Maximum number of lines (truncates with ellipsis)\n * @param maxWidth - Maximum width per line (including indent)\n */\nexport function wrapDescription(\n text: string,\n indent: number,\n maxLines: number,\n maxWidth: number,\n): string {\n if (!text) return '';\n\n const indentStr = ' '.repeat(indent);\n const contentWidth = maxWidth - indent;\n\n // Split into words and wrap\n const words = text.split(/\\s+/);\n const lines: string[] = [];\n let currentLine = '';\n\n for (const word of words) {\n if (!currentLine) {\n currentLine = word;\n } else if (currentLine.length + 1 + word.length <= contentWidth) {\n currentLine += ' ' + word;\n } else {\n lines.push(currentLine);\n currentLine = word;\n\n // Stop if we've hit max lines\n if (lines.length >= maxLines) {\n break;\n }\n }\n }\n\n // Add remaining content\n if (currentLine && lines.length < maxLines) {\n lines.push(currentLine);\n }\n\n // Truncate last line if we have more content\n if (lines.length === maxLines && currentLine && !lines.includes(currentLine)) {\n const lastLine = lines[maxLines - 1];\n if (lastLine) {\n lines[maxLines - 1] = truncate(lastLine, contentWidth - 1) + ELLIPSIS;\n }\n }\n\n return lines.map((line) => indentStr + line).join('\\n');\n}\n\n/**\n * Format a spec path with the filename portion bolded.\n *\n * e.g. \"docs/project/specs/active/plan-2026-01-27-my-feature.md\"\n * → \"docs/project/specs/active/\" + bold(\"plan-2026-01-27-my-feature.md\")\n */\nexport function formatSpecName(specPath: string, colors: ReturnType<typeof createColors>): string {\n const lastSlash = specPath.lastIndexOf('/');\n if (lastSlash === -1) {\n return colors.bold(specPath);\n }\n const dir = specPath.slice(0, lastSlash + 1);\n const filename = specPath.slice(lastSlash + 1);\n return dir + colors.bold(filename);\n}\n\n/**\n * Format a spec group header for --specs output.\n *\n * Renders \"Spec: path/to/bold-filename.md (count)\".\n */\nexport function formatSpecGroupHeader(\n specPath: string,\n count: number,\n colors: ReturnType<typeof createColors>,\n): string {\n return 'Spec: ' + formatSpecName(specPath, colors) + colors.dim(` (${count})`);\n}\n\n/**\n * Format the \"No spec\" group header for beads without a linked spec.\n */\nexport function formatNoSpecGroupHeader(\n count: number,\n colors: ReturnType<typeof createColors>,\n): string {\n return colors.bold('(No spec)') + colors.dim(` (${count})`);\n}\n","/**\n * Tree view utilities for displaying issues with parent-child relationships.\n *\n * Used by `tbd list --pretty` to show hierarchical issue structure.\n */\n\nimport type { createColors } from './output.js';\nimport { formatPriority, getPriorityColor } from '../../lib/priority.js';\nimport { getStatusIcon, getStatusColor } from '../../lib/status.js';\nimport {\n formatKind,\n wrapDescription,\n ISSUE_COLUMNS,\n type IssueForDisplay,\n} from './issue-format.js';\nimport { comparisonChain, ordering } from '../../lib/comparison-chain.js';\nimport type { InternalIssueId } from '../../lib/ids.js';\n\n/**\n * Options for tree rendering.\n */\nexport interface TreeRenderOptions {\n /** Show descriptions (--long mode) */\n long?: boolean;\n /** Terminal width for description wrapping */\n maxWidth?: number;\n}\n\n/**\n * Tree node representing an issue with its children.\n */\nexport interface TreeNode {\n issue: IssueForDisplay;\n children: TreeNode[];\n}\n\n/**\n * Unicode box-drawing characters for tree display.\n */\nconst TREE_CHARS = {\n /** Middle child connector: ├── */\n BRANCH: '├── ',\n /** Last child connector: └── */\n LAST: '└── ',\n /** Vertical line continuation: │ */\n VERTICAL: '│ ',\n /** Empty space for alignment: */\n SPACE: ' ',\n} as const;\n\n/**\n * Issue input for tree building, with optional parent and ordering hints.\n */\nexport interface IssueForTree extends IssueForDisplay {\n parentId?: string;\n /** Internal ID for matching against order hints (optional, defaults to id) */\n internalId?: InternalIssueId;\n /** Ordered list of child internal IDs for preferred display order */\n child_order_hints?: InternalIssueId[];\n}\n\n/**\n * Get the internal ID for an issue (used for matching against order hints).\n * Falls back to display ID if internalId is not set.\n */\nfunction getInternalId(issue: IssueForTree): string {\n return issue.internalId ?? issue.id;\n}\n\n/**\n * Sort children using order hints from the parent.\n *\n * Children in hints appear first, in hints order.\n * Children not in hints appear after, sorted by ID for determinism.\n * Uses internalId for matching against hints (which contain internal IDs).\n */\nfunction sortChildren(children: TreeNode[], hints: InternalIssueId[] | undefined): void {\n if (!hints || hints.length === 0) {\n // No hints - sort by ID for determinism\n children.sort(\n comparisonChain<TreeNode>()\n .compare((n) => n.issue.id)\n .result(),\n );\n return;\n }\n\n // Sort using manual ordering: items in hints first, then by ID\n // Use internalId for matching since hints contain internal IDs\n // Cast to string[] since ordering.manual works with any strings\n children.sort(\n comparisonChain<TreeNode>()\n .compare((n) => getInternalId(n.issue as IssueForTree), ordering.manual(hints as string[]))\n .compare((n) => n.issue.id) // Secondary sort for items not in hints\n .result(),\n );\n}\n\n/**\n * Build a tree structure from a flat list of issues.\n *\n * Groups children under their parents based on parent_id.\n * Issues without a parent (or whose parent is not in the list) become root nodes.\n * Children are sorted according to parent's child_order_hints if available.\n *\n * @param issues - Flat list of issues with optional parent_id and child_order_hints\n * @returns Array of root tree nodes with nested children\n */\nexport function buildIssueTree(issues: IssueForTree[]): TreeNode[] {\n // Create a map for quick lookup by ID\n const issueMap = new Map<string, TreeNode>();\n // Store order hints per parent (internal IDs for child ordering)\n const orderHintsMap = new Map<string, InternalIssueId[]>();\n const roots: TreeNode[] = [];\n\n // First pass: create nodes for all issues and collect order hints\n for (const issue of issues) {\n issueMap.set(issue.id, { issue, children: [] });\n if (issue.child_order_hints) {\n orderHintsMap.set(issue.id, issue.child_order_hints);\n }\n }\n\n // Second pass: build parent-child relationships\n for (const issue of issues) {\n const node = issueMap.get(issue.id)!;\n\n if (issue.parentId && issueMap.has(issue.parentId)) {\n // Has a parent that's in our list - add as child\n const parentNode = issueMap.get(issue.parentId)!;\n parentNode.children.push(node);\n } else {\n // No parent or parent not in list - this is a root\n roots.push(node);\n }\n }\n\n // Third pass: sort children using parent's order hints\n for (const node of issueMap.values()) {\n if (node.children.length > 0) {\n const hints = orderHintsMap.get(node.issue.id);\n sortChildren(node.children, hints);\n }\n }\n\n // Root nodes preserve their input order (list command already sorts by priority)\n\n return roots;\n}\n\n/**\n * Format a single issue line for tree view (no header, compact format).\n *\n * Format: {ID} {PRI} {STATUS} [kind] {TITLE}\n *\n * ID column is padded to ISSUE_COLUMNS.ID width for consistent alignment.\n */\nfunction formatTreeIssueLine(\n issue: IssueForDisplay,\n colors: ReturnType<typeof createColors>,\n): string {\n const id = colors.id(issue.id.padEnd(ISSUE_COLUMNS.ID));\n const pri = getPriorityColor(issue.priority, colors)(formatPriority(issue.priority));\n const statusText = `${getStatusIcon(issue.status)} ${issue.status}`;\n const status = getStatusColor(issue.status, colors)(statusText);\n const kind = colors.dim(formatKind(issue.kind));\n\n return `${id} ${pri} ${status} ${kind} ${issue.title}`;\n}\n\n/**\n * Render a tree node and its children as formatted lines.\n *\n * @param node - The tree node to render\n * @param colors - Color functions for formatting\n * @param prefix - Current line prefix (for nested indentation)\n * @param options - Rendering options (long mode, max width)\n * @returns Array of formatted lines\n */\nfunction renderTreeNode(\n node: TreeNode,\n colors: ReturnType<typeof createColors>,\n prefix = '',\n options: TreeRenderOptions = {},\n): string[] {\n const lines: string[] = [];\n const { long = false, maxWidth = 80 } = options;\n\n // Render this node\n const issueLine = formatTreeIssueLine(node.issue, colors);\n lines.push(prefix + issueLine);\n\n // Render description if --long and description exists\n if (long && node.issue.description) {\n // Calculate indent: prefix length + 6 spaces for description alignment\n const descIndent = prefix.length + 6;\n const descWidth = maxWidth - descIndent;\n if (descWidth > 20) {\n const wrapped = wrapDescription(node.issue.description, 6, 2, descWidth + 6);\n if (wrapped) {\n // Add prefix to each description line\n const descLines = wrapped.split('\\n');\n for (const descLine of descLines) {\n lines.push(prefix + colors.dim(descLine));\n }\n }\n }\n }\n\n // Render children\n const childCount = node.children.length;\n node.children.forEach((child, index) => {\n const isLastChild = index === childCount - 1;\n\n // Determine the connector for this child\n const connector = isLastChild ? TREE_CHARS.LAST : TREE_CHARS.BRANCH;\n\n // Determine the prefix for continuation lines (descriptions, grandchildren)\n // If this child is not last, we need a vertical line; otherwise space\n const childPrefix = prefix + (isLastChild ? TREE_CHARS.SPACE : TREE_CHARS.VERTICAL);\n\n // Render child with childPrefix so it knows the correct indentation for descriptions\n const childLines = renderTreeNode(child, colors, childPrefix, options);\n\n // Process lines: first line gets connector, others keep childPrefix\n childLines.forEach((line, lineIndex) => {\n if (lineIndex === 0) {\n // Replace childPrefix with connector for the first line\n const lineWithoutPrefix = line.slice(childPrefix.length);\n lines.push(colors.dim(connector) + lineWithoutPrefix);\n } else {\n // Keep childPrefix for continuation lines (already included)\n lines.push(line);\n }\n });\n });\n\n return lines;\n}\n\n/**\n * Render a complete tree view of issues.\n *\n * @param roots - Array of root tree nodes\n * @param colors - Color functions for formatting\n * @param options - Rendering options (long mode, max width)\n * @returns Array of formatted lines (without header, count is separate)\n */\nexport function renderIssueTree(\n roots: TreeNode[],\n colors: ReturnType<typeof createColors>,\n options: TreeRenderOptions = {},\n): string[] {\n const lines: string[] = [];\n\n for (const root of roots) {\n const rootLines = renderTreeNode(root, colors, '', options);\n lines.push(...rootLines);\n }\n\n return lines;\n}\n\n/**\n * Count total issues in a tree (including all nested children).\n */\nexport function countTreeIssues(roots: TreeNode[]): number {\n let count = 0;\n\n function countNode(node: TreeNode): void {\n count++;\n for (const child of node.children) {\n countNode(child);\n }\n }\n\n for (const root of roots) {\n countNode(root);\n }\n\n return count;\n}\n","/**\n * Spec path matching utilities.\n *\n * Provides gradual path matching for linking beads to spec documents.\n * Supports matching by filename, partial path suffix, or full path.\n *\n * See: plan-2026-01-26-spec-linking.md §Gradual Path Matching Algorithm\n */\n\nimport { basename } from 'node:path';\n\n/**\n * Check if a stored spec path matches a query path using gradual matching.\n *\n * Matching rules (in order of precedence):\n * 1. Exact match after normalization\n * 2. Suffix match: stored path ends with query path at a path separator\n * 3. Filename match: query matches the filename portion of stored path\n *\n * @param storedPath - The spec_path stored in the issue (e.g., \"docs/specs/plan-feature.md\")\n * @param queryPath - The path to match against (e.g., \"plan-feature.md\" or \"specs/plan-feature.md\")\n * @returns true if the paths match\n *\n * @example\n * // All these queries match stored path \"docs/project/specs/active/plan-2026-01-26-feature.md\":\n * matchesSpecPath(stored, \"plan-2026-01-26-feature.md\") // filename match\n * matchesSpecPath(stored, \"feature.md\") // partial filename - NO MATCH (too ambiguous)\n * matchesSpecPath(stored, \"active/plan-2026-01-26-feature.md\") // suffix match\n * matchesSpecPath(stored, \"docs/project/specs/active/plan-2026-01-26-feature.md\") // exact match\n */\nexport function matchesSpecPath(storedPath: string, queryPath: string): boolean {\n // Handle empty/null cases\n if (!storedPath || !queryPath) {\n return false;\n }\n\n // Normalize paths: remove leading ./ and trailing /\n const normalizedStored = normalizePath(storedPath);\n const normalizedQuery = normalizePath(queryPath);\n\n // Empty after normalization\n if (!normalizedStored || !normalizedQuery) {\n return false;\n }\n\n // 1. Exact match\n if (normalizedStored === normalizedQuery) {\n return true;\n }\n\n // 2. Suffix match: stored path ends with /query\n // This handles partial path matches like \"active/plan.md\" matching \"docs/specs/active/plan.md\"\n if (normalizedStored.endsWith('/' + normalizedQuery)) {\n return true;\n }\n\n // 3. Filename match: query is just a filename that matches stored's filename\n // Only do filename match if query has no directory components\n // (otherwise it would have matched in suffix check above)\n if (!normalizedQuery.includes('/') && basename(normalizedStored) === normalizedQuery) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Normalize a path for comparison.\n * - Removes leading ./\n * - Removes trailing /\n * - Collapses multiple slashes\n */\nfunction normalizePath(path: string): string {\n return path\n .replace(/^\\.\\//, '') // Remove leading ./\n .replace(/\\/+$/, '') // Remove trailing /\n .replace(/\\/+/g, '/'); // Collapse multiple slashes\n}\n","/**\n * `tbd list` - List issues.\n *\n * See: tbd-design.md §4.4 List\n */\n\nimport { Command } from 'commander';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { applyLimit } from '../lib/limit-utils.js';\nimport { requireInit } from '../lib/errors.js';\nimport { loadDataContext } from '../lib/data-context.js';\nimport type { Issue, IssueStatusType, IssueKindType } from '../../lib/types.js';\nimport { listIssues } from '../../file/storage.js';\nimport { formatDisplayId, formatDebugId, extractUlidFromInternalId } from '../../lib/ids.js';\nimport type { IdMapping } from '../../file/id-mapping.js';\nimport { resolveToInternalId } from '../../file/id-mapping.js';\nimport { comparisonChain, ordering } from '../../lib/comparison-chain.js';\nimport {\n formatIssueLine,\n formatIssueLong,\n formatIssueHeader,\n formatSpecGroupHeader,\n formatNoSpecGroupHeader,\n type IssueForDisplay,\n} from '../lib/issue-format.js';\nimport { parsePriority } from '../../lib/priority.js';\nimport { buildIssueTree, renderIssueTree } from '../lib/tree-view.js';\nimport { getTerminalWidth, type createColors } from '../lib/output.js';\nimport { matchesSpecPath } from '../../lib/spec-matching.js';\n\ninterface ListOptions {\n status?: IssueStatusType;\n all?: boolean;\n type?: IssueKindType;\n priority?: string;\n assignee?: string;\n label?: string[];\n parent?: string;\n spec?: string;\n deferred?: boolean;\n deferBefore?: string;\n sort?: string;\n limit?: string;\n count?: boolean;\n long?: boolean;\n pretty?: boolean;\n specs?: boolean;\n}\n\nclass ListHandler extends BaseCommand {\n async run(options: ListOptions): Promise<void> {\n const tbdRoot = await requireInit();\n\n // Load shared data context (dataSyncDir, mapping, config, prefix)\n const dataCtx = await loadDataContext(tbdRoot);\n let issues = await listIssues(dataCtx.dataSyncDir);\n\n // Apply filters\n issues = this.filterIssues(issues, options, dataCtx.mapping);\n\n // Sort results (with secondary sort by short ID for stable ordering)\n issues = this.sortIssues(issues, options.sort ?? 'priority', dataCtx.mapping);\n\n // Apply limit\n issues = applyLimit(issues, options.limit);\n\n // Count-only mode for testing\n if (options.count) {\n this.output.data({ count: issues.length }, () => {\n console.log(issues.length);\n });\n return;\n }\n\n const showDebug = this.ctx.debug;\n const { mapping, prefix } = dataCtx;\n\n // Format output - use short display IDs instead of internal ULIDs\n const displayIssues = issues.map((i) => ({\n id: showDebug ? formatDebugId(i.id, mapping, prefix) : formatDisplayId(i.id, mapping, prefix),\n internalId: i.id,\n parentId: i.parent_id\n ? showDebug\n ? formatDebugId(i.parent_id, mapping, prefix)\n : formatDisplayId(i.parent_id, mapping, prefix)\n : undefined,\n priority: i.priority,\n status: i.status,\n kind: i.kind,\n title: i.title,\n description: i.description ?? undefined,\n assignee: i.assignee ?? undefined,\n labels: i.labels,\n spec_path: i.spec_path ?? undefined,\n // Use internal IDs for order hints (buildIssueTree compares against internal IDs)\n child_order_hints: i.child_order_hints ?? undefined,\n }));\n\n this.output.data(displayIssues, () => {\n if (issues.length === 0) {\n console.log('No issues found');\n return;\n }\n\n const colors = this.output.getColors();\n\n if (options.specs) {\n this.renderGroupedBySpec(displayIssues, options, colors);\n } else {\n this.renderFlat(displayIssues, options, colors);\n }\n\n console.log('');\n console.log(colors.dim(`${issues.length} issue(s)`));\n });\n }\n\n private renderFlat(\n displayIssues: (IssueForDisplay & { parentId?: string; spec_path?: string })[],\n options: ListOptions,\n colors: ReturnType<typeof createColors>,\n ): void {\n if (options.pretty) {\n const tree = buildIssueTree(displayIssues);\n const lines = renderIssueTree(tree, colors, {\n long: options.long,\n maxWidth: getTerminalWidth(),\n });\n for (const line of lines) {\n console.log(line);\n }\n } else {\n console.log(formatIssueHeader(colors));\n for (const issue of displayIssues) {\n if (options.long) {\n console.log(formatIssueLong(issue, colors));\n } else {\n console.log(formatIssueLine(issue, colors));\n }\n }\n }\n }\n\n private renderGroupedBySpec(\n displayIssues: (IssueForDisplay & { parentId?: string; spec_path?: string })[],\n options: ListOptions,\n colors: ReturnType<typeof createColors>,\n ): void {\n // Group issues by spec_path\n const specGroups = new Map<string, typeof displayIssues>();\n const noSpecIssues: typeof displayIssues = [];\n\n for (const issue of displayIssues) {\n if (issue.spec_path) {\n const group = specGroups.get(issue.spec_path);\n if (group) {\n group.push(issue);\n } else {\n specGroups.set(issue.spec_path, [issue]);\n }\n } else {\n noSpecIssues.push(issue);\n }\n }\n\n // Render each spec group\n let first = true;\n for (const [specPath, groupIssues] of specGroups) {\n if (!first) {\n console.log('');\n }\n first = false;\n\n console.log(formatSpecGroupHeader(specPath, groupIssues.length, colors));\n console.log('');\n this.renderFlat(groupIssues, options, colors);\n }\n\n // Render \"No spec\" group at the end\n if (noSpecIssues.length > 0) {\n if (!first) {\n console.log('');\n }\n console.log(formatNoSpecGroupHeader(noSpecIssues.length, colors));\n console.log('');\n this.renderFlat(noSpecIssues, options, colors);\n }\n }\n\n private filterIssues(issues: Issue[], options: ListOptions, mapping: IdMapping): Issue[] {\n // Resolve parent filter to internal ID if provided\n let resolvedParentId: string | undefined;\n if (options.parent) {\n try {\n resolvedParentId = resolveToInternalId(options.parent, mapping);\n } catch {\n // If parent ID cannot be resolved, no issues will match\n return [];\n }\n }\n\n return issues.filter((issue) => {\n // By default, exclude closed issues unless --all or --status closed\n if (!options.all && options.status !== 'closed' && issue.status === 'closed') {\n return false;\n }\n\n // Status filter\n if (options.status && issue.status !== options.status) {\n return false;\n }\n\n // Type filter\n if (options.type && issue.kind !== options.type) {\n return false;\n }\n\n // Priority filter - supports both numeric (1) and prefixed (P1) formats\n if (options.priority !== undefined) {\n const priority = parsePriority(options.priority);\n if (priority !== undefined && issue.priority !== priority) {\n return false;\n }\n }\n\n // Assignee filter\n if (options.assignee && issue.assignee !== options.assignee) {\n return false;\n }\n\n // Label filter (all must match)\n if (options.label && options.label.length > 0) {\n const hasAllLabels = options.label.every((l) => issue.labels.includes(l));\n if (!hasAllLabels) {\n return false;\n }\n }\n\n // Parent filter (compare resolved internal IDs)\n if (resolvedParentId && issue.parent_id !== resolvedParentId) {\n return false;\n }\n\n // Spec path filter (uses gradual matching)\n if (options.spec) {\n if (!issue.spec_path || !matchesSpecPath(issue.spec_path, options.spec)) {\n return false;\n }\n }\n\n // Deferred filter\n if (options.deferred && issue.status !== 'deferred') {\n return false;\n }\n\n return true;\n });\n }\n\n private sortIssues(issues: Issue[], sortField: string, _mapping: IdMapping): Issue[] {\n const primarySelector: (i: Issue) => number =\n sortField === 'created'\n ? (i) => new Date(i.created_at).getTime()\n : sortField === 'updated'\n ? (i) => new Date(i.updated_at).getTime()\n : (i) => i.priority;\n\n // For created/updated, reverse so newest comes first; for priority, ascending\n const primaryOrdering =\n sortField === 'created' || sortField === 'updated' ? ordering.reversed : ordering.default;\n\n return [...issues].sort(\n comparisonChain<Issue>()\n .compare(primarySelector, primaryOrdering)\n // Tiebreak by internal ULID (chronological and deterministic, unlike random short IDs)\n .compare((i) => extractUlidFromInternalId(i.id))\n .result(),\n );\n }\n}\n\nexport const listCommand = new Command('list')\n .description('List issues')\n .option('--status <status>', 'Filter: open, in_progress, blocked, deferred, closed')\n .option('--all', 'Include closed issues')\n .option('--type <type>', 'Filter: bug, feature, task, epic')\n .option('--priority <0-4>', 'Filter by priority')\n .option('--assignee <name>', 'Filter by assignee')\n .option('--label <label>', 'Filter by label (repeatable)', (val, prev: string[] = []) => [\n ...prev,\n val,\n ])\n .option('--parent <id>', 'List children of parent')\n .option(\n '--spec <path>',\n 'Filter by spec path (matches full path, partial path suffix, or filename)',\n )\n .option('--deferred', 'Show only deferred issues')\n .option('--defer-before <date>', 'Deferred before date')\n .option('--sort <field>', 'Sort by: priority, created, updated', 'priority')\n .option('--limit <n>', 'Limit results')\n .option('--count', 'Output only the count of matching issues')\n .option('--long', 'Show descriptions')\n .option('--pretty', 'Show tree view with parent-child relationships')\n .option('--specs', 'Group output by linked spec')\n .action(async (options, command) => {\n const handler = new ListHandler(command);\n await handler.run(options);\n });\n","/**\n * `tbd show` - Show issue details.\n *\n * See: tbd-design.md §4.4 Show\n */\n\nimport { Command } from 'commander';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { NotFoundError } from '../lib/errors.js';\nimport { loadFullContext } from '../lib/data-context.js';\nimport { readIssue } from '../../file/storage.js';\nimport { serializeIssue } from '../../file/parser.js';\nimport { formatPriority, getPriorityColor } from '../../lib/priority.js';\nimport { getStatusColor } from '../../lib/status.js';\nimport { PARENT_CONTEXT_MAX_LINES } from '../../lib/settings.js';\nimport type { createColors } from '../lib/output.js';\nimport type { Issue, IssueStatusType } from '../../lib/types.js';\n\ninterface ShowOptions {\n showOrder?: boolean;\n parent?: boolean; // Commander: --no-parent sets this to false (default: true)\n maxLines?: string;\n}\n\n/**\n * Render a serialized issue with colorized output, optionally truncated.\n *\n * @param issue - The issue to render\n * @param colors - Color functions\n * @param maxLines - If set, truncate output to this many lines with an omission notice\n * @returns Array of formatted lines\n */\nfunction renderIssueLines(issue: Issue, colors: ReturnType<typeof createColors>): string[] {\n const serialized = serializeIssue(issue);\n const output: string[] = [];\n\n for (const line of serialized.split('\\n')) {\n if (line === '---') {\n output.push(colors.dim(line));\n } else if (line.startsWith('id:')) {\n output.push(`${colors.dim('id:')} ${colors.id(line.slice(4))}`);\n } else if (line.startsWith('status:')) {\n const status = line.slice(8).trim() as IssueStatusType;\n const statusColor = getStatusColor(status, colors);\n output.push(`${colors.dim('status:')} ${statusColor(status)}`);\n } else if (line.startsWith('priority:')) {\n const priority = parseInt(line.slice(10).trim(), 10);\n const priorityColor = getPriorityColor(priority, colors);\n output.push(`${colors.dim('priority:')} ${priorityColor(formatPriority(priority))}`);\n } else if (line.startsWith('title:')) {\n output.push(`${colors.dim('title:')} ${colors.bold(line.slice(7))}`);\n } else if (line.startsWith('spec_path:')) {\n output.push(`${colors.dim('spec_path:')} ${colors.id(line.slice(11))}`);\n } else if (line.startsWith('## Notes')) {\n output.push(colors.bold(line));\n } else if (line.startsWith(' - ')) {\n output.push(` - ${colors.label(line.slice(4))}`);\n } else {\n output.push(line);\n }\n }\n\n return output;\n}\n\n/**\n * Print lines with optional max-lines truncation.\n * If maxLines is set and the output exceeds it, truncates and appends an omission notice.\n */\nfunction printWithTruncation(\n lines: string[],\n colors: ReturnType<typeof createColors>,\n maxLines?: number,\n): void {\n if (maxLines && lines.length > maxLines) {\n const omitted = lines.length - maxLines;\n for (const line of lines.slice(0, maxLines)) {\n console.log(line);\n }\n console.log(colors.dim(`… [${omitted} line${omitted === 1 ? '' : 's'} omitted]`));\n } else {\n for (const line of lines) {\n console.log(line);\n }\n }\n}\n\nclass ShowHandler extends BaseCommand {\n async run(id: string, command: Command, options: ShowOptions): Promise<void> {\n // Load unified context with data and helpers\n const ctx = await loadFullContext(command);\n\n // Resolve input ID to internal ID using helper\n const internalId = ctx.resolveId(id);\n\n let issue;\n try {\n issue = await readIssue(ctx.dataSyncDir, internalId);\n } catch {\n throw new NotFoundError('Issue', id);\n }\n\n // Load parent issue if this is a child and --no-parent not specified\n let parentIssue: Issue | undefined;\n if (issue.parent_id && options.parent !== false) {\n try {\n parentIssue = await readIssue(ctx.dataSyncDir, issue.parent_id);\n } catch {\n // Parent referenced but missing - silently skip\n }\n }\n\n // Parse --max-lines if provided\n const maxLines = options.maxLines ? parseInt(options.maxLines, 10) : undefined;\n\n // Format display ID using helper (respects debug mode automatically)\n const displayId = ctx.displayId(issue.id);\n\n // Create display version with short display ID\n const displayIssue = {\n ...issue,\n displayId,\n ...(parentIssue\n ? {\n parent: {\n ...parentIssue,\n displayId: ctx.displayId(parentIssue.id),\n },\n }\n : {}),\n };\n\n this.output.data(displayIssue, () => {\n const colors = this.output.getColors();\n\n // Render main issue first (what the user asked for)\n const issueLines = renderIssueLines(issue, colors);\n printWithTruncation(issueLines, colors, maxLines);\n\n // Show parent context below if this is a child issue\n if (parentIssue) {\n console.log('');\n console.log(colors.dim('The parent of this bead is:'));\n const parentLines = renderIssueLines(parentIssue, colors);\n printWithTruncation(parentLines, colors, PARENT_CONTEXT_MAX_LINES);\n }\n\n // Show child_order_hints if --show-order is specified\n if (options.showOrder) {\n console.log('');\n console.log(colors.dim('child_order_hints:'));\n if (issue.child_order_hints && issue.child_order_hints.length > 0) {\n for (const hintId of issue.child_order_hints) {\n const shortId = ctx.displayId(hintId);\n console.log(` - ${colors.id(shortId)}`);\n }\n } else {\n console.log(` ${colors.dim('(none)')}`);\n }\n }\n });\n }\n}\n\nexport const showCommand = new Command('show')\n .description('Show issue details')\n .argument('<id>', 'Issue ID')\n .option('--show-order', 'Display children ordering hints')\n .option('--no-parent', 'Suppress automatic parent context display')\n .option('--max-lines <n>', 'Truncate output to N lines')\n .action(async (id, options, command) => {\n const handler = new ShowHandler(command);\n await handler.run(id, command, options);\n });\n","/**\n * `tbd update` - Update an issue.\n *\n * See: tbd-design.md §4.4 Update\n */\n\nimport { Command } from 'commander';\nimport { readFile } from 'node:fs/promises';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { requireInit, NotFoundError, ValidationError, CLIError } from '../lib/errors.js';\nimport { readIssue, writeIssue, listIssues } from '../../file/storage.js';\nimport { parseMarkdownWithFrontmatter } from '../../file/parser.js';\nimport { formatDisplayId, formatDebugId } from '../../lib/ids.js';\nimport { IssueStatus, IssueKind } from '../../lib/schemas.js';\nimport { parsePriority } from '../../lib/priority.js';\nimport type { IssueStatusType, IssueKindType, PriorityType } from '../../lib/types.js';\nimport { resolveDataSyncDir } from '../../lib/paths.js';\nimport { now } from '../../utils/time-utils.js';\nimport { loadIdMapping, resolveToInternalId, type IdMapping } from '../../file/id-mapping.js';\nimport { readConfig } from '../../file/config.js';\nimport { resolveAndValidatePath, getPathErrorMessage } from '../../lib/project-paths.js';\n\ninterface UpdateOptions {\n fromFile?: string;\n title?: string;\n status?: string;\n type?: string;\n priority?: string;\n assignee?: string;\n description?: string;\n notes?: string;\n notesFile?: string;\n due?: string;\n defer?: string;\n addLabel?: string[];\n removeLabel?: string[];\n parent?: string;\n spec?: string;\n childOrder?: string;\n}\n\nclass UpdateHandler extends BaseCommand {\n async run(id: string, options: UpdateOptions): Promise<void> {\n const tbdRoot = await requireInit();\n\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n\n // Load ID mapping for resolution\n const mapping = await loadIdMapping(dataSyncDir);\n\n // Resolve input ID to internal ID\n let internalId: string;\n try {\n internalId = resolveToInternalId(id, mapping);\n } catch {\n throw new NotFoundError('Issue', id);\n }\n\n // Load existing issue\n let issue;\n try {\n issue = await readIssue(dataSyncDir, internalId);\n } catch {\n throw new NotFoundError('Issue', id);\n }\n\n // Parse and validate options\n const updates = await this.parseUpdates(options, mapping, tbdRoot);\n if (updates === null) return;\n\n if (this.checkDryRun('Would update issue', { id: internalId, ...updates })) {\n return;\n }\n\n // Capture old spec_path before applying updates (for propagation)\n const oldSpecPath = issue.spec_path;\n\n // Apply updates\n if (updates.title !== undefined) issue.title = updates.title;\n if (updates.status !== undefined) issue.status = updates.status;\n if (updates.kind !== undefined) issue.kind = updates.kind;\n if (updates.priority !== undefined) issue.priority = updates.priority;\n if (updates.assignee !== undefined) issue.assignee = updates.assignee;\n if (updates.description !== undefined) issue.description = updates.description;\n if (updates.notes !== undefined) issue.notes = updates.notes;\n if (updates.due_date !== undefined) issue.due_date = updates.due_date;\n if (updates.deferred_until !== undefined) issue.deferred_until = updates.deferred_until;\n if (updates.parent_id !== undefined) issue.parent_id = updates.parent_id;\n if (updates.spec_path !== undefined) issue.spec_path = updates.spec_path;\n if (updates.child_order_hints !== undefined)\n issue.child_order_hints = updates.child_order_hints;\n\n // Inherit spec_path from new parent when re-parenting without explicit --spec\n if (updates.parent_id && options.spec === undefined && !issue.spec_path) {\n try {\n const parentIssue = await readIssue(dataSyncDir, updates.parent_id);\n if (parentIssue.spec_path) {\n issue.spec_path = parentIssue.spec_path;\n }\n } catch {\n // Parent not found — skip inheritance\n }\n }\n\n // Handle full labels replacement (from --from-file)\n if (updates.labels !== undefined) {\n issue.labels = updates.labels;\n }\n\n // Handle label updates\n if (updates.addLabels && updates.addLabels.length > 0) {\n const labelsSet = new Set(issue.labels);\n for (const label of updates.addLabels) {\n labelsSet.add(label);\n }\n issue.labels = [...labelsSet];\n }\n if (updates.removeLabels && updates.removeLabels.length > 0) {\n const removeSet = new Set(updates.removeLabels);\n issue.labels = issue.labels.filter((l) => !removeSet.has(l));\n }\n\n // Update metadata\n issue.version += 1;\n issue.updated_at = now();\n\n // Save\n await this.execute(async () => {\n await writeIssue(dataSyncDir, issue);\n }, 'Failed to update issue');\n\n // When setting a new parent, append child to parent's child_order_hints\n if (updates.parent_id) {\n try {\n const parentIssue = await readIssue(dataSyncDir, updates.parent_id);\n const hints = parentIssue.child_order_hints ?? [];\n\n // Only append if not already in hints\n if (!hints.includes(internalId)) {\n parentIssue.child_order_hints = [...hints, internalId];\n parentIssue.version += 1;\n parentIssue.updated_at = now();\n await writeIssue(dataSyncDir, parentIssue);\n }\n } catch {\n // Parent not found or other error - skip order hint update\n }\n }\n\n // Propagate spec_path to children when parent's spec changes\n if (updates.spec_path !== undefined && issue.spec_path && issue.spec_path !== oldSpecPath) {\n const allIssues = await listIssues(dataSyncDir);\n const children = allIssues.filter((i) => i.parent_id === issue.id);\n const timestamp = now();\n for (const child of children) {\n if (!child.spec_path || child.spec_path === oldSpecPath) {\n child.spec_path = issue.spec_path;\n child.version += 1;\n child.updated_at = timestamp;\n await writeIssue(dataSyncDir, child);\n }\n }\n }\n\n // Use already loaded mapping for display\n const showDebug = this.ctx.debug;\n const config = await readConfig(tbdRoot);\n const prefix = config.display.id_prefix;\n const displayId = showDebug\n ? formatDebugId(issue.id, mapping, prefix)\n : formatDisplayId(issue.id, mapping, prefix);\n\n this.output.data({ id: displayId, updated: true }, () => {\n this.output.success(`Updated ${displayId}`);\n });\n }\n\n private async parseUpdates(\n options: UpdateOptions,\n mapping: IdMapping,\n tbdRoot: string,\n ): Promise<{\n title?: string;\n status?: IssueStatusType;\n kind?: IssueKindType;\n priority?: PriorityType;\n assignee?: string | null;\n description?: string | null;\n notes?: string | null;\n due_date?: string | null;\n deferred_until?: string | null;\n parent_id?: string | null;\n spec_path?: string | null;\n child_order_hints?: string[] | null;\n addLabels?: string[];\n removeLabels?: string[];\n labels?: string[];\n } | null> {\n const updates: {\n title?: string;\n status?: IssueStatusType;\n kind?: IssueKindType;\n priority?: PriorityType;\n assignee?: string | null;\n description?: string | null;\n notes?: string | null;\n due_date?: string | null;\n deferred_until?: string | null;\n parent_id?: string | null;\n spec_path?: string | null;\n child_order_hints?: string[] | null;\n addLabels?: string[];\n removeLabels?: string[];\n labels?: string[];\n } = {};\n\n // Handle --from-file: read all mutable fields from YAML+Markdown file\n if (options.fromFile) {\n let content: string;\n try {\n content = await readFile(options.fromFile, 'utf-8');\n } catch {\n throw new CLIError(`Failed to read file: ${options.fromFile}`);\n }\n\n try {\n const { frontmatter, description, notes } = parseMarkdownWithFrontmatter(content);\n\n // Extract mutable fields from frontmatter\n if (typeof frontmatter.title === 'string') {\n updates.title = frontmatter.title;\n }\n if (typeof frontmatter.status === 'string') {\n const result = IssueStatus.safeParse(frontmatter.status);\n if (result.success) {\n updates.status = result.data;\n }\n }\n if (typeof frontmatter.kind === 'string') {\n const result = IssueKind.safeParse(frontmatter.kind);\n if (result.success) {\n updates.kind = result.data;\n }\n }\n if (typeof frontmatter.priority === 'number') {\n const priority = parsePriority(String(frontmatter.priority));\n if (priority !== undefined) {\n updates.priority = priority;\n }\n }\n if (frontmatter.assignee !== undefined) {\n updates.assignee = typeof frontmatter.assignee === 'string' ? frontmatter.assignee : null;\n }\n if (frontmatter.due_date !== undefined) {\n updates.due_date = typeof frontmatter.due_date === 'string' ? frontmatter.due_date : null;\n }\n if (frontmatter.deferred_until !== undefined) {\n updates.deferred_until =\n typeof frontmatter.deferred_until === 'string' ? frontmatter.deferred_until : null;\n }\n if (frontmatter.parent_id !== undefined) {\n updates.parent_id =\n typeof frontmatter.parent_id === 'string' ? frontmatter.parent_id : null;\n }\n if (frontmatter.spec_path !== undefined) {\n if (typeof frontmatter.spec_path === 'string' && frontmatter.spec_path) {\n // Validate and normalize the spec path from file\n try {\n const resolved = await resolveAndValidatePath(\n frontmatter.spec_path,\n tbdRoot,\n process.cwd(),\n );\n updates.spec_path = resolved.relativePath;\n } catch (error) {\n throw new ValidationError(getPathErrorMessage(error));\n }\n } else {\n updates.spec_path = null;\n }\n }\n if (Array.isArray(frontmatter.labels)) {\n updates.labels = frontmatter.labels.filter((l): l is string => typeof l === 'string');\n }\n\n // Set description and notes from body\n updates.description = description || null;\n updates.notes = notes || null;\n } catch (error) {\n throw new CLIError(\n `Failed to parse file: ${options.fromFile}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n return updates;\n }\n\n if (options.title !== undefined) {\n if (!options.title.trim()) {\n throw new ValidationError('Title cannot be empty');\n }\n updates.title = options.title;\n }\n\n if (options.status) {\n const result = IssueStatus.safeParse(options.status);\n if (!result.success) {\n throw new ValidationError(`Invalid status: ${options.status}`);\n }\n updates.status = result.data;\n }\n\n if (options.type) {\n const result = IssueKind.safeParse(options.type);\n if (!result.success) {\n throw new ValidationError(`Invalid type: ${options.type}`);\n }\n updates.kind = result.data;\n }\n\n if (options.priority) {\n // Use shared parsePriority which accepts both \"P1\" and \"1\" formats\n const priority = parsePriority(options.priority);\n if (priority === undefined) {\n throw new ValidationError(`Invalid priority: ${options.priority}. Use P0-P4 or 0-4.`);\n }\n updates.priority = priority;\n }\n\n if (options.assignee !== undefined) {\n updates.assignee = options.assignee || null;\n }\n\n if (options.description !== undefined) {\n updates.description = options.description || null;\n }\n\n if (options.notes !== undefined) {\n updates.notes = options.notes || null;\n }\n\n if (options.notesFile) {\n try {\n updates.notes = await readFile(options.notesFile, 'utf-8');\n } catch {\n throw new CLIError(`Failed to read notes from file: ${options.notesFile}`);\n }\n }\n\n if (options.due !== undefined) {\n updates.due_date = options.due || null;\n }\n\n if (options.defer !== undefined) {\n updates.deferred_until = options.defer || null;\n }\n\n if (options.parent !== undefined) {\n if (options.parent) {\n try {\n updates.parent_id = resolveToInternalId(options.parent, mapping);\n } catch {\n throw new ValidationError(`Invalid parent ID: ${options.parent}`);\n }\n } else {\n updates.parent_id = null;\n }\n }\n\n if (options.spec !== undefined) {\n if (options.spec) {\n // Non-empty spec path: validate and normalize\n try {\n const resolved = await resolveAndValidatePath(options.spec, tbdRoot, process.cwd());\n updates.spec_path = resolved.relativePath;\n } catch (error) {\n throw new ValidationError(getPathErrorMessage(error));\n }\n } else {\n // Empty string: clear the spec path (no validation needed)\n updates.spec_path = null;\n }\n }\n\n if (options.addLabel && options.addLabel.length > 0) {\n updates.addLabels = options.addLabel;\n }\n\n if (options.removeLabel && options.removeLabel.length > 0) {\n updates.removeLabels = options.removeLabel;\n }\n\n // Handle --child-order: set the ordering hints for children\n if (options.childOrder !== undefined) {\n if (options.childOrder === '' || options.childOrder === '\"\"') {\n // Empty string: clear the hints\n updates.child_order_hints = null;\n } else {\n // Parse comma-separated short IDs and resolve to internal IDs\n const shortIds = options.childOrder.split(',').map((s) => s.trim());\n const internalIds: string[] = [];\n for (const shortId of shortIds) {\n if (!shortId) continue; // Skip empty strings\n try {\n const internalId = resolveToInternalId(shortId, mapping);\n internalIds.push(internalId);\n } catch {\n throw new ValidationError(`Invalid ID in --child-order: ${shortId}`);\n }\n }\n updates.child_order_hints = internalIds.length > 0 ? internalIds : null;\n }\n }\n\n return updates;\n }\n}\n\nexport const updateCommand = new Command('update')\n .description('Update an issue')\n .argument('<id>', 'Issue ID')\n .option('--from-file <path>', 'Update all fields from YAML+Markdown file')\n .option('--title <text>', 'Set title')\n .option('--status <status>', 'Set status')\n .option('--type <type>', 'Set type')\n .option('--priority <0-4>', 'Set priority')\n .option('--assignee <name>', 'Set assignee')\n .option('--description <text>', 'Set description')\n .option('--notes <text>', 'Set working notes')\n .option('--notes-file <path>', 'Set notes from file')\n .option('--due <date>', 'Set due date')\n .option('--defer <date>', 'Set deferred until date')\n .option('--add-label <label>', 'Add label', (val, prev: string[] = []) => [...prev, val])\n .option('--remove-label <label>', 'Remove label', (val, prev: string[] = []) => [...prev, val])\n .option('--parent <id>', 'Set parent')\n .option('--spec <path>', 'Set or clear spec path (empty string clears)')\n .option('--child-order <ids>', 'Set child ordering hints (comma-separated IDs)')\n .action(async (id, options, command) => {\n const handler = new UpdateHandler(command);\n await handler.run(id, options);\n });\n","/**\n * `tbd close` - Close an issue.\n *\n * See: tbd-design.md §4.4 Close\n */\n\nimport { Command } from 'commander';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { requireInit, NotFoundError } from '../lib/errors.js';\nimport { readIssue, writeIssue } from '../../file/storage.js';\nimport { formatDisplayId, formatDebugId } from '../../lib/ids.js';\nimport { resolveDataSyncDir } from '../../lib/paths.js';\nimport { now } from '../../utils/time-utils.js';\nimport { loadIdMapping, resolveToInternalId } from '../../file/id-mapping.js';\nimport { readConfig } from '../../file/config.js';\n\ninterface CloseOptions {\n reason?: string;\n}\n\nclass CloseHandler extends BaseCommand {\n async run(id: string, options: CloseOptions): Promise<void> {\n const tbdRoot = await requireInit();\n\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n\n // Load ID mapping for resolution\n const mapping = await loadIdMapping(dataSyncDir);\n\n // Resolve input ID to internal ID\n let internalId: string;\n try {\n internalId = resolveToInternalId(id, mapping);\n } catch {\n throw new NotFoundError('Issue', id);\n }\n\n // Load existing issue\n let issue;\n try {\n issue = await readIssue(dataSyncDir, internalId);\n } catch {\n throw new NotFoundError('Issue', id);\n }\n\n // Get display ID for output\n const showDebug = this.ctx.debug;\n const config = await readConfig(tbdRoot);\n const prefix = config.display.id_prefix;\n const displayId = showDebug\n ? formatDebugId(issue.id, mapping, prefix)\n : formatDisplayId(issue.id, mapping, prefix);\n\n // Idempotent: if already closed, succeed silently without modification\n if (issue.status === 'closed') {\n this.output.data({ id: displayId, closed: true, alreadyClosed: true }, () => {\n this.output.success(`Closed ${displayId}`);\n });\n return;\n }\n\n if (this.checkDryRun('Would close issue', { id: internalId, reason: options.reason })) {\n return;\n }\n\n // Update issue\n issue.status = 'closed';\n issue.closed_at = now();\n issue.close_reason = options.reason ?? null;\n issue.version += 1;\n issue.updated_at = now();\n\n // Save\n await this.execute(async () => {\n await writeIssue(dataSyncDir, issue);\n }, 'Failed to close issue');\n\n this.output.data({ id: displayId, closed: true }, () => {\n this.output.success(`Closed ${displayId}`);\n });\n }\n}\n\nexport const closeCommand = new Command('close')\n .description('Close an issue')\n .argument('<id>', 'Issue ID')\n .option('--reason <text>', 'Close reason')\n .action(async (id, options, command) => {\n const handler = new CloseHandler(command);\n await handler.run(id, options);\n });\n","/**\n * `tbd reopen` - Reopen a closed issue.\n *\n * See: tbd-design.md §4.4 Reopen\n */\n\nimport { Command } from 'commander';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { requireInit, NotFoundError, CLIError } from '../lib/errors.js';\nimport { readIssue, writeIssue } from '../../file/storage.js';\nimport { formatDisplayId, formatDebugId } from '../../lib/ids.js';\nimport { resolveDataSyncDir } from '../../lib/paths.js';\nimport { now } from '../../utils/time-utils.js';\nimport { loadIdMapping, resolveToInternalId } from '../../file/id-mapping.js';\nimport { readConfig } from '../../file/config.js';\n\ninterface ReopenOptions {\n reason?: string;\n}\n\nclass ReopenHandler extends BaseCommand {\n async run(id: string, options: ReopenOptions): Promise<void> {\n const tbdRoot = await requireInit();\n\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n\n // Load ID mapping for resolution\n const mapping = await loadIdMapping(dataSyncDir);\n\n // Resolve input ID to internal ID\n let internalId: string;\n try {\n internalId = resolveToInternalId(id, mapping);\n } catch {\n throw new NotFoundError('Issue', id);\n }\n\n // Load existing issue\n let issue;\n try {\n issue = await readIssue(dataSyncDir, internalId);\n } catch {\n throw new NotFoundError('Issue', id);\n }\n\n // Check if not closed\n if (issue.status !== 'closed') {\n throw new CLIError(`Issue ${id} is not closed (status: ${issue.status})`);\n }\n\n if (this.checkDryRun('Would reopen issue', { id: internalId, reason: options.reason })) {\n return;\n }\n\n // Update issue\n issue.status = 'open';\n issue.closed_at = null;\n issue.close_reason = null;\n issue.version += 1;\n issue.updated_at = now();\n\n // Optionally store reopen reason in notes if provided\n if (options.reason) {\n const reopenNote = `Reopened: ${options.reason}`;\n issue.notes = issue.notes ? `${issue.notes}\\n\\n${reopenNote}` : reopenNote;\n }\n\n // Save\n await this.execute(async () => {\n await writeIssue(dataSyncDir, issue);\n }, 'Failed to reopen issue');\n\n // Use already loaded mapping for display\n const showDebug = this.ctx.debug;\n const config = await readConfig(tbdRoot);\n const prefix = config.display.id_prefix;\n const displayId = showDebug\n ? formatDebugId(issue.id, mapping, prefix)\n : formatDisplayId(issue.id, mapping, prefix);\n\n this.output.data({ id: displayId, reopened: true }, () => {\n this.output.success(`Reopened ${displayId}`);\n });\n }\n}\n\nexport const reopenCommand = new Command('reopen')\n .description('Reopen a closed issue')\n .argument('<id>', 'Issue ID')\n .option('--reason <text>', 'Reopen reason')\n .action(async (id, options, command) => {\n const handler = new ReopenHandler(command);\n await handler.run(id, options);\n });\n","/**\n * `tbd ready` - List issues ready to work on.\n *\n * See: tbd-design.md §4.4 Ready\n */\n\nimport { Command } from 'commander';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { applyLimit } from '../lib/limit-utils.js';\nimport { loadDataContext } from '../lib/data-context.js';\nimport { requireInit, NotInitializedError, ValidationError } from '../lib/errors.js';\nimport { listIssues } from '../../file/storage.js';\nimport { IssueKind } from '../../lib/schemas.js';\nimport type { Issue, IssueKindType } from '../../lib/types.js';\nimport { formatDisplayId, formatDebugId } from '../../lib/ids.js';\nimport { comparisonChain } from '../../lib/comparison-chain.js';\nimport {\n formatIssueLine,\n formatIssueLong,\n formatIssueHeader,\n type IssueForDisplay,\n} from '../lib/issue-format.js';\n\ninterface ReadyOptions {\n type?: string;\n limit?: string;\n long?: boolean;\n}\n\nclass ReadyHandler extends BaseCommand {\n async run(options: ReadyOptions): Promise<void> {\n const tbdRoot = await requireInit();\n\n // Load data context and issues\n let issues: Issue[];\n let dataCtx;\n try {\n dataCtx = await loadDataContext(tbdRoot);\n issues = await listIssues(dataCtx.dataSyncDir);\n } catch {\n throw new NotInitializedError('No issue store found. Run `tbd init` first.');\n }\n\n // Build lookup map for dependency resolution\n const issueMap = new Map(issues.map((i) => [i.id, i]));\n\n // Build reverse lookup: which issues are blocked by which\n // \"blocks\" dependency means \"this issue blocks target\"\n const blockedByMap = new Map<string, string[]>();\n for (const issue of issues) {\n for (const dep of issue.dependencies) {\n if (dep.type === 'blocks') {\n const existing = blockedByMap.get(dep.target) ?? [];\n existing.push(issue.id);\n blockedByMap.set(dep.target, existing);\n }\n }\n }\n\n // Filter for ready issues\n let readyIssues = issues.filter((issue) => {\n // Must be open (not in_progress, blocked, deferred, or closed)\n if (issue.status !== 'open') return false;\n\n // Must not have an assignee\n if (issue.assignee) return false;\n\n // Must not have unresolved blocking dependencies\n const blockers = blockedByMap.get(issue.id) ?? [];\n const hasUnresolvedBlocker = blockers.some((blockerId) => {\n const blocker = issueMap.get(blockerId);\n return blocker && blocker.status !== 'closed';\n });\n if (hasUnresolvedBlocker) return false;\n\n return true;\n });\n\n // Filter by type if specified\n if (options.type) {\n const result = IssueKind.safeParse(options.type);\n if (!result.success) {\n throw new ValidationError(`Invalid type: ${options.type}`);\n }\n const kind: IssueKindType = result.data;\n readyIssues = readyIssues.filter((i) => i.kind === kind);\n }\n\n // Sort by priority (lowest number = highest priority), then by ID for determinism\n readyIssues.sort(\n comparisonChain<Issue>()\n .compare((i) => i.priority)\n .compare((i) => i.id)\n .result(),\n );\n\n // Apply limit\n readyIssues = applyLimit(readyIssues, options.limit);\n\n const { mapping, prefix } = dataCtx;\n const showDebug = this.ctx.debug;\n\n // Format output\n const outputIssues = readyIssues.map((i) => ({\n id: showDebug ? formatDebugId(i.id, mapping, prefix) : formatDisplayId(i.id, mapping, prefix),\n priority: i.priority,\n status: i.status,\n kind: i.kind,\n title: i.title,\n description: i.description,\n }));\n\n this.output.data(outputIssues, () => {\n if (outputIssues.length === 0) {\n this.output.info('No ready issues found');\n return;\n }\n\n const colors = this.output.getColors();\n console.log(formatIssueHeader(colors));\n for (const issue of outputIssues) {\n if (options.long) {\n console.log(formatIssueLong(issue as IssueForDisplay, colors));\n } else {\n console.log(formatIssueLine(issue as IssueForDisplay, colors));\n }\n }\n });\n }\n}\n\nexport const readyCommand = new Command('ready')\n .description('List issues ready to work on (open, unblocked, unclaimed)')\n .option('--type <type>', 'Filter by type')\n .option('--limit <n>', 'Limit results')\n .option('--long', 'Show descriptions')\n .action(async (options, command) => {\n const handler = new ReadyHandler(command);\n await handler.run(options);\n });\n","/**\n * `tbd blocked` - List blocked issues.\n *\n * See: tbd-design.md §4.4 Blocked\n */\n\nimport { Command } from 'commander';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { loadDataContext, type TbdDataContext } from '../lib/data-context.js';\nimport { requireInit, NotInitializedError } from '../lib/errors.js';\nimport { applyLimit } from '../lib/limit-utils.js';\nimport { listIssues } from '../../file/storage.js';\nimport type { Issue } from '../../lib/types.js';\nimport { formatDisplayId, formatDebugId } from '../../lib/ids.js';\nimport { comparisonChain } from '../../lib/comparison-chain.js';\nimport {\n formatIssueLine,\n formatIssueLong,\n formatIssueHeader,\n formatIssueCompact,\n type IssueForDisplay,\n} from '../lib/issue-format.js';\n\ninterface BlockedOptions {\n limit?: string;\n long?: boolean;\n}\n\nclass BlockedHandler extends BaseCommand {\n async run(options: BlockedOptions): Promise<void> {\n const tbdRoot = await requireInit();\n\n // Load data context and issues\n let issues: Issue[];\n let dataCtx: TbdDataContext;\n try {\n dataCtx = await loadDataContext(tbdRoot);\n issues = await listIssues(dataCtx.dataSyncDir);\n } catch {\n throw new NotInitializedError('No issue store found. Run `tbd init` first.');\n }\n\n const { mapping, prefix } = dataCtx;\n const showDebug = this.ctx.debug;\n\n // Build lookup map for dependency resolution\n const issueMap = new Map(issues.map((i) => [i.id, i]));\n\n // Build reverse lookup: which issues are blocked by which\n // \"blocks\" dependency means \"this issue blocks target\"\n const blockedByMap = new Map<string, string[]>();\n for (const issue of issues) {\n for (const dep of issue.dependencies) {\n if (dep.type === 'blocks') {\n const existing = blockedByMap.get(dep.target) ?? [];\n existing.push(issue.id);\n blockedByMap.set(dep.target, existing);\n }\n }\n }\n\n // Find blocked issues (status=blocked OR has unresolved blocking dependencies)\n let blockedIssues: {\n issue: Issue;\n blockedBy: { id: string; issue: Issue }[];\n explicitlyBlocked?: boolean;\n }[] = [];\n\n for (const issue of issues) {\n // Skip closed issues\n if (issue.status === 'closed') continue;\n\n const unresolvedBlockers: { id: string; issue: Issue }[] = [];\n\n // Check if status is explicitly blocked\n const isExplicitlyBlocked = issue.status === 'blocked';\n\n // Check for unresolved blocking dependencies (from reverse lookup)\n const blockerIds = blockedByMap.get(issue.id) ?? [];\n for (const blockerId of blockerIds) {\n const blocker = issueMap.get(blockerId);\n if (blocker && blocker.status !== 'closed') {\n const blockerDisplayId = showDebug\n ? formatDebugId(blockerId, mapping, prefix)\n : formatDisplayId(blockerId, mapping, prefix);\n unresolvedBlockers.push({ id: blockerDisplayId, issue: blocker });\n }\n }\n\n if (isExplicitlyBlocked || unresolvedBlockers.length > 0) {\n blockedIssues.push({\n issue,\n blockedBy: unresolvedBlockers,\n explicitlyBlocked: isExplicitlyBlocked && unresolvedBlockers.length === 0,\n });\n }\n }\n\n // Sort by priority\n blockedIssues.sort(\n comparisonChain<{ issue: Issue }>()\n .compare((b) => b.issue.priority)\n .compare((b) => b.issue.id)\n .result(),\n );\n\n // Apply limit\n blockedIssues = applyLimit(blockedIssues, options.limit);\n\n // Format output\n const colors = this.output.getColors();\n const outputIssues = blockedIssues.map((b) => {\n const displayId = showDebug\n ? formatDebugId(b.issue.id, mapping, prefix)\n : formatDisplayId(b.issue.id, mapping, prefix);\n return {\n id: displayId,\n priority: b.issue.priority,\n status: b.issue.status,\n kind: b.issue.kind,\n title: b.issue.title,\n description: b.issue.description,\n blockedBy: b.explicitlyBlocked\n ? ['(explicitly blocked)']\n : b.blockedBy.map((blocker) =>\n formatIssueCompact(\n {\n id: blocker.id,\n priority: blocker.issue.priority,\n status: blocker.issue.status,\n kind: blocker.issue.kind,\n title: blocker.issue.title.slice(0, 20),\n },\n colors,\n ),\n ),\n };\n });\n\n this.output.data(outputIssues, () => {\n if (outputIssues.length === 0) {\n this.output.info('No blocked issues found');\n return;\n }\n\n console.log(formatIssueHeader(colors));\n for (const issue of outputIssues) {\n if (options.long) {\n console.log(formatIssueLong(issue as IssueForDisplay, colors));\n } else {\n console.log(formatIssueLine(issue as IssueForDisplay, colors));\n }\n // Show blockers on indented line\n console.log(` ${colors.dim('blocked by:')} ${issue.blockedBy.join(', ')}`);\n }\n });\n }\n}\n\nexport const blockedCommand = new Command('blocked')\n .description('List blocked issues')\n .option('--limit <n>', 'Limit results')\n .option('--long', 'Show descriptions')\n .action(async (options, command) => {\n const handler = new BlockedHandler(command);\n await handler.run(options);\n });\n","/**\n * `tbd stale` - List stale issues.\n *\n * See: tbd-design.md §4.4 Stale\n */\n\nimport { Command } from 'commander';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { applyLimit } from '../lib/limit-utils.js';\nimport { loadDataContext } from '../lib/data-context.js';\nimport { requireInit, NotInitializedError, ValidationError } from '../lib/errors.js';\nimport { listIssues } from '../../file/storage.js';\nimport { IssueStatus } from '../../lib/schemas.js';\nimport type { Issue, IssueStatusType } from '../../lib/types.js';\nimport { nowDate, parseDate } from '../../utils/time-utils.js';\nimport { formatDisplayId, formatDebugId } from '../../lib/ids.js';\nimport { comparisonChain, ordering } from '../../lib/comparison-chain.js';\n\ninterface StaleOptions {\n days?: string;\n status?: string;\n limit?: string;\n}\n\nclass StaleHandler extends BaseCommand {\n async run(options: StaleOptions): Promise<void> {\n const tbdRoot = await requireInit();\n\n // Load data context and issues\n let issues: Issue[];\n let dataCtx;\n try {\n dataCtx = await loadDataContext(tbdRoot);\n issues = await listIssues(dataCtx.dataSyncDir);\n } catch {\n throw new NotInitializedError('No issue store found. Run `tbd init` first.');\n }\n\n // Parse days threshold (default: 7)\n const daysThreshold = options.days ? parseInt(options.days, 10) : 7;\n if (isNaN(daysThreshold) || daysThreshold < 0) {\n throw new ValidationError('Invalid days value. Must be a positive number.');\n }\n\n // Parse status filter (default: open, in_progress)\n const allowedStatuses = new Set<IssueStatusType>();\n if (options.status) {\n const statuses = options.status.split(',').map((s) => s.trim());\n for (const s of statuses) {\n const result = IssueStatus.safeParse(s);\n if (!result.success) {\n throw new ValidationError(`Invalid status: ${s}`);\n }\n allowedStatuses.add(result.data);\n }\n } else {\n // Default: open and in_progress\n allowedStatuses.add('open');\n allowedStatuses.add('in_progress');\n }\n\n const currentTime = nowDate();\n const msPerDay = 24 * 60 * 60 * 1000;\n\n // Filter stale issues\n let staleIssues: { issue: Issue; daysSinceUpdate: number }[] = [];\n\n for (const issue of issues) {\n // Check status filter\n if (!allowedStatuses.has(issue.status)) continue;\n\n // Calculate days since last update\n const updatedAt = parseDate(issue.updated_at);\n if (!updatedAt) continue;\n const daysSinceUpdate = Math.floor((currentTime.getTime() - updatedAt.getTime()) / msPerDay);\n\n if (daysSinceUpdate >= daysThreshold) {\n staleIssues.push({ issue, daysSinceUpdate });\n }\n }\n\n // Sort by days since update (most stale first), then by ID for determinism\n staleIssues.sort(\n comparisonChain<{ issue: Issue; daysSinceUpdate: number }>()\n .compare((s) => s.daysSinceUpdate, ordering.reversed)\n .compare((s) => s.issue.id)\n .result(),\n );\n\n // Apply limit\n staleIssues = applyLimit(staleIssues, options.limit);\n\n const { mapping, prefix } = dataCtx;\n const showDebug = this.ctx.debug;\n\n // Format output\n const outputIssues = staleIssues.map((s) => ({\n id: showDebug\n ? formatDebugId(s.issue.id, mapping, prefix)\n : formatDisplayId(s.issue.id, mapping, prefix),\n days: s.daysSinceUpdate,\n status: s.issue.status,\n title: s.issue.title,\n }));\n\n this.output.data(outputIssues, () => {\n if (outputIssues.length === 0) {\n this.output.info(`No stale issues found (threshold: ${daysThreshold} days)`);\n return;\n }\n\n const colors = this.output.getColors();\n console.log(\n `${colors.dim('ISSUE'.padEnd(12))}${colors.dim('DAYS'.padEnd(6))}${colors.dim('STATUS'.padEnd(14))}${colors.dim('TITLE')}`,\n );\n for (const issue of outputIssues) {\n console.log(\n `${colors.id(issue.id.padEnd(12))}${String(issue.days).padEnd(6)}${issue.status.padEnd(14)}${issue.title}`,\n );\n }\n });\n }\n}\n\nexport const staleCommand = new Command('stale')\n .description('List issues not updated recently')\n .option('--days <n>', 'Days since last update (default: 7)')\n .option('--status <status>', 'Filter by status (default: open, in_progress)')\n .option('--limit <n>', 'Limit results')\n .action(async (options, command) => {\n const handler = new StaleHandler(command);\n await handler.run(options);\n });\n","/**\n * `tbd label` - Label management commands.\n *\n * See: tbd-design.md §4.5 Label Commands\n */\n\nimport { Command } from 'commander';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { requireInit, NotFoundError, NotInitializedError } from '../lib/errors.js';\nimport { readIssue, writeIssue, listIssues } from '../../file/storage.js';\nimport { formatDisplayId, formatDebugId } from '../../lib/ids.js';\nimport { resolveDataSyncDir } from '../../lib/paths.js';\nimport { now } from '../../utils/time-utils.js';\nimport { loadIdMapping, resolveToInternalId } from '../../file/id-mapping.js';\nimport { readConfig } from '../../file/config.js';\n\n// Add label\nclass LabelAddHandler extends BaseCommand {\n async run(id: string, labels: string[]): Promise<void> {\n const tbdRoot = await requireInit();\n\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n\n // Load ID mapping for resolution\n const mapping = await loadIdMapping(dataSyncDir);\n\n // Resolve input ID to internal ID\n let internalId: string;\n try {\n internalId = resolveToInternalId(id, mapping);\n } catch {\n throw new NotFoundError('Issue', id);\n }\n\n // Load existing issue\n let issue;\n try {\n issue = await readIssue(dataSyncDir, internalId);\n } catch {\n throw new NotFoundError('Issue', id);\n }\n\n if (this.checkDryRun('Would add labels', { id: internalId, labels })) {\n return;\n }\n\n // Add labels (avoiding duplicates)\n const labelsSet = new Set(issue.labels);\n let added = 0;\n for (const label of labels) {\n if (!labelsSet.has(label)) {\n labelsSet.add(label);\n added++;\n }\n }\n\n if (added === 0) {\n this.output.info('All labels already present');\n return;\n }\n\n issue.labels = [...labelsSet];\n issue.version += 1;\n issue.updated_at = now();\n\n await this.execute(async () => {\n await writeIssue(dataSyncDir, issue);\n }, 'Failed to update issue');\n\n // Use already loaded mapping for display\n const showDebug = this.ctx.debug;\n const config = await readConfig(tbdRoot);\n const prefix = config.display.id_prefix;\n const displayId = showDebug\n ? formatDebugId(issue.id, mapping, prefix)\n : formatDisplayId(issue.id, mapping, prefix);\n\n this.output.data({ id: displayId, addedLabels: labels }, () => {\n this.output.success(`Added labels to ${displayId}: ${labels.join(', ')}`);\n });\n }\n}\n\n// Remove label\nclass LabelRemoveHandler extends BaseCommand {\n async run(id: string, labels: string[]): Promise<void> {\n const tbdRoot = await requireInit();\n\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n\n // Load ID mapping for resolution\n const mapping = await loadIdMapping(dataSyncDir);\n\n // Resolve input ID to internal ID\n let internalId: string;\n try {\n internalId = resolveToInternalId(id, mapping);\n } catch {\n throw new NotFoundError('Issue', id);\n }\n\n // Load existing issue\n let issue;\n try {\n issue = await readIssue(dataSyncDir, internalId);\n } catch {\n throw new NotFoundError('Issue', id);\n }\n\n if (this.checkDryRun('Would remove labels', { id: internalId, labels })) {\n return;\n }\n\n // Remove labels\n const removeSet = new Set(labels);\n const originalCount = issue.labels.length;\n issue.labels = issue.labels.filter((l) => !removeSet.has(l));\n const removed = originalCount - issue.labels.length;\n\n if (removed === 0) {\n this.output.info('No matching labels found');\n return;\n }\n\n issue.version += 1;\n issue.updated_at = now();\n\n await this.execute(async () => {\n await writeIssue(dataSyncDir, issue);\n }, 'Failed to update issue');\n\n // Use already loaded mapping for display\n const showDebug = this.ctx.debug;\n const config = await readConfig(tbdRoot);\n const prefix = config.display.id_prefix;\n const displayId = showDebug\n ? formatDebugId(issue.id, mapping, prefix)\n : formatDisplayId(issue.id, mapping, prefix);\n\n this.output.data({ id: displayId, removedLabels: labels }, () => {\n this.output.success(`Removed labels from ${displayId}: ${labels.join(', ')}`);\n });\n }\n}\n\n// List labels\nclass LabelListHandler extends BaseCommand {\n async run(): Promise<void> {\n const tbdRoot = await requireInit();\n\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n\n // Load all issues and collect unique labels\n let issues;\n try {\n issues = await listIssues(dataSyncDir);\n } catch {\n throw new NotInitializedError('No issue store found. Run `tbd init` first.');\n }\n\n // Collect labels with counts\n const labelCounts = new Map<string, number>();\n for (const issue of issues) {\n for (const label of issue.labels) {\n labelCounts.set(label, (labelCounts.get(label) ?? 0) + 1);\n }\n }\n\n // Sort by count (descending), then alphabetically\n const sortedLabels = [...labelCounts.entries()].sort((a, b) => {\n if (b[1] !== a[1]) return b[1] - a[1];\n return a[0].localeCompare(b[0]);\n });\n\n const output = sortedLabels.map(([label, count]) => ({ label, count }));\n\n this.output.data(output, () => {\n if (output.length === 0) {\n this.output.info('No labels in use');\n return;\n }\n\n const colors = this.output.getColors();\n console.log(`${colors.dim('LABEL'.padEnd(24))}${colors.dim('COUNT')}`);\n for (const { label, count } of output) {\n console.log(`${colors.label(label.padEnd(24))}${count}`);\n }\n });\n }\n}\n\nconst addCommand = new Command('add')\n .description('Add labels to an issue')\n .argument('<id>', 'Issue ID')\n .argument('<labels...>', 'Labels to add')\n .action(async (id, labels, _options, command) => {\n const handler = new LabelAddHandler(command);\n await handler.run(id, labels);\n });\n\nconst removeCommand = new Command('remove')\n .description('Remove labels from an issue')\n .argument('<id>', 'Issue ID')\n .argument('<labels...>', 'Labels to remove')\n .action(async (id, labels, _options, command) => {\n const handler = new LabelRemoveHandler(command);\n await handler.run(id, labels);\n });\n\nconst listLabelCommand = new Command('list')\n .description('List all labels in use')\n .action(async (_options, command) => {\n const handler = new LabelListHandler(command);\n await handler.run();\n });\n\nexport const labelCommand = new Command('label')\n .description('Manage issue labels')\n .addCommand(addCommand)\n .addCommand(removeCommand)\n .addCommand(listLabelCommand);\n","/**\n * `tbd dep` - Dependency management commands.\n *\n * See: tbd-design.md §4.6 Dependency Commands\n */\n\nimport { Command } from 'commander';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { requireInit, NotFoundError, ValidationError } from '../lib/errors.js';\nimport { readIssue, writeIssue, listIssues } from '../../file/storage.js';\nimport { formatDisplayId, formatDebugId } from '../../lib/ids.js';\nimport type { Issue } from '../../lib/types.js';\nimport { resolveDataSyncDir } from '../../lib/paths.js';\nimport { now } from '../../utils/time-utils.js';\nimport { loadIdMapping, resolveToInternalId } from '../../file/id-mapping.js';\nimport { readConfig } from '../../file/config.js';\n\n// Add dependency: \"A depends on B\" means B blocks A\nclass DependsAddHandler extends BaseCommand {\n async run(issueId: string, dependsOnId: string): Promise<void> {\n const tbdRoot = await requireInit();\n\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n\n // Load ID mapping for resolution\n const mapping = await loadIdMapping(dataSyncDir);\n\n // Resolve both IDs to internal IDs\n // issueId = the issue that depends on something\n // dependsOnId = the issue it depends on (the blocker)\n let internalIssueId: string;\n let internalDependsOnId: string;\n try {\n internalIssueId = resolveToInternalId(issueId, mapping);\n } catch {\n throw new NotFoundError('Issue', issueId);\n }\n try {\n internalDependsOnId = resolveToInternalId(dependsOnId, mapping);\n } catch {\n throw new NotFoundError('Issue', dependsOnId);\n }\n\n // Verify issueId exists\n try {\n await readIssue(dataSyncDir, internalIssueId);\n } catch {\n throw new NotFoundError('Issue', issueId);\n }\n\n // Load the blocking issue (dependsOnId) - this is where we add the dependency\n let blockerIssue;\n try {\n blockerIssue = await readIssue(dataSyncDir, internalDependsOnId);\n } catch {\n throw new NotFoundError('Issue', dependsOnId);\n }\n\n // Check for self-reference\n if (internalIssueId === internalDependsOnId) {\n throw new ValidationError('Issue cannot depend on itself');\n }\n\n if (\n this.checkDryRun('Would add dependency', {\n issue: internalIssueId,\n dependsOn: internalDependsOnId,\n })\n ) {\n return;\n }\n\n // Check if dependency already exists (dependsOnId blocks issueId)\n const exists = blockerIssue.dependencies.some(\n (dep) => dep.type === 'blocks' && dep.target === internalIssueId,\n );\n if (exists) {\n this.output.info('Dependency already exists');\n return;\n }\n\n // Add the dependency: dependsOnId blocks issueId\n blockerIssue.dependencies.push({ type: 'blocks', target: internalIssueId });\n blockerIssue.version += 1;\n blockerIssue.updated_at = now();\n\n await this.execute(async () => {\n await writeIssue(dataSyncDir, blockerIssue);\n }, 'Failed to update issue');\n\n // Use already loaded mapping for display\n const showDebug = this.ctx.debug;\n const config = await readConfig(tbdRoot);\n const prefix = config.display.id_prefix;\n const displayIssueId = showDebug\n ? formatDebugId(internalIssueId, mapping, prefix)\n : formatDisplayId(internalIssueId, mapping, prefix);\n const displayDependsOnId = showDebug\n ? formatDebugId(internalDependsOnId, mapping, prefix)\n : formatDisplayId(internalDependsOnId, mapping, prefix);\n\n this.output.data({ issue: displayIssueId, dependsOn: displayDependsOnId }, () => {\n this.output.success(`${displayIssueId} now depends on ${displayDependsOnId}`);\n });\n }\n}\n\n// Remove dependency: \"A no longer depends on B\" means B no longer blocks A\nclass DependsRemoveHandler extends BaseCommand {\n async run(issueId: string, dependsOnId: string): Promise<void> {\n const tbdRoot = await requireInit();\n\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n\n // Load ID mapping for resolution\n const mapping = await loadIdMapping(dataSyncDir);\n\n // Resolve both IDs to internal IDs\n let internalIssueId: string;\n let internalDependsOnId: string;\n try {\n internalIssueId = resolveToInternalId(issueId, mapping);\n } catch {\n throw new NotFoundError('Issue', issueId);\n }\n try {\n internalDependsOnId = resolveToInternalId(dependsOnId, mapping);\n } catch {\n throw new NotFoundError('Issue', dependsOnId);\n }\n\n // Load the blocker issue (dependsOnId) - this is where the dependency is stored\n let blockerIssue;\n try {\n blockerIssue = await readIssue(dataSyncDir, internalDependsOnId);\n } catch {\n throw new NotFoundError('Issue', dependsOnId);\n }\n\n if (\n this.checkDryRun('Would remove dependency', {\n issue: internalIssueId,\n dependsOn: internalDependsOnId,\n })\n ) {\n return;\n }\n\n // Find and remove the dependency (dependsOnId blocks issueId)\n const initialLength = blockerIssue.dependencies.length;\n blockerIssue.dependencies = blockerIssue.dependencies.filter(\n (dep) => !(dep.type === 'blocks' && dep.target === internalIssueId),\n );\n\n if (blockerIssue.dependencies.length === initialLength) {\n this.output.info('Dependency not found');\n return;\n }\n\n blockerIssue.version += 1;\n blockerIssue.updated_at = now();\n\n await this.execute(async () => {\n await writeIssue(dataSyncDir, blockerIssue);\n }, 'Failed to update issue');\n\n // Use already loaded mapping for display\n const showDebug = this.ctx.debug;\n const config = await readConfig(tbdRoot);\n const prefix = config.display.id_prefix;\n const displayIssueId = showDebug\n ? formatDebugId(internalIssueId, mapping, prefix)\n : formatDisplayId(internalIssueId, mapping, prefix);\n const displayDependsOnId = showDebug\n ? formatDebugId(internalDependsOnId, mapping, prefix)\n : formatDisplayId(internalDependsOnId, mapping, prefix);\n\n this.output.data({ issue: displayIssueId, removed: displayDependsOnId }, () => {\n this.output.success(`${displayIssueId} no longer depends on ${displayDependsOnId}`);\n });\n }\n}\n\n// List dependencies\nclass DependsListHandler extends BaseCommand {\n async run(id: string): Promise<void> {\n const tbdRoot = await requireInit();\n\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n\n // Load ID mapping for resolution and display\n const mapping = await loadIdMapping(dataSyncDir);\n\n // Resolve input ID to internal ID\n let internalId: string;\n try {\n internalId = resolveToInternalId(id, mapping);\n } catch {\n throw new NotFoundError('Issue', id);\n }\n\n // Load the issue\n let issue;\n try {\n issue = await readIssue(dataSyncDir, internalId);\n } catch {\n throw new NotFoundError('Issue', id);\n }\n\n // Load all issues to find reverse dependencies\n let allIssues: Issue[];\n try {\n allIssues = await listIssues(dataSyncDir);\n } catch {\n allIssues = [];\n }\n\n const showDebug = this.ctx.debug;\n const config = await readConfig(tbdRoot);\n const prefix = config.display.id_prefix;\n\n // Find what this issue blocks (from its dependencies)\n const blocks = issue.dependencies\n .filter((dep) => dep.type === 'blocks')\n .map((dep) =>\n showDebug\n ? formatDebugId(dep.target, mapping, prefix)\n : formatDisplayId(dep.target, mapping, prefix),\n );\n\n // Find what blocks this issue (reverse lookup)\n const blockedBy: string[] = [];\n for (const other of allIssues) {\n for (const dep of other.dependencies) {\n if (dep.type === 'blocks' && dep.target === internalId) {\n blockedBy.push(\n showDebug\n ? formatDebugId(other.id, mapping, prefix)\n : formatDisplayId(other.id, mapping, prefix),\n );\n }\n }\n }\n\n const deps = { blocks, blockedBy };\n this.output.data(deps, () => {\n const colors = this.output.getColors();\n if (deps.blocks.length > 0) {\n console.log(`${colors.bold('Blocks:')} ${deps.blocks.join(', ')}`);\n }\n if (deps.blockedBy.length > 0) {\n console.log(`${colors.bold('Blocked by:')} ${deps.blockedBy.join(', ')}`);\n }\n if (deps.blocks.length === 0 && deps.blockedBy.length === 0) {\n console.log('No dependencies');\n }\n });\n }\n}\n\nconst addCommand = new Command('add')\n .description('Add dependency (issue depends on depends-on)')\n .argument('<issue>', 'Issue ID that depends on something')\n .argument('<depends-on>', 'Issue ID that must be completed first')\n .action(async (issue, dependsOn, _options, command) => {\n const handler = new DependsAddHandler(command);\n await handler.run(issue, dependsOn);\n });\n\nconst removeCommand = new Command('remove')\n .description('Remove dependency (issue no longer depends on depends-on)')\n .argument('<issue>', 'Issue ID')\n .argument('<depends-on>', 'Issue ID it depended on')\n .action(async (issue, dependsOn, _options, command) => {\n const handler = new DependsRemoveHandler(command);\n await handler.run(issue, dependsOn);\n });\n\nconst listDepsCommand = new Command('list')\n .description('List dependencies for an issue')\n .argument('<id>', 'Issue ID')\n .action(async (id, _options, command) => {\n const handler = new DependsListHandler(command);\n await handler.run(id);\n });\n\nexport const depCommand = new Command('dep')\n .description('Manage issue dependencies')\n .addCommand(addCommand)\n .addCommand(removeCommand)\n .addCommand(listDepsCommand);\n","/**\n * Sync summary formatting utilities.\n *\n * Provides consistent formatting for sync operation results.\n */\n\n/**\n * Tallies for a sync direction (sent or received).\n */\nexport interface SyncTallies {\n new: number;\n updated: number;\n deleted: number;\n}\n\n/**\n * Full sync summary with both directions.\n */\nexport interface SyncSummary {\n sent: SyncTallies;\n received: SyncTallies;\n conflicts: number;\n /** True if push to remote failed */\n pushFailed?: boolean;\n /** Error message if push failed */\n pushError?: string;\n}\n\n/**\n * Create empty sync tallies.\n */\nexport function emptyTallies(): SyncTallies {\n return { new: 0, updated: 0, deleted: 0 };\n}\n\n/**\n * Create empty sync summary.\n */\nexport function emptySummary(): SyncSummary {\n return {\n sent: emptyTallies(),\n received: emptyTallies(),\n conflicts: 0,\n };\n}\n\n/**\n * Check if tallies have any non-zero values.\n */\nexport function hasTallies(tallies: SyncTallies): boolean {\n return tallies.new > 0 || tallies.updated > 0 || tallies.deleted > 0;\n}\n\n/**\n * Format tallies for display (e.g., \"1 new, 2 updated\").\n * Omits zero counts.\n */\nexport function formatTallies(tallies: SyncTallies): string {\n const parts: string[] = [];\n\n if (tallies.new > 0) {\n parts.push(`${tallies.new} new`);\n }\n if (tallies.updated > 0) {\n parts.push(`${tallies.updated} updated`);\n }\n if (tallies.deleted > 0) {\n parts.push(`${tallies.deleted} deleted`);\n }\n\n return parts.join(', ');\n}\n\n/**\n * Format sync summary for display.\n *\n * Examples:\n * - \"sent 1 new\"\n * - \"sent 2 updated, received 1 new\"\n * - \"received 3 new, 1 updated\"\n * - \"\" (empty if nothing to report - caller should show \"Already in sync\")\n */\nexport function formatSyncSummary(summary: SyncSummary): string {\n const parts: string[] = [];\n\n const sentStr = formatTallies(summary.sent);\n const receivedStr = formatTallies(summary.received);\n\n if (sentStr) {\n parts.push(`sent ${sentStr}`);\n }\n if (receivedStr) {\n parts.push(`received ${receivedStr}`);\n }\n\n if (parts.length === 0) {\n return '';\n }\n\n let result = parts.join(', ');\n\n if (summary.conflicts > 0) {\n result += ` (${summary.conflicts} conflict${summary.conflicts === 1 ? '' : 's'} resolved)`;\n }\n\n return result;\n}\n\n/**\n * Parse git status --porcelain output to get tallies.\n *\n * @param statusOutput - Output from `git status --porcelain`\n * @returns Tallies for new, updated, deleted files\n */\nexport function parseGitStatus(statusOutput: string): SyncTallies {\n const tallies = emptyTallies();\n\n if (!statusOutput || statusOutput.trim() === '') {\n return tallies;\n }\n\n for (const line of statusOutput.split('\\n')) {\n if (!line) continue;\n\n const statusCode = line.slice(0, 2).trim();\n\n switch (statusCode) {\n case 'A':\n case '??':\n tallies.new++;\n break;\n case 'M':\n case 'MM':\n tallies.updated++;\n break;\n case 'D':\n tallies.deleted++;\n break;\n }\n }\n\n return tallies;\n}\n\n/**\n * Parse git diff --name-status output to get tallies.\n *\n * @param diffOutput - Output from `git diff --name-status`\n * @returns Tallies for new, updated, deleted files\n */\nexport function parseGitDiff(diffOutput: string): SyncTallies {\n const tallies = emptyTallies();\n\n if (!diffOutput || diffOutput.trim() === '') {\n return tallies;\n }\n\n for (const line of diffOutput.split('\\n')) {\n if (!line) continue;\n\n const statusCode = line[0];\n\n switch (statusCode) {\n case 'A':\n tallies.new++;\n break;\n case 'M':\n tallies.updated++;\n break;\n case 'D':\n tallies.deleted++;\n break;\n }\n }\n\n return tallies;\n}\n","/**\n * GitHub URL utilities and content fetching with `gh` CLI fallback.\n *\n * Consolidates all GitHub-specific URL handling and fetching logic:\n * - GitHub blob URL → raw URL conversion\n * - Direct HTTP fetch with timeout\n * - Fallback to `gh api` on 403 errors (common in restricted environments)\n *\n * This module is reusable across doc-sync, doc-add, and any future code\n * that needs to fetch content from GitHub.\n */\n\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\n\nconst execFileAsync = promisify(execFile);\n\n// =============================================================================\n// GitHub URL Conversion\n// =============================================================================\n\n/**\n * Regular expression to match GitHub blob URLs.\n *\n * Matches patterns like:\n * https://github.com/{owner}/{repo}/blob/{ref}/{path}\n */\nconst GITHUB_BLOB_RE = /^https?:\\/\\/github\\.com\\/([^/]+)\\/([^/]+)\\/blob\\/([^/]+)\\/(.+)$/;\n\n/**\n * Regular expression to match raw.githubusercontent.com URLs.\n *\n * Matches patterns like:\n * https://raw.githubusercontent.com/{owner}/{repo}/{ref}/{path}\n */\nconst RAW_GITHUB_RE = /^https?:\\/\\/raw\\.githubusercontent\\.com\\/([^/]+)\\/([^/]+)\\/([^/]+)\\/(.+)$/;\n\n/**\n * Convert a GitHub blob URL to a raw.githubusercontent.com URL.\n *\n * If the URL is already a raw URL or not a GitHub blob URL, returns it unchanged.\n *\n * @example\n * githubBlobToRawUrl('https://github.com/org/repo/blob/main/docs/file.md')\n * // => 'https://raw.githubusercontent.com/org/repo/main/docs/file.md'\n *\n * @example\n * githubBlobToRawUrl('https://raw.githubusercontent.com/org/repo/main/docs/file.md')\n * // => 'https://raw.githubusercontent.com/org/repo/main/docs/file.md' (unchanged)\n */\nexport function githubBlobToRawUrl(url: string): string {\n const match = GITHUB_BLOB_RE.exec(url);\n if (!match) {\n return url;\n }\n const [, owner, repo, ref, path] = match;\n return `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${path}`;\n}\n\n/**\n * Check if a URL is a GitHub-hosted URL (github.com or raw.githubusercontent.com).\n */\nexport function isGitHubUrl(url: string): boolean {\n return /^https?:\\/\\/(github\\.com|raw\\.githubusercontent\\.com)\\//.test(url);\n}\n\n/**\n * Parse a raw.githubusercontent.com URL into its components.\n *\n * @returns The parsed components, or null if not a raw GitHub URL\n */\nexport function parseRawGitHubUrl(\n url: string,\n): { owner: string; repo: string; ref: string; path: string } | null {\n const match = RAW_GITHUB_RE.exec(url);\n if (!match) {\n return null;\n }\n return {\n owner: match[1]!,\n repo: match[2]!,\n ref: match[3]!,\n path: match[4]!,\n };\n}\n\n// =============================================================================\n// HTTP Fetching\n// =============================================================================\n\n/** Default timeout for URL fetches in milliseconds */\nconst DEFAULT_FETCH_TIMEOUT = 30000;\n\n/**\n * Options for fetching content.\n */\nexport interface FetchOptions {\n /** Timeout in milliseconds (default: 30000) */\n timeout?: number;\n}\n\n/**\n * Result of a fetch operation.\n */\nexport interface FetchResult {\n /** The fetched content */\n content: string;\n /** Whether the gh CLI was used as a fallback */\n usedGhCli: boolean;\n}\n\n/**\n * Fetch content from a URL via direct HTTP.\n *\n * @throws If the request fails or returns a non-OK status\n */\nexport async function directFetch(url: string, options?: FetchOptions): Promise<string> {\n const timeout = options?.timeout ?? DEFAULT_FETCH_TIMEOUT;\n const controller = new AbortController();\n const timer = setTimeout(() => {\n controller.abort();\n }, timeout);\n\n try {\n const response = await fetch(url, {\n signal: controller.signal,\n headers: {\n 'User-Agent': 'get-tbd/1.0',\n Accept: 'text/plain, text/markdown, */*',\n },\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n return await response.text();\n } finally {\n clearTimeout(timer);\n }\n}\n\n// =============================================================================\n// gh CLI Fetching\n// =============================================================================\n\n/**\n * Fetch content from a GitHub raw URL using `gh api`.\n *\n * Converts raw.githubusercontent.com URLs to GitHub API content endpoints\n * and decodes the base64-encoded response.\n *\n * @throws If the URL is not a GitHub URL or gh CLI fails\n */\nexport async function ghCliFetch(rawUrl: string): Promise<string> {\n const parsed = parseRawGitHubUrl(rawUrl);\n\n if (parsed) {\n try {\n const { stdout } = await execFileAsync('gh', [\n 'api',\n `/repos/${parsed.owner}/${parsed.repo}/contents/${parsed.path}?ref=${parsed.ref}`,\n '--jq',\n '.content',\n '-H',\n 'Accept: application/vnd.github.v3+json',\n ]);\n\n // GitHub API returns base64-encoded content\n const base64Content = stdout.trim();\n return Buffer.from(base64Content, 'base64').toString('utf-8');\n } catch (error) {\n throw new Error(\n `Failed to fetch via gh CLI: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n // For non-raw-GitHub URLs, attempt a direct gh api call\n try {\n const { stdout } = await execFileAsync('gh', ['api', rawUrl]);\n return stdout;\n } catch (error) {\n throw new Error(\n `Failed to fetch via gh CLI: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n}\n\n// =============================================================================\n// Combined Fetch with Fallback\n// =============================================================================\n\n/**\n * Fetch content from a URL, falling back to `gh` CLI on 403 errors.\n *\n * GitHub.com and raw.githubusercontent.com may block requests from\n * certain environments (e.g., CI runners, corporate proxies). When\n * a 403 is received, we retry using `gh api` which authenticates\n * via the user's GitHub CLI token.\n *\n * This function also auto-converts GitHub blob URLs to raw URLs.\n *\n * @returns The fetched content and whether gh CLI was used\n * @throws If both direct fetch and gh CLI fallback fail\n */\nexport async function fetchWithGhFallback(\n url: string,\n options?: FetchOptions,\n): Promise<FetchResult> {\n const rawUrl = githubBlobToRawUrl(url);\n\n // Try direct fetch first\n try {\n const content = await directFetch(rawUrl, options);\n return { content, usedGhCli: false };\n } catch (error) {\n const is403 = error instanceof Error && error.message.includes('HTTP 403');\n if (!is403) {\n throw error;\n }\n }\n\n // 403 error - try gh CLI fallback\n const content = await ghCliFetch(rawUrl);\n return { content, usedGhCli: true };\n}\n","/**\n * DocSync - Sync documentation files from configured sources.\n *\n * Syncs docs from internal bundled sources and external URLs to .tbd/docs/.\n *\n * See: docs/project/specs/active/plan-2026-01-26-configurable-doc-cache-sync.md\n */\n\nimport { readdir, readFile, rm, mkdir, access } from 'node:fs/promises';\nimport { join, dirname } from 'node:path';\nimport { writeFile } from 'atomically';\nimport { fileURLToPath } from 'node:url';\n\nimport { TBD_DOCS_DIR } from '../lib/paths.js';\nimport { fetchWithGhFallback } from './github-fetch.js';\nimport { readConfig, writeConfig, updateLocalState } from './config.js';\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * A parsed document source.\n */\nexport interface DocSource {\n /** Source type: internal bundled or external URL */\n type: 'internal' | 'url';\n /** The source location - either a relative path or full URL */\n location: string;\n}\n\n/**\n * Result of a sync operation.\n */\nexport interface SyncResult {\n /** Paths of newly downloaded/copied docs */\n added: string[];\n /** Paths of updated docs (content changed) */\n updated: string[];\n /** Paths of removed docs (no longer in config) */\n removed: string[];\n /** Errors encountered during sync */\n errors: { path: string; error: string }[];\n /** Whether the sync was successful overall */\n success: boolean;\n}\n\n/**\n * Options for doc sync operations.\n */\nexport interface DocSyncOptions {\n /** If true, don't actually write/delete files (dry run) */\n dryRun?: boolean;\n /** If true, suppress normal output (only report errors) */\n silent?: boolean;\n}\n\n// =============================================================================\n// Constants\n// =============================================================================\n\n/** Prefix for internal bundled doc sources */\nconst INTERNAL_SOURCE_PREFIX = 'internal:';\n\n// =============================================================================\n// DocSync Class\n// =============================================================================\n\n/**\n * Syncs documentation files from configured sources.\n *\n * Supports:\n * - Internal bundled docs (using internal: prefix)\n * - External URLs (raw.githubusercontent.com, etc.)\n */\nexport class DocSync {\n private readonly docsDir: string;\n\n /**\n * Create a new DocSync instance.\n *\n * @param tbdRoot - The tbd project root directory (parent of .tbd/)\n * @param config - The doc_cache configuration mapping dest paths to sources\n */\n constructor(\n private readonly tbdRoot: string,\n private readonly config: Record<string, string>,\n ) {\n this.docsDir = join(tbdRoot, TBD_DOCS_DIR);\n }\n\n /**\n * Parse a source string into a DocSource.\n *\n * @example\n * parseSource('internal:shortcuts/standard/code-review-and-commit.md')\n * // => { type: 'internal', location: 'shortcuts/standard/code-review-and-commit.md' }\n *\n * @example\n * parseSource('https://raw.githubusercontent.com/org/repo/main/file.md')\n * // => { type: 'url', location: 'https://...' }\n */\n parseSource(source: string): DocSource {\n if (source.startsWith(INTERNAL_SOURCE_PREFIX)) {\n return {\n type: 'internal',\n location: source.slice(INTERNAL_SOURCE_PREFIX.length),\n };\n }\n\n // Anything else is treated as a URL\n return {\n type: 'url',\n location: source,\n };\n }\n\n /**\n * Fetch content from a source.\n *\n * @throws If the source cannot be fetched\n */\n async fetchContent(source: DocSource): Promise<string> {\n if (source.type === 'internal') {\n return this.fetchInternalContent(source.location);\n }\n return this.fetchUrlContent(source.location);\n }\n\n /**\n * Fetch content from an internal bundled doc.\n */\n private async fetchInternalContent(location: string): Promise<string> {\n const basePaths = getDocsBasePath();\n\n for (const basePath of basePaths) {\n const fullPath = join(basePath, location);\n try {\n await access(fullPath);\n return await readFile(fullPath, 'utf-8');\n } catch {\n // Try next path\n }\n }\n\n throw new Error(`Internal doc not found: ${location}`);\n }\n\n /**\n * Fetch content from a URL (with gh CLI fallback on 403).\n */\n private async fetchUrlContent(url: string): Promise<string> {\n const { content } = await fetchWithGhFallback(url);\n return content;\n }\n\n /**\n * Get the current state of the docs directory.\n * Returns a set of relative paths that exist in .tbd/docs/.\n */\n async getCurrentState(): Promise<Set<string>> {\n const paths = new Set<string>();\n\n try {\n await access(this.docsDir);\n } catch {\n // Directory doesn't exist yet\n return paths;\n }\n\n await this.scanDirectory(this.docsDir, '', paths);\n return paths;\n }\n\n /**\n * Recursively scan a directory and add all .md file paths to the set.\n */\n private async scanDirectory(baseDir: string, prefix: string, paths: Set<string>): Promise<void> {\n try {\n const dirEntries = await readdir(baseDir, { withFileTypes: true });\n\n for (const entry of dirEntries) {\n const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;\n\n if (entry.isDirectory()) {\n await this.scanDirectory(join(baseDir, entry.name), relativePath, paths);\n } else if (entry.isFile() && entry.name.endsWith('.md')) {\n paths.add(relativePath);\n }\n }\n } catch {\n // Directory doesn't exist or not readable\n }\n }\n\n /**\n * Sync all docs from config to .tbd/docs/.\n *\n * - Downloads/copies new docs\n * - Updates docs whose source has changed\n * - Removes docs no longer in config\n *\n * @param options - Sync options (dryRun, silent)\n */\n async sync(options: DocSyncOptions = {}): Promise<SyncResult> {\n const result: SyncResult = {\n added: [],\n updated: [],\n removed: [],\n errors: [],\n success: true,\n };\n\n // Get current state\n const currentPaths = await this.getCurrentState();\n const configPaths = new Set(Object.keys(this.config));\n\n // Process each doc in config\n for (const [destPath, sourceStr] of Object.entries(this.config)) {\n try {\n const source = this.parseSource(sourceStr);\n const content = await this.fetchContent(source);\n const fullPath = join(this.docsDir, destPath);\n\n // Check if file exists and compare content\n let exists = false;\n let existingContent = '';\n\n try {\n existingContent = await readFile(fullPath, 'utf-8');\n exists = true;\n } catch {\n // File doesn't exist\n }\n\n if (!exists) {\n // New file\n if (!options.dryRun) {\n await mkdir(dirname(fullPath), { recursive: true });\n await writeFile(fullPath, content);\n }\n result.added.push(destPath);\n } else if (existingContent !== content) {\n // Content changed\n if (!options.dryRun) {\n await writeFile(fullPath, content);\n }\n result.updated.push(destPath);\n }\n // else: unchanged, do nothing\n } catch (err) {\n result.errors.push({\n path: destPath,\n error: (err as Error).message,\n });\n result.success = false;\n }\n }\n\n // Remove docs not in config\n for (const existingPath of currentPaths) {\n if (!configPaths.has(existingPath)) {\n try {\n if (!options.dryRun) {\n await rm(join(this.docsDir, existingPath));\n }\n result.removed.push(existingPath);\n } catch (err) {\n result.errors.push({\n path: existingPath,\n error: `Failed to remove: ${(err as Error).message}`,\n });\n }\n }\n }\n\n return result;\n }\n\n /**\n * Get a status of what would change without actually making changes.\n */\n async status(): Promise<SyncResult> {\n return this.sync({ dryRun: true });\n }\n}\n\n// =============================================================================\n// Helper Functions\n// =============================================================================\n\n/**\n * Get base docs paths (with fallbacks for development).\n * Matches the logic in setup.ts.\n */\nfunction getDocsBasePath(): string[] {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n return [\n // Bundled location (dist/docs/)\n join(__dirname, 'docs'),\n // Development: packages/tbd/docs/\n join(__dirname, '..', '..', 'docs'),\n ];\n}\n\n/**\n * Generate default doc_cache config by scanning bundled docs.\n *\n * This creates a config entry for each .md file found in the bundled\n * docs directories (shortcuts, guidelines, templates).\n *\n * @returns A doc_cache config mapping destination paths to internal: sources\n */\nexport async function generateDefaultDocCacheConfig(): Promise<Record<string, string>> {\n const config: Record<string, string> = {};\n const basePaths = getDocsBasePath();\n\n // Find the first valid base path\n let docsDir: string | null = null;\n for (const path of basePaths) {\n try {\n await access(path);\n docsDir = path;\n break;\n } catch {\n // Try next path\n }\n }\n\n if (!docsDir) {\n return config;\n }\n\n // Directories to scan\n const scanDirs = [\n { subdir: 'shortcuts/system', prefix: 'shortcuts/system' },\n { subdir: 'shortcuts/standard', prefix: 'shortcuts/standard' },\n { subdir: 'guidelines', prefix: 'guidelines' },\n { subdir: 'templates', prefix: 'templates' },\n ];\n\n for (const { subdir, prefix } of scanDirs) {\n const fullDir = join(docsDir, subdir);\n try {\n const entries = await readdir(fullDir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isFile() && entry.name.endsWith('.md')) {\n const relativePath = `${prefix}/${entry.name}`;\n config[relativePath] = `${INTERNAL_SOURCE_PREFIX}${relativePath}`;\n }\n }\n } catch {\n // Directory doesn't exist, skip\n }\n }\n\n return config;\n}\n\n/**\n * Merge user's doc_cache config with default bundled docs.\n *\n * This ensures:\n * - New bundled docs from tbd updates are added to existing configs\n * - User's custom sources (URLs, etc.) are preserved\n * - User's overrides of bundled docs are respected\n *\n * @param userConfig - The user's existing doc_cache config (may be undefined/empty)\n * @param defaults - The default config from generateDefaultDocCacheConfig()\n * @returns Merged config with defaults as base, user config overlaid\n */\nexport function mergeDocCacheConfig(\n userConfig: Record<string, string> | undefined,\n defaults: Record<string, string>,\n): Record<string, string> {\n // Start with defaults, overlay user config (user takes precedence)\n return {\n ...defaults,\n ...userConfig,\n };\n}\n\n/**\n * Check if docs are stale based on last sync time and configured hours.\n *\n * @param lastSyncAt - ISO timestamp of last sync (or undefined if never synced)\n * @param autoSyncHours - Hours between auto-syncs (0 = disabled)\n * @returns true if docs should be synced\n */\nexport function isDocsStale(lastSyncAt: string | undefined, autoSyncHours: number): boolean {\n // Auto-sync disabled\n if (autoSyncHours <= 0) {\n return false;\n }\n\n // Never synced\n if (!lastSyncAt) {\n return true;\n }\n\n const lastSync = new Date(lastSyncAt).getTime();\n const now = Date.now();\n const hoursSinceSync = (now - lastSync) / (1000 * 60 * 60);\n\n return hoursSinceSync >= autoSyncHours;\n}\n\n// =============================================================================\n// Unified Doc Sync with Defaults\n// =============================================================================\n\n/**\n * Options for syncDocsWithDefaults.\n */\nexport interface SyncDocsOptions {\n /** If true, suppress output (for auto-sync) */\n quiet?: boolean;\n /** If true, don't write files or config (dry run for --status) */\n dryRun?: boolean;\n}\n\n/**\n * Result of syncDocsWithDefaults.\n */\nexport interface SyncDocsResult {\n /** Paths of newly downloaded/copied docs */\n added: string[];\n /** Paths of updated docs (content changed) */\n updated: string[];\n /** Paths of removed docs (no longer in config) */\n removed: string[];\n /** Entries removed due to missing internal sources */\n pruned: string[];\n /** Whether the config was modified (new defaults merged or stale pruned) */\n configChanged: boolean;\n /** Errors encountered during sync */\n errors: { path: string; error: string }[];\n /** Whether the sync was successful overall */\n success: boolean;\n}\n\n/**\n * Check if an internal bundled doc exists.\n *\n * @param location - The internal doc path (without 'internal:' prefix)\n * @returns true if the doc exists in any of the bundled doc paths\n */\nexport async function internalDocExists(location: string): Promise<boolean> {\n const basePaths = getDocsBasePath();\n\n for (const basePath of basePaths) {\n const fullPath = join(basePath, location);\n try {\n await access(fullPath);\n return true;\n } catch {\n // Try next path\n }\n }\n\n return false;\n}\n\n/**\n * Prune entries from config that point to non-existent internal sources.\n *\n * This handles the case where a bundled doc is removed in a tbd update -\n * the stale config entry is automatically cleaned up.\n *\n * @param config - The doc_cache config to prune\n * @returns Object with pruned config and list of removed entries\n */\nexport async function pruneStaleInternals(\n config: Record<string, string>,\n): Promise<{ config: Record<string, string>; pruned: string[] }> {\n const result: Record<string, string> = {};\n const pruned: string[] = [];\n\n for (const [dest, source] of Object.entries(config)) {\n if (source.startsWith(INTERNAL_SOURCE_PREFIX)) {\n const location = source.slice(INTERNAL_SOURCE_PREFIX.length);\n const exists = await internalDocExists(location);\n if (!exists) {\n pruned.push(dest);\n continue; // Don't include in result\n }\n }\n result[dest] = source;\n }\n\n return { config: result, pruned };\n}\n\n/**\n * Deep equality check for config objects.\n */\nfunction configsEqual(a: Record<string, string>, b: Record<string, string>): boolean {\n const keysA = Object.keys(a).sort();\n const keysB = Object.keys(b).sort();\n\n if (keysA.length !== keysB.length) {\n return false;\n }\n\n for (const key of keysA) {\n if (!Object.hasOwn(b, key) || a[key] !== b[key]) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Sync docs with merged defaults and auto-pruning.\n *\n * This is the single entry point for all doc sync operations that need\n * to pick up new bundled docs from tbd upgrades.\n *\n * Steps:\n * 1. Read current config\n * 2. Generate defaults from bundled docs\n * 3. Merge: defaults as base, user config overlays\n * 4. Prune entries with missing internal sources\n * 5. Sync files to .tbd/docs/\n * 6. Write config if changed\n * 7. Update last_doc_sync_at in state\n *\n * @param tbdRoot - The tbd project root directory\n * @param options - Sync options (quiet, dryRun)\n * @returns Sync result with added/updated/removed/pruned counts\n */\nexport async function syncDocsWithDefaults(\n tbdRoot: string,\n options: SyncDocsOptions = {},\n): Promise<SyncDocsResult> {\n // 1. Read current config\n const config = await readConfig(tbdRoot);\n const originalFiles = config.docs_cache?.files ?? {};\n\n // 2. Generate defaults from bundled docs\n const defaults = await generateDefaultDocCacheConfig();\n\n // 3. Merge: defaults as base, user config overlays\n const merged = mergeDocCacheConfig(originalFiles, defaults);\n\n // 4. Prune entries with missing internal sources\n const { config: prunedConfig, pruned } = await pruneStaleInternals(merged);\n\n // 5. Sync files to .tbd/docs/\n const docSync = new DocSync(tbdRoot, prunedConfig);\n const syncResult = await docSync.sync({ dryRun: options.dryRun });\n\n // 6. Check if config changed\n const configChanged = !configsEqual(prunedConfig, originalFiles);\n\n // 7. Write config if changed (and not dry run)\n if (configChanged && !options.dryRun) {\n // Preserve existing lookup_path or use default\n const lookupPath = config.docs_cache?.lookup_path ?? [\n '.tbd/docs/shortcuts/system',\n '.tbd/docs/shortcuts/standard',\n ];\n config.docs_cache = {\n lookup_path: lookupPath,\n files: prunedConfig,\n };\n await writeConfig(tbdRoot, config);\n }\n\n // 8. Update state (and not dry run)\n if (!options.dryRun) {\n await updateLocalState(tbdRoot, {\n last_doc_sync_at: new Date().toISOString(),\n });\n }\n\n return {\n added: syncResult.added,\n updated: syncResult.updated,\n removed: syncResult.removed,\n pruned,\n configChanged,\n errors: syncResult.errors,\n success: syncResult.success,\n };\n}\n","/**\n * Workspace operations for sync failure recovery, backups, and bulk editing.\n *\n * Workspaces are directories under .tbd/workspaces/ that store issue data.\n * They mirror the data-sync directory structure:\n * .tbd/workspaces/{name}/\n * issues/\n * mappings/\n * attic/\n *\n * See: plan-2026-01-30-workspace-sync-alt.md\n */\n\nimport { mkdir, readdir, rm, stat } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { writeFile } from 'atomically';\n\nimport { sortKeys, stringifyYaml } from '../utils/yaml-utils.js';\nimport { ATTIC_ENTRY_FIELD_ORDER } from '../lib/schemas.js';\n\nimport { listIssues, writeIssue, readIssue } from './storage.js';\nimport { parseIssue } from './parser.js';\nimport { mergeIssues, issuesSubstantivelyEqual, git, type ConflictEntry } from './git.js';\nimport { loadIdMapping, saveIdMapping, addIdMapping, reconcileMappings } from './id-mapping.js';\nimport {\n WORKSPACES_DIR,\n getWorkspaceDir,\n isValidWorkspaceName,\n DATA_SYNC_DIR,\n} from '../lib/paths.js';\nimport { extractUlidFromInternalId } from '../lib/ids.js';\nimport { now } from '../utils/time-utils.js';\nimport { noopLogger } from '../lib/types.js';\nimport type { AtticEntry, Issue, OperationLogger } from '../lib/types.js';\n\n/**\n * Options for saveToWorkspace.\n * One of workspace, dir, or outbox must be specified.\n */\nexport interface SaveOptions {\n /** Named workspace under .tbd/workspaces/ */\n workspace?: string;\n /** Arbitrary directory path */\n dir?: string;\n /** Shortcut for --workspace=outbox --updates-only */\n outbox?: boolean;\n /** Only save issues modified since last sync */\n updatesOnly?: boolean;\n /** Optional logger for progress reporting */\n logger?: OperationLogger;\n}\n\n/**\n * Result from saveToWorkspace operation.\n */\nexport interface SaveResult {\n /** Number of issues saved */\n saved: number;\n /** Number of conflicts (went to attic) */\n conflicts: number;\n /** Target directory where issues were saved */\n targetDir: string;\n /** Total issues in source before filtering (for informational messages) */\n totalSource: number;\n /** Whether filtering was applied (updatesOnly or outbox) */\n filtered: boolean;\n}\n\n/**\n * Options for importFromWorkspace.\n * One of workspace, dir, or outbox must be specified.\n */\nexport interface ImportOptions {\n /** Named workspace under .tbd/workspaces/ */\n workspace?: string;\n /** Arbitrary directory path */\n dir?: string;\n /** Shortcut for --workspace=outbox --clear-on-success */\n outbox?: boolean;\n /** Delete workspace after successful import */\n clearOnSuccess?: boolean;\n /** Optional logger for progress reporting */\n logger?: OperationLogger;\n}\n\n/**\n * Result from importFromWorkspace operation.\n */\nexport interface ImportResult {\n /** Number of issues imported */\n imported: number;\n /** Number of conflicts (went to attic) */\n conflicts: number;\n /** Source directory where issues were imported from */\n sourceDir: string;\n /** Whether the source was deleted after import */\n cleared: boolean;\n}\n\n/**\n * Compare local issues with remote issues and return only those that are new or modified.\n *\n * An issue is considered \"updated\" if:\n * - It doesn't exist in the remote (new issue)\n * - Its substantive content differs from the remote version (modified issue)\n *\n * Uses issuesSubstantivelyEqual to ignore metadata-only changes (version, updated_at)\n * that don't represent meaningful content changes. This prevents trivial timestamp\n * bumps from causing thousands of issues to be saved to the outbox.\n *\n * @param localIssues - Issues from the local worktree\n * @param remoteIssues - Issues from the remote tbd-sync branch\n * @returns Issues that are new or substantively modified compared to remote\n */\nexport function getUpdatedIssues(localIssues: Issue[], remoteIssues: Issue[]): Issue[] {\n // Build a map of remote issues by ID for quick lookup\n const remoteById = new Map<string, Issue>();\n for (const issue of remoteIssues) {\n remoteById.set(issue.id, issue);\n }\n\n // Filter local issues to only those that are new or substantively different\n return localIssues.filter((local) => {\n const remote = remoteById.get(local.id);\n\n // New issue - not in remote\n if (!remote) {\n return true;\n }\n\n // Modified issue - substantive content differs from remote\n // Ignores version and updated_at which change on every merge\n return !issuesSubstantivelyEqual(local, remote);\n });\n}\n\n/**\n * Ensure a directory exists.\n */\nasync function ensureDir(dir: string): Promise<void> {\n await mkdir(dir, { recursive: true });\n}\n\n/**\n * Read issues from a git ref (e.g., origin/tbd-sync).\n *\n * @param baseDir - The base directory of the git repo\n * @param remote - The remote name (e.g., 'origin')\n * @param branch - The branch name (e.g., 'tbd-sync')\n * @returns Array of issues from the remote ref\n */\nasync function readRemoteIssues(baseDir: string, remote: string, branch: string): Promise<Issue[]> {\n const ref = `${remote}/${branch}`;\n const issuesPath = `${DATA_SYNC_DIR}/issues`;\n\n // List all issue files from the remote ref\n let fileList: string;\n try {\n fileList = await git('-C', baseDir, 'ls-tree', '-r', '--name-only', ref, issuesPath);\n } catch {\n // Remote branch doesn't exist or has no issues\n return [];\n }\n\n const issueFiles = fileList\n .trim()\n .split('\\n')\n .filter((f) => f.endsWith('.md'));\n const issues: Issue[] = [];\n\n for (const filepath of issueFiles) {\n try {\n // Read file content from the remote ref\n const content = await git('-C', baseDir, 'show', `${ref}:${filepath}`);\n const issue = parseIssue(content);\n issues.push(issue);\n } catch {\n // Skip files that can't be parsed\n }\n }\n\n return issues;\n}\n\n/**\n * Convert ConflictEntry to AtticEntry format and save to workspace attic.\n */\nasync function saveConflictToAttic(\n atticDir: string,\n conflict: ConflictEntry,\n winnerSource: 'local' | 'remote',\n): Promise<void> {\n const timestamp = now();\n\n // Convert lost_value to string - handle objects, primitives, and nullish\n const lostValueStr =\n conflict.lost_value == null\n ? ''\n : typeof conflict.lost_value === 'object'\n ? JSON.stringify(conflict.lost_value)\n : JSON.stringify(conflict.lost_value);\n\n const entry: AtticEntry = {\n entity_id: conflict.issue_id,\n timestamp,\n field: conflict.field,\n lost_value: lostValueStr,\n winner_source: winnerSource,\n loser_source: winnerSource === 'local' ? 'remote' : 'local',\n context: {\n local_version: conflict.local_version,\n remote_version: conflict.remote_version,\n local_updated_at: timestamp,\n remote_updated_at: timestamp,\n },\n };\n\n // Create filename: {entity_id}_{timestamp}_{field}.yml\n const safeTimestamp = timestamp.replace(/:/g, '-');\n const filename = `${conflict.issue_id}_${safeTimestamp}_${conflict.field}.yml`;\n const filepath = join(atticDir, filename);\n\n // Sort keys using canonical field order, then serialize\n const sorted = sortKeys(entry as unknown as Record<string, unknown>, ATTIC_ENTRY_FIELD_ORDER);\n const content = stringifyYaml(sorted, { sortMapEntries: false });\n await writeFile(filepath, content);\n}\n\n/**\n * Get the target/source directory for workspace operations.\n */\nfunction resolveWorkspaceDir(\n tbdRoot: string,\n options: { workspace?: string; dir?: string; outbox?: boolean },\n): string {\n if (options.dir) {\n return options.dir;\n }\n\n const workspaceName = options.outbox ? 'outbox' : options.workspace;\n if (!workspaceName) {\n throw new Error('One of --workspace, --dir, or --outbox is required');\n }\n\n if (!isValidWorkspaceName(workspaceName)) {\n throw new Error(`Invalid workspace name: ${workspaceName}`);\n }\n\n return join(tbdRoot, getWorkspaceDir(workspaceName));\n}\n\n/**\n * Get the target directory for save operation.\n * @deprecated Use resolveWorkspaceDir instead\n */\nfunction getTargetDir(tbdRoot: string, options: SaveOptions): string {\n return resolveWorkspaceDir(tbdRoot, options);\n}\n\n/**\n * Save issues from data-sync directory to a workspace or directory.\n *\n * Uses mergeIssues() for proper conflict detection when an issue exists\n * in both source (worktree) and target (workspace). Conflicts are saved\n * to the workspace's attic.\n *\n * @param tbdRoot - The root directory of the tbd project\n * @param dataSyncDir - The data-sync directory containing source issues\n * @param options - Save options (workspace name, directory, or outbox)\n * @returns Save result with counts\n */\nexport async function saveToWorkspace(\n tbdRoot: string,\n dataSyncDir: string,\n options: SaveOptions,\n): Promise<SaveResult> {\n const log = options.logger ?? noopLogger;\n const targetDir = getTargetDir(tbdRoot, options);\n log.debug(`Target directory: ${targetDir}`);\n log.debug(`Source directory: ${dataSyncDir}`);\n\n // Create target directory structure\n const atticDir = join(targetDir, 'attic');\n\n await ensureDir(join(targetDir, 'issues'));\n await ensureDir(join(targetDir, 'mappings'));\n await ensureDir(atticDir);\n\n // List all issues in source (worktree)\n log.progress('Loading issues from worktree...');\n const allSourceIssues = await listIssues(dataSyncDir);\n const totalSource = allSourceIssues.length;\n log.info(`Loaded ${totalSource} issue(s) from worktree`);\n let sourceIssues = allSourceIssues;\n\n // Filter to only updated issues if requested\n const isUpdatesOnly = options.updatesOnly ?? options.outbox;\n if (isUpdatesOnly) {\n // Try to fetch latest remote state (may fail if offline)\n try {\n log.progress('Fetching remote for comparison...');\n await git('-C', tbdRoot, 'fetch', 'origin', 'tbd-sync');\n log.debug('Fetch succeeded');\n } catch (fetchError) {\n const fetchMsg = fetchError instanceof Error ? fetchError.message : String(fetchError);\n log.info(`Fetch failed (${fetchMsg}), using cached remote state for comparison`);\n }\n\n // Try to read remote issues (works even if fetch failed, using cached ref).\n // This prevents saving ALL issues when the network is down but we have a\n // cached copy of the remote state from a previous fetch.\n try {\n const remoteIssues = await readRemoteIssues(tbdRoot, 'origin', 'tbd-sync');\n log.info(`Loaded ${remoteIssues.length} remote issue(s) for comparison`);\n sourceIssues = getUpdatedIssues(allSourceIssues, remoteIssues);\n log.info(`Filtered to ${sourceIssues.length} updated issue(s) (of ${totalSource} total)`);\n } catch {\n // No cached remote state either (first sync, or ref was never fetched)\n log.info(`No remote state available, falling back to saving all ${totalSource} issues`);\n }\n }\n\n let saved = 0;\n let conflicts = 0;\n\n log.progress(`Saving ${sourceIssues.length} issue(s)...`);\n // Save each issue to target, merging if needed\n for (const sourceIssue of sourceIssues) {\n // Check if issue already exists in workspace\n let targetIssue = null;\n try {\n targetIssue = await readIssue(targetDir, sourceIssue.id);\n } catch {\n // Issue doesn't exist in target - will be created\n }\n\n if (targetIssue) {\n // Issue exists in both - merge using field-level LWW\n // Pass older version as \"base\" to trigger field-by-field merge instead of whole_issue conflict\n const sourceTime = new Date(sourceIssue.updated_at).getTime();\n const targetTime = new Date(targetIssue.updated_at).getTime();\n\n // Use older version as base for field-by-field merge\n let result;\n let winnerSource: 'local' | 'remote';\n\n if (sourceTime !== targetTime) {\n // Different timestamps - clear ordering, use older as base\n const older = sourceTime > targetTime ? targetIssue : sourceIssue;\n winnerSource = sourceTime > targetTime ? 'local' : 'remote';\n result = mergeIssues(older, sourceIssue, targetIssue);\n } else {\n // Equal timestamps - concurrent edits\n // Create synthetic base to force field-by-field comparison\n const syntheticBase = {\n ...sourceIssue,\n version: 0,\n updated_at: '1970-01-01T00:00:00.000Z',\n } as Issue;\n winnerSource = 'local'; // Arbitrary since timestamps are equal\n result = mergeIssues(syntheticBase, sourceIssue, targetIssue);\n }\n\n // Save merged issue\n await writeIssue(targetDir, result.merged);\n saved++;\n log.debug(`Merged issue ${sourceIssue.id} (${result.conflicts.length} field conflict(s))`);\n\n // Save any conflicts to workspace attic\n for (const conflict of result.conflicts) {\n await saveConflictToAttic(atticDir, conflict, winnerSource);\n conflicts++;\n }\n } else {\n // New issue - just save\n await writeIssue(targetDir, sourceIssue);\n saved++;\n }\n\n // Log progress periodically for large saves\n if (saved % 100 === 0 && saved > 0) {\n log.progress(`Saving issues... (${saved}/${sourceIssues.length})`);\n }\n }\n\n log.info(`Saved ${saved} issue(s), ${conflicts} conflict(s)`);\n\n // Copy ID mappings from source to target (only for saved issues)\n // Build set of saved issue ULIDs (without prefix) to filter mappings\n log.debug('Merging ID mappings...');\n const savedIssueUlids = new Set(sourceIssues.map((issue) => extractUlidFromInternalId(issue.id)));\n\n const sourceMapping = await loadIdMapping(dataSyncDir);\n const targetMapping = await loadIdMapping(targetDir);\n\n // Merge: add source mappings to target (only for saved issues, don't overwrite existing)\n for (const [shortId, ulid] of sourceMapping.shortToUlid) {\n // Only copy mapping if the ULID corresponds to a saved issue\n if (savedIssueUlids.has(ulid) && !targetMapping.shortToUlid.has(shortId)) {\n addIdMapping(targetMapping, ulid, shortId);\n }\n }\n await saveIdMapping(targetDir, targetMapping);\n\n return {\n saved,\n conflicts,\n targetDir,\n totalSource,\n filtered: isUpdatesOnly ?? false,\n };\n}\n\n/**\n * Import issues from a workspace or directory to the data-sync directory.\n *\n * Uses mergeIssues() for proper conflict detection when an issue exists\n * in both source (workspace) and target (worktree). Conflicts are saved\n * to the worktree's attic.\n *\n * @param tbdRoot - The root directory of the tbd project\n * @param dataSyncDir - The data-sync directory to import into\n * @param options - Import options (workspace name, directory, or outbox)\n * @returns Import result with counts\n */\nexport async function importFromWorkspace(\n tbdRoot: string,\n dataSyncDir: string,\n options: ImportOptions,\n): Promise<ImportResult> {\n const log = options.logger ?? noopLogger;\n const sourceDir = resolveWorkspaceDir(tbdRoot, options);\n log.debug(`Source directory: ${sourceDir}`);\n log.debug(`Target directory: ${dataSyncDir}`);\n\n // Determine if we should clear on success\n // --outbox implies --clear-on-success\n const shouldClear = options.clearOnSuccess ?? options.outbox ?? false;\n\n // Create attic directory in target (worktree)\n const atticDir = join(dataSyncDir, 'attic');\n await ensureDir(atticDir);\n\n // List all issues in source workspace\n log.progress('Loading issues from workspace...');\n const sourceIssues = await listIssues(sourceDir);\n log.info(`Loaded ${sourceIssues.length} issue(s) from workspace`);\n\n let imported = 0;\n let conflicts = 0;\n\n log.progress(`Importing ${sourceIssues.length} issue(s)...`);\n // Import each issue to data-sync, merging if needed\n for (const sourceIssue of sourceIssues) {\n // Check if issue already exists in worktree\n let targetIssue = null;\n try {\n targetIssue = await readIssue(dataSyncDir, sourceIssue.id);\n } catch {\n // Issue doesn't exist in target - will be created\n }\n\n if (targetIssue) {\n // Issue exists in both - merge using field-level LWW\n // Pass older version as \"base\" to trigger field-by-field merge instead of whole_issue conflict\n const sourceTime = new Date(sourceIssue.updated_at).getTime();\n const targetTime = new Date(targetIssue.updated_at).getTime();\n\n // Use older version as base for field-by-field merge\n let result;\n let winnerSource: 'local' | 'remote';\n\n if (sourceTime !== targetTime) {\n // Different timestamps - clear ordering, use older as base\n const older = sourceTime > targetTime ? targetIssue : sourceIssue;\n winnerSource = sourceTime > targetTime ? 'local' : 'remote';\n result = mergeIssues(older, sourceIssue, targetIssue);\n } else {\n // Equal timestamps - concurrent edits\n // Create synthetic base to force field-by-field comparison\n const syntheticBase = {\n ...sourceIssue,\n version: 0,\n updated_at: '1970-01-01T00:00:00.000Z',\n } as Issue;\n winnerSource = 'local'; // Arbitrary since timestamps are equal\n result = mergeIssues(syntheticBase, sourceIssue, targetIssue);\n }\n\n // Save merged issue\n await writeIssue(dataSyncDir, result.merged);\n imported++;\n log.debug(`Merged issue ${sourceIssue.id} (${result.conflicts.length} field conflict(s))`);\n\n // Save any conflicts to worktree attic\n for (const conflict of result.conflicts) {\n await saveConflictToAttic(atticDir, conflict, winnerSource);\n conflicts++;\n }\n } else {\n // New issue - just save\n await writeIssue(dataSyncDir, sourceIssue);\n imported++;\n }\n\n // Log progress periodically for large imports\n if (imported % 100 === 0 && imported > 0) {\n log.progress(`Importing issues... (${imported}/${sourceIssues.length})`);\n }\n }\n\n log.info(`Imported ${imported} issue(s), ${conflicts} conflict(s)`);\n\n // Merge ID mappings from source (workspace) to target (worktree) - union operation\n log.debug('Merging ID mappings...');\n const sourceMapping = await loadIdMapping(sourceDir);\n const targetMapping = await loadIdMapping(dataSyncDir);\n\n // Merge: add source mappings to target (don't overwrite existing)\n for (const [shortId, ulid] of sourceMapping.shortToUlid) {\n if (!targetMapping.shortToUlid.has(shortId)) {\n addIdMapping(targetMapping, ulid, shortId);\n }\n }\n\n // Reconcile: ensure all imported issues have mappings.\n // Imported issues may have ULIDs not present in either source or target mapping\n // (e.g., when outbox issues were created but their mappings were lost in a merge).\n // Use the source mapping as historical reference to recover original short IDs.\n const reconcileResult = reconcileMappings(\n sourceIssues.map((i) => i.id),\n targetMapping,\n sourceMapping,\n );\n if (reconcileResult.recovered.length > 0) {\n log.info(`Recovered ${reconcileResult.recovered.length} ID mapping(s) from workspace`);\n }\n if (reconcileResult.created.length > 0) {\n log.info(`Created ${reconcileResult.created.length} new ID mapping(s) for imported issues`);\n }\n\n await saveIdMapping(dataSyncDir, targetMapping);\n\n // Clear source workspace if requested\n let cleared = false;\n if (shouldClear && imported > 0) {\n const workspaceName = options.outbox ? 'outbox' : options.workspace;\n if (workspaceName) {\n await deleteWorkspace(tbdRoot, workspaceName);\n cleared = true;\n }\n }\n\n return {\n imported,\n conflicts,\n sourceDir,\n cleared,\n };\n}\n\n/**\n * Issue counts by status for a workspace.\n */\nexport interface WorkspaceIssueCounts {\n open: number;\n in_progress: number;\n closed: number;\n total: number;\n}\n\n/**\n * Information about a workspace including issue counts.\n */\nexport interface WorkspaceInfo {\n name: string;\n counts: WorkspaceIssueCounts;\n}\n\n/**\n * List all workspaces in .tbd/workspaces/.\n *\n * @param tbdRoot - The root directory of the tbd project\n * @returns Array of workspace names\n */\nexport async function listWorkspaces(tbdRoot: string): Promise<string[]> {\n const workspacesDir = join(tbdRoot, WORKSPACES_DIR);\n\n let entries: string[];\n try {\n entries = await readdir(workspacesDir);\n } catch {\n // Directory doesn't exist\n return [];\n }\n\n // Filter to directories only\n const workspaces: string[] = [];\n for (const entry of entries) {\n try {\n const entryPath = join(workspacesDir, entry);\n const entryStat = await stat(entryPath);\n if (entryStat.isDirectory()) {\n workspaces.push(entry);\n }\n } catch {\n // Skip entries we can't stat\n }\n }\n\n return workspaces;\n}\n\n/**\n * List all workspaces with issue counts by status.\n *\n * @param tbdRoot - The root directory of the tbd project\n * @returns Array of workspace info with names and counts\n */\nexport async function listWorkspacesWithCounts(tbdRoot: string): Promise<WorkspaceInfo[]> {\n const workspaceNames = await listWorkspaces(tbdRoot);\n const result: WorkspaceInfo[] = [];\n\n for (const name of workspaceNames) {\n const workspaceDir = join(tbdRoot, getWorkspaceDir(name));\n let issues: Issue[] = [];\n\n try {\n issues = await listIssues(workspaceDir);\n } catch {\n // No issues or can't read - counts will be 0\n }\n\n // Count by status\n const counts: WorkspaceIssueCounts = {\n open: 0,\n in_progress: 0,\n closed: 0,\n total: issues.length,\n };\n\n for (const issue of issues) {\n if (issue.status === 'open' || issue.status === 'blocked' || issue.status === 'deferred') {\n counts.open++;\n } else if (issue.status === 'in_progress') {\n counts.in_progress++;\n } else if (issue.status === 'closed') {\n counts.closed++;\n }\n }\n\n result.push({ name, counts });\n }\n\n return result;\n}\n\n/**\n * Delete a workspace.\n *\n * @param tbdRoot - The root directory of the tbd project\n * @param name - Workspace name\n */\nexport async function deleteWorkspace(tbdRoot: string, name: string): Promise<void> {\n const workspaceDir = join(tbdRoot, getWorkspaceDir(name));\n\n try {\n await rm(workspaceDir, { recursive: true, force: true });\n } catch {\n // Ignore errors if workspace doesn't exist\n }\n}\n\n/**\n * Check if a workspace exists.\n *\n * @param tbdRoot - The root directory of the tbd project\n * @param name - Workspace name\n * @returns true if the workspace directory exists\n */\nexport async function workspaceExists(tbdRoot: string, name: string): Promise<boolean> {\n const workspaceDir = join(tbdRoot, getWorkspaceDir(name));\n\n try {\n const s = await stat(workspaceDir);\n return s.isDirectory();\n } catch {\n return false;\n }\n}\n","/**\n * `tbd sync` - Synchronization commands.\n *\n * See: tbd-design.md §4.7 Sync Commands\n */\n\nimport { Command } from 'commander';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport {\n requireInit,\n NotInitializedError,\n SyncError,\n WorktreeMissingError,\n WorktreeCorruptedError,\n classifySyncError,\n} from '../lib/errors.js';\nimport { readConfig } from '../../file/config.js';\nimport { listIssues, readIssue, writeIssue } from '../../file/storage.js';\nimport {\n git,\n withIsolatedIndex,\n mergeIssues,\n pushWithRetry,\n checkWorktreeHealth,\n repairWorktree,\n ensureWorktreeAttached,\n type ConflictEntry,\n type PushResult,\n} from '../../file/git.js';\nimport { resolveDataSyncDir, DATA_SYNC_DIR, WORKTREE_DIR } from '../../lib/paths.js';\nimport { join } from 'node:path';\nimport {\n type SyncSummary,\n type SyncTallies,\n emptySummary,\n emptyTallies,\n hasTallies,\n formatSyncSummary,\n parseGitStatus,\n parseGitDiff,\n} from '../../lib/sync-summary.js';\nimport { syncDocsWithDefaults, type SyncDocsResult } from '../../file/doc-sync.js';\nimport { ValidationError } from '../lib/errors.js';\nimport {\n loadIdMapping,\n saveIdMapping,\n mergeIdMappings,\n parseIdMappingFromYaml,\n reconcileMappings,\n resolveIdMappingConflicts,\n} from '../../file/id-mapping.js';\nimport {\n saveToWorkspace,\n workspaceExists,\n importFromWorkspace,\n deleteWorkspace,\n} from '../../file/workspace.js';\n\ninterface SyncOptions {\n push?: boolean;\n pull?: boolean;\n status?: boolean;\n force?: boolean;\n fix?: boolean;\n issues?: boolean;\n docs?: boolean;\n autoSave?: boolean; // Commander: --no-auto-save sets this to false (default: true)\n outbox?: boolean; // Commander: --no-outbox sets this to false (default: true)\n}\n\ninterface SyncStatus {\n synced: boolean;\n localChanges: string[];\n remoteChanges: string[];\n syncBranch: string;\n remote: string;\n ahead: number;\n behind: number;\n}\n\nclass SyncHandler extends BaseCommand {\n private dataSyncDir = '';\n private tbdRoot = '';\n\n async run(options: SyncOptions): Promise<void> {\n const tbdRoot = await requireInit();\n this.tbdRoot = tbdRoot;\n\n // Validate mutually exclusive options\n // --push/--pull only apply to issues (network operations)\n if ((options.push || options.pull) && options.docs) {\n throw new ValidationError('--push/--pull only work with issue sync, not --docs');\n }\n\n // Determine what to sync:\n // - If neither --issues nor --docs specified, sync both\n // - If --push or --pull specified (without --issues/--docs), sync only issues\n const hasExclusiveIssueFlag = Boolean(options.push) || Boolean(options.pull);\n const hasSelectiveFlag = Boolean(options.issues) || Boolean(options.docs);\n\n // Sync docs: explicit --docs, or default (no selective flags and no push/pull)\n const syncDocs = Boolean(options.docs) || (!hasSelectiveFlag && !hasExclusiveIssueFlag);\n // Sync issues: explicit --issues, push/pull flags, or default (no selective flags)\n const syncIssues = Boolean(options.issues) || hasExclusiveIssueFlag || !hasSelectiveFlag;\n\n // STEP 1: Sync docs first (fast, local operations)\n // This ensures docs are updated even if issue sync fails\n if (syncDocs) {\n await this.syncDocs(options.status);\n\n // If only doing docs, return after doc sync\n if (!syncIssues) {\n return;\n }\n }\n\n // STEP 2: Sync issues (network operations)\n // Check worktree health before any issue sync operations\n // See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md\n let worktreeHealth = await checkWorktreeHealth(tbdRoot);\n if (!worktreeHealth.valid) {\n // Auto-create worktree if it's simply missing (normal for fresh clones)\n // Only require --fix for corrupted/prunable states that need repair\n if (worktreeHealth.status === 'missing') {\n // Auto-create worktree - this is the expected state on fresh clones\n await this.doRepairWorktree(tbdRoot, 'missing');\n worktreeHealth = await checkWorktreeHealth(tbdRoot);\n if (!worktreeHealth.valid) {\n throw new WorktreeCorruptedError(\n `Failed to create worktree. Status: ${worktreeHealth.status}. Run 'tbd doctor' for diagnostics.`,\n );\n }\n } else if (options.fix) {\n // Attempt repair when --fix is provided for corrupted/prunable states\n await this.doRepairWorktree(tbdRoot, worktreeHealth.status as 'prunable' | 'corrupted');\n // Re-check health after repair\n worktreeHealth = await checkWorktreeHealth(tbdRoot);\n if (!worktreeHealth.valid) {\n throw new WorktreeCorruptedError(\n `Worktree repair failed. Status: ${worktreeHealth.status}. Run 'tbd doctor' for diagnostics.`,\n );\n }\n } else {\n // No --fix flag, throw appropriate error for corrupted/prunable states\n if (worktreeHealth.status === 'prunable') {\n throw new WorktreeMissingError(\n \"Worktree directory was deleted but git still tracks it. Run 'tbd sync --fix' or 'tbd doctor --fix' to repair.\",\n );\n }\n if (worktreeHealth.status === 'corrupted') {\n throw new WorktreeCorruptedError(\n `Worktree is corrupted: ${worktreeHealth.error ?? 'unknown error'}. Run 'tbd sync --fix' or 'tbd doctor --fix' to repair.`,\n );\n }\n }\n }\n\n this.dataSyncDir = await resolveDataSyncDir(tbdRoot);\n\n // Load config to get sync branch\n let config;\n try {\n config = await readConfig(tbdRoot);\n } catch {\n throw new NotInitializedError('Not a tbd repository. Run `tbd init` first.');\n }\n\n const syncBranch = config.sync.branch;\n const remote = config.sync.remote;\n\n if (options.status) {\n await this.showIssueStatus(syncBranch, remote);\n return;\n }\n\n if (this.checkDryRun('Would sync repository', { syncBranch, remote })) {\n return;\n }\n\n if (options.pull) {\n await this.pullChanges(syncBranch, remote);\n } else if (options.push) {\n await this.pushChanges(syncBranch, remote);\n } else {\n // Full sync: pull then push\n await this.fullSync(syncBranch, remote, {\n force: options.force,\n autoSave: options.autoSave,\n outbox: options.outbox,\n });\n }\n }\n\n /**\n * Sync docs from bundled sources and config.\n * This is a fast, local operation (no network required).\n */\n private async syncDocs(statusOnly?: boolean): Promise<SyncDocsResult> {\n if (statusOnly) {\n // Show status without making changes\n const result = await syncDocsWithDefaults(this.tbdRoot, { dryRun: true });\n this.showDocStatus(result);\n return result;\n }\n\n const spinner = this.output.spinner('Syncing docs...');\n const result = await syncDocsWithDefaults(this.tbdRoot);\n spinner.stop();\n\n // Report results\n this.showDocSyncResult(result);\n return result;\n }\n\n /**\n * Show doc sync status (what would change).\n */\n private showDocStatus(result: SyncDocsResult): void {\n const colors = this.output.getColors();\n const hasChanges =\n result.added.length > 0 ||\n result.updated.length > 0 ||\n result.removed.length > 0 ||\n result.pruned.length > 0;\n\n if (!hasChanges) {\n this.output.success('Docs up to date');\n return;\n }\n\n console.log(colors.bold('Docs:'));\n if (result.added.length > 0) {\n console.log(` ${colors.success(`+${result.added.length}`)} new doc(s) available`);\n }\n if (result.updated.length > 0) {\n console.log(` ${colors.warn(`~${result.updated.length}`)} doc(s) to update`);\n }\n if (result.removed.length > 0) {\n console.log(` ${colors.error(`-${result.removed.length}`)} doc(s) to remove`);\n }\n if (result.pruned.length > 0) {\n console.log(` ${colors.dim(`${result.pruned.length}`)} stale config entry/entries`);\n }\n }\n\n /**\n * Show doc sync result after sync.\n */\n private showDocSyncResult(result: SyncDocsResult): void {\n const hasChanges =\n result.added.length > 0 ||\n result.updated.length > 0 ||\n result.removed.length > 0 ||\n result.pruned.length > 0;\n\n if (!hasChanges) {\n this.output.success('Docs up to date');\n return;\n }\n\n // Build summary string\n const parts: string[] = [];\n if (result.added.length > 0) {\n parts.push(`+${result.added.length}`);\n }\n if (result.updated.length > 0) {\n parts.push(`~${result.updated.length}`);\n }\n if (result.removed.length > 0) {\n parts.push(`-${result.removed.length}`);\n }\n\n if (parts.length > 0) {\n this.output.success(`Synced docs: ${parts.join(' ')} doc(s)`);\n }\n\n // Report pruned entries\n if (result.pruned.length > 0) {\n this.output.info(`Removed ${result.pruned.length} stale config entry/entries`);\n }\n\n // Report errors\n for (const err of result.errors) {\n this.output.warn(`Doc sync error: ${err.path}: ${err.error}`);\n }\n }\n\n /**\n * Attempt to repair an unhealthy worktree.\n * See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md\n */\n private async doRepairWorktree(\n tbdRoot: string,\n status: 'missing' | 'prunable' | 'corrupted',\n ): Promise<void> {\n const spinner = this.output.spinner(`Repairing worktree (${status})...`);\n\n try {\n // Use shared repairWorktree from git.ts\n const result = await repairWorktree(tbdRoot, status);\n\n spinner.stop();\n\n if (!result.success) {\n throw new WorktreeCorruptedError(`Failed to repair worktree: ${result.error}`);\n }\n\n if (result.backedUp) {\n this.output.info(`Corrupted worktree backed up to: ${result.backedUp}`);\n }\n this.output.success('Worktree repaired successfully');\n } catch (error) {\n spinner.stop();\n if (error instanceof WorktreeCorruptedError) throw error;\n throw new WorktreeCorruptedError(`Failed to repair worktree: ${(error as Error).message}`);\n }\n }\n\n private async showIssueStatus(syncBranch: string, remote: string): Promise<void> {\n const status = await this.getSyncStatus(syncBranch, remote);\n\n this.output.data(status, () => {\n const colors = this.output.getColors();\n\n if (status.synced) {\n this.output.success('Repository is in sync');\n return;\n }\n\n console.log(colors.bold(`Sync status: ${syncBranch} ↔ ${remote}/${syncBranch}`));\n console.log('');\n\n if (status.ahead > 0) {\n console.log(` ${colors.id(`↑ ${status.ahead}`)} commit(s) ahead (to push)`);\n }\n if (status.behind > 0) {\n console.log(` ${colors.dim(`↓ ${status.behind}`)} commit(s) behind (to pull)`);\n }\n\n if (status.localChanges.length > 0) {\n console.log('');\n console.log(colors.bold('Local changes (not yet pushed):'));\n for (const change of status.localChanges) {\n console.log(` ${change}`);\n }\n }\n\n if (status.remoteChanges.length > 0) {\n console.log('');\n console.log(colors.bold('Remote changes (not yet pulled):'));\n for (const change of status.remoteChanges) {\n console.log(` ${change}`);\n }\n }\n });\n }\n\n private async getSyncStatus(syncBranch: string, remote: string): Promise<SyncStatus> {\n const localChanges: string[] = [];\n const remoteChanges: string[] = [];\n let ahead = 0;\n let behind = 0;\n\n // Check for uncommitted changes in the worktree\n // FIX Bug 2: Previously ran git status on main branch where data-sync/ is gitignored.\n // Now check worktree status directly.\n // See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md\n try {\n const worktreePath = join(this.tbdRoot, WORKTREE_DIR);\n const status = await git('-C', worktreePath, 'status', '--porcelain');\n if (status) {\n for (const line of status.split('\\n')) {\n if (!line) continue;\n const statusCode = line.slice(0, 2).trim();\n const file = line.slice(3);\n if (statusCode === 'M') {\n localChanges.push(`modified: ${file}`);\n } else if (statusCode === 'A' || statusCode === '??') {\n localChanges.push(`new: ${file}`);\n } else if (statusCode === 'D') {\n localChanges.push(`deleted: ${file}`);\n }\n }\n }\n } catch {\n this.output.debug('Git worktree not available');\n }\n\n // Check for remote changes\n try {\n await git('fetch', remote, syncBranch);\n\n // Count commits ahead/behind\n try {\n const aheadOutput = await git(\n 'rev-list',\n '--count',\n `${remote}/${syncBranch}..${syncBranch}`,\n );\n ahead = parseInt(aheadOutput, 10) || 0;\n } catch {\n this.output.debug('Branch does not exist locally');\n }\n\n try {\n const behindOutput = await git(\n 'rev-list',\n '--count',\n `${syncBranch}..${remote}/${syncBranch}`,\n );\n behind = parseInt(behindOutput, 10) || 0;\n } catch {\n this.output.debug('Remote branch does not exist');\n }\n\n // Get commit messages for remote changes\n if (behind > 0) {\n const logOutput = await git(\n 'log',\n '--oneline',\n `${syncBranch}..${remote}/${syncBranch}`,\n '--limit=10',\n );\n for (const line of logOutput.split('\\n')) {\n if (line) {\n remoteChanges.push(line);\n }\n }\n }\n } catch {\n this.output.debug('Remote not available or sync branch does not exist');\n }\n\n return {\n synced:\n localChanges.length === 0 && remoteChanges.length === 0 && ahead === 0 && behind === 0,\n localChanges,\n remoteChanges,\n syncBranch,\n remote,\n ahead,\n behind,\n };\n }\n\n private async pullChanges(syncBranch: string, remote: string): Promise<void> {\n const spinner = this.output.spinner('Pulling from remote...');\n try {\n await git('fetch', remote, syncBranch);\n\n // Get list of changed files\n let behind = 0;\n try {\n const behindOutput = await git(\n 'rev-list',\n '--count',\n `${syncBranch}..${remote}/${syncBranch}`,\n );\n behind = parseInt(behindOutput, 10) || 0;\n } catch {\n this.output.debug('Branch does not exist');\n }\n\n spinner.stop();\n if (behind === 0) {\n this.output.success('Already up to date');\n return;\n }\n\n // Merge changes using isolated index\n await withIsolatedIndex(async () => {\n // Read the remote tree\n await git('read-tree', `${remote}/${syncBranch}`);\n\n // Update local branch to remote\n const remoteCommit = await git('rev-parse', `${remote}/${syncBranch}`);\n await git('update-ref', `refs/heads/${syncBranch}`, remoteCommit);\n });\n\n this.output.success(`Pulled ${behind} change(s) from ${remote}/${syncBranch}`);\n } catch (error) {\n spinner.stop();\n const msg = (error as Error).message;\n if (msg.includes('not found') || msg.includes('does not exist')) {\n this.output.info(`Remote branch ${remote}/${syncBranch} does not exist yet`);\n } else {\n throw new SyncError(`Failed to pull: ${msg}`);\n }\n }\n }\n\n /**\n * Commit any uncommitted changes in the worktree to the sync branch.\n * This must be called before pushing to ensure changes are captured.\n *\n * @returns Tallies of new/updated/deleted files committed\n */\n private async commitWorktreeChanges(): Promise<SyncTallies> {\n // Use tbdRoot to derive worktree path consistently\n // FIX Bug 1: Previously used process.cwd() which fails if not in repo root\n // See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md\n const worktreePath = join(this.tbdRoot, WORKTREE_DIR);\n\n try {\n // Ensure worktree is attached to sync branch (repair old tbd repos)\n await ensureWorktreeAttached(worktreePath);\n\n // Check for uncommitted changes (untracked, modified, or deleted)\n const status = await git('-C', worktreePath, 'status', '--porcelain');\n if (!status || status.trim() === '') {\n return emptyTallies(); // Nothing to commit\n }\n\n // Parse status to get tallies\n const tallies = parseGitStatus(status);\n const fileCount = tallies.new + tallies.updated + tallies.deleted;\n\n // Stage all changes\n await git('-C', worktreePath, 'add', '-A');\n\n // Commit the changes\n const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);\n await git(\n '-C',\n worktreePath,\n 'commit',\n '-m',\n `tbd sync: ${timestamp} (${fileCount} file${fileCount === 1 ? '' : 's'})`,\n );\n\n return tallies;\n } catch (error) {\n // If commit fails (e.g., nothing to commit after staging), that's ok\n const msg = (error as Error).message;\n if (msg.includes('nothing to commit')) {\n return emptyTallies();\n }\n throw error;\n }\n }\n\n private async pushChanges(syncBranch: string, remote: string): Promise<void> {\n const spinner = this.output.spinner('Pushing to remote...');\n try {\n // Commit any uncommitted changes in the worktree before pushing\n const committedTallies = await this.commitWorktreeChanges();\n const committedCount =\n committedTallies.new + committedTallies.updated + committedTallies.deleted;\n if (committedCount > 0) {\n this.output.info(`Committed ${committedCount} file(s) to sync branch`);\n }\n\n // Check how many commits we're ahead of remote\n let ahead = 0;\n try {\n await git('fetch', remote, syncBranch);\n const aheadOutput = await git(\n 'rev-list',\n '--count',\n `${remote}/${syncBranch}..${syncBranch}`,\n );\n ahead = parseInt(aheadOutput, 10) || 0;\n this.output.debug(`Ahead of remote by ${ahead} commit(s)`);\n } catch {\n // Remote branch doesn't exist - count all local commits\n try {\n const countOutput = await git('rev-list', '--count', syncBranch);\n ahead = parseInt(countOutput, 10) || 0;\n this.output.debug(`Remote branch not found, ${ahead} local commit(s) to push`);\n } catch {\n ahead = 0;\n this.output.debug('Could not count local commits');\n }\n }\n\n if (ahead === 0) {\n spinner.stop();\n this.output.success('Already up to date');\n return;\n }\n\n // Use push with retry\n const result = await this.doPushWithRetry(syncBranch, remote);\n spinner.stop();\n\n if (result.success) {\n this.output.success(`Pushed ${ahead} commit(s) to ${remote}/${syncBranch}`);\n } else if (result.conflicts && result.conflicts.length > 0) {\n this.output.warn(\n `Push completed with ${result.conflicts.length} conflict(s) (see attic for details)`,\n );\n } else {\n throw new SyncError(`Failed to push: ${result.error}`);\n }\n } catch (error) {\n spinner.stop();\n if (error instanceof SyncError) throw error;\n throw new SyncError(`Failed to push: ${(error as Error).message}`);\n }\n }\n\n private async doPushWithRetry(syncBranch: string, remote: string): Promise<PushResult> {\n return pushWithRetry(\n syncBranch,\n remote,\n async () => {\n // Merge callback - called when we need to merge remote changes\n const conflicts: ConflictEntry[] = [];\n\n // Get list of issues that need merging\n const localIssues = await listIssues(this.dataSyncDir);\n\n for (const localIssue of localIssues) {\n try {\n // Try to get the remote version (use relative path for git show)\n const remoteContent = await git(\n 'show',\n `${remote}/${syncBranch}:${DATA_SYNC_DIR}/issues/${localIssue.id}.md`,\n );\n\n if (remoteContent) {\n // Parse remote issue and merge\n const remoteIssue = await readIssue(this.dataSyncDir, localIssue.id);\n const result = mergeIssues(null, localIssue, remoteIssue);\n\n // Write merged result\n await writeIssue(this.dataSyncDir, result.merged);\n conflicts.push(...result.conflicts);\n }\n } catch {\n // Issue doesn't exist remotely - no merge needed\n this.output.debug(`Issue ${localIssue.id} not on remote, no merge needed`);\n }\n }\n\n return conflicts;\n },\n this.tbdRoot,\n );\n }\n\n /**\n * Show git log --stat output in debug mode.\n * Used to display commits that were synced.\n *\n * @param label - Label for the debug output (e.g., \"Commits sent\")\n * @param args - Arguments to pass to `git log` after `--stat --oneline`\n * Must include explicit branch/ref to avoid showing commits from the wrong branch.\n */\n private async showGitLogDebug(label: string, ...args: string[]): Promise<void> {\n try {\n const logOutput = await git('log', '--stat', '--oneline', ...args);\n if (logOutput.trim()) {\n this.output.debug(`${label}:`);\n for (const line of logOutput.split('\\n')) {\n this.output.debug(` ${line}`);\n }\n }\n } catch {\n // Ignore errors - log is just for debugging\n }\n }\n\n private async fullSync(\n syncBranch: string,\n remote: string,\n options: { force?: boolean; autoSave?: boolean; outbox?: boolean } = {},\n ): Promise<void> {\n const spinner = this.output.spinner('Syncing with remote...');\n const summary: SyncSummary = emptySummary();\n const conflicts: ConflictEntry[] = [];\n // Use tbdRoot for consistent path resolution\n const worktreePath = join(this.tbdRoot, WORKTREE_DIR);\n\n try {\n // STEP 1: Commit local changes FIRST (before pulling)\n // This ensures local work is preserved before we incorporate remote changes.\n const committedTallies = await this.commitWorktreeChanges();\n // Add committed changes to sent tallies\n summary.sent.new += committedTallies.new;\n summary.sent.updated += committedTallies.updated;\n summary.sent.deleted += committedTallies.deleted;\n if (hasTallies(committedTallies)) {\n const count = committedTallies.new + committedTallies.updated + committedTallies.deleted;\n this.output.debug(`Committed ${count} file(s) to sync branch`);\n }\n\n // STEP 2: Fetch remote\n await git('fetch', remote, syncBranch);\n\n // Get file-level changes from remote using git diff\n let behindCommits = 0;\n try {\n const behindOutput = await git(\n 'rev-list',\n '--count',\n `${syncBranch}..${remote}/${syncBranch}`,\n );\n behindCommits = parseInt(behindOutput, 10) || 0;\n this.output.debug(`Behind remote by ${behindCommits} commit(s)`);\n\n // Get file-level tallies for received changes\n if (behindCommits > 0) {\n try {\n const diffOutput = await git(\n 'diff',\n '--name-status',\n `${syncBranch}..${remote}/${syncBranch}`,\n );\n const receivedTallies = parseGitDiff(diffOutput);\n summary.received.new += receivedTallies.new;\n summary.received.updated += receivedTallies.updated;\n summary.received.deleted += receivedTallies.deleted;\n } catch {\n // If we can't get detailed diff, just track commit count\n this.output.debug('Could not get detailed diff for received changes');\n }\n }\n } catch {\n // Branch doesn't exist on remote\n this.output.debug('Remote sync branch does not exist yet');\n }\n\n // Ensure .gitattributes exists in the worktree so ids.yml uses merge=union.\n // This prevents conflicts when both sides add non-overlapping keys.\n // Written before every merge so existing repos get it on their next sync.\n {\n const { access, writeFile } = await import('node:fs/promises');\n const attrPath = join(this.dataSyncDir, 'mappings', '.gitattributes');\n try {\n await access(attrPath);\n } catch {\n await writeFile(attrPath, 'ids.yml merge=union\\n');\n await git('-C', worktreePath, 'add', attrPath);\n try {\n await git(\n '-C',\n worktreePath,\n 'commit',\n '--no-verify',\n '-m',\n 'chore: add merge=union for ids.yml',\n );\n } catch {\n // May fail if nothing to commit (already staged elsewhere)\n }\n }\n }\n\n // STEP 3: If remote has changes, merge them in\n if (behindCommits > 0) {\n // Track HEAD before merge for debug log\n let headBeforeMerge = '';\n try {\n headBeforeMerge = (await git('-C', worktreePath, 'rev-parse', 'HEAD')).trim();\n } catch {\n // Ignore - just won't show debug log\n }\n\n // Merge remote into local using worktree\n // This is a proper git merge that preserves both local and remote changes\n try {\n await git(\n '-C',\n worktreePath,\n 'merge',\n `${remote}/${syncBranch}`,\n '-m',\n 'tbd sync: merge remote changes',\n );\n this.output.debug(`Merged ${behindCommits} commit(s) from remote`);\n\n // Show received commits in debug mode\n // Use syncBranch explicitly — bare `HEAD` would resolve to the user's\n // current working branch, not the tbd-sync branch in the worktree.\n if (headBeforeMerge) {\n await this.showGitLogDebug('Commits received', `${headBeforeMerge}..${syncBranch}`);\n }\n\n // Reconcile ID mappings after clean merge.\n // A git merge may add issue files without corresponding ids.yml entries\n // (e.g., when outbox issues were committed to a feature branch).\n // Try to recover original short IDs from the remote's mapping to preserve\n // ID stability (so existing references in docs/PRs remain valid).\n const postMergeIssues = await listIssues(this.dataSyncDir);\n const postMergeMapping = await loadIdMapping(this.dataSyncDir);\n\n // Load historical mapping from remote to recover original short IDs\n let historicalMapping: Awaited<ReturnType<typeof loadIdMapping>> | undefined;\n try {\n const remoteIdsContent = await git(\n 'show',\n `${remote}/${syncBranch}:${DATA_SYNC_DIR}/mappings/ids.yml`,\n );\n if (remoteIdsContent) {\n historicalMapping = parseIdMappingFromYaml(remoteIdsContent);\n }\n } catch {\n // Remote mapping not available - will generate new IDs\n }\n\n const reconcileResult = reconcileMappings(\n postMergeIssues.map((i) => i.id),\n postMergeMapping,\n historicalMapping,\n );\n const totalReconciled = reconcileResult.created.length + reconcileResult.recovered.length;\n if (totalReconciled > 0) {\n await saveIdMapping(this.dataSyncDir, postMergeMapping);\n // Commit the updated mapping so it's included in the push\n await git('-C', worktreePath, 'add', '-A');\n try {\n await git(\n '-C',\n worktreePath,\n 'commit',\n '--no-verify',\n '-m',\n `tbd sync: reconcile ${totalReconciled} missing ID mapping(s)`,\n );\n } catch {\n // Nothing to commit if mapping file was unchanged\n }\n if (reconcileResult.recovered.length > 0) {\n this.output.debug(\n `Recovered ${reconcileResult.recovered.length} ID mapping(s) from history`,\n );\n }\n if (reconcileResult.created.length > 0) {\n this.output.debug(\n `Created ${reconcileResult.created.length} new ID mapping(s) (no history available)`,\n );\n }\n }\n } catch {\n // Merge conflict - try to resolve at file level\n this.output.info(`Merge conflict, attempting file-level resolution`);\n\n // For each conflicted issue, do field-level merge\n const localIssues = await listIssues(this.dataSyncDir);\n for (const localIssue of localIssues) {\n try {\n const remoteContent = await git(\n 'show',\n `${remote}/${syncBranch}:${DATA_SYNC_DIR}/issues/${localIssue.id}.md`,\n );\n if (remoteContent) {\n const remoteIssue = await readIssue(this.dataSyncDir, localIssue.id);\n const result = mergeIssues(null, localIssue, remoteIssue);\n await writeIssue(this.dataSyncDir, result.merged);\n conflicts.push(...result.conflicts);\n }\n } catch {\n // Issue doesn't exist remotely - keep local version\n this.output.debug(`Issue ${localIssue.id} not on remote, keeping local`);\n }\n }\n\n // Merge ids.yml (ID mappings are always additive, so we union both sides)\n // Also capture the remote mapping for recovery of original short IDs.\n // The on-disk ids.yml may contain conflict markers after a failed git merge,\n // so we resolve conflicts by extracting both sides and merging.\n let conflictRemoteMapping: Awaited<ReturnType<typeof loadIdMapping>> | undefined;\n try {\n const remoteIdsContent = await git(\n 'show',\n `${remote}/${syncBranch}:${DATA_SYNC_DIR}/mappings/ids.yml`,\n );\n if (remoteIdsContent) {\n conflictRemoteMapping = parseIdMappingFromYaml(remoteIdsContent);\n // Read the on-disk file (which may have conflict markers) and resolve\n const { readFile } = await import('node:fs/promises');\n const idsPath = join(this.dataSyncDir, 'mappings', 'ids.yml');\n const rawContent = await readFile(idsPath, 'utf-8');\n const localMapping = resolveIdMappingConflicts(rawContent);\n const mergedMapping = mergeIdMappings(localMapping, conflictRemoteMapping);\n await saveIdMapping(this.dataSyncDir, mergedMapping);\n this.output.debug(\n `Merged ID mappings: ${localMapping.shortToUlid.size} local + ${conflictRemoteMapping.shortToUlid.size} remote = ${mergedMapping.shortToUlid.size} total`,\n );\n }\n } catch (error) {\n // Remote ids.yml doesn't exist or can't be parsed - keep local\n this.output.debug(`Could not merge ids.yml: ${(error as Error).message}`);\n }\n\n // Reconcile any remaining issues without mappings after conflict resolution.\n // Use the remote mapping as historical source to recover original short IDs.\n {\n const allIssues = await listIssues(this.dataSyncDir);\n const currentMapping = await loadIdMapping(this.dataSyncDir);\n const reconcileResult = reconcileMappings(\n allIssues.map((i) => i.id),\n currentMapping,\n conflictRemoteMapping,\n );\n const totalReconciled =\n reconcileResult.created.length + reconcileResult.recovered.length;\n if (totalReconciled > 0) {\n await saveIdMapping(this.dataSyncDir, currentMapping);\n if (reconcileResult.recovered.length > 0) {\n this.output.debug(\n `Recovered ${reconcileResult.recovered.length} ID mapping(s) from remote`,\n );\n }\n if (reconcileResult.created.length > 0) {\n this.output.debug(\n `Created ${reconcileResult.created.length} new ID mapping(s) after conflict resolution`,\n );\n }\n }\n }\n\n // Stage resolved files and complete merge\n // Use --no-verify to bypass parent repo hooks (lefthook, husky, etc.)\n await git('-C', worktreePath, 'add', '-A');\n\n // SAFETY CHECK: Never commit files with unresolved merge conflict markers\n // This prevents the bug where ids.yml or other files get committed with\n // <<<<<<< HEAD markers still present\n const conflictCheck = await git(\n '-C',\n worktreePath,\n 'diff',\n '--cached',\n '-S<<<<<<< ',\n '--name-only',\n );\n if (conflictCheck.trim()) {\n const conflictedFiles = conflictCheck.trim().split('\\n');\n throw new SyncError(\n `Cannot commit: ${conflictedFiles.length} file(s) still have merge conflict markers:\\n` +\n conflictedFiles.map((f) => ` - ${f}`).join('\\n') +\n `\\n\\nThis is a bug in tbd sync. Please report it and manually resolve conflicts in:\\n` +\n ` ${worktreePath}`,\n );\n }\n\n try {\n await git(\n '-C',\n worktreePath,\n 'commit',\n '--no-verify',\n '-m',\n 'tbd sync: resolved merge conflicts',\n );\n } catch {\n // May fail if no conflicts needed resolving\n this.output.debug('No merge commit needed (conflicts already resolved)');\n }\n }\n }\n } catch (error) {\n // Remote not available - that's ok for first sync\n this.output.debug(`Fetch failed (may be first sync): ${(error as Error).message}`);\n }\n\n // Check how many commits we're ahead of remote (if any)\n let aheadCommits = 0;\n try {\n const aheadOutput = await git(\n 'rev-list',\n '--count',\n `${remote}/${syncBranch}..${syncBranch}`,\n );\n aheadCommits = parseInt(aheadOutput, 10) || 0;\n this.output.debug(`Ahead of remote by ${aheadCommits} commit(s)`);\n } catch {\n // Remote branch doesn't exist - count all local commits on sync branch\n try {\n const countOutput = await git('rev-list', '--count', syncBranch);\n aheadCommits = parseInt(countOutput, 10) || 0;\n this.output.debug(`Remote branch not found, ${aheadCommits} local commit(s) to push`);\n } catch {\n aheadCommits = 0;\n this.output.debug('Could not count local commits');\n }\n }\n\n // Push if we have commits ahead of remote\n let pushFailed = false;\n let pushError = '';\n if (aheadCommits > 0) {\n this.output.debug(`Pushing ${aheadCommits} commit(s) to remote`);\n const result = await this.doPushWithRetry(syncBranch, remote);\n if (result.conflicts) {\n conflicts.push(...result.conflicts);\n }\n if (!result.success) {\n pushFailed = true;\n pushError = result.error ?? 'Unknown push error';\n this.output.debug(`Push failed: ${pushError}`);\n } else {\n // Show pushed commits in debug mode\n // Use syncBranch explicitly — bare `-N` would resolve against the user's\n // current working branch (HEAD), not the tbd-sync branch.\n await this.showGitLogDebug('Commits sent', syncBranch, `-${aheadCommits}`);\n }\n } else {\n this.output.debug('No commits to push');\n }\n\n summary.conflicts = conflicts.length;\n spinner.stop();\n\n // Report push failure - classify error and take appropriate action\n if (pushFailed) {\n // Extract meaningful error display string\n let displayError = pushError;\n const httpMatch = /HTTP (\\d+)/.exec(pushError);\n const curlMatch = /curl \\d+ (.+?)(?:\\n|$)/.exec(pushError);\n if (httpMatch) {\n displayError = `HTTP ${httpMatch[1]}${curlMatch ? ` - ${curlMatch[1]}` : ''}`;\n } else {\n // Fall back to first meaningful line (skip \"Command failed: git push...\")\n const lines = pushError.split('\\n').filter((l) => l && !l.startsWith('Command failed'));\n displayError = lines[0] ?? pushError;\n }\n\n // Classify the error to determine recovery action\n const errorType = classifySyncError(pushError);\n\n this.output.data(\n {\n summary,\n conflicts: conflicts.length,\n pushFailed,\n pushError,\n unpushedCommits: aheadCommits,\n errorType,\n },\n () => {\n this.output.error(`Push failed: ${displayError}`);\n console.log(` ${aheadCommits} commit(s) not pushed to remote.`);\n },\n );\n\n // Handle recovery based on error type (after output.data to avoid async callback)\n // Only show options in non-JSON mode\n if (errorType === 'permanent' && options.autoSave !== false) {\n // Auto-save to outbox on permanent failure\n await this.handlePermanentFailure();\n } else if (!this.ctx.json) {\n if (errorType === 'transient') {\n // Suggest retry for transient failures\n console.log('');\n console.log(' This appears to be a temporary issue. Options:');\n console.log(' • Retry: tbd sync');\n console.log(' • Save for later: tbd save --outbox');\n } else {\n // Unknown error - suggest both options\n console.log('');\n console.log(' Options:');\n console.log(' • Retry: tbd sync');\n console.log(\" • Run 'tbd sync --status' to check status\");\n console.log(' • Save for later: tbd save --outbox');\n }\n }\n return;\n }\n\n // After successful push, import from outbox if it has data\n if (options.outbox !== false) {\n await this.maybeImportOutbox(syncBranch, remote);\n }\n\n this.output.data({ summary, conflicts: conflicts.length }, () => {\n const summaryText = formatSyncSummary(summary);\n if (!summaryText) {\n this.output.success('Already in sync');\n } else {\n this.output.success(`Synced: ${summaryText}`);\n }\n });\n }\n\n /**\n * Handle permanent push failure by auto-saving to outbox.\n * Called when push fails with a permanent error (e.g., HTTP 403).\n */\n private async handlePermanentFailure(): Promise<void> {\n // Count issues in worktree to see if there's anything to save\n const worktreeIssues = await listIssues(this.dataSyncDir);\n if (worktreeIssues.length === 0) {\n console.log('');\n console.log(' No unsynced issues to save (already in sync with remote).');\n return;\n }\n\n // Check existing outbox count before save\n let existingOutboxCount = 0;\n if (await workspaceExists(this.tbdRoot, 'outbox')) {\n const outboxPath = join(this.tbdRoot, '.tbd', 'workspaces', 'outbox');\n try {\n const existingIssues = await listIssues(outboxPath);\n existingOutboxCount = existingIssues.length;\n } catch {\n // Outbox exists but couldn't read - will be handled by saveToWorkspace\n }\n }\n\n try {\n // Auto-save to outbox (merges with existing outbox data via updatesOnly)\n const result = await saveToWorkspace(this.tbdRoot, this.dataSyncDir, { outbox: true });\n\n if (result.saved === 0) {\n // Nothing new to save - issues already in outbox from previous failure\n console.log('');\n console.log(' Issues already saved to outbox from previous sync attempt.');\n console.log('');\n console.log(' Your issues are safe. To recover later:');\n console.log(\" 1. Commit: git add .tbd/workspaces && git commit -m 'tbd: save outbox'\");\n console.log(' 2. Push your working branch: git push');\n console.log(\" 3. Run 'tbd sync' when push access is available\");\n console.log('');\n console.log(\n ' WARNING: Do NOT add .tbd/workspaces/ to .gitignore -- that would cause data loss.',\n );\n } else {\n // Show saved count and total in outbox\n const totalInOutbox = existingOutboxCount + result.saved;\n if (existingOutboxCount > 0) {\n console.log('');\n this.output.success(\n `Saved ${result.saved} issue(s) to outbox (${totalInOutbox} total in outbox)`,\n );\n } else {\n console.log('');\n this.output.success(`Saved ${result.saved} issue(s) to outbox (automatic backup)`);\n }\n console.log('');\n console.log(' Your issues are safe. To recover later:');\n console.log(\" 1. Commit: git add .tbd/workspaces && git commit -m 'tbd: save outbox'\");\n console.log(' 2. Push your working branch: git push');\n console.log(\" 3. Run 'tbd sync' when push access is available\");\n console.log(' (outbox will be imported automatically on successful sync)');\n console.log('');\n console.log(\n ' WARNING: Do NOT add .tbd/workspaces/ to .gitignore -- that would cause data loss.',\n );\n }\n } catch (saveError) {\n // Auto-save failed - report both errors\n const saveErrorMsg = saveError instanceof Error ? saveError.message : String(saveError);\n console.log('');\n this.output.error(`Auto-save to outbox also failed: ${saveErrorMsg}`);\n console.log('');\n console.log(\" Run 'tbd save --outbox' manually, or 'tbd doctor' to diagnose.\");\n }\n }\n\n /**\n * Import pending issues from outbox after a successful push.\n * Uses two-phase sync: import → commit → push → clear.\n * Only clears the outbox if all steps succeed.\n *\n * @param syncBranch - The sync branch name\n * @param remote - The remote name\n */\n private async maybeImportOutbox(syncBranch: string, remote: string): Promise<void> {\n // Check if outbox exists and has issues\n if (!(await workspaceExists(this.tbdRoot, 'outbox'))) {\n return; // No outbox - nothing to import\n }\n\n const outboxPath = join(this.tbdRoot, '.tbd', 'workspaces', 'outbox');\n let outboxIssues: Awaited<ReturnType<typeof listIssues>> = [];\n try {\n outboxIssues = await listIssues(outboxPath);\n } catch {\n return; // Can't read outbox - skip silently\n }\n\n if (outboxIssues.length === 0) {\n return; // Outbox is empty - nothing to import\n }\n\n try {\n // Step 1: Import from outbox (don't clear yet)\n const importResult = await importFromWorkspace(this.tbdRoot, this.dataSyncDir, {\n outbox: true,\n clearOnSuccess: false, // We'll clear manually after push succeeds\n });\n\n if (importResult.imported === 0) {\n return; // Nothing was actually imported\n }\n\n // Step 2: Commit the imported issues\n const committedTallies = await this.commitWorktreeChanges();\n if (committedTallies.new + committedTallies.updated + committedTallies.deleted === 0) {\n // Nothing to commit - issues were already in worktree\n // This can happen if outbox data was identical to worktree\n // Still clear the outbox since the data is already synced\n await deleteWorkspace(this.tbdRoot, 'outbox');\n return;\n }\n\n // Step 3: Push the imported issues\n const pushResult = await this.doPushWithRetry(syncBranch, remote);\n if (!pushResult.success) {\n // Secondary push failed - DON'T clear outbox\n // The issues are now in the worktree, so they'll be synced next time\n this.output.warn(\n `Could not push imported outbox issues: ${pushResult.error ?? 'unknown error'}`,\n );\n console.log(' Outbox preserved. Issues are in worktree and will sync next time.');\n return;\n }\n\n // Step 4: All succeeded - now clear the outbox\n await deleteWorkspace(this.tbdRoot, 'outbox');\n\n this.output.success(`Imported ${importResult.imported} issue(s) from outbox (also synced)`);\n } catch (err) {\n // Don't fail the whole sync - primary sync already succeeded\n const errMsg = err instanceof Error ? err.message : String(err);\n this.output.warn(`Could not sync outbox: ${errMsg}`);\n console.log(' Outbox preserved. Will retry on next sync.');\n }\n }\n}\n\nexport const syncCommand = new Command('sync')\n .description('Synchronize issues and docs (both by default)')\n .option('--issues', 'Sync only issues (not docs)')\n .option('--docs', 'Sync only docs (not issues)')\n .option('--push', 'Push local issue changes only')\n .option('--pull', 'Pull remote issue changes only')\n .option('--status', 'Show sync status')\n .option('--force', 'Force sync (overwrite conflicts)')\n .option('--fix', 'Attempt to repair unhealthy worktree before syncing')\n .option('--no-auto-save', 'Skip auto-save to outbox on permanent failure')\n .option('--no-outbox', 'Skip auto-import from outbox on success')\n .action(async (options, command) => {\n const handler = new SyncHandler(command);\n await handler.run(options);\n });\n","/**\n * Human-readable formatting utilities for tbd CLI.\n *\n * Uses sindresorhus libraries for consistent, well-tested formatting:\n * - pretty-bytes: Byte sizes (1337 -> \"1.34 kB\")\n * - pretty-ms: Durations (3600000 -> \"1h\")\n */\n\nimport prettyBytes from 'pretty-bytes';\nimport prettyMs from 'pretty-ms';\n\nimport { CHARS_PER_TOKEN } from './paths.js';\n\n// Re-export for direct use when needed\nexport { prettyBytes, prettyMs };\n\n// =============================================================================\n// Token Estimation\n// =============================================================================\n\n/**\n * Estimate token count from text content.\n * Uses CHARS_PER_TOKEN (~3.5) for markdown/code content.\n */\nexport function estimateTokens(text: string): number {\n return Math.ceil(text.length / CHARS_PER_TOKEN);\n}\n\n/**\n * Format token count: \"~1.2k tok\" or \"~450 tok\"\n */\nexport function formatTokens(tokens: number): string {\n if (tokens >= 1000) {\n return `~${(tokens / 1000).toFixed(1)}k tok`;\n }\n return `~${tokens} tok`;\n}\n\n// =============================================================================\n// Size Formatting\n// =============================================================================\n\n/**\n * Format doc size for list display: \"(1.8 kB, ~450 tok)\"\n */\nexport function formatDocSize(sizeBytes: number, approxTokens: number): string {\n return `(${prettyBytes(sizeBytes)}, ${formatTokens(approxTokens)})`;\n}\n\n// =============================================================================\n// Time Formatting\n// =============================================================================\n\n/**\n * Format relative time from Date: \"2d ago\", \"3h ago\", \"5m ago\"\n * Uses compact format for concise display.\n */\nexport function formatTimeAgo(date: Date): string {\n const ms = Date.now() - date.getTime();\n if (ms < 0) return 'just now';\n if (ms < 60000) return 'just now'; // Less than 1 minute\n return `${prettyMs(ms, { compact: true })} ago`;\n}\n\n/**\n * Format relative time from ISO timestamp string.\n * Returns null if timestamp is invalid.\n */\nexport function formatTimestampAgo(timestamp: string | undefined | null): string | null {\n if (!timestamp) return null;\n const date = new Date(timestamp);\n if (isNaN(date.getTime())) return null;\n return formatTimeAgo(date);\n}\n\n/**\n * Format duration in milliseconds: \"2h 15m\", \"3d 4h\"\n * Uses verbose format for clarity.\n */\nexport function formatDuration(ms: number): string {\n return prettyMs(ms, { verbose: true });\n}\n\n/**\n * Format duration compactly: \"2h\", \"3d\"\n * Uses compact format for tables and tight spaces.\n */\nexport function formatDurationCompact(ms: number): string {\n return prettyMs(ms, { compact: true });\n}\n","/**\n * `tbd search` - Search issues.\n *\n * See: tbd-design.md §4.8 Search Commands\n */\n\nimport { Command } from 'commander';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { applyLimit } from '../lib/limit-utils.js';\nimport { loadDataContext, type TbdDataContext } from '../lib/data-context.js';\nimport { requireInit, NotInitializedError, ValidationError } from '../lib/errors.js';\nimport { listIssues } from '../../file/storage.js';\nimport { readLocalState, updateLocalState } from '../../file/config.js';\nimport { IssueStatus } from '../../lib/schemas.js';\nimport type { Issue, IssueStatusType } from '../../lib/types.js';\nimport { formatTimestampAgo } from '../../lib/format-utils.js';\nimport { now } from '../../utils/time-utils.js';\nimport { formatDisplayId, formatDebugId } from '../../lib/ids.js';\nimport { formatIssueCompact, type IssueForDisplay } from '../lib/issue-format.js';\n\n// Staleness threshold for worktree (5 minutes)\nconst STALE_THRESHOLD_MS = 5 * 60 * 1000;\n\ninterface SearchOptions {\n status?: string;\n limit?: string;\n noRefresh?: boolean;\n field?: string;\n caseSensitive?: boolean;\n}\n\ninterface SearchResult {\n issue: Issue;\n matchField: string;\n matchText: string;\n}\n\nclass SearchHandler extends BaseCommand {\n async run(query: string, options: SearchOptions): Promise<void> {\n const tbdRoot = await requireInit();\n\n // Check worktree staleness and auto-refresh if needed\n if (!options.noRefresh) {\n const state = await readLocalState(tbdRoot);\n const lastSync = state.last_sync_at ? new Date(state.last_sync_at).getTime() : 0;\n const stale = Date.now() - lastSync > STALE_THRESHOLD_MS;\n if (stale) {\n const lastSyncAgo = formatTimestampAgo(state.last_sync_at);\n const staleInfo = lastSyncAgo ? ` (last synced ${lastSyncAgo})` : '';\n this.output.info(`Refreshing worktree${staleInfo}...`);\n await updateLocalState(tbdRoot, { last_sync_at: now() });\n }\n }\n\n // Load data context and issues\n let issues: Issue[];\n let dataCtx: TbdDataContext;\n try {\n dataCtx = await loadDataContext(tbdRoot);\n issues = await listIssues(dataCtx.dataSyncDir);\n } catch {\n throw new NotInitializedError('No issue store found. Run `tbd init` first.');\n }\n\n // Parse status filter\n let statusFilter: IssueStatusType | null = null;\n if (options.status) {\n const result = IssueStatus.safeParse(options.status);\n if (!result.success) {\n throw new ValidationError(`Invalid status: ${options.status}`);\n }\n statusFilter = result.data;\n }\n\n // Search (case-insensitive by default)\n const caseSensitive = options.caseSensitive ?? false;\n const queryForMatch = caseSensitive ? query : query.toLowerCase();\n let results: SearchResult[] = [];\n\n for (const issue of issues) {\n // Apply status filter\n if (statusFilter && issue.status !== statusFilter) continue;\n\n // Determine which fields to search\n const searchFields = options.field\n ? [options.field]\n : ['title', 'description', 'notes', 'labels'];\n\n for (const field of searchFields) {\n const match = this.searchField(issue, field, queryForMatch, caseSensitive);\n if (match) {\n results.push(match);\n break; // Only one match per issue\n }\n }\n }\n\n // Apply limit\n results = applyLimit(results, options.limit);\n\n const { mapping, prefix } = dataCtx;\n const showDebug = this.ctx.debug;\n\n // Format output\n const output = results.map((r) => ({\n id: showDebug\n ? formatDebugId(r.issue.id, mapping, prefix)\n : formatDisplayId(r.issue.id, mapping, prefix),\n priority: r.issue.priority,\n status: r.issue.status,\n kind: r.issue.kind,\n title: r.issue.title,\n matchField: r.matchField,\n match: r.matchText,\n }));\n\n this.output.data(output, () => {\n if (output.length === 0) {\n this.output.info(`No issues matching \"${query}\"`);\n return;\n }\n\n const colors = this.output.getColors();\n console.log(`Found ${output.length} result${output.length === 1 ? '' : 's'}:\\n`);\n for (const result of output) {\n console.log(formatIssueCompact(result as IssueForDisplay, colors));\n console.log(` ${colors.dim(`[${result.matchField}]`)} ${result.match}`);\n console.log('');\n }\n });\n }\n\n private searchField(\n issue: Issue,\n field: string,\n query: string,\n caseSensitive: boolean,\n ): SearchResult | null {\n switch (field) {\n case 'title': {\n const text = caseSensitive ? issue.title : issue.title.toLowerCase();\n if (text.includes(query)) {\n return { issue, matchField: 'title', matchText: issue.title };\n }\n break;\n }\n case 'description': {\n if (issue.description) {\n const text = caseSensitive ? issue.description : issue.description.toLowerCase();\n if (text.includes(query)) {\n const snippet = this.extractSnippet(issue.description, query, caseSensitive);\n return { issue, matchField: 'description', matchText: snippet };\n }\n }\n break;\n }\n case 'notes': {\n if (issue.notes) {\n const text = caseSensitive ? issue.notes : issue.notes.toLowerCase();\n if (text.includes(query)) {\n const snippet = this.extractSnippet(issue.notes, query, caseSensitive);\n return { issue, matchField: 'notes', matchText: snippet };\n }\n }\n break;\n }\n case 'labels': {\n for (const label of issue.labels) {\n const text = caseSensitive ? label : label.toLowerCase();\n if (text.includes(query)) {\n return { issue, matchField: 'labels', matchText: `label: ${label}` };\n }\n }\n break;\n }\n }\n return null;\n }\n\n private extractSnippet(text: string, query: string, caseSensitive: boolean): string {\n const searchText = caseSensitive ? text : text.toLowerCase();\n const index = searchText.indexOf(query);\n if (index === -1) return text.slice(0, 60);\n\n // Extract snippet around match\n const start = Math.max(0, index - 20);\n const end = Math.min(text.length, index + query.length + 40);\n let snippet = text.slice(start, end);\n\n if (start > 0) snippet = '...' + snippet;\n if (end < text.length) snippet = snippet + '...';\n\n return snippet.replace(/\\n/g, ' ');\n }\n}\n\nexport const searchCommand = new Command('search')\n .description('Search issues by text')\n .argument('<query>', 'Search query')\n .option('--status <status>', 'Filter by status')\n .option('--field <field>', 'Search specific field (title, description, notes, labels)')\n .option('--limit <n>', 'Limit results')\n .option('--no-refresh', 'Skip worktree refresh')\n .option('--case-sensitive', 'Case-sensitive search')\n .action(async (query, options, command) => {\n const handler = new SearchHandler(command);\n await handler.run(query, options);\n });\n","/**\n * Shared section rendering functions for CLI output.\n *\n * These functions ensure identical output formatting across commands that\n * share sections (status, doctor, stats). When doctor subsumes status,\n * it calls the same rendering functions to guarantee consistency.\n *\n * See: plan-2026-01-29-terminal-design-system.md\n */\n\nimport type { createColors } from './output.js';\nimport { ICONS, formatHeading } from './output.js';\nimport { MIN_GIT_VERSION } from '../../file/git.js';\n\n/**\n * Repository section data for rendering.\n */\nexport interface RepositorySectionData {\n version: string;\n workingDirectory: string;\n initialized: boolean;\n gitRepository: boolean;\n gitBranch: string | null;\n gitVersion: string | null;\n gitVersionSupported: boolean;\n}\n\n/**\n * Config section data for rendering.\n */\nexport interface ConfigSectionData {\n syncBranch: string | null;\n remote: string | null;\n displayPrefix: string | null;\n}\n\n/**\n * Integration check result for rendering.\n */\nexport interface IntegrationCheck {\n name: string;\n installed: boolean;\n path: string;\n}\n\n/**\n * Statistics section data for rendering.\n */\nexport interface StatisticsSectionData {\n ready: number;\n inProgress: number;\n blocked: number;\n open: number;\n total: number;\n /** Number of issues on remote branch (when local is empty but remote has data) */\n remoteTotal?: number | null;\n}\n\n/**\n * Render the REPOSITORY section.\n *\n * Used by: status, doctor\n *\n * Shows:\n * - tbd version\n * - Repository path\n * - Initialization status\n * - Git repository status and branch\n * - Git version (with support warning if needed)\n *\n * @param data - Repository data to render\n * @param colors - Color functions\n * @param options - Rendering options\n */\nexport function renderRepositorySection(\n data: RepositorySectionData,\n colors: ReturnType<typeof createColors>,\n options?: { showHeading?: boolean },\n): void {\n // Show heading if requested (doctor uses heading, status doesn't)\n if (options?.showHeading) {\n console.log(colors.bold(formatHeading('Repository')));\n }\n\n // Version line\n console.log(`${colors.bold('tbd')} v${data.version}`);\n\n // Repository path\n console.log(`Repository: ${data.workingDirectory}`);\n\n // Initialization status\n if (data.initialized) {\n console.log(` ${colors.success(ICONS.SUCCESS)} Initialized (.tbd/)`);\n } else {\n console.log(` ${colors.error(ICONS.ERROR)} Not initialized`);\n }\n\n // Git repository status\n if (data.gitRepository) {\n const branchInfo = data.gitBranch ? ` (${data.gitBranch})` : '';\n console.log(` ${colors.success(ICONS.SUCCESS)} Git repository${branchInfo}`);\n\n // Git version\n if (data.gitVersion) {\n const versionIcon = data.gitVersionSupported\n ? colors.success(ICONS.SUCCESS)\n : colors.warn(ICONS.WARN);\n const versionNote = data.gitVersionSupported\n ? ''\n : ` ${colors.dim(`(requires ${MIN_GIT_VERSION}+)`)}`;\n console.log(` ${versionIcon} Git ${data.gitVersion}${versionNote}`);\n }\n } else {\n console.log(` ${colors.error(ICONS.ERROR)} Git repository not found`);\n }\n}\n\n/**\n * Render the CONFIG section (sync branch, remote, prefix).\n *\n * Used by: status, doctor\n *\n * Shows key-value pairs with dim keys.\n *\n * @param data - Config data to render\n * @param colors - Color functions\n */\nexport function renderConfigSection(\n data: ConfigSectionData,\n colors: ReturnType<typeof createColors>,\n): void {\n if (!data.syncBranch && !data.remote && !data.displayPrefix) {\n return;\n }\n\n console.log('');\n\n if (data.syncBranch) {\n console.log(`${colors.dim('Sync branch:')} ${data.syncBranch}`);\n }\n if (data.remote) {\n console.log(`${colors.dim('Remote:')} ${data.remote}`);\n }\n if (data.displayPrefix) {\n console.log(`${colors.dim('ID prefix:')} ${data.displayPrefix}-`);\n }\n}\n\n/**\n * Render the INTEGRATIONS section.\n *\n * Used by: status, doctor\n *\n * Shows diagnostic lines for each integration check.\n *\n * @param checks - Array of integration checks\n * @param colors - Color functions\n * @returns Whether any integrations are missing (for follow-up suggestions)\n */\nexport function renderIntegrationsSection(\n checks: IntegrationCheck[],\n colors: ReturnType<typeof createColors>,\n): boolean {\n console.log('');\n console.log(colors.bold(formatHeading('Integrations')));\n\n let hasMissing = false;\n\n for (const check of checks) {\n const icon = check.installed ? colors.success(ICONS.SUCCESS) : colors.dim(ICONS.ERROR);\n const pathDim = colors.dim(`(${check.path})`);\n console.log(` ${icon} ${check.name} ${pathDim}`);\n\n if (!check.installed) {\n hasMissing = true;\n }\n }\n\n return hasMissing;\n}\n\n/**\n * Render the STATISTICS section.\n *\n * Used by: stats, doctor\n *\n * Shows aligned statistic block with labels and values.\n * When local is 0 but remote has data, shows a hint to run tbd sync.\n *\n * @param data - Statistics data to render\n * @param colors - Color functions\n * @param options - Rendering options\n */\nexport function renderStatisticsSection(\n data: StatisticsSectionData,\n colors: ReturnType<typeof createColors>,\n options?: { showHeading?: boolean },\n): void {\n if (options?.showHeading !== false) {\n console.log('');\n console.log(colors.bold(formatHeading('Statistics')));\n }\n\n // Calculate padding for alignment\n const labels = ['Ready', 'In progress', 'Blocked', 'Open', 'Total'];\n const maxLabelLen = Math.max(...labels.map((l) => l.length));\n\n const formatLine = (label: string, value: number, suffix?: string): string => {\n const padding = ' '.repeat(maxLabelLen - label.length + 1);\n const suffixStr = suffix ? ` ${colors.dim(suffix)}` : '';\n return ` ${label}:${padding}${value}${suffixStr}`;\n };\n\n console.log(formatLine('Ready', data.ready));\n console.log(formatLine('In progress', data.inProgress));\n console.log(formatLine('Blocked', data.blocked));\n console.log(formatLine('Open', data.open));\n\n // Show remote count hint when local is 0 but remote has data\n if (data.total === 0 && data.remoteTotal && data.remoteTotal > 0) {\n console.log(\n formatLine('Total', data.total, `(${data.remoteTotal} on remote - run 'tbd sync')`),\n );\n } else {\n console.log(formatLine('Total', data.total));\n }\n}\n\n/**\n * Render a warning block about beads coexistence.\n *\n * Used by: status\n *\n * @param colors - Color functions\n */\nexport function renderBeadsWarning(colors: ReturnType<typeof createColors>): void {\n console.log('');\n console.log(`${colors.warn(ICONS.WARN)} Beads directory detected alongside tbd`);\n console.log('This may cause confusion for AI agents.');\n console.log(`Run ${colors.bold('tbd setup beads --disable')} for migration options`);\n}\n\n/**\n * Render worktree status line.\n *\n * Used by: status\n *\n * @param path - Worktree path\n * @param healthy - Whether worktree is healthy\n * @param colors - Color functions\n */\nexport function renderWorktreeStatus(\n path: string,\n healthy: boolean,\n colors: ReturnType<typeof createColors>,\n): void {\n console.log('');\n if (healthy) {\n console.log(`${colors.dim('Worktree:')} ${path} (healthy)`);\n } else {\n console.log(`${colors.warn('Worktree:')} ${path} (${colors.error('unhealthy')})`);\n console.log(' Run: tbd doctor --fix');\n }\n}\n\n/**\n * Render footer with command suggestions.\n *\n * Used by: status, doctor, stats\n *\n * @param suggestions - Array of {command, description} pairs\n * @param colors - Color functions\n */\nexport function renderFooter(\n suggestions: { command: string; description: string }[],\n colors: ReturnType<typeof createColors>,\n): void {\n console.log('');\n\n if (suggestions.length === 0) {\n return;\n }\n\n const parts = suggestions.map((s) => `${colors.bold(`'${s.command}'`)} for ${s.description}`);\n\n if (parts.length === 1) {\n console.log(`Use ${parts[0]}.`);\n } else {\n console.log(`Use ${parts.join(', ')}.`);\n }\n}\n","/**\n * Centralized path constants and utilities for coding agent integrations.\n *\n * IMPORTANT: All tbd integration files (hooks, settings, skills) are installed\n * to PROJECT-LOCAL directories (.claude/, AGENTS.md) ONLY. We do NOT install to\n * global/user directories (~/.claude/).\n *\n * This file defines all path constants in one place to:\n * 1. Ensure consistency across the codebase\n * 2. Make the project-local policy explicit and auditable\n * 3. Simplify future changes to path conventions\n */\n\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\n\n// =============================================================================\n// Claude Code Integration Paths (project-local)\n// =============================================================================\n\n/**\n * Relative path to Claude Code settings file from project root.\n * This is where hooks are configured.\n */\nexport const CLAUDE_SETTINGS_REL = '.claude/settings.json';\n\n/**\n * Relative path to Claude Code directory from project root.\n */\nexport const CLAUDE_DIR_REL = '.claude';\n\n/**\n * Relative path to Claude Code scripts directory from project root.\n */\nexport const CLAUDE_SCRIPTS_DIR_REL = '.claude/scripts';\n\n/**\n * Relative path to Claude Code hooks directory from project root.\n */\nexport const CLAUDE_HOOKS_DIR_REL = '.claude/hooks';\n\n/**\n * Relative path to tbd skill file from project root.\n */\nexport const CLAUDE_SKILL_REL = '.claude/skills/tbd/SKILL.md';\n\n/**\n * Relative path to tbd session script from project root.\n */\nexport const TBD_SESSION_SCRIPT_REL = '.claude/scripts/tbd-session.sh';\n\n/**\n * Relative path to tbd closing reminder hook script from project root.\n */\nexport const TBD_CLOSING_REMINDER_REL = '.claude/hooks/tbd-closing-reminder.sh';\n\n/**\n * Relative path to gh CLI ensure script from project root.\n */\nexport const GH_CLI_SCRIPT_REL = '.claude/scripts/ensure-gh-cli.sh';\n\n// =============================================================================\n// Codex / AGENTS.md Integration Paths (project-local)\n// =============================================================================\n\n/**\n * Relative path to AGENTS.md file from project root.\n * Used by Codex, Factory.ai, Cursor (v1.6+), and other compatible tools.\n */\nexport const AGENTS_MD_REL = 'AGENTS.md';\n\n// =============================================================================\n// Global Paths (for detection only - NOT for installation)\n// =============================================================================\n\n/**\n * Global Claude Code directory in user's home.\n * Used ONLY for detecting if Claude Code is installed (for agent detection).\n * All installations are project-local.\n */\nexport const GLOBAL_CLAUDE_DIR = join(homedir(), '.claude');\n\n// =============================================================================\n// Path Resolution Utilities\n// =============================================================================\n\n/**\n * Get project-local Claude Code paths.\n *\n * @param projectRoot - The project root directory (containing .tbd/)\n * @returns Object with all Claude Code paths resolved to absolute paths\n */\nexport function getClaudePaths(projectRoot: string) {\n return {\n /** .claude/ directory */\n dir: join(projectRoot, CLAUDE_DIR_REL),\n /** .claude/settings.json */\n settings: join(projectRoot, CLAUDE_SETTINGS_REL),\n /** .claude/scripts/ directory */\n scriptsDir: join(projectRoot, CLAUDE_SCRIPTS_DIR_REL),\n /** .claude/hooks/ directory */\n hooksDir: join(projectRoot, CLAUDE_HOOKS_DIR_REL),\n /** .claude/skills/tbd/SKILL.md */\n skill: join(projectRoot, CLAUDE_SKILL_REL),\n /** .claude/scripts/tbd-session.sh */\n sessionScript: join(projectRoot, TBD_SESSION_SCRIPT_REL),\n /** .claude/hooks/tbd-closing-reminder.sh */\n closingReminder: join(projectRoot, TBD_CLOSING_REMINDER_REL),\n /** .claude/scripts/ensure-gh-cli.sh */\n ghCliScript: join(projectRoot, GH_CLI_SCRIPT_REL),\n };\n}\n\n/**\n * Get project-local Codex/AGENTS.md path.\n *\n * @param projectRoot - The project root directory\n * @returns Absolute path to AGENTS.md\n */\nexport function getAgentsMdPath(projectRoot: string): string {\n return join(projectRoot, AGENTS_MD_REL);\n}\n\n// =============================================================================\n// Display Paths (for user-facing output)\n// =============================================================================\n\n/**\n * Display path for Claude Code settings in status/doctor output.\n */\nexport const CLAUDE_SETTINGS_DISPLAY = './.claude/settings.json';\n\n/**\n * Display path for AGENTS.md in status/doctor output.\n */\nexport const AGENTS_MD_DISPLAY = './AGENTS.md';\n","/**\n * `tbd status` - Show repository status and orientation.\n *\n * This is the \"orientation\" command—like `git status`, it works regardless of\n * initialization state and helps users understand where they are.\n *\n * Unlike Beads where `bd status` is just an alias for `bd stats`, `tbd status`\n * is a distinct command that provides system orientation, not issue statistics.\n *\n * See: tbd-design.md §4.9 Status\n */\n\nimport { Command } from 'commander';\nimport { access, readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nimport { VERSION } from '../lib/version.js';\nimport { BaseCommand } from '../lib/base-command.js';\nimport { ICONS } from '../lib/output.js';\nimport {\n renderRepositorySection,\n renderConfigSection,\n renderIntegrationsSection,\n renderBeadsWarning,\n renderWorktreeStatus,\n renderFooter,\n type IntegrationCheck,\n} from '../lib/sections.js';\nimport { readConfig, findTbdRoot } from '../../file/config.js';\nimport { WORKTREE_DIR } from '../../lib/paths.js';\nimport {\n getClaudePaths,\n getAgentsMdPath,\n CLAUDE_SETTINGS_DISPLAY,\n AGENTS_MD_DISPLAY,\n} from '../../lib/integration-paths.js';\nimport {\n git,\n getCurrentBranch,\n checkWorktreeHealth,\n checkGitVersion,\n findGitRoot,\n MIN_GIT_VERSION,\n} from '../../file/git.js';\nimport { listWorkspaces } from '../../file/workspace.js';\n\ninterface StatusData {\n initialized: boolean;\n tbd_version: string;\n working_directory: string;\n\n // Git info (always available)\n git_repository: boolean;\n git_branch: string | null;\n git_version: string | null;\n git_version_supported: boolean;\n\n // Beads detection (pre-init only)\n beads_detected: boolean;\n beads_issue_count: number | null;\n\n // Post-init only\n sync_branch: string | null;\n remote: string | null;\n display_prefix: string | null;\n worktree_path: string | null;\n worktree_healthy: boolean | null;\n workspaces: string[];\n\n // Integrations\n integrations: {\n claude_code: boolean;\n claude_code_path: string;\n codex: boolean;\n codex_path: string;\n };\n}\n\nclass StatusHandler extends BaseCommand {\n async run(): Promise<void> {\n const cwd = process.cwd();\n\n // Find tbd root (may be in parent directory)\n const tbdRoot = await findTbdRoot(cwd);\n\n // Find git root for checking integrations (.claude/, .beads/ are at git root)\n const gitRoot = await findGitRoot(cwd);\n\n // Use tbdRoot if available, otherwise gitRoot, otherwise cwd\n // .tbd/, .claude/, .beads/ are all at the project root (adjacent to .git/)\n const projectRoot = tbdRoot ?? gitRoot ?? cwd;\n\n const statusData: StatusData = {\n initialized: tbdRoot !== null,\n tbd_version: VERSION,\n working_directory: cwd,\n git_repository: false,\n git_branch: null,\n git_version: null,\n git_version_supported: false,\n beads_detected: false,\n beads_issue_count: null,\n sync_branch: null,\n remote: null,\n display_prefix: null,\n worktree_path: null,\n worktree_healthy: null,\n workspaces: [],\n integrations: {\n claude_code: false,\n claude_code_path: CLAUDE_SETTINGS_DISPLAY,\n codex: false,\n codex_path: AGENTS_MD_DISPLAY,\n },\n };\n\n // Check git repository\n const gitInfo = await this.checkGitRepo();\n statusData.git_repository = gitInfo.isRepo;\n statusData.git_branch = gitInfo.branch;\n\n // Check git version (only if git is available)\n if (gitInfo.isRepo) {\n try {\n const { version, supported } = await checkGitVersion();\n statusData.git_version = `${version.major}.${version.minor}.${version.patch}`;\n statusData.git_version_supported = supported;\n } catch {\n // Git version check failed - leave as null/false\n }\n }\n\n // Check for beads (at project root, not cwd)\n const beadsInfo = await this.checkBeads(projectRoot);\n statusData.beads_detected = beadsInfo.detected;\n statusData.beads_issue_count = beadsInfo.issueCount;\n\n // Check integrations at project root (not cwd)\n statusData.integrations = await this.checkIntegrations(projectRoot);\n\n if (statusData.initialized && tbdRoot) {\n // Load config and issue info\n await this.loadPostInitInfo(tbdRoot, statusData);\n }\n\n this.output.data(statusData, () => {\n this.renderText(statusData);\n });\n }\n\n private async checkGitRepo(): Promise<{ isRepo: boolean; branch: string | null }> {\n try {\n const branch = await getCurrentBranch();\n return { isRepo: true, branch };\n } catch {\n // getCurrentBranch may fail in repos with no commits\n // Fall back to checking if we're in a git repo using git rev-parse --git-dir\n try {\n await git('rev-parse', '--git-dir');\n // We're in a git repo but can't get branch (maybe no commits)\n return { isRepo: true, branch: null };\n } catch {\n return { isRepo: false, branch: null };\n }\n }\n }\n\n private async checkBeads(\n projectRoot: string,\n ): Promise<{ detected: boolean; issueCount: number | null }> {\n const beadsDir = join(projectRoot, '.beads');\n try {\n await access(beadsDir);\n // Count issues in beads\n const issuesFile = join(beadsDir, 'issues.jsonl');\n try {\n const content = await readFile(issuesFile, 'utf-8');\n const lines = content\n .trim()\n .split('\\n')\n .filter((l) => l.trim());\n return { detected: true, issueCount: lines.length };\n } catch {\n return { detected: true, issueCount: null };\n }\n } catch {\n return { detected: false, issueCount: null };\n }\n }\n\n private async checkIntegrations(projectRoot: string): Promise<StatusData['integrations']> {\n // All integrations use project-local paths (relative to git/project root)\n const claudePaths = getClaudePaths(projectRoot);\n const agentsPath = getAgentsMdPath(projectRoot);\n\n const result: StatusData['integrations'] = {\n claude_code: false,\n claude_code_path: CLAUDE_SETTINGS_DISPLAY,\n codex: false,\n codex_path: AGENTS_MD_DISPLAY,\n };\n\n // Check Claude Code hooks in project-local settings\n try {\n await access(claudePaths.settings);\n const content = await readFile(claudePaths.settings, 'utf-8');\n const settings = JSON.parse(content) as Record<string, unknown>;\n const hooks = settings.hooks as Record<string, unknown> | undefined;\n if (hooks) {\n const sessionStart = hooks.SessionStart as { hooks?: { command?: string }[] }[];\n result.claude_code =\n sessionStart?.some((h) => h.hooks?.some((hook) => hook.command?.includes('tbd'))) ??\n false;\n }\n } catch {\n // Not installed\n }\n\n // Check Codex AGENTS.md (also used by Cursor since v1.6)\n try {\n await access(agentsPath);\n const content = await readFile(agentsPath, 'utf-8');\n result.codex = content.includes('BEGIN TBD INTEGRATION');\n } catch {\n // Not installed\n }\n\n return result;\n }\n\n private async loadPostInitInfo(cwd: string, data: StatusData): Promise<void> {\n // Load config\n try {\n const config = await readConfig(cwd);\n data.sync_branch = config.sync.branch;\n data.remote = config.sync.remote;\n data.display_prefix = config.display.id_prefix;\n } catch {\n // Config read failed\n }\n\n // Check worktree health\n const worktreePath = join(cwd, WORKTREE_DIR);\n const worktreeHealth = await checkWorktreeHealth(cwd);\n data.worktree_path = worktreePath;\n data.worktree_healthy = worktreeHealth.valid;\n\n // Check workspaces\n try {\n data.workspaces = await listWorkspaces(cwd);\n } catch {\n // Workspace check failed - leave as empty\n }\n }\n\n private renderText(data: StatusData): void {\n const colors = this.output.getColors();\n\n if (!data.initialized) {\n // Pre-init output - unique to status, not shared with doctor\n this.renderPreInitText(data, colors);\n return;\n }\n\n // Post-init output - uses shared section renderers\n // REPOSITORY section (shared with doctor)\n renderRepositorySection(\n {\n version: data.tbd_version,\n workingDirectory: data.working_directory,\n initialized: data.initialized,\n gitRepository: data.git_repository,\n gitBranch: data.git_branch,\n gitVersion: data.git_version,\n gitVersionSupported: data.git_version_supported,\n },\n colors,\n );\n\n // Beads coexistence warning\n if (data.beads_detected) {\n renderBeadsWarning(colors);\n }\n\n // CONFIG section (shared with doctor)\n renderConfigSection(\n {\n syncBranch: data.sync_branch,\n remote: data.remote,\n displayPrefix: data.display_prefix,\n },\n colors,\n );\n\n // INTEGRATIONS section (shared with doctor)\n const integrationChecks: IntegrationCheck[] = [\n {\n name: 'Claude Code hooks',\n installed: data.integrations.claude_code,\n path: data.integrations.claude_code_path,\n },\n {\n name: 'Codex AGENTS.md',\n installed: data.integrations.codex,\n path: data.integrations.codex_path,\n },\n ];\n const hasMissingIntegrations = renderIntegrationsSection(integrationChecks, colors);\n\n if (hasMissingIntegrations) {\n console.log('');\n console.log(`Run ${colors.bold('tbd setup auto')} to configure detected agents`);\n }\n\n // Worktree health\n if (data.worktree_healthy !== null && data.worktree_path) {\n renderWorktreeStatus(data.worktree_path, data.worktree_healthy, colors);\n }\n\n // Workspaces (only show if there are any)\n if (data.workspaces.length > 0) {\n console.log('');\n console.log(colors.bold('WORKSPACES'));\n for (const ws of data.workspaces) {\n console.log(` ${ws}`);\n }\n }\n\n // Footer (shared format)\n renderFooter(\n [\n { command: 'tbd stats', description: 'issue statistics' },\n { command: 'tbd doctor', description: 'health checks' },\n ],\n colors,\n );\n }\n\n /**\n * Render pre-init text (unique to status command).\n * This is not shared with doctor since doctor requires initialization.\n */\n private renderPreInitText(\n data: StatusData,\n colors: ReturnType<typeof this.output.getColors>,\n ): void {\n console.log(`${colors.warn('Not a tbd repository.')}`);\n console.log('');\n console.log('Detected:');\n\n // Git status\n if (data.git_repository) {\n const branchInfo = data.git_branch ? ` (${data.git_branch} branch)` : '';\n console.log(` ${colors.success(ICONS.SUCCESS)} Git repository${branchInfo}`);\n // Show git version\n if (data.git_version) {\n const versionStatus = data.git_version_supported\n ? colors.success(ICONS.SUCCESS)\n : colors.warn(ICONS.WARN);\n const versionNote = data.git_version_supported\n ? ''\n : ` ${colors.dim(`(requires ${MIN_GIT_VERSION}+)`)}`;\n console.log(` ${versionStatus} Git ${data.git_version}${versionNote}`);\n }\n } else {\n console.log(` ${colors.error(ICONS.ERROR)} Git repository not found`);\n }\n\n // Beads status\n if (data.beads_detected) {\n const countInfo =\n data.beads_issue_count !== null ? ` (.beads/ with ${data.beads_issue_count} issues)` : '';\n console.log(` ${colors.success(ICONS.SUCCESS)} Beads repository${countInfo}`);\n } else {\n console.log(` ${colors.dim(ICONS.ERROR)} Beads not detected`);\n }\n\n // tbd status\n console.log(` ${colors.error(ICONS.ERROR)} tbd not initialized`);\n\n console.log('');\n console.log('To get started:');\n if (data.beads_detected) {\n console.log(\n ` ${colors.bold('tbd setup --auto')} # Migrate from Beads (recommended)`,\n );\n } else {\n console.log(\n ` ${colors.bold('tbd setup --auto --prefix=<name>')} # Full setup with prefix`,\n );\n }\n console.log(` ${colors.bold('tbd init --prefix=X')} # Surgical init only`);\n }\n}\n\nexport const statusCommand = new Command('status')\n .description('Show repository status and orientation')\n .action(async (_options, command) => {\n const handler = new StatusHandler(command);\n await handler.run();\n });\n","/**\n * `tbd stats` - Show repository statistics.\n *\n * See: tbd-design.md §4.9 Stats\n */\n\nimport { Command } from 'commander';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { requireInit, NotInitializedError } from '../lib/errors.js';\nimport { listIssues } from '../../file/storage.js';\nimport type { Issue, IssueStatusType, IssueKindType } from '../../lib/types.js';\nimport { resolveDataSyncDir } from '../../lib/paths.js';\nimport { formatPriority } from '../../lib/priority.js';\nimport { renderFooter } from '../lib/sections.js';\nimport { getStatusIcon, getStatusColor } from '../../lib/status.js';\n\n/**\n * Active statuses (non-closed).\n */\nconst ACTIVE_STATUSES: IssueStatusType[] = ['open', 'in_progress', 'blocked', 'deferred'];\n\n/**\n * All statuses in display order.\n */\nconst STATUS_ORDER: IssueStatusType[] = ['open', 'in_progress', 'blocked', 'deferred', 'closed'];\n\n/**\n * All kinds in display order.\n */\nconst KIND_ORDER: IssueKindType[] = ['bug', 'feature', 'task', 'epic', 'chore'];\n\n/**\n * Priority labels for display.\n */\nconst PRIORITY_LABELS = ['Critical', 'High', 'Medium', 'Low', 'Lowest'];\n\nclass StatsHandler extends BaseCommand {\n async run(): Promise<void> {\n const tbdRoot = await requireInit();\n\n // Load all issues\n let issues: Issue[];\n try {\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n issues = await listIssues(dataSyncDir);\n } catch {\n throw new NotInitializedError('No issue store found. Run `tbd init` first.');\n }\n\n // Count by status\n const byStatus: Record<IssueStatusType, number> = {\n open: 0,\n in_progress: 0,\n blocked: 0,\n deferred: 0,\n closed: 0,\n };\n\n // Count by kind (active vs closed)\n const byKindActive: Record<IssueKindType, number> = {\n bug: 0,\n feature: 0,\n task: 0,\n epic: 0,\n chore: 0,\n };\n const byKindClosed: Record<IssueKindType, number> = {\n bug: 0,\n feature: 0,\n task: 0,\n epic: 0,\n chore: 0,\n };\n\n // Count by priority (active vs closed)\n const byPriorityActive: Record<number, number> = { 0: 0, 1: 0, 2: 0, 3: 0, 4: 0 };\n const byPriorityClosed: Record<number, number> = { 0: 0, 1: 0, 2: 0, 3: 0, 4: 0 };\n\n // Accumulate counts\n for (const issue of issues) {\n byStatus[issue.status]++;\n\n const isActive = issue.status !== 'closed';\n if (isActive) {\n byKindActive[issue.kind]++;\n if (issue.priority >= 0 && issue.priority <= 4) {\n byPriorityActive[issue.priority]!++;\n }\n } else {\n byKindClosed[issue.kind]++;\n if (issue.priority >= 0 && issue.priority <= 4) {\n byPriorityClosed[issue.priority]!++;\n }\n }\n }\n\n // Calculate totals\n const activeTotal = ACTIVE_STATUSES.reduce((sum, s) => sum + byStatus[s], 0);\n const closedTotal = byStatus.closed;\n const total = issues.length;\n\n const stats = {\n total,\n active: activeTotal,\n closed: closedTotal,\n byStatus,\n byKindActive,\n byKindClosed,\n byPriorityActive,\n byPriorityClosed,\n };\n\n this.output.data(stats, () => {\n const colors = this.output.getColors();\n\n if (stats.total === 0) {\n console.log(colors.dim('No issues found.'));\n renderFooter(\n [\n { command: 'tbd status', description: 'setup info' },\n { command: 'tbd doctor', description: 'health checks' },\n ],\n colors,\n );\n return;\n }\n\n // Column width for counts (right-aligned)\n const countWidth = 6;\n\n // === BY STATUS SECTION ===\n console.log(colors.bold('By status:'));\n\n // Find max count for determining column alignment\n const maxStatusCount = Math.max(...Object.values(stats.byStatus), activeTotal, total);\n const statusCountWidth = Math.max(countWidth, String(maxStatusCount).length + 2);\n\n // Show each status with icon and color\n for (const status of STATUS_ORDER) {\n const count = stats.byStatus[status];\n if (status === 'closed') continue; // Show closed after subtotal\n const icon = getStatusIcon(status);\n const colorFn = getStatusColor(status, colors);\n const countStr = String(count).padStart(statusCountWidth);\n console.log(` ${colorFn(icon)} ${status.padEnd(14)}${countStr}`);\n }\n\n // Subtotal separator and active total\n console.log(` ${'─'.repeat(16 + statusCountWidth)}`);\n console.log(` ${'active'.padEnd(14)}${String(activeTotal).padStart(statusCountWidth)}`);\n\n // Closed with icon\n const closedIcon = getStatusIcon('closed');\n const closedColorFn = getStatusColor('closed', colors);\n console.log(\n ` ${closedColorFn(closedIcon)} ${'closed'.padEnd(14)}${String(closedTotal).padStart(statusCountWidth)}`,\n );\n\n // Total separator and total\n console.log(` ${'═'.repeat(16 + statusCountWidth)}`);\n console.log(` ${'total'.padEnd(14)}${String(total).padStart(statusCountWidth)}`);\n\n // === BY KIND SECTION ===\n console.log('');\n const kindHeader = `${'By kind:'.padEnd(18)}${'active'.padStart(countWidth + 2)}${'closed'.padStart(countWidth + 2)}${'total'.padStart(countWidth + 2)}`;\n console.log(colors.bold(kindHeader));\n\n for (const kind of KIND_ORDER) {\n const active = stats.byKindActive[kind];\n const closed = stats.byKindClosed[kind];\n const kindTotal = active + closed;\n if (kindTotal === 0) continue;\n\n const line = ` ${kind.padEnd(16)}${String(active).padStart(countWidth + 2)}${String(closed).padStart(countWidth + 2)}${String(kindTotal).padStart(countWidth + 2)}`;\n console.log(line);\n }\n\n // === BY PRIORITY SECTION ===\n console.log('');\n const priorityHeader = `${'By priority:'.padEnd(18)}${'active'.padStart(countWidth + 2)}${'closed'.padStart(countWidth + 2)}${'total'.padStart(countWidth + 2)}`;\n console.log(colors.bold(priorityHeader));\n\n for (let i = 0; i <= 4; i++) {\n const active = stats.byPriorityActive[i] ?? 0;\n const closed = stats.byPriorityClosed[i] ?? 0;\n const priorityTotal = active + closed;\n if (priorityTotal === 0) continue;\n\n const label = `${formatPriority(i)} (${PRIORITY_LABELS[i]})`;\n const line = ` ${label.padEnd(16)}${String(active).padStart(countWidth + 2)}${String(closed).padStart(countWidth + 2)}${String(priorityTotal).padStart(countWidth + 2)}`;\n console.log(line);\n }\n\n // Footer (shared format)\n renderFooter(\n [\n { command: 'tbd status', description: 'setup info' },\n { command: 'tbd doctor', description: 'health checks' },\n ],\n colors,\n );\n });\n }\n}\n\nexport const statsCommand = new Command('stats')\n .description('Show repository statistics')\n .action(async (_options, command) => {\n const handler = new StatsHandler(command);\n await handler.run();\n });\n","/**\n * Shared diagnostic output utilities for consistent diagnostic messages\n * across doctor, setup --check, and status commands.\n *\n * See: plan-2026-01-17-cli-output-design-system.md\n */\n\nimport { ICONS } from './output.js';\n\n/**\n * Result of a diagnostic check. Used by doctor, setup --check, and status commands\n * to report configuration and health status.\n *\n * @property name - Display name of the check (e.g., \"Config file\", \"Git version\")\n * @property status - Check result: ok (pass), warn (non-blocking issue), error (failure)\n * @property message - Optional message with additional context (e.g., version number, count)\n * @property path - Optional file/directory path being checked\n * @property details - Optional list of specific items when issues found (e.g., orphaned deps)\n * @property fixable - Whether the issue can be auto-fixed (shown as [fixable] suffix)\n * @property suggestion - Optional actionable fix suggestion (e.g., \"Run: tbd setup claude\")\n */\nexport interface DiagnosticResult {\n name: string;\n status: 'ok' | 'warn' | 'error';\n message?: string;\n path?: string;\n details?: string[];\n fixable?: boolean;\n suggestion?: string;\n}\n\n/**\n * Color function type for consistent coloring across the module.\n */\ntype ColorFn = (text: string) => string;\n\n/**\n * Colors interface matching createColors() return type.\n */\ninterface Colors {\n success: ColorFn;\n error: ColorFn;\n warn: ColorFn;\n dim: ColorFn;\n}\n\n/**\n * Render a single diagnostic result to console.\n *\n * Format examples:\n * - ✓ Config file (.tbd/config.yml)\n * - ⚠ Dependencies - 2 orphaned reference(s) [fixable]\n * tbd-abc1 -> tbd-xyz9 (missing)\n * tbd-def2 -> tbd-uvw8 (missing)\n * - ✗ Issue validity - 2 invalid issue(s) (.tbd/issues)\n * tbd-aaa1: missing required field 'title'\n * Run: tbd doctor --fix\n *\n * @param result - The diagnostic result to render\n * @param colors - Color functions from createColors()\n */\nexport function renderDiagnostic(result: DiagnosticResult, colors: Colors): void {\n // Build the main line\n let line = '';\n\n // Icon based on status\n const icon =\n result.status === 'ok'\n ? colors.success(ICONS.SUCCESS)\n : result.status === 'warn'\n ? colors.warn(ICONS.WARN)\n : colors.error(ICONS.ERROR);\n\n line += `${icon} ${result.name}`;\n\n // Message after dash\n if (result.message) {\n line += ` - ${result.message}`;\n }\n\n // Path in parentheses\n if (result.path) {\n line += ` ${colors.dim(`(${result.path})`)}`;\n }\n\n // Fixable suffix (only for non-ok)\n if (result.fixable && result.status !== 'ok') {\n line += ` ${colors.dim('[fixable]')}`;\n }\n\n console.log(line);\n\n // Details (only for non-ok status)\n if (result.details && result.details.length > 0 && result.status !== 'ok') {\n for (const detail of result.details) {\n console.log(` ${colors.dim(detail)}`);\n }\n }\n\n // Suggestion (only for non-ok status)\n if (result.suggestion && result.status !== 'ok') {\n console.log(` ${colors.dim(result.suggestion)}`);\n }\n}\n\n/**\n * Render multiple diagnostic results.\n *\n * @param results - Array of diagnostic results to render\n * @param colors - Color functions from createColors()\n */\nexport function renderDiagnostics(results: DiagnosticResult[], colors: Colors): void {\n for (const result of results) {\n renderDiagnostic(result, colors);\n }\n}\n","/**\n * `tbd doctor` - Diagnose and repair repository.\n *\n * A comprehensive health check that includes status, stats, and health checks.\n *\n * See: tbd-design.md §4.9 Doctor\n */\n\nimport { Command } from 'commander';\nimport { access, readdir, readFile, unlink } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { requireInit } from '../lib/errors.js';\nimport { listIssues } from '../../file/storage.js';\nimport { readConfig } from '../../file/config.js';\nimport type { Config, Issue, IssueStatusType } from '../../lib/types.js';\nimport { resolveDataSyncDir, TBD_DIR, WORKTREE_DIR, DATA_SYNC_DIR } from '../../lib/paths.js';\nimport { detectDuplicateYamlKeys } from '../../utils/yaml-utils.js';\nimport {\n getClaudePaths,\n getAgentsMdPath,\n CLAUDE_SKILL_REL,\n AGENTS_MD_REL,\n} from '../../lib/integration-paths.js';\nimport { validateIssueId, extractUlidFromInternalId } from '../../lib/ids.js';\nimport { git } from '../../file/git.js';\nimport {\n checkGitVersion,\n MIN_GIT_VERSION,\n getCurrentBranch,\n checkWorktreeHealth,\n checkLocalBranchHealth,\n checkRemoteBranchHealth,\n checkSyncConsistency,\n repairWorktree,\n migrateDataToWorktree,\n initWorktree,\n countRemoteIssues,\n} from '../../file/git.js';\nimport { type DiagnosticResult, renderDiagnostics } from '../lib/diagnostics.js';\nimport { VERSION } from '../lib/version.js';\nimport { formatHeading } from '../lib/output.js';\nimport {\n renderRepositorySection,\n renderConfigSection,\n renderStatisticsSection,\n} from '../lib/sections.js';\n\nconst CONFIG_DIR = TBD_DIR;\n\ninterface DoctorOptions {\n fix?: boolean;\n maxHistory?: string;\n}\n\nclass DoctorHandler extends BaseCommand {\n private dataSyncDir = '';\n private cwd = '';\n private config: Config | null = null;\n private issues: Issue[] = [];\n\n async run(options: DoctorOptions): Promise<void> {\n const tbdRoot = await requireInit();\n\n this.cwd = tbdRoot;\n this.dataSyncDir = await resolveDataSyncDir(tbdRoot);\n\n // Load config\n try {\n this.config = await readConfig(this.cwd);\n } catch {\n // Config may be invalid - will be caught by health checks\n }\n\n // Load issues\n try {\n this.issues = await listIssues(this.dataSyncDir);\n } catch {\n // May fail if no issues yet\n }\n\n // Gather status info\n const statusInfo = await this.gatherStatusInfo();\n\n // Gather stats info (async to check remote when local is empty)\n const statsInfo = await this.gatherStatsInfo();\n\n // Run health checks (core system checks)\n const healthChecks: DiagnosticResult[] = [];\n\n // Check 1: Git version\n healthChecks.push(await this.checkGitVersion());\n\n // Check 2: Config directory and file\n healthChecks.push(await this.checkConfig());\n\n // Check 3: Issues directory\n healthChecks.push(await this.checkIssuesDirectory());\n\n // Check 4: Orphaned dependencies\n healthChecks.push(this.checkOrphanedDependencies(this.issues));\n\n // Check 5: Duplicate IDs\n healthChecks.push(this.checkDuplicateIds(this.issues));\n\n // Check 5b: Merge conflict markers in ids.yml\n healthChecks.push(await this.checkIdMappingConflicts(options.fix));\n\n // Check 6: Duplicate mapping keys in ids.yml\n healthChecks.push(await this.checkIdMappingDuplicates(options.fix));\n\n // Check 7: Orphaned temp files\n healthChecks.push(await this.checkTempFiles(options.fix));\n\n // Check 8: Issue validity\n healthChecks.push(this.checkIssueValidity(this.issues));\n\n // Check 9: Worktree health (with fix support)\n // Run BEFORE ID mapping check — worktree repair and data migration can\n // overwrite ids.yml, so mappings must be verified after migration.\n healthChecks.push(await this.checkWorktree(options.fix));\n\n // Check 10: Data location (issues in wrong path, with fix support)\n const dataLocationResult = await this.checkDataLocation(options.fix);\n healthChecks.push(dataLocationResult);\n\n // If data was migrated, reload issues and refresh dataSyncDir so\n // subsequent checks (especially ID mappings) see the current state.\n if (dataLocationResult.status === 'ok' && dataLocationResult.message?.includes('migrated')) {\n this.dataSyncDir = await resolveDataSyncDir(this.cwd);\n try {\n this.issues = await listIssues(this.dataSyncDir);\n } catch {\n // Will be caught by other health checks\n }\n }\n\n // Check 8b: Missing ID mappings (issues without short IDs)\n // Runs AFTER worktree/migration checks to ensure ids.yml is in its final location.\n const parsedMaxHistory = options.maxHistory ? parseInt(options.maxHistory, 10) : 50;\n const maxHistory =\n Number.isNaN(parsedMaxHistory) || parsedMaxHistory < 0 ? 50 : parsedMaxHistory;\n healthChecks.push(await this.checkMissingMappings(options.fix, maxHistory));\n\n // Check 11: Local sync branch health\n healthChecks.push(await this.checkLocalSyncBranch());\n\n // Check 12: Remote sync branch health\n healthChecks.push(await this.checkRemoteSyncBranch());\n\n // Check 13: Local has data but remote empty (ai-trade-arena bug detection)\n healthChecks.push(await this.checkLocalVsRemoteData());\n\n // Check 14: Multi-user/clone scenario detection\n healthChecks.push(await this.checkCloneScenarios());\n\n // Check 15: Sync consistency (worktree matches local, ahead/behind counts)\n healthChecks.push(await this.checkSyncConsistency());\n\n // Run integration checks (optional IDE/agent integrations)\n const integrationChecks: DiagnosticResult[] = [];\n\n // Integration 1: Claude Code skill file\n integrationChecks.push(await this.checkClaudeSkill());\n\n // Integration 2: Codex AGENTS.md (also used by Cursor since v1.6)\n integrationChecks.push(await this.checkCodexAgents());\n\n // Combine for overall status\n const allChecks = [...healthChecks, ...integrationChecks];\n const allOk = allChecks.every((c) => c.status === 'ok');\n const hasFixable = allChecks.some((c) => c.fixable && c.status !== 'ok');\n\n this.output.data(\n { statusInfo, statsInfo, healthChecks, integrationChecks, healthy: allOk },\n () => {\n const colors = this.output.getColors();\n\n // REPOSITORY section (shared with status command)\n renderRepositorySection(\n {\n version: VERSION,\n workingDirectory: this.cwd,\n initialized: true, // doctor requires init\n gitRepository: !!statusInfo.gitBranch,\n gitBranch: statusInfo.gitBranch,\n gitVersion: null, // Git version is shown in health checks\n gitVersionSupported: true,\n },\n colors,\n { showHeading: true },\n );\n\n // CONFIG section (shared with status command)\n if (this.config) {\n renderConfigSection(\n {\n syncBranch: this.config.sync.branch,\n remote: this.config.sync.remote,\n displayPrefix: this.config.display.id_prefix,\n },\n colors,\n );\n }\n\n // STATISTICS section (shared with stats command)\n renderStatisticsSection(statsInfo, colors);\n\n // INTEGRATIONS section\n console.log('');\n console.log(colors.bold(formatHeading('Integrations')));\n renderDiagnostics(integrationChecks, colors);\n\n // HEALTH CHECKS section (doctor-only)\n console.log('');\n console.log(colors.bold(formatHeading('Health Checks')));\n renderDiagnostics(healthChecks, colors);\n\n // Final summary\n console.log('');\n if (allOk) {\n this.output.success('Repository is healthy');\n } else if (hasFixable && !options.fix) {\n this.output.warn('Issues found. Run with --fix to repair.');\n } else {\n this.output.warn('Issues found that may require manual intervention.');\n }\n },\n );\n }\n\n private async gatherStatusInfo(): Promise<{\n gitBranch: string | null;\n worktreeHealthy: boolean;\n }> {\n let gitBranch: string | null = null;\n try {\n gitBranch = await getCurrentBranch();\n } catch {\n // Not in a git repo or no commits\n }\n\n const worktreeHealth = await checkWorktreeHealth(this.cwd);\n\n return {\n gitBranch,\n worktreeHealthy: worktreeHealth.valid,\n };\n }\n\n private async gatherStatsInfo(): Promise<{\n total: number;\n ready: number;\n inProgress: number;\n blocked: number;\n open: number;\n remoteTotal: number | null;\n }> {\n // Count by status\n const byStatus: Record<IssueStatusType, number> = {\n open: 0,\n in_progress: 0,\n blocked: 0,\n deferred: 0,\n closed: 0,\n };\n\n // Build set of blocked issue IDs\n const blockedIds = new Set<string>();\n for (const issue of this.issues) {\n for (const dep of issue.dependencies) {\n if (dep.type === 'blocks') {\n const blockedIssue = this.issues.find((i) => i.id === dep.target);\n if (blockedIssue && blockedIssue.status !== 'closed') {\n blockedIds.add(dep.target);\n }\n }\n }\n }\n\n // Count ready issues (open and not blocked)\n let readyCount = 0;\n\n for (const issue of this.issues) {\n byStatus[issue.status]++;\n if (issue.status === 'open' && !blockedIds.has(issue.id)) {\n readyCount++;\n }\n }\n\n // Check remote issue count when local is empty\n // This helps users on fresh clones understand that data exists\n let remoteTotal: number | null = null;\n if (this.issues.length === 0 && this.config) {\n const remote = this.config.sync.remote ?? 'origin';\n const syncBranch = this.config.sync.branch ?? 'tbd-sync';\n remoteTotal = await countRemoteIssues(remote, syncBranch);\n }\n\n return {\n total: this.issues.length,\n ready: readyCount,\n inProgress: byStatus.in_progress,\n blocked: blockedIds.size,\n open: byStatus.open,\n remoteTotal,\n };\n }\n\n private async checkGitVersion(): Promise<DiagnosticResult> {\n try {\n const { version, supported } = await checkGitVersion();\n const versionStr = `${version.major}.${version.minor}.${version.patch}`;\n\n if (supported) {\n return {\n name: 'Git version',\n status: 'ok',\n message: versionStr,\n };\n }\n\n return {\n name: 'Git version',\n status: 'error',\n message: `${versionStr} (requires ${MIN_GIT_VERSION}+)`,\n suggestion: 'Upgrade Git: https://git-scm.com/downloads',\n };\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n if (msg.includes('git') || msg.includes('not found') || msg.includes('ENOENT')) {\n return {\n name: 'Git version',\n status: 'error',\n message: 'Git not found',\n suggestion: 'Install Git: https://git-scm.com/downloads',\n };\n }\n return {\n name: 'Git version',\n status: 'warn',\n message: `Unable to check: ${msg}`,\n };\n }\n }\n\n private async checkConfig(): Promise<DiagnosticResult> {\n const configPath = join(CONFIG_DIR, 'config.yml');\n try {\n await access(join(this.cwd, configPath));\n await readConfig(this.cwd);\n return { name: 'Config file', status: 'ok', path: configPath };\n } catch (error) {\n const msg = (error as Error).message;\n if (msg.includes('ENOENT')) {\n return {\n name: 'Config file',\n status: 'error',\n message: 'not found',\n path: configPath,\n suggestion: 'Run: tbd init',\n };\n }\n return {\n name: 'Config file',\n status: 'error',\n message: 'Invalid config file',\n path: configPath,\n };\n }\n }\n\n private async checkIssuesDirectory(): Promise<DiagnosticResult> {\n const issuesPath = join(CONFIG_DIR, 'issues');\n try {\n await access(join(this.dataSyncDir, 'issues'));\n return { name: 'Issues directory', status: 'ok', path: issuesPath };\n } catch {\n // No issues directory is normal for a fresh/empty repo\n return {\n name: 'Issues directory',\n status: 'ok',\n message: 'empty (no issues yet)',\n path: issuesPath,\n };\n }\n }\n\n private checkOrphanedDependencies(issues: Issue[]): DiagnosticResult {\n const issueIds = new Set(issues.map((i) => i.id));\n const orphans: string[] = [];\n\n for (const issue of issues) {\n for (const dep of issue.dependencies) {\n if (!issueIds.has(dep.target)) {\n orphans.push(`${issue.id} -> ${dep.target} (missing)`);\n }\n }\n }\n\n if (orphans.length === 0) {\n return { name: 'Dependencies', status: 'ok' };\n }\n\n return {\n name: 'Dependencies',\n status: 'warn',\n message: `${orphans.length} orphaned reference(s)`,\n details: orphans,\n fixable: true,\n suggestion: 'Run: tbd doctor --fix',\n };\n }\n\n private checkDuplicateIds(issues: Issue[]): DiagnosticResult {\n const seen = new Set<string>();\n const duplicates: string[] = [];\n\n for (const issue of issues) {\n if (seen.has(issue.id)) {\n duplicates.push(issue.id);\n }\n seen.add(issue.id);\n }\n\n if (duplicates.length === 0) {\n return { name: 'Unique IDs', status: 'ok' };\n }\n\n return {\n name: 'Unique IDs',\n status: 'error',\n message: `${duplicates.length} duplicate ID(s)`,\n details: duplicates.map((id) => `${id} (duplicate)`),\n suggestion: 'Manually remove duplicate issue files',\n };\n }\n\n /**\n * Check 5b: Merge conflict markers in ids.yml.\n *\n * After a failed git merge during sync, ids.yml may retain unresolved\n * conflict markers (<<<<<<< / ======= / >>>>>>>). This blocks all tbd\n * commands since YAML parsing throws MergeConflictError.\n *\n * For ids.yml, both sides are simple key-value pairs that are append-only,\n * so the resolution is trivial: keep all entries from both sides.\n *\n * With --fix, extracts both sides, merges them, and re-saves.\n */\n private async checkIdMappingConflicts(fix?: boolean): Promise<DiagnosticResult> {\n const mappingPath = join(this.dataSyncDir, 'mappings', 'ids.yml');\n let content: string;\n\n try {\n content = await readFile(mappingPath, 'utf-8');\n } catch {\n return { name: 'ID mapping conflicts', status: 'ok' };\n }\n\n const { hasMergeConflictMarkers } = await import('../../utils/yaml-utils.js');\n if (!hasMergeConflictMarkers(content)) {\n return { name: 'ID mapping conflicts', status: 'ok' };\n }\n\n if (fix && !this.checkDryRun('Resolve merge conflicts in ids.yml')) {\n try {\n const { resolveIdMappingConflicts, saveIdMapping } =\n await import('../../file/id-mapping.js');\n const resolved = resolveIdMappingConflicts(content);\n await saveIdMapping(this.dataSyncDir, resolved);\n return {\n name: 'ID mapping conflicts',\n status: 'ok',\n message: `resolved merge conflicts (${resolved.shortToUlid.size} entries)`,\n };\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n return {\n name: 'ID mapping conflicts',\n status: 'error',\n message: `failed to resolve conflicts: ${msg}`,\n };\n }\n }\n\n return {\n name: 'ID mapping conflicts',\n status: 'error',\n message: 'ids.yml contains unresolved merge conflict markers',\n fixable: true,\n suggestion: 'Run: tbd doctor --fix to auto-resolve',\n };\n }\n\n /**\n * Check for duplicate keys in the ID mapping file (ids.yml).\n *\n * After a git merge conflict resolution that keeps entries from both sides,\n * ids.yml can end up with duplicate YAML keys. The yaml parser throws\n * \"Map keys must be unique\" which breaks tbd commands.\n *\n * With --fix, re-saves the file to eliminate duplicates.\n */\n private async checkIdMappingDuplicates(fix?: boolean): Promise<DiagnosticResult> {\n const mappingPath = join(this.dataSyncDir, 'mappings', 'ids.yml');\n let content: string;\n\n try {\n content = await readFile(mappingPath, 'utf-8');\n } catch {\n // File doesn't exist — normal for repos with no issues yet\n return { name: 'ID mapping keys', status: 'ok' };\n }\n\n const duplicates = detectDuplicateYamlKeys(content);\n\n if (duplicates.length === 0) {\n return { name: 'ID mapping keys', status: 'ok' };\n }\n\n if (fix && !this.checkDryRun('Fix duplicate ID mapping keys')) {\n // Load and re-save to deduplicate (Map + saveIdMapping naturally dedupes)\n try {\n const { loadIdMapping, saveIdMapping } = await import('../../file/id-mapping.js');\n const mapping = await loadIdMapping(this.dataSyncDir);\n await saveIdMapping(this.dataSyncDir, mapping);\n return {\n name: 'ID mapping keys',\n status: 'ok',\n message: `fixed ${duplicates.length} duplicate key(s)`,\n };\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n return {\n name: 'ID mapping keys',\n status: 'error',\n message: `failed to fix duplicates: ${msg}`,\n };\n }\n }\n\n return {\n name: 'ID mapping keys',\n status: 'warn',\n message: `${duplicates.length} duplicate key(s) in ids.yml`,\n details: duplicates.map((k) => `\"${k}\" appears multiple times`),\n fixable: true,\n suggestion: 'Run: tbd doctor --fix to deduplicate',\n };\n }\n\n private async checkTempFiles(fix?: boolean): Promise<DiagnosticResult> {\n const issuesPath = join(CONFIG_DIR, 'issues');\n const issuesDir = join(this.dataSyncDir, 'issues');\n let tempFiles: string[] = [];\n\n try {\n const files = await readdir(issuesDir);\n tempFiles = files.filter((f) => f.endsWith('.tmp'));\n } catch {\n // Directory doesn't exist - no temp files\n return { name: 'Temp files', status: 'ok', path: issuesPath };\n }\n\n if (tempFiles.length === 0) {\n return { name: 'Temp files', status: 'ok', path: issuesPath };\n }\n\n if (fix && !this.checkDryRun('Clean temp files')) {\n // Clean up temp files\n for (const file of tempFiles) {\n try {\n await unlink(join(issuesDir, file));\n } catch {\n // Ignore errors\n }\n }\n return {\n name: 'Temp files',\n status: 'ok',\n message: `Cleaned ${tempFiles.length} temp file(s)`,\n path: issuesPath,\n };\n }\n\n return {\n name: 'Temp files',\n status: 'warn',\n message: `${tempFiles.length} orphaned temp file(s)`,\n path: issuesPath,\n details: tempFiles,\n fixable: true,\n suggestion: 'Run: tbd doctor --fix',\n };\n }\n\n private checkIssueValidity(issues: Issue[]): DiagnosticResult {\n const invalid: { id: string; reason: string }[] = [];\n\n for (const issue of issues) {\n const issueId = issue.id ?? 'unknown';\n // Check required fields\n if (!issue.id) {\n invalid.push({ id: issueId, reason: 'missing required field: id' });\n continue;\n }\n if (!issue.title) {\n invalid.push({ id: issueId, reason: 'missing required field: title' });\n continue;\n }\n if (!issue.status) {\n invalid.push({ id: issueId, reason: 'missing required field: status' });\n continue;\n }\n if (!issue.kind) {\n invalid.push({ id: issueId, reason: 'missing required field: kind' });\n continue;\n }\n // Check ID format\n if (!validateIssueId(issue.id)) {\n invalid.push({ id: issueId, reason: 'invalid ID format' });\n continue;\n }\n // Check priority range\n if (issue.priority < 0 || issue.priority > 4) {\n invalid.push({ id: issueId, reason: `invalid priority ${issue.priority} (must be 0-4)` });\n }\n }\n\n if (invalid.length === 0) {\n return { name: 'Issue validity', status: 'ok' };\n }\n\n return {\n name: 'Issue validity',\n status: 'error',\n message: `${invalid.length} invalid issue(s)`,\n details: invalid.map((i) => `${i.id}: ${i.reason}`),\n suggestion: 'Manually fix or delete invalid issue files',\n };\n }\n\n /**\n * Check for issues that have no short ID mapping in ids.yml.\n *\n * This can happen when a git merge brings in issue files (e.g., from\n * a feature branch with outbox issues) without the corresponding\n * ids.yml entries. Without a mapping, any command that tries to\n * display the issue ID will crash.\n *\n * With --fix, creates missing mappings automatically.\n */\n private async checkMissingMappings(fix?: boolean, maxHistory = 50): Promise<DiagnosticResult> {\n if (this.issues.length === 0) {\n return { name: 'ID mapping coverage', status: 'ok' };\n }\n\n const { loadIdMapping, saveIdMapping, reconcileMappings } =\n await import('../../file/id-mapping.js');\n const mapping = await loadIdMapping(this.dataSyncDir);\n\n // Find issues missing from the mapping\n const missingIds: string[] = [];\n for (const issue of this.issues) {\n const ulid = extractUlidFromInternalId(issue.id);\n if (!mapping.ulidToShort.has(ulid)) {\n missingIds.push(issue.id);\n }\n }\n\n if (missingIds.length === 0) {\n return { name: 'ID mapping coverage', status: 'ok' };\n }\n\n if (fix && !this.checkDryRun('Create missing ID mappings')) {\n // Try to recover original short IDs from git history before generating new ones.\n // Search recent commits on the tbd-sync branch that touched ids.yml, not\n // just the latest. This handles the case where a bug (e.g., migration\n // overwrite) destroyed entries in a recent commit — the entries still exist\n // in earlier commits. Since mappings are append-only, merging all versions\n // is safe. Capped via --max-history (default 50, 0 = full history).\n const { parseIdMappingFromYaml, mergeIdMappings } = await import('../../file/id-mapping.js');\n let historicalMapping: Awaited<ReturnType<typeof loadIdMapping>> | undefined;\n try {\n const config = await import('../../file/config.js').then((m) => m.readConfig(this.cwd));\n const syncBranch = config.sync.branch;\n // Get recent commits that touched ids.yml (most recent first, capped)\n const logArgs = ['log', '--format=%H'];\n if (maxHistory > 0) {\n logArgs.push(`-${maxHistory}`);\n }\n logArgs.push(syncBranch, '--', `${DATA_SYNC_DIR}/mappings/ids.yml`);\n const commitLog = await git(...logArgs);\n const commitHashes = commitLog.trim().split('\\n').filter(Boolean);\n for (const commitHash of commitHashes) {\n try {\n const idsContent = await git('show', `${commitHash}:${DATA_SYNC_DIR}/mappings/ids.yml`);\n if (idsContent) {\n const versionMapping = parseIdMappingFromYaml(idsContent);\n if (!historicalMapping) {\n historicalMapping = versionMapping;\n } else {\n historicalMapping = mergeIdMappings(historicalMapping, versionMapping);\n }\n }\n } catch {\n // Individual commit may be unreachable — skip\n }\n }\n } catch {\n // Git history not available - will generate new IDs\n }\n\n const historicalCount = historicalMapping?.shortToUlid.size ?? 0;\n const result = reconcileMappings(missingIds, mapping, historicalMapping);\n await saveIdMapping(this.dataSyncDir, mapping);\n\n const parts: string[] = [];\n if (result.recovered.length > 0) {\n parts.push(`recovered ${result.recovered.length} from git history`);\n }\n if (result.created.length > 0) {\n parts.push(`created ${result.created.length} new`);\n }\n const details: string[] = [\n `Scanned ${maxHistory > 0 ? `up to ${maxHistory}` : 'all'} git commits for ids.yml history`,\n `Found ${historicalCount} historical mapping(s) to use for recovery`,\n `${missingIds.length} issue(s) were missing short ID mappings`,\n ];\n if (result.recovered.length > 0) {\n details.push(`Recovered ${result.recovered.length} original short ID(s) from git history`);\n }\n if (result.created.length > 0) {\n details.push(\n `Generated ${result.created.length} new short ID(s) (originals not found in history)`,\n );\n }\n return {\n name: 'ID mapping coverage',\n status: 'ok',\n message: parts.join(', '),\n details,\n };\n }\n\n return {\n name: 'ID mapping coverage',\n status: 'error',\n message: `${missingIds.length} issue(s) without short ID mapping`,\n details: missingIds.map((id) => `${id} (no short ID)`),\n fixable: true,\n suggestion: 'Run: tbd doctor --fix to create missing mappings',\n };\n }\n\n private async checkClaudeSkill(): Promise<DiagnosticResult> {\n const claudePaths = getClaudePaths(this.cwd);\n try {\n await access(claudePaths.skill);\n return { name: 'Claude Code skill', status: 'ok', path: CLAUDE_SKILL_REL };\n } catch {\n return {\n name: 'Claude Code skill',\n status: 'warn',\n message: 'not installed',\n path: CLAUDE_SKILL_REL,\n suggestion: 'Run: tbd setup --auto',\n };\n }\n }\n\n private async checkCodexAgents(): Promise<DiagnosticResult> {\n const agentsPath = getAgentsMdPath(this.cwd);\n try {\n await access(agentsPath);\n const content = await readFile(agentsPath, 'utf-8');\n if (content.includes('BEGIN TBD INTEGRATION')) {\n return { name: 'Codex AGENTS.md', status: 'ok', path: AGENTS_MD_REL };\n }\n return {\n name: 'Codex AGENTS.md',\n status: 'warn',\n message: 'exists but missing tbd integration',\n path: AGENTS_MD_REL,\n suggestion: 'Run: tbd setup --auto',\n };\n } catch {\n return {\n name: 'Codex AGENTS.md',\n status: 'warn',\n message: 'not installed',\n path: AGENTS_MD_REL,\n suggestion: 'Run: tbd setup --auto',\n };\n }\n }\n\n /**\n * Check worktree health with enhanced status detection.\n * See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md §4\n */\n private async checkWorktree(fix?: boolean): Promise<DiagnosticResult> {\n const worktreePath = WORKTREE_DIR;\n const worktreeHealth = await checkWorktreeHealth(this.cwd);\n\n switch (worktreeHealth.status) {\n case 'valid':\n return { name: 'Worktree', status: 'ok', path: worktreePath };\n\n case 'missing':\n // Worktree not existing is OK - it's created on demand\n return { name: 'Worktree', status: 'ok', message: 'not created yet', path: worktreePath };\n\n case 'prunable':\n case 'corrupted': {\n // Attempt repair if --fix is provided and not in dry-run mode\n if (fix && !this.checkDryRun('Repair worktree')) {\n const result = await repairWorktree(this.cwd, worktreeHealth.status);\n\n if (result.success) {\n const message = result.backedUp\n ? `repaired (backed up to ${result.backedUp})`\n : 'repaired successfully';\n return { name: 'Worktree', status: 'ok', message, path: worktreePath };\n }\n\n return {\n name: 'Worktree',\n status: 'error',\n message: `repair failed: ${result.error}`,\n path: worktreePath,\n };\n }\n\n // No --fix flag, report the issue\n if (worktreeHealth.status === 'prunable') {\n return {\n name: 'Worktree',\n status: 'error',\n message: 'prunable (directory deleted)',\n path: worktreePath,\n details: [\n 'The worktree directory was deleted but git still tracks it.',\n 'This can cause data to be written to the wrong location.',\n ],\n fixable: true,\n suggestion: 'Run: tbd doctor --fix to recreate worktree',\n };\n }\n\n return {\n name: 'Worktree',\n status: 'error',\n message: worktreeHealth.error ?? 'corrupted',\n path: worktreePath,\n details: ['The worktree exists but is not a valid git worktree.'],\n fixable: true,\n suggestion: 'Run: tbd doctor --fix to repair',\n };\n }\n\n default:\n return {\n name: 'Worktree',\n status: 'warn',\n message: worktreeHealth.error ?? 'unknown status',\n path: worktreePath,\n fixable: true,\n suggestion: 'Run: tbd doctor --fix',\n };\n }\n }\n\n /**\n * Check for issues in wrong location.\n * See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md §5\n *\n * Issues should be in .tbd/data-sync-worktree/.tbd/data-sync/issues/\n * If they're in .tbd/data-sync/issues/ on main branch, the worktree was missing\n * and data was written to the fallback path - this is a bug requiring migration.\n */\n private async checkDataLocation(fix?: boolean): Promise<DiagnosticResult> {\n const wrongPath = join(this.cwd, DATA_SYNC_DIR);\n const wrongIssuesPath = join(wrongPath, 'issues');\n\n // Try to list issues in the wrong location\n let wrongPathIssues: Issue[] = [];\n try {\n wrongPathIssues = await listIssues(wrongPath);\n } catch {\n // No issues in wrong path - this is expected\n }\n\n if (wrongPathIssues.length === 0) {\n return { name: 'Data location', status: 'ok' };\n }\n\n // Issues found in wrong location - attempt migration if --fix and not dry-run\n if (fix && !this.checkDryRun('Migrate data to worktree')) {\n // First ensure worktree exists - create it if missing\n let worktreeHealth = await checkWorktreeHealth(this.cwd);\n if (worktreeHealth.status === 'missing') {\n // Worktree doesn't exist yet - create it for migration\n const initResult = await initWorktree(this.cwd);\n if (!initResult.success) {\n return {\n name: 'Data location',\n status: 'error',\n message: `${wrongPathIssues.length} issue(s) in wrong location, failed to create worktree: ${initResult.error}`,\n path: wrongIssuesPath,\n };\n }\n worktreeHealth = await checkWorktreeHealth(this.cwd);\n }\n\n if (worktreeHealth.status !== 'valid') {\n return {\n name: 'Data location',\n status: 'error',\n message: `${wrongPathIssues.length} issue(s) in wrong location, worktree not ready`,\n path: wrongIssuesPath,\n details: [\n 'Cannot migrate: worktree must be repaired first.',\n 'The worktree repair should have run before this check.',\n ],\n };\n }\n\n // Migrate data to worktree (remove source after backup + copy)\n const result = await migrateDataToWorktree(this.cwd, true);\n\n if (result.success) {\n const details: string[] = [];\n if (result.backupPath) {\n details.push(`Backed up to ${result.backupPath}`);\n }\n details.push(\n `Migrated ${result.migratedCount} file(s) from .tbd/data-sync/ to worktree`,\n 'Source files removed after successful migration',\n );\n const message = result.backupPath\n ? `migrated ${result.migratedCount} file(s), backed up to ${result.backupPath}`\n : `migrated ${result.migratedCount} file(s)`;\n return {\n name: 'Data location',\n status: 'ok',\n message,\n path: wrongIssuesPath,\n details,\n };\n }\n\n return {\n name: 'Data location',\n status: 'error',\n message: `migration failed: ${result.error}`,\n path: wrongIssuesPath,\n };\n }\n\n // No --fix flag, report the issue\n return {\n name: 'Data location',\n status: 'error',\n message: `${wrongPathIssues.length} issue(s) in wrong location`,\n path: wrongIssuesPath,\n details: [\n `Found ${wrongPathIssues.length} issues in .tbd/data-sync/ (wrong)`,\n 'Issues should be in .tbd/data-sync-worktree/.tbd/data-sync/',\n 'This indicates the worktree was missing when issues were created',\n ],\n fixable: true,\n suggestion: 'Run: tbd doctor --fix to migrate issues to worktree',\n };\n }\n\n /**\n * Check local sync branch health.\n * See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md §4b\n */\n private async checkLocalSyncBranch(): Promise<DiagnosticResult> {\n const syncBranch = this.config?.sync.branch ?? 'tbd-sync';\n const localHealth = await checkLocalBranchHealth(syncBranch);\n\n if (localHealth.exists && !localHealth.orphaned) {\n return { name: 'Local sync branch', status: 'ok', message: syncBranch };\n }\n\n if (!localHealth.exists) {\n // Local branch doesn't exist - check if remote exists\n const remote = this.config?.sync.remote ?? 'origin';\n const remoteHealth = await checkRemoteBranchHealth(remote, syncBranch);\n\n if (remoteHealth.exists) {\n // Remote exists but local doesn't - can be created from remote\n return {\n name: 'Local sync branch',\n status: 'warn',\n message: `${syncBranch} not found (remote exists)`,\n suggestion: 'Run: tbd sync to create from remote',\n };\n }\n\n // Neither local nor remote - new repo, this is OK\n return {\n name: 'Local sync branch',\n status: 'ok',\n message: 'not created yet',\n };\n }\n\n // Branch exists but is orphaned (no commits)\n return {\n name: 'Local sync branch',\n status: 'warn',\n message: `${syncBranch} exists but has no commits`,\n suggestion: 'Run: tbd sync to push data',\n };\n }\n\n /**\n * Check remote sync branch health.\n * See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md §4b\n */\n private async checkRemoteSyncBranch(): Promise<DiagnosticResult> {\n const syncBranch = this.config?.sync.branch ?? 'tbd-sync';\n const remote = this.config?.sync.remote ?? 'origin';\n const remoteHealth = await checkRemoteBranchHealth(remote, syncBranch);\n\n if (remoteHealth.exists) {\n if (remoteHealth.diverged) {\n return {\n name: 'Remote sync branch',\n status: 'warn',\n message: `${remote}/${syncBranch} has diverged`,\n suggestion: 'Run: tbd sync to reconcile changes',\n };\n }\n return { name: 'Remote sync branch', status: 'ok', message: `${remote}/${syncBranch}` };\n }\n\n // Remote branch doesn't exist\n const localHealth = await checkLocalBranchHealth(syncBranch);\n if (localHealth.exists) {\n // Local exists but remote doesn't - needs push\n return {\n name: 'Remote sync branch',\n status: 'warn',\n message: `${remote}/${syncBranch} not found`,\n suggestion: 'Run: tbd sync to push local branch',\n };\n }\n\n // Neither exists - new repo, this is OK\n return {\n name: 'Remote sync branch',\n status: 'ok',\n message: 'not created yet',\n };\n }\n\n /**\n * Check for local data that hasn't been synced to remote.\n * This detects the ai-trade-arena bug scenario.\n * See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md §4\n */\n private async checkLocalVsRemoteData(): Promise<DiagnosticResult> {\n // Only check if worktree exists and has issues\n const worktreeHealth = await checkWorktreeHealth(this.cwd);\n if (worktreeHealth.status !== 'valid') {\n // Worktree not valid - can't compare\n return { name: 'Sync status', status: 'ok', message: 'worktree not active' };\n }\n\n // Count local issues in worktree\n const localIssueCount = this.issues.length;\n if (localIssueCount === 0) {\n return { name: 'Sync status', status: 'ok' };\n }\n\n // Check if remote branch exists and has commits\n const syncBranch = this.config?.sync.branch ?? 'tbd-sync';\n const remote = this.config?.sync.remote ?? 'origin';\n const remoteHealth = await checkRemoteBranchHealth(remote, syncBranch);\n\n if (!remoteHealth.exists) {\n // Remote doesn't exist - issues haven't been pushed\n return {\n name: 'Sync status',\n status: 'warn',\n message: `${localIssueCount} local issues, remote branch not found`,\n suggestion: 'Run: tbd sync to push issues to remote',\n };\n }\n\n // Note: Full remote issue count comparison would require fetching the remote\n // For now, we flag if local has issues but remote branch exists but is empty\n // This is detected by comparing commit counts or checking issue files\n // A simpler check: if worktree has uncommitted changes, we know they aren't synced\n\n return { name: 'Sync status', status: 'ok' };\n }\n\n /**\n * Check for multi-user/clone scenarios that indicate lost data.\n * See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md §6\n */\n private async checkCloneScenarios(): Promise<DiagnosticResult> {\n // Only relevant if we have no issues\n const localIssueCount = this.issues.length;\n if (localIssueCount > 0) {\n return { name: 'Clone status', status: 'ok' };\n }\n\n // Check 1: Beads migration evidence exists but tbd has no issues\n const beadsDisabledPath = join(this.cwd, '.beads-disabled');\n let beadsMigrationExists = false;\n try {\n await access(beadsDisabledPath);\n beadsMigrationExists = true;\n } catch {\n // No beads migration - that's fine\n }\n\n if (beadsMigrationExists) {\n // Check if beads had issues\n const beadsJsonl = join(beadsDisabledPath, '.beads', 'issues.jsonl');\n let beadsIssueCount = 0;\n try {\n const content = await readFile(beadsJsonl, 'utf-8');\n beadsIssueCount = content.trim().split('\\n').filter(Boolean).length;\n } catch {\n // Can't read beads file - ignore\n }\n\n if (beadsIssueCount > 0) {\n return {\n name: 'Clone status',\n status: 'error',\n message: `Beads migration has ${beadsIssueCount} issues, tbd has none`,\n details: [\n 'This repo was migrated from beads but issues were never synced.',\n 'Another user may have the issues locally but they were not pushed.',\n ],\n suggestion: 'Contact the repo owner to run: tbd sync',\n };\n }\n }\n\n // Check 2: Config has id_prefix but no issues (suggests prior usage)\n if (!beadsMigrationExists && this.config?.display?.id_prefix) {\n return {\n name: 'Clone status',\n status: 'warn',\n message: `Config has prefix '${this.config.display.id_prefix}' but no issues`,\n details: [\n 'This suggests issues may have been created but not synced,',\n 'or were lost due to sync issues on another machine.',\n ],\n suggestion: 'If you expect issues to exist, contact the repo owner',\n };\n }\n\n // Check 3: Active beads directory exists (not migrated yet)\n const beadsPath = join(this.cwd, '.beads');\n let beadsActiveExists = false;\n try {\n await access(beadsPath);\n beadsActiveExists = true;\n } catch {\n // No active beads - that's fine\n }\n\n if (beadsActiveExists) {\n return {\n name: 'Clone status',\n status: 'ok',\n message: 'beads directory exists (migration available)',\n };\n }\n\n return { name: 'Clone status', status: 'ok' };\n }\n\n /**\n * Check sync consistency - worktree matches local, ahead/behind counts.\n * See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md §4\n */\n private async checkSyncConsistency(): Promise<DiagnosticResult> {\n const syncBranch = this.config?.sync.branch ?? 'tbd-sync';\n const remote = this.config?.sync.remote ?? 'origin';\n\n // Only check if worktree is valid\n const worktreeHealth = await checkWorktreeHealth(this.cwd);\n if (worktreeHealth.status !== 'valid') {\n return { name: 'Sync consistency', status: 'ok', message: 'worktree not active' };\n }\n\n try {\n const consistency = await checkSyncConsistency(this.cwd, syncBranch, remote);\n\n // Check if worktree matches local\n if (!consistency.worktreeMatchesLocal) {\n return {\n name: 'Sync consistency',\n status: 'error',\n message: 'worktree HEAD does not match local branch',\n details: [\n `Worktree HEAD: ${consistency.worktreeHead.slice(0, 7)}`,\n `Local ${syncBranch}: ${consistency.localHead.slice(0, 7)}`,\n ],\n fixable: true,\n suggestion: 'Run: tbd doctor --fix to synchronize',\n };\n }\n\n // Check ahead/behind status\n if (consistency.localAhead > 0 && consistency.localBehind > 0) {\n return {\n name: 'Sync consistency',\n status: 'warn',\n message: `diverged (${consistency.localAhead} ahead, ${consistency.localBehind} behind)`,\n suggestion: 'Run: tbd sync to reconcile',\n };\n }\n\n if (consistency.localAhead > 0) {\n return {\n name: 'Sync consistency',\n status: 'ok',\n message: `${consistency.localAhead} local commit(s) not yet pushed — run \\`tbd sync\\` to push`,\n };\n }\n\n if (consistency.localBehind > 0) {\n return {\n name: 'Sync consistency',\n status: 'ok',\n message: `${consistency.localBehind} remote commit(s) not yet pulled — run \\`tbd sync\\` to pull`,\n };\n }\n\n return { name: 'Sync consistency', status: 'ok' };\n } catch (error) {\n // Sync consistency check failed - may be normal if branches don't exist yet\n const msg = error instanceof Error ? error.message : String(error);\n if (msg.includes('not found') || msg.includes('no commits')) {\n return { name: 'Sync consistency', status: 'ok', message: 'branches not yet established' };\n }\n return {\n name: 'Sync consistency',\n status: 'warn',\n message: `Unable to check: ${msg}`,\n };\n }\n }\n}\n\nexport const doctorCommand = new Command('doctor')\n .description('Diagnose and repair repository')\n .option('--fix', 'Attempt to fix issues')\n .option(\n '--max-history <n>',\n 'Max git commits to scan for ID mapping recovery (0 = full history)',\n '50',\n )\n .action(async (options, command) => {\n const handler = new DoctorHandler(command);\n await handler.run(options);\n });\n","/**\n * `tbd config` - Configuration management.\n *\n * See: tbd-design.md §4.9 Config\n */\n\nimport { Command } from 'commander';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { requireInit, NotInitializedError, ValidationError } from '../lib/errors.js';\nimport { readConfig, writeConfig } from '../../file/config.js';\nimport type { Config } from '../../lib/types.js';\n\n// Show config\nclass ConfigShowHandler extends BaseCommand {\n async run(): Promise<void> {\n const tbdRoot = await requireInit();\n\n let config: Config;\n try {\n config = await readConfig(tbdRoot);\n } catch {\n throw new NotInitializedError('No configuration found. Run `tbd init` first.');\n }\n\n this.output.data(config, () => {\n // Output as YAML format\n const colors = this.output.getColors();\n console.log(`${colors.dim('tbd_version:')} ${config.tbd_version}`);\n console.log(`${colors.dim('sync:')}`);\n console.log(` ${colors.dim('branch:')} ${config.sync.branch}`);\n console.log(` ${colors.dim('remote:')} ${config.sync.remote}`);\n console.log(`${colors.dim('display:')}`);\n console.log(` ${colors.dim('id_prefix:')} ${config.display.id_prefix}`);\n console.log(`${colors.dim('settings:')}`);\n console.log(` ${colors.dim('auto_sync:')} ${config.settings.auto_sync}`);\n });\n }\n}\n\n// Set config value\nclass ConfigSetHandler extends BaseCommand {\n async run(key: string, value: string): Promise<void> {\n const tbdRoot = await requireInit();\n\n let config: Config;\n try {\n config = await readConfig(tbdRoot);\n } catch {\n throw new NotInitializedError('No configuration found. Run `tbd init` first.');\n }\n\n if (this.checkDryRun('Would set config', { key, value })) {\n return;\n }\n\n // Parse the key path and set value\n const keys = key.split('.');\n const parsedValue = this.parseValue(value);\n\n try {\n this.setNestedValue(config, keys, parsedValue);\n } catch {\n throw new ValidationError(`Invalid key: ${key}`);\n }\n\n await this.execute(async () => {\n await writeConfig(tbdRoot, config);\n }, 'Failed to write config');\n\n this.output.success(`Set ${key} = ${value}`);\n }\n\n private parseValue(value: string): unknown {\n // Parse boolean\n if (value === 'true') return true;\n if (value === 'false') return false;\n // Parse number\n const num = Number(value);\n if (!isNaN(num)) return num;\n // Return as string\n return value;\n }\n\n private setNestedValue(obj: Record<string, unknown>, keys: string[], value: unknown): void {\n let current = obj;\n for (let i = 0; i < keys.length - 1; i++) {\n const key = keys[i]!;\n if (typeof current[key] !== 'object' || current[key] === null) {\n throw new Error(`Invalid path: ${keys.slice(0, i + 1).join('.')}`);\n }\n current = current[key] as Record<string, unknown>;\n }\n const lastKey = keys[keys.length - 1]!;\n if (!(lastKey in current)) {\n throw new Error(`Unknown key: ${keys.join('.')}`);\n }\n current[lastKey] = value;\n }\n}\n\n// Get config value\nclass ConfigGetHandler extends BaseCommand {\n async run(key: string): Promise<void> {\n const tbdRoot = await requireInit();\n\n let config: Config;\n try {\n config = await readConfig(tbdRoot);\n } catch {\n throw new NotInitializedError('No configuration found. Run `tbd init` first.');\n }\n\n const keys = key.split('.');\n let value: unknown = config;\n\n for (const k of keys) {\n if (typeof value !== 'object' || value === null || !(k in value)) {\n throw new ValidationError(`Unknown key: ${key}`);\n }\n value = (value as Record<string, unknown>)[k];\n }\n\n this.output.data({ key, value }, () => {\n console.log(String(value));\n });\n }\n}\n\nconst showConfigCommand = new Command('show')\n .description('Show all configuration')\n .action(async (_options, command) => {\n const handler = new ConfigShowHandler(command);\n await handler.run();\n });\n\nconst setConfigCommand = new Command('set')\n .description('Set a configuration value')\n .argument('<key>', 'Configuration key (e.g., sync.branch)')\n .argument('<value>', 'Value to set')\n .action(async (key, value, _options, command) => {\n const handler = new ConfigSetHandler(command);\n await handler.run(key, value);\n });\n\nconst getConfigCommand = new Command('get')\n .description('Get a configuration value')\n .argument('<key>', 'Configuration key')\n .action(async (key, _options, command) => {\n const handler = new ConfigGetHandler(command);\n await handler.run(key);\n });\n\nexport const configCommand = new Command('config')\n .description('Manage configuration')\n .addCommand(showConfigCommand)\n .addCommand(setConfigCommand)\n .addCommand(getConfigCommand);\n","/**\n * `tbd attic` - Attic (conflict archive) commands.\n *\n * See: tbd-design.md §4.11 Attic Commands\n */\n\nimport { Command } from 'commander';\nimport { readdir, readFile, mkdir } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nimport { parseYamlWithConflictDetection, sortKeys, stringifyYaml } from '../../utils/yaml-utils.js';\n\nimport { writeFile } from 'atomically';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { requireInit, NotFoundError, ValidationError } from '../lib/errors.js';\nimport { readIssue, writeIssue } from '../../file/storage.js';\nimport { normalizeIssueId, formatDisplayId, formatDebugId } from '../../lib/ids.js';\nimport { resolveDataSyncDir, resolveAtticDir } from '../../lib/paths.js';\nimport { formatTimestampAgo } from '../../lib/format-utils.js';\nimport { now } from '../../utils/time-utils.js';\nimport { loadIdMapping } from '../../file/id-mapping.js';\nimport { readConfig } from '../../file/config.js';\nimport type { AtticEntry } from '../../lib/types.js';\nimport { AtticEntrySchema, ATTIC_ENTRY_FIELD_ORDER } from '../../lib/schemas.js';\n\n/**\n * Get attic entry filename from components.\n */\nfunction getAtticFilename(entityId: string, timestamp: string, field: string): string {\n // Convert timestamp colons to hyphens for filesystem safety\n const safeTimestamp = timestamp.replace(/:/g, '-');\n return `${entityId}_${safeTimestamp}_${field}.yml`;\n}\n\n/**\n * Parse attic entry filename to components.\n */\nfunction parseAtticFilename(\n filename: string,\n): { entityId: string; timestamp: string; field: string } | null {\n // Format: is-abc123_2025-01-07T10-30-00Z_description.yml\n const match = /^(is-[a-f0-9]+)_(.+)_([^_]+)\\.yml$/.exec(filename);\n if (!match) return null;\n const [, entityId, timestamp, field] = match;\n // Convert hyphens back to colons in timestamp\n const isoTimestamp = timestamp!.replace(/T(\\d{2})-(\\d{2})-(\\d{2})/, 'T$1:$2:$3');\n return { entityId: entityId!, timestamp: isoTimestamp, field: field! };\n}\n\n/**\n * List all attic entries.\n */\nasync function listAtticEntries(tbdRoot: string, filterById?: string): Promise<AtticEntry[]> {\n const atticPath = await resolveAtticDir(tbdRoot);\n let files: string[];\n\n try {\n files = await readdir(atticPath);\n } catch {\n // Attic directory doesn't exist - return empty\n return [];\n }\n\n const entries: AtticEntry[] = [];\n\n for (const file of files) {\n if (!file.endsWith('.yml')) continue;\n\n const parsed = parseAtticFilename(file);\n if (!parsed) continue;\n\n // Filter by ID if specified\n if (filterById && parsed.entityId !== filterById) continue;\n\n try {\n const filePath = join(atticPath, file);\n const content = await readFile(filePath, 'utf-8');\n const rawData = parseYamlWithConflictDetection<unknown>(content, filePath);\n const entry = AtticEntrySchema.parse(rawData);\n entries.push(entry);\n } catch {\n // Skip invalid files (including those with merge conflicts or schema errors)\n }\n }\n\n // Sort by timestamp descending (most recent first)\n entries.sort((a, b) => b.timestamp.localeCompare(a.timestamp));\n\n return entries;\n}\n\n/**\n * Save an attic entry.\n */\nexport async function saveAtticEntry(tbdRoot: string, entry: AtticEntry): Promise<void> {\n const atticPath = await resolveAtticDir(tbdRoot);\n await mkdir(atticPath, { recursive: true });\n\n const filename = getAtticFilename(entry.entity_id, entry.timestamp, entry.field);\n const filepath = join(atticPath, filename);\n // Sort keys using canonical field order, then serialize\n const sorted = sortKeys(entry as unknown as Record<string, unknown>, ATTIC_ENTRY_FIELD_ORDER);\n const content = stringifyYaml(sorted, { sortMapEntries: false });\n\n await writeFile(filepath, content);\n}\n\n// List attic entries\nclass AtticListHandler extends BaseCommand {\n async run(id?: string): Promise<void> {\n const tbdRoot = await requireInit();\n\n const filterId = id ? normalizeIssueId(id) : undefined;\n const entries = await listAtticEntries(tbdRoot, filterId);\n\n // Load ID mapping and config for display\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n const mapping = await loadIdMapping(dataSyncDir);\n const config = await readConfig(tbdRoot);\n const prefix = config.display.id_prefix;\n const showDebug = this.ctx.debug;\n\n const output = entries.map((e) => ({\n id: showDebug\n ? formatDebugId(e.entity_id, mapping, prefix)\n : formatDisplayId(e.entity_id, mapping, prefix),\n timestamp: e.timestamp,\n field: e.field,\n winner: e.winner_source,\n }));\n\n this.output.data(output, () => {\n const colors = this.output.getColors();\n if (output.length === 0) {\n console.log('No attic entries');\n return;\n }\n console.log(\n `${colors.dim('ID'.padEnd(12))}${colors.dim('WHEN'.padEnd(14))}${colors.dim('FIELD'.padEnd(14))}${colors.dim('WINNER')}`,\n );\n for (const entry of output) {\n const when = formatTimestampAgo(entry.timestamp) ?? entry.timestamp;\n console.log(\n `${colors.id(entry.id.padEnd(12))}${when.padEnd(14)}${entry.field.padEnd(14)}${entry.winner}`,\n );\n }\n });\n }\n}\n\n// Show attic entry\nclass AtticShowHandler extends BaseCommand {\n async run(id: string, timestamp: string): Promise<void> {\n const tbdRoot = await requireInit();\n\n const normalizedId = normalizeIssueId(id);\n const entries = await listAtticEntries(tbdRoot, normalizedId);\n\n // Find entry matching timestamp (approximate match for different formats)\n const entry = entries.find(\n (e) => e.timestamp === timestamp || e.timestamp.replace(/:/g, '-') === timestamp,\n );\n\n if (!entry) {\n throw new NotFoundError('Attic entry', `${id} at ${timestamp}`);\n }\n\n // Load ID mapping and config for display\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n const mapping = await loadIdMapping(dataSyncDir);\n const config = await readConfig(tbdRoot);\n const prefix = config.display.id_prefix;\n const showDebug = this.ctx.debug;\n const displayId = showDebug\n ? formatDebugId(entry.entity_id, mapping, prefix)\n : formatDisplayId(entry.entity_id, mapping, prefix);\n\n this.output.data(entry, () => {\n const colors = this.output.getColors();\n console.log(`${colors.bold('Entity:')} ${displayId}`);\n console.log(`${colors.bold('Timestamp:')} ${entry.timestamp}`);\n console.log(`${colors.bold('Field:')} ${entry.field}`);\n console.log(`${colors.bold('Winner:')} ${entry.winner_source}`);\n console.log(`${colors.bold('Loser:')} ${entry.loser_source}`);\n console.log('');\n console.log(`${colors.bold('Lost value:')}`);\n console.log(entry.lost_value);\n console.log('');\n console.log(`${colors.bold('Context:')}`);\n console.log(` Local version: ${entry.context.local_version}`);\n console.log(` Remote version: ${entry.context.remote_version}`);\n const localAgo = formatTimestampAgo(entry.context.local_updated_at);\n const remoteAgo = formatTimestampAgo(entry.context.remote_updated_at);\n console.log(` Local updated: ${localAgo ?? entry.context.local_updated_at}`);\n console.log(` Remote updated: ${remoteAgo ?? entry.context.remote_updated_at}`);\n });\n }\n}\n\n// Restore from attic\nclass AtticRestoreHandler extends BaseCommand {\n async run(id: string, timestamp: string): Promise<void> {\n const tbdRoot = await requireInit();\n\n const normalizedId = normalizeIssueId(id);\n const entries = await listAtticEntries(tbdRoot, normalizedId);\n\n // Find entry matching timestamp\n const entry = entries.find(\n (e) => e.timestamp === timestamp || e.timestamp.replace(/:/g, '-') === timestamp,\n );\n\n if (!entry) {\n throw new NotFoundError('Attic entry', `${id} at ${timestamp}`);\n }\n\n if (this.checkDryRun('Would restore from attic', { id: normalizedId, field: entry.field })) {\n return;\n }\n\n // Load the current issue\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n let issue;\n try {\n issue = await readIssue(dataSyncDir, normalizedId);\n } catch {\n throw new NotFoundError('Issue', id);\n }\n\n // Restore the field value\n const field = entry.field as keyof typeof issue;\n if (field === 'description' || field === 'notes' || field === 'title') {\n (issue as Record<string, unknown>)[field] = entry.lost_value;\n } else {\n throw new ValidationError(`Cannot restore field: ${entry.field}`);\n }\n\n issue.version += 1;\n issue.updated_at = now();\n\n await this.execute(async () => {\n await writeIssue(dataSyncDir, issue);\n }, 'Failed to restore from attic');\n\n // Load ID mapping and config for display\n const mapping = await loadIdMapping(dataSyncDir);\n const config = await readConfig(tbdRoot);\n const prefix = config.display.id_prefix;\n const showDebug = this.ctx.debug;\n const displayId = showDebug\n ? formatDebugId(normalizedId, mapping, prefix)\n : formatDisplayId(normalizedId, mapping, prefix);\n\n this.output.success(`Restored ${entry.field} for ${displayId} from attic entry ${timestamp}`);\n }\n}\n\ninterface AtticListOptions {\n since?: string;\n limit?: string;\n}\n\nconst listAtticCommand = new Command('list')\n .description('List attic entries')\n .argument('[id]', 'Filter by issue ID')\n .option('--since <date>', 'Entries since date')\n .option('--limit <n>', 'Limit results')\n .action(async (id, options: AtticListOptions, command) => {\n const handler = new AtticListHandler(command);\n await handler.run(id);\n });\n\nconst showAtticCommand = new Command('show')\n .description('Show attic entry details')\n .argument('<id>', 'Issue ID')\n .argument('<timestamp>', 'Entry timestamp')\n .action(async (id, timestamp, _options, command) => {\n const handler = new AtticShowHandler(command);\n await handler.run(id, timestamp);\n });\n\nconst restoreAtticCommand = new Command('restore')\n .description('Restore lost value from attic')\n .argument('<id>', 'Issue ID')\n .argument('<timestamp>', 'Entry timestamp')\n .action(async (id, timestamp, _options, command) => {\n const handler = new AtticRestoreHandler(command);\n await handler.run(id, timestamp);\n });\n\nexport const atticCommand = new Command('attic')\n .description('Manage conflict archive (attic)')\n .addCommand(listAtticCommand)\n .addCommand(showAtticCommand)\n .addCommand(restoreAtticCommand);\n","/**\n * `tbd import` - Import from Beads or other sources.\n *\n * See: tbd-design.md §5.1 Import Strategy\n */\n\nimport { Command } from 'commander';\nimport { readFile, access } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { requireInit, ValidationError, NotFoundError } from '../lib/errors.js';\nimport { writeIssue, listIssues } from '../../file/storage.js';\nimport {\n generateInternalId,\n extractShortId,\n extractUlidFromInternalId,\n makeInternalId,\n extractPrefix,\n} from '../../lib/ids.js';\nimport {\n loadIdMapping,\n saveIdMapping,\n addIdMapping,\n hasShortId,\n generateUniqueShortId,\n} from '../../file/id-mapping.js';\nimport { IssueStatus, IssueKind } from '../../lib/schemas.js';\nimport type { Issue, IssueStatusType, IssueKindType, DependencyType } from '../../lib/types.js';\nimport { resolveDataSyncDir } from '../../lib/paths.js';\nimport { now, normalizeTimestamp } from '../../utils/time-utils.js';\nimport { readConfig, writeConfig } from '../../file/config.js';\nimport {\n importFromWorkspace,\n type ImportOptions as WorkspaceImportOptions,\n} from '../../file/workspace.js';\n\ninterface ImportOptions {\n beadsDir?: string;\n merge?: boolean;\n verbose?: boolean;\n validate?: boolean;\n // Workspace import options\n workspace?: string;\n dir?: string;\n outbox?: boolean;\n clearOnSuccess?: boolean;\n}\n\ninterface ValidationIssue {\n beadsId: string;\n tbdId?: string;\n issue: string;\n severity: 'error' | 'warning';\n}\n\n/**\n * Beads issue structure (from JSONL export).\n */\ninterface BeadsIssue {\n id: string;\n title: string;\n description?: string;\n notes?: string;\n type?: string;\n issue_type?: string;\n status: string;\n priority?: number | string;\n assignee?: string;\n labels?: string[];\n dependencies?: { type: string; target: string }[];\n created_at: string;\n updated_at: string;\n closed_at?: string;\n close_reason?: string;\n due?: string;\n defer?: string;\n parent?: string;\n}\n\n/**\n * BeadsTotbd mapping: maps beads external ID to tbd internal ID.\n * This is a local structure used during import processing.\n */\ntype BeadsTotbdMapping = Record<string, string>;\n\n/**\n * Map Beads status to tbd status.\n */\nfunction mapStatus(beadsStatus: string): IssueStatusType {\n const statusMap: Record<string, IssueStatusType> = {\n open: 'open',\n in_progress: 'in_progress',\n blocked: 'blocked',\n deferred: 'deferred',\n done: 'closed', // Beads uses 'done' for completed items\n closed: 'closed',\n tombstone: 'closed',\n };\n const result = IssueStatus.safeParse(statusMap[beadsStatus] ?? beadsStatus);\n return result.success ? result.data : 'open';\n}\n\n/**\n * Map Beads issue type to tbd kind.\n */\nfunction mapKind(beadsType?: string): IssueKindType {\n const kindMap: Record<string, IssueKindType> = {\n bug: 'bug',\n feature: 'feature',\n task: 'task',\n epic: 'epic',\n chore: 'chore',\n };\n if (!beadsType) return 'task';\n const result = IssueKind.safeParse(kindMap[beadsType] ?? beadsType);\n return result.success ? result.data : 'task';\n}\n\n/**\n * Map Beads priority to tbd priority (0-4 integer).\n * Handles numeric values, \"P0\"-\"P4\" strings, and fallback to default P2.\n */\nfunction mapPriority(priority: unknown): number {\n if (\n typeof priority === 'number' &&\n Number.isInteger(priority) &&\n priority >= 0 &&\n priority <= 4\n ) {\n return priority;\n }\n if (typeof priority === 'string') {\n const match = /^[Pp]?(\\d)$/.exec(priority.trim());\n if (match) {\n const num = parseInt(match[1]!, 10);\n if (num >= 0 && num <= 4) return num;\n }\n }\n return 2; // Default P2\n}\n\n/**\n * Convert Beads issue to tbd issue.\n */\nfunction convertIssue(beads: BeadsIssue, tbdId: string, depMapping: BeadsTotbdMapping): Issue {\n // Convert dependencies, translating IDs\n const dependencies: DependencyType[] = [];\n if (beads.dependencies) {\n for (const dep of beads.dependencies) {\n if (dep.type === 'blocks' || dep.type === 'blocked_by') {\n const targetId = depMapping[dep.target];\n if (targetId) {\n // \"blocked_by\" in Beads means the target blocks this issue\n // In tbd, we only have \"blocks\", so we need to handle this carefully\n // For now, we store \"blocks\" dependencies directly\n if (dep.type === 'blocks') {\n dependencies.push({ type: 'blocks', target: targetId });\n }\n // Note: blocked_by would need to be added to the target issue's dependencies\n }\n }\n }\n }\n\n return {\n type: 'is',\n id: tbdId,\n version: 1,\n kind: mapKind(beads.type ?? beads.issue_type),\n title: beads.title,\n description: beads.description,\n notes: beads.notes,\n status: mapStatus(beads.status),\n priority: mapPriority(beads.priority),\n assignee: beads.assignee,\n labels: beads.labels ?? [],\n dependencies,\n created_at: normalizeTimestamp(beads.created_at) ?? now(),\n updated_at: normalizeTimestamp(beads.updated_at) ?? now(),\n closed_at: normalizeTimestamp(beads.closed_at),\n close_reason: beads.close_reason ?? null,\n due_date: normalizeTimestamp(beads.due),\n deferred_until: normalizeTimestamp(beads.defer),\n parent_id: beads.parent ? depMapping[beads.parent] : null,\n extensions: {\n beads: {\n original_id: beads.id,\n imported_at: now(),\n },\n },\n };\n}\n\nclass ImportHandler extends BaseCommand {\n private dataSyncDir = '';\n private tbdRoot = '';\n\n async run(file: string | undefined, options: ImportOptions): Promise<void> {\n // Check if this is a workspace import\n const isWorkspaceImport =\n options.workspace != null || options.dir != null || options.outbox === true;\n\n if (isWorkspaceImport) {\n await this.importFromWorkspaceCmd(options);\n return;\n }\n\n // Validate input first\n if (!file && !options.validate) {\n throw new ValidationError(\n 'Provide a JSONL file path to import.\\n\\n' +\n 'For Beads migration, use: tbd setup --from-beads\\n' +\n 'For workspace import, use: tbd import --workspace=<name> or --outbox',\n );\n }\n\n // Handle validation mode - requires init\n if (options.validate) {\n this.tbdRoot = await requireInit();\n this.dataSyncDir = await resolveDataSyncDir(this.tbdRoot);\n await this.validateImport(options);\n return;\n }\n\n // File import requires initialization\n if (file) {\n this.tbdRoot = await requireInit();\n this.dataSyncDir = await resolveDataSyncDir(this.tbdRoot);\n await this.importFromFile(file, options);\n }\n }\n\n /**\n * Import issues from a workspace.\n */\n private async importFromWorkspaceCmd(options: ImportOptions): Promise<void> {\n this.tbdRoot = await requireInit();\n this.dataSyncDir = await resolveDataSyncDir(this.tbdRoot);\n\n const wsOptions: WorkspaceImportOptions = {\n workspace: options.workspace,\n dir: options.dir,\n outbox: options.outbox,\n clearOnSuccess: options.clearOnSuccess,\n };\n\n if (this.checkDryRun('Would import from workspace', wsOptions)) {\n return;\n }\n\n const spinner = this.output.spinner('Importing from workspace...');\n wsOptions.logger = this.output.logger(spinner);\n\n const result = await this.execute(async () => {\n return await importFromWorkspace(this.tbdRoot, this.dataSyncDir, wsOptions);\n }, 'Failed to import from workspace');\n\n spinner.stop();\n\n if (!result) {\n return;\n }\n\n // Format output\n const sourceName = options.outbox ? 'outbox' : (options.workspace ?? options.dir ?? 'unknown');\n\n this.output.data(\n {\n imported: result.imported,\n conflicts: result.conflicts,\n source: sourceName,\n cleared: result.cleared,\n },\n () => {\n if (result.imported === 0) {\n this.output.info('No issues to import');\n } else {\n this.output.success(`Imported ${result.imported} issue(s) from ${sourceName}`);\n if (result.conflicts > 0) {\n this.output.warn(`${result.conflicts} conflict(s) moved to attic`);\n }\n if (result.cleared) {\n this.output.info(`Workspace \"${sourceName}\" cleared`);\n }\n // Suggest next step\n this.output.info('Run `tbd sync` to commit and push imported issues');\n }\n },\n );\n }\n\n /**\n * Validate import by comparing Beads source with imported tbd issues.\n * Reports any discrepancies or missing issues.\n */\n private async validateImport(options: ImportOptions): Promise<void> {\n const beadsDir = options.beadsDir ?? '.beads';\n const jsonlPath = join(beadsDir, 'issues.jsonl');\n\n try {\n await access(jsonlPath);\n } catch {\n throw new NotFoundError('Beads database', `${beadsDir} (use --beads-dir to specify)`);\n }\n\n console.log('Validating import...\\n');\n\n // Load Beads issues\n const content = await readFile(jsonlPath, 'utf-8');\n const lines = content\n .trim()\n .split('\\n')\n .filter((l) => l);\n const beadsIssues: BeadsIssue[] = [];\n\n for (const line of lines) {\n try {\n const issue = JSON.parse(line) as BeadsIssue;\n if (issue.id && issue.title) {\n beadsIssues.push(issue);\n }\n } catch {\n // Skip invalid lines\n }\n }\n\n // Load tbd issues and short ID mapping\n const tbdIssues = await this.loadExistingIssues();\n const shortIdMapping = await loadIdMapping(this.dataSyncDir);\n\n // Build mapping from beads ID to tbd internal ID using preserved short IDs\n // e.g., \"tbd-100\" -> extract \"100\" -> lookup in shortIdMapping -> \"is-{ulid}\"\n const beadsTotbd: BeadsTotbdMapping = {};\n const reverseMapping: Record<string, string> = {};\n\n for (const beads of beadsIssues) {\n const shortId = extractShortId(beads.id);\n const ulid = shortIdMapping.shortToUlid.get(shortId);\n if (ulid) {\n const internalId = makeInternalId(ulid);\n beadsTotbd[beads.id] = internalId;\n reverseMapping[internalId] = beads.id;\n }\n }\n\n // Build lookup by tbd ID\n const tbdById = new Map<string, Issue>();\n for (const issue of tbdIssues) {\n tbdById.set(issue.id, issue);\n }\n\n // Validate each Beads issue\n const issues: ValidationIssue[] = [];\n let validCount = 0;\n\n for (const beads of beadsIssues) {\n const tbdId = beadsTotbd[beads.id];\n\n if (!tbdId) {\n issues.push({\n beadsId: beads.id,\n issue: 'Not imported - no ID mapping exists',\n severity: 'error',\n });\n continue;\n }\n\n const tbdIssue = tbdById.get(tbdId);\n if (!tbdIssue) {\n issues.push({\n beadsId: beads.id,\n tbdId,\n issue: 'ID mapping exists but issue file not found',\n severity: 'error',\n });\n continue;\n }\n\n // Validate fields\n const fieldIssues: string[] = [];\n\n if (tbdIssue.title !== beads.title) {\n fieldIssues.push(`title mismatch: \"${tbdIssue.title}\" vs \"${beads.title}\"`);\n }\n\n const expectedStatus = mapStatus(beads.status);\n if (tbdIssue.status !== expectedStatus) {\n fieldIssues.push(`status mismatch: \"${tbdIssue.status}\" vs expected \"${expectedStatus}\"`);\n }\n\n const expectedKind = mapKind(beads.type ?? beads.issue_type);\n if (tbdIssue.kind !== expectedKind) {\n fieldIssues.push(`kind mismatch: \"${tbdIssue.kind}\" vs expected \"${expectedKind}\"`);\n }\n\n if (mapPriority(beads.priority) !== tbdIssue.priority) {\n fieldIssues.push(\n `priority mismatch: ${tbdIssue.priority} vs ${mapPriority(beads.priority)}`,\n );\n }\n\n // Check labels\n const beadsLabels = new Set(beads.labels ?? []);\n const tbdLabels = new Set(tbdIssue.labels ?? []);\n const missingLabels = [...beadsLabels].filter((l) => !tbdLabels.has(l));\n if (missingLabels.length > 0) {\n fieldIssues.push(`missing labels: ${missingLabels.join(', ')}`);\n }\n\n if (fieldIssues.length > 0) {\n issues.push({\n beadsId: beads.id,\n tbdId,\n issue: fieldIssues.join('; '),\n severity: 'warning',\n });\n } else {\n validCount++;\n }\n }\n\n // Check for orphaned tbd issues (not in Beads)\n const beadsIds = new Set(beadsIssues.map((b) => b.id));\n for (const tbdIssue of tbdIssues) {\n const beadsId = reverseMapping[tbdIssue.id];\n if (beadsId && !beadsIds.has(beadsId)) {\n issues.push({\n beadsId,\n tbdId: tbdIssue.id,\n issue: 'tbd issue has mapping but Beads issue no longer exists',\n severity: 'warning',\n });\n }\n }\n\n // Report results\n const errors = issues.filter((i) => i.severity === 'error');\n const warnings = issues.filter((i) => i.severity === 'warning');\n\n console.log('Validation Results');\n console.log('─'.repeat(60));\n console.log(`Total Beads issues: ${beadsIssues.length}`);\n console.log(`Total tbd issues: ${tbdIssues.length}`);\n console.log(`Valid imports: ${validCount}`);\n console.log(`Errors: ${errors.length}`);\n console.log(`Warnings: ${warnings.length}`);\n console.log('─'.repeat(60));\n\n if (errors.length > 0) {\n console.log('\\nErrors:');\n for (const err of errors) {\n console.log(` ✗ ${err.beadsId}: ${err.issue}`);\n }\n }\n\n if (warnings.length > 0 && options.verbose) {\n console.log('\\nWarnings:');\n for (const warn of warnings) {\n console.log(` ⚠ ${warn.beadsId}: ${warn.issue}`);\n }\n }\n\n console.log();\n if (errors.length === 0 && warnings.length === 0) {\n this.output.success('All imports validated successfully!');\n } else if (errors.length === 0) {\n this.output.warn(`Validation complete with ${warnings.length} warnings`);\n if (!options.verbose) {\n console.log(' Use --verbose to see warning details');\n }\n } else {\n this.output.error(`Validation failed with ${errors.length} errors`);\n }\n\n // Output JSON for programmatic use\n this.output.data({\n valid: validCount,\n errors: errors.length,\n warnings: warnings.length,\n total: beadsIssues.length,\n issues: options.verbose ? issues : undefined,\n });\n }\n\n private async importFromFile(filePath: string, options: ImportOptions): Promise<void> {\n // Check file exists\n try {\n await access(filePath);\n } catch {\n throw new NotFoundError('File', filePath);\n }\n\n if (this.checkDryRun('Would import issues', { file: filePath })) {\n // For dry run, still parse and show what would happen\n const content = await readFile(filePath, 'utf-8');\n const lines = content\n .trim()\n .split('\\n')\n .filter((l) => l);\n this.output.info(`Would import ${lines.length} issues from ${filePath}`);\n return;\n }\n\n // Load file content\n const content = await readFile(filePath, 'utf-8');\n const lines = content\n .trim()\n .split('\\n')\n .filter((l) => l);\n\n // Parse JSONL\n const beadsIssues: BeadsIssue[] = [];\n for (const line of lines) {\n try {\n const issue = JSON.parse(line) as BeadsIssue;\n if (issue.id && issue.title) {\n beadsIssues.push(issue);\n }\n } catch {\n if (options.verbose) {\n this.output.warn(`Skipping invalid JSON line`);\n }\n }\n }\n\n if (beadsIssues.length === 0) {\n this.output.info('No valid issues found in file');\n return;\n }\n\n // Auto-detect prefix from imported issues and update config if needed\n const detectedPrefix = this.detectPrefixFromIssues(beadsIssues);\n await this.updateConfigPrefixIfNeeded(detectedPrefix);\n\n // Load existing issues and short ID mapping\n const existingIssues = await this.loadExistingIssues();\n const shortIdMapping = await loadIdMapping(this.dataSyncDir);\n\n // Build lookup maps\n const existingByBeadsId = new Map<string, Issue>();\n const existingByShortId = new Map<string, Issue>();\n\n // Build reverse lookup from extensions and from short ID mapping\n for (const issue of existingIssues) {\n const beadsExt = issue.extensions?.beads as { original_id?: string } | undefined;\n if (beadsExt?.original_id) {\n existingByBeadsId.set(beadsExt.original_id, issue);\n }\n // Also track by short ID\n const ulid = extractUlidFromInternalId(issue.id);\n const shortId = shortIdMapping.ulidToShort.get(ulid);\n if (shortId) {\n existingByShortId.set(shortId, issue);\n }\n }\n\n // Build beads-to-tbd mapping, preserving original short IDs\n // e.g., \"tbd-100\" preserves \"100\" as the short ID\n const beadsTotbd: BeadsTotbdMapping = {};\n\n // First pass: assign IDs to all issues (needed for dependency translation)\n for (const beads of beadsIssues) {\n // Extract the short ID from beads ID (e.g., \"tbd-100\" -> \"100\")\n const shortId = extractShortId(beads.id);\n\n // Check if we already have this issue by beads ID (from previous import)\n const existingByBeads = existingByBeadsId.get(beads.id);\n if (existingByBeads) {\n beadsTotbd[beads.id] = existingByBeads.id;\n continue;\n }\n\n // Check if we already have a mapping for this short ID\n const existingByShort = existingByShortId.get(shortId);\n if (existingByShort) {\n beadsTotbd[beads.id] = existingByShort.id;\n continue;\n }\n\n // Check if the short ID is already in the mapping (collision check)\n if (hasShortId(shortIdMapping, shortId)) {\n // Short ID already exists but for a different issue - generate a new one\n if (options.verbose) {\n this.output.warn(\n `Short ID \"${shortId}\" already exists, generating new ID for ${beads.id}`,\n );\n }\n const internalId = generateInternalId();\n beadsTotbd[beads.id] = internalId;\n // Generate a random short ID since the original is taken\n const ulid = extractUlidFromInternalId(internalId);\n const newShortId = generateUniqueShortId(shortIdMapping);\n addIdMapping(shortIdMapping, ulid, newShortId);\n } else {\n // Create new mapping, preserving the original short ID\n const internalId = generateInternalId();\n beadsTotbd[beads.id] = internalId;\n const ulid = extractUlidFromInternalId(internalId);\n addIdMapping(shortIdMapping, ulid, shortId);\n }\n }\n\n // Second pass: convert and save issues\n let imported = 0;\n let skipped = 0;\n let merged = 0;\n\n for (const beads of beadsIssues) {\n const tbdId = beadsTotbd[beads.id]!;\n const existing = existingByBeadsId.get(beads.id);\n\n if (existing && !options.merge) {\n // Check if Beads is newer\n if (new Date(beads.updated_at) <= new Date(existing.updated_at)) {\n skipped++;\n continue;\n }\n }\n\n const issue = convertIssue(beads, tbdId, beadsTotbd);\n\n if (existing) {\n // Merge: keep higher version, update fields\n issue.version = existing.version + 1;\n merged++;\n } else {\n imported++;\n }\n\n try {\n await writeIssue(this.dataSyncDir, issue);\n } catch (error) {\n if (options.verbose) {\n this.output.warn(`Failed to write issue ${beads.id}: ${(error as Error).message}`);\n }\n }\n }\n\n // Save updated short ID mapping (no separate beads.yml needed - IDs are preserved)\n await saveIdMapping(this.dataSyncDir, shortIdMapping);\n\n const result = { imported, skipped, merged, total: beadsIssues.length };\n\n this.output.data(result, () => {\n this.output.success(`Import complete from ${filePath}`);\n console.log(` New issues: ${imported}`);\n console.log(` Merged: ${merged}`);\n console.log(` Skipped: ${skipped}`);\n });\n }\n\n private async loadExistingIssues(): Promise<Issue[]> {\n try {\n return await listIssues(this.dataSyncDir);\n } catch {\n return [];\n }\n }\n\n /**\n * Detect the prefix used by beads issues from a file path.\n * Reads the first few issues and extracts the common prefix pattern.\n * Falls back to 'tbd' if no consistent prefix is found.\n */\n private async detectBeadsPrefix(jsonlPath: string): Promise<string> {\n try {\n const content = await readFile(jsonlPath, 'utf-8');\n const lines = content\n .trim()\n .split('\\n')\n .filter((l) => l)\n .slice(0, 10); // Sample first 10 issues\n\n const issues: BeadsIssue[] = [];\n for (const line of lines) {\n try {\n const issue = JSON.parse(line) as BeadsIssue;\n if (issue.id) {\n issues.push(issue);\n }\n } catch {\n // Skip invalid lines\n }\n }\n\n return this.detectPrefixFromIssues(issues);\n } catch {\n return 'tbd'; // Default fallback\n }\n }\n\n /**\n * Detect the prefix used by a list of beads issues.\n * Extracts the common prefix pattern from issue IDs.\n * Falls back to 'tbd' if no consistent prefix is found.\n */\n private detectPrefixFromIssues(issues: BeadsIssue[]): string {\n const prefixes = new Map<string, number>();\n\n for (const issue of issues.slice(0, 10)) {\n // Sample first 10\n if (issue.id) {\n const prefix = extractPrefix(issue.id);\n if (prefix) {\n prefixes.set(prefix, (prefixes.get(prefix) ?? 0) + 1);\n }\n }\n }\n\n // Find the most common prefix\n let maxCount = 0;\n let mostCommonPrefix = 'tbd';\n for (const [prefix, count] of prefixes) {\n if (count > maxCount) {\n maxCount = count;\n mostCommonPrefix = prefix;\n }\n }\n\n return mostCommonPrefix;\n }\n\n /**\n * Update config prefix if it differs from the detected prefix.\n * Returns true if prefix was updated.\n */\n private async updateConfigPrefixIfNeeded(detectedPrefix: string): Promise<boolean> {\n try {\n const config = await readConfig(this.tbdRoot);\n if (config.display.id_prefix !== detectedPrefix) {\n const oldPrefix = config.display.id_prefix;\n config.display.id_prefix = detectedPrefix;\n await writeConfig(this.tbdRoot, config);\n this.output.info(`Updated ID prefix: ${oldPrefix} → ${detectedPrefix}`);\n return true;\n }\n return false;\n } catch {\n // Config doesn't exist or can't be read - skip update\n return false;\n }\n }\n}\n\nexport const importCommand = new Command('import')\n .description(\n 'Import issues from JSONL file or workspace.\\n' +\n 'For Beads migration, use: tbd setup --from-beads\\n' +\n 'For workspace import, use: tbd import --workspace=<name> or --outbox',\n )\n .argument('[file]', 'JSONL file to import')\n .option('--beads-dir <path>', 'Beads data directory (for --validate)')\n .option('--merge', 'Merge with existing issues instead of skipping duplicates')\n .option('--verbose', 'Show detailed import progress')\n .option('--validate', 'Validate existing import against Beads source')\n // Workspace import options\n .option('--workspace <name>', 'Import from named workspace under .tbd/workspaces/')\n .option('--dir <path>', 'Import from arbitrary directory')\n .option('--outbox', 'Shortcut for --workspace=outbox --clear-on-success')\n .option('--clear-on-success', 'Delete workspace after successful import')\n .action(async (file, options, command) => {\n const handler = new ImportHandler(command);\n await handler.run(file, options);\n });\n","/**\n * `tbd docs` - Display CLI documentation.\n *\n * Shows the bundled documentation for tbd CLI.\n * Documentation can be filtered by section.\n *\n * Note: Doc cache sync functionality has moved to `tbd sync --docs`.\n * See: docs/project/specs/active/plan-2026-01-29-unified-sync-command.md\n */\n\nimport { Command } from 'commander';\nimport { readFile } from 'node:fs/promises';\nimport { fileURLToPath } from 'node:url';\nimport { dirname, join } from 'node:path';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { shouldUseInteractiveOutput } from '../lib/context.js';\nimport { CLIError, NotFoundError } from '../lib/errors.js';\nimport { renderMarkdown, paginateOutput } from '../lib/output.js';\nimport type { DocSection } from '../../lib/types.js';\nimport GithubSlugger from 'github-slugger';\n\n/**\n * Get the path to the bundled docs file.\n * The docs file is copied to dist/docs/ during build.\n */\nfunction getDocsPath(): string {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n // When bundled, runs from dist/bin.mjs or dist/cli.mjs\n // Docs are at dist/docs/tbd-docs.md (same level as the bundle)\n return join(__dirname, 'docs', 'tbd-docs.md');\n}\n\ninterface DocsOptions {\n section?: string;\n list?: boolean;\n all?: boolean;\n}\n\nclass DocsHandler extends BaseCommand {\n async run(topic: string | undefined, options: DocsOptions): Promise<void> {\n let content: string;\n try {\n content = await readFile(getDocsPath(), 'utf-8');\n } catch {\n // Fallback: try to read from source location during development\n try {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n // During development: src/cli/commands -> packages/tbd/docs\n const devPath = join(__dirname, '..', '..', '..', 'docs', 'tbd-docs.md');\n content = await readFile(devPath, 'utf-8');\n } catch {\n throw new CLIError('Documentation file not found. Please rebuild the CLI.');\n }\n }\n\n const sections = this.extractSections(content);\n\n // Show comprehensive documentation listing\n if (options.all) {\n await this.showComprehensiveListing();\n return;\n }\n\n // List available sections\n if (options.list) {\n this.output.data(sections, () => {\n const colors = this.output.getColors();\n console.log(colors.bold('Available documentation sections:'));\n console.log('');\n // Calculate max slug length for alignment\n const maxSlugLen = Math.max(...sections.map((s) => s.slug.length));\n for (const section of sections) {\n const paddedSlug = section.slug.padEnd(maxSlugLen);\n console.log(` ${colors.id(paddedSlug)} ${section.title}`);\n }\n console.log('');\n console.log(`Use ${colors.dim('tbd docs <topic>')} to view a specific section.`);\n });\n return;\n }\n\n // Determine which section to show (positional topic takes precedence)\n const sectionQuery = topic ?? options.section;\n\n // Filter by section if specified\n if (sectionQuery) {\n const sectionContent = this.extractSection(content, sections, sectionQuery);\n if (!sectionContent) {\n throw new NotFoundError(\n 'Section',\n `\"${sectionQuery}\" (use --list to see available sections)`,\n );\n }\n content = sectionContent;\n }\n\n // Output the documentation with Markdown colorization and pagination for interactive\n if (shouldUseInteractiveOutput(this.ctx)) {\n const rendered = renderMarkdown(content, this.ctx.color);\n await paginateOutput(rendered, true);\n } else {\n console.log(content);\n }\n }\n\n /**\n * Extract section metadata from the documentation.\n * Sections are top-level headers (## ).\n * Returns title and slugified ID for each section.\n */\n private extractSections(content: string): DocSection[] {\n const sections: DocSection[] = [];\n const lines = content.split('\\n');\n const slugger = new GithubSlugger();\n\n for (const line of lines) {\n if (line.startsWith('## ')) {\n const title = line.slice(3).trim();\n const slug = slugger.slug(title);\n sections.push({ title, slug });\n }\n }\n\n return sections;\n }\n\n /**\n * Extract a specific section from the documentation.\n * Matches by slug or partial title match.\n * Returns content from the section header to the next section header.\n */\n private extractSection(content: string, sections: DocSection[], query: string): string | null {\n const lowerQuery = query.toLowerCase();\n\n // Find matching section - first try exact slug match, then partial title match\n const matchedSection =\n sections.find((s) => s.slug === lowerQuery) ??\n sections.find((s) => s.title.toLowerCase().includes(lowerQuery));\n\n if (!matchedSection) {\n return null;\n }\n\n const lines = content.split('\\n');\n let inSection = false;\n const sectionLines: string[] = [];\n\n for (const line of lines) {\n if (line.startsWith('## ')) {\n if (inSection) {\n // End of our section\n break;\n }\n const currentTitle = line.slice(3).trim();\n if (currentTitle === matchedSection.title) {\n inSection = true;\n sectionLines.push(line);\n }\n } else if (inSection) {\n sectionLines.push(line);\n }\n }\n\n if (sectionLines.length === 0) {\n return null;\n }\n\n // Trim trailing empty lines\n while (sectionLines.length > 0) {\n const lastLine = sectionLines[sectionLines.length - 1];\n if (lastLine?.trim() === '') {\n sectionLines.pop();\n } else {\n break;\n }\n }\n\n return sectionLines.join('\\n');\n }\n\n /**\n * Show a comprehensive listing of all documentation resources organized by purpose.\n */\n private async showComprehensiveListing(): Promise<void> {\n const colors = this.output.getColors();\n\n console.log(colors.bold('=== tbd Documentation Resources ==='));\n console.log('');\n\n // Getting Started\n console.log(colors.bold('Getting Started:'));\n console.log(' tbd Full orientation and project status');\n console.log(' tbd prime Workflow context and guidance');\n console.log(' tbd prime --brief Quick reference (~35 lines)');\n console.log(' tbd --help CLI command reference');\n console.log('');\n\n // Workflows (Shortcuts)\n console.log(colors.bold('Workflows (Shortcuts):'));\n console.log(' tbd shortcut --list List all available shortcuts');\n console.log(' tbd shortcut new-plan-spec Plan a new feature');\n console.log(' tbd shortcut code-review-and-commit Commit code properly');\n console.log(' tbd shortcut create-or-update-pr-simple Create a pull request');\n console.log('');\n\n // Guidelines\n console.log(colors.bold('Guidelines (Coding Standards):'));\n console.log(' tbd guidelines --list List all available guidelines');\n console.log(' tbd guidelines typescript-rules TypeScript best practices');\n console.log(' tbd guidelines general-tdd-guidelines Test-driven development');\n console.log(' tbd guidelines golden-testing-guidelines Snapshot/golden testing');\n console.log('');\n\n // Templates\n console.log(colors.bold('Templates:'));\n console.log(' tbd template --list List all available templates');\n console.log(' tbd template plan-spec Feature planning template');\n console.log(' tbd template architecture-doc Architecture document template');\n console.log('');\n\n // Design & Reference\n console.log(colors.bold('Design & Reference:'));\n console.log(' tbd docs --list List documentation sections');\n console.log(' tbd design tbd design document');\n console.log(' tbd closing Session closing protocol');\n console.log('');\n\n // Quick Tips\n console.log(colors.bold('Quick Tips:'));\n console.log(' - Run tbd ready to see what issues are available to work on');\n console.log(' - Run tbd shortcut <name> to get step-by-step instructions');\n console.log(' - Run tbd guidelines <name> to get coding standards');\n console.log(' - Always run tbd sync at the end of a session');\n }\n}\n\nexport const docsCommand = new Command('docs')\n .description('Display CLI documentation (use tbd sync --docs for doc cache sync)')\n .argument('[topic]', 'Topic to display (e.g., \"commands\", \"id-system\")')\n .option('--section <name>', 'Show specific section (e.g., \"commands\", \"workflows\")')\n .option('--list', 'List available sections')\n .option('--all', 'Show comprehensive listing of all documentation resources')\n .action(async (topic: string | undefined, options: DocsOptions, command: Command) => {\n const handler = new DocsHandler(command);\n await handler.run(topic, options);\n });\n","/**\n * `tbd closing` - Display the session closing protocol reminder.\n *\n * Shows the close protocol checklist for completing work.\n * Used by the Claude Code PostToolUse hook after git push.\n */\n\nimport { Command } from 'commander';\nimport { readFile } from 'node:fs/promises';\nimport { fileURLToPath } from 'node:url';\nimport { dirname, join } from 'node:path';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { CLIError } from '../lib/errors.js';\nimport { renderMarkdown } from '../lib/output.js';\n\n/**\n * Get the path to the bundled closing file.\n * The file is copied to dist/docs/ during build.\n */\nfunction getCloseProtocolPath(): string {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n return join(__dirname, 'docs', 'tbd-closing.md');\n}\n\nclass CloseProtocolHandler extends BaseCommand {\n async run(): Promise<void> {\n let content: string;\n try {\n content = await readFile(getCloseProtocolPath(), 'utf-8');\n } catch {\n // Fallback: try to read from source location during development\n try {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n const devPath = join(__dirname, '..', '..', 'docs', 'tbd-closing.md');\n content = await readFile(devPath, 'utf-8');\n } catch {\n // Last fallback: repo-level docs\n try {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n const repoPath = join(__dirname, '..', '..', '..', 'docs', 'tbd-closing.md');\n content = await readFile(repoPath, 'utf-8');\n } catch {\n throw new CLIError('Close protocol file not found. Please rebuild the CLI.');\n }\n }\n }\n\n console.log(renderMarkdown(content, this.ctx.color));\n }\n}\n\nexport const closeProtocolCommand = new Command('closing')\n .description('Display the session closing protocol reminder')\n .action(async (_options: unknown, command: Command) => {\n const handler = new CloseProtocolHandler(command);\n await handler.run();\n });\n","/**\n * `tbd design` - Display design documentation.\n *\n * Shows the bundled design documentation for tbd,\n * including architecture, design decisions, and Beads comparison.\n */\n\nimport { Command } from 'commander';\nimport { readFile } from 'node:fs/promises';\nimport { fileURLToPath } from 'node:url';\nimport { dirname, join } from 'node:path';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { CLIError, NotFoundError } from '../lib/errors.js';\nimport { renderMarkdown } from '../lib/output.js';\nimport type { DocSection } from '../../lib/types.js';\nimport GithubSlugger from 'github-slugger';\n\n/**\n * Get the path to the bundled design doc file.\n * The design doc is copied to dist/docs/ during build.\n */\nfunction getDesignPath(): string {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n // When bundled, runs from dist/bin.mjs or dist/cli.mjs\n // Docs are at dist/docs/tbd-design.md (same level as the bundle)\n return join(__dirname, 'docs', 'tbd-design.md');\n}\n\ninterface DesignOptions {\n section?: string;\n list?: boolean;\n}\n\nclass DesignHandler extends BaseCommand {\n async run(topic: string | undefined, options: DesignOptions): Promise<void> {\n let content: string;\n try {\n content = await readFile(getDesignPath(), 'utf-8');\n } catch {\n // Fallback: try to read from source location during development\n try {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n // During development: src/cli/commands -> packages/tbd/docs\n const devPath = join(__dirname, '..', '..', '..', 'docs', 'tbd-design.md');\n content = await readFile(devPath, 'utf-8');\n } catch {\n throw new CLIError('Design documentation file not found. Please rebuild the CLI.');\n }\n }\n\n const sections = this.extractSections(content);\n\n // List available sections\n if (options.list) {\n this.output.data(sections, () => {\n const colors = this.output.getColors();\n console.log(colors.bold('Available design documentation sections:'));\n console.log('');\n // Calculate max slug length for alignment\n const maxSlugLen = Math.max(...sections.map((s) => s.slug.length));\n for (const section of sections) {\n const paddedSlug = section.slug.padEnd(maxSlugLen);\n console.log(` ${colors.id(paddedSlug)} ${section.title}`);\n }\n console.log('');\n console.log(`Use ${colors.dim('tbd design <topic>')} to view a specific section.`);\n });\n return;\n }\n\n // Determine which section to show (positional topic takes precedence)\n const sectionQuery = topic ?? options.section;\n\n // Filter by section if specified\n if (sectionQuery) {\n const sectionContent = this.extractSection(content, sections, sectionQuery);\n if (!sectionContent) {\n throw new NotFoundError(\n 'Section',\n `\"${sectionQuery}\" (use --list to see available sections)`,\n );\n }\n content = sectionContent;\n }\n\n // Output the documentation with Markdown colorization\n console.log(renderMarkdown(content, this.ctx.color));\n }\n\n /**\n * Extract section metadata from the documentation.\n * Sections are top-level headers (## ).\n * Returns title and slugified ID for each section.\n */\n private extractSections(content: string): DocSection[] {\n const sections: DocSection[] = [];\n const lines = content.split('\\n');\n const slugger = new GithubSlugger();\n\n for (const line of lines) {\n if (line.startsWith('## ')) {\n const title = line.slice(3).trim();\n const slug = slugger.slug(title);\n sections.push({ title, slug });\n }\n }\n\n return sections;\n }\n\n /**\n * Extract a specific section from the documentation.\n * Matches by slug or partial title match.\n * Returns content from the section header to the next section header.\n */\n private extractSection(content: string, sections: DocSection[], query: string): string | null {\n const lowerQuery = query.toLowerCase();\n\n // Find matching section - first try exact slug match, then partial title match\n const matchedSection =\n sections.find((s) => s.slug === lowerQuery) ??\n sections.find((s) => s.title.toLowerCase().includes(lowerQuery));\n\n if (!matchedSection) {\n return null;\n }\n\n const lines = content.split('\\n');\n let inSection = false;\n const sectionLines: string[] = [];\n\n for (const line of lines) {\n if (line.startsWith('## ')) {\n if (inSection) {\n // End of our section\n break;\n }\n const currentTitle = line.slice(3).trim();\n if (currentTitle === matchedSection.title) {\n inSection = true;\n sectionLines.push(line);\n }\n } else if (inSection) {\n sectionLines.push(line);\n }\n }\n\n if (sectionLines.length === 0) {\n return null;\n }\n\n // Trim trailing empty lines\n while (sectionLines.length > 0) {\n const lastLine = sectionLines[sectionLines.length - 1];\n if (lastLine?.trim() === '') {\n sectionLines.pop();\n } else {\n break;\n }\n }\n\n return sectionLines.join('\\n');\n }\n}\n\nexport const designCommand = new Command('design')\n .description('Display design documentation and Beads comparison')\n .argument('[topic]', 'Topic to display (e.g., \"architecture\", \"tbd-vs-beads\")')\n .option('--section <name>', 'Show specific section')\n .option('--list', 'List available sections')\n .action(async (topic: string | undefined, options: DesignOptions, command: Command) => {\n const handler = new DesignHandler(command);\n await handler.run(topic, options);\n });\n","/**\n * `tbd readme` - Display the README.\n *\n * Shows the bundled README (same as the GitHub landing page).\n */\n\nimport { Command } from 'commander';\nimport { readFile } from 'node:fs/promises';\nimport { fileURLToPath } from 'node:url';\nimport { dirname, join } from 'node:path';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { shouldUseInteractiveOutput } from '../lib/context.js';\nimport { CLIError } from '../lib/errors.js';\nimport { renderMarkdown, paginateOutput } from '../lib/output.js';\n\n/**\n * Get the path to the bundled README file.\n * The README is copied to dist/docs/ during build.\n */\nfunction getReadmePath(): string {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n // When bundled, runs from dist/bin.mjs or dist/cli.mjs\n // README is at dist/docs/README.md (same level as the bundle)\n return join(__dirname, 'docs', 'README.md');\n}\n\nclass ReadmeHandler extends BaseCommand {\n async run(): Promise<void> {\n let content: string;\n try {\n content = await readFile(getReadmePath(), 'utf-8');\n } catch {\n // Fallback: try to read from source location during development\n try {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n // During development without bundle: src/cli/commands -> repo root\n const devPath = join(__dirname, '..', '..', '..', '..', '..', 'README.md');\n content = await readFile(devPath, 'utf-8');\n } catch {\n // Last fallback: try package-level README\n try {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n // From packages/tbd/src/cli/commands -> packages/tbd/README.md\n const pkgPath = join(__dirname, '..', '..', '..', 'README.md');\n content = await readFile(pkgPath, 'utf-8');\n } catch {\n throw new CLIError('README file not found. Please rebuild the CLI.');\n }\n }\n }\n\n // Output the README with Markdown colorization and pagination for interactive\n if (shouldUseInteractiveOutput(this.ctx)) {\n const rendered = renderMarkdown(content, this.ctx.color);\n await paginateOutput(rendered, true);\n } else {\n console.log(content);\n }\n }\n}\n\nexport const readmeCommand = new Command('readme')\n .description('Display the README (same as GitHub landing page)')\n .action(async (_options: object, command: Command) => {\n const handler = new ReadmeHandler(command);\n await handler.run();\n });\n","/**\n * `tbd uninstall` - Remove tbd from a repository.\n *\n * Removes the .tbd directory, worktree, and optionally the sync branch.\n */\n\nimport { Command } from 'commander';\nimport { rm, access, readdir, stat } from 'node:fs/promises';\nimport { execSync } from 'node:child_process';\nimport { join, relative } from 'node:path';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { NotInitializedError, CLIError } from '../lib/errors.js';\nimport { findTbdRoot, readConfig } from '../../file/config.js';\nimport { SYNC_BRANCH } from '../../lib/paths.js';\n\ninterface UninstallOptions {\n confirm?: boolean;\n keepBranch?: boolean;\n removeRemote?: boolean;\n}\n\nclass UninstallHandler extends BaseCommand {\n async run(options: UninstallOptions): Promise<void> {\n const colors = this.output.getColors();\n\n // Resolve tbd root (walks up from cwd)\n const tbdRoot = await findTbdRoot(process.cwd());\n if (!tbdRoot) {\n throw new NotInitializedError('No .tbd directory found. Nothing to uninstall.');\n }\n\n // Read config to get branch info\n let config;\n try {\n config = await readConfig(tbdRoot);\n } catch {\n config = null;\n }\n\n const syncBranch = config?.sync.branch ?? SYNC_BRANCH;\n const remote = config?.sync.remote ?? 'origin';\n const tbdDir = join(tbdRoot, '.tbd');\n const worktreePath = join(tbdDir, 'data-sync-worktree');\n\n // Display paths relative to cwd for readability\n const displayPath = (p: string) => relative(process.cwd(), p) || p;\n\n // Check what exists\n const items: string[] = [];\n\n // Check worktree\n let worktreeExists = false;\n try {\n await access(worktreePath);\n worktreeExists = true;\n const worktreeStats = await this.getDirectoryStats(worktreePath);\n items.push(` - Worktree: ${displayPath(worktreePath)} (${worktreeStats.files} files)`);\n } catch {\n // Worktree doesn't exist\n }\n\n // Check local sync branch\n let localBranchExists = false;\n try {\n execSync(`git rev-parse --verify ${syncBranch}`, {\n encoding: 'utf-8',\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n localBranchExists = true;\n if (!options.keepBranch) {\n items.push(` - Local branch: ${syncBranch}`);\n }\n } catch {\n // Branch doesn't exist\n }\n\n // Check remote sync branch\n let remoteBranchExists = false;\n if (options.removeRemote) {\n try {\n execSync(`git rev-parse --verify ${remote}/${syncBranch}`, {\n encoding: 'utf-8',\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n remoteBranchExists = true;\n items.push(` - Remote branch: ${remote}/${syncBranch}`);\n } catch {\n // Remote branch doesn't exist\n }\n }\n\n // Count .tbd contents\n const tbdStats = await this.getDirectoryStats(tbdDir);\n items.push(` - Directory: ${displayPath(tbdDir)}/ (${tbdStats.files} files)`);\n\n // Show what will be removed\n console.log(colors.bold('The following will be removed:'));\n console.log('');\n for (const item of items) {\n console.log(colors.warn(item));\n }\n console.log('');\n\n if (!options.confirm) {\n console.log(`This action is ${colors.bold('irreversible')}.`);\n console.log('');\n console.log(`To confirm, run: ${colors.dim('tbd uninstall --confirm')}`);\n if (!options.keepBranch && localBranchExists) {\n console.log(\n `To keep the sync branch: ${colors.dim('tbd uninstall --confirm --keep-branch')}`,\n );\n }\n if (!options.removeRemote) {\n console.log(\n `To also remove from remote: ${colors.dim('tbd uninstall --confirm --remove-remote')}`,\n );\n }\n return;\n }\n\n // Check dry-run\n if (this.checkDryRun('Would remove tbd from repository', { items })) {\n return;\n }\n\n // Perform uninstall\n this.output.info('Uninstalling tbd...');\n\n // 1. Remove worktree first (git worktree remove)\n if (worktreeExists) {\n try {\n // First try to remove the worktree through git\n execSync(`git worktree remove --force \"${worktreePath}\"`, {\n encoding: 'utf-8',\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n console.log(` ${colors.success('✓')} Removed git worktree`);\n } catch {\n // If git worktree remove fails, force delete the directory\n try {\n await rm(worktreePath, { recursive: true, force: true });\n console.log(` ${colors.success('✓')} Removed worktree directory`);\n } catch {\n console.log(` ${colors.warn('⚠')} Could not remove worktree directory`);\n }\n }\n }\n\n // 2. Remove local sync branch\n if (localBranchExists && !options.keepBranch) {\n try {\n execSync(`git branch -D ${syncBranch}`, {\n encoding: 'utf-8',\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n console.log(` ${colors.success('✓')} Removed local branch: ${syncBranch}`);\n } catch {\n console.log(` ${colors.warn('⚠')} Could not remove local branch: ${syncBranch}`);\n }\n }\n\n // 3. Remove remote sync branch\n if (remoteBranchExists && options.removeRemote) {\n try {\n execSync(`git push ${remote} --delete ${syncBranch}`, {\n encoding: 'utf-8',\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n console.log(` ${colors.success('✓')} Removed remote branch: ${remote}/${syncBranch}`);\n } catch {\n console.log(\n ` ${colors.warn('⚠')} Could not remove remote branch: ${remote}/${syncBranch}`,\n );\n }\n }\n\n // 4. Clean up orphaned worktree references\n try {\n execSync('git worktree prune', {\n encoding: 'utf-8',\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n } catch {\n // Ignore errors\n }\n\n // 5. Remove .tbd directory\n try {\n await rm(tbdDir, { recursive: true, force: true });\n console.log(` ${colors.success('✓')} Removed .tbd directory`);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new CLIError(`Failed to remove .tbd directory: ${message}`);\n }\n\n console.log('');\n this.output.success('tbd has been uninstalled from this repository.');\n\n if (options.keepBranch && localBranchExists) {\n console.log('');\n console.log(colors.dim(`Note: The ${syncBranch} branch was preserved. Delete it with:`));\n console.log(colors.dim(` git branch -D ${syncBranch}`));\n }\n\n if (!options.removeRemote && remoteBranchExists) {\n console.log('');\n console.log(\n colors.dim(\n `Note: The remote ${remote}/${syncBranch} branch was preserved. Delete it with:`,\n ),\n );\n console.log(colors.dim(` git push ${remote} --delete ${syncBranch}`));\n }\n }\n\n /**\n * Get stats about a directory (file count, size).\n */\n private async getDirectoryStats(dirPath: string): Promise<{ files: number; size: number }> {\n let files = 0;\n let size = 0;\n\n const walk = async (dir: string): Promise<void> => {\n try {\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n await walk(fullPath);\n } else {\n files++;\n try {\n const stats = await stat(fullPath);\n size += stats.size;\n } catch {\n // Ignore stat errors\n }\n }\n }\n } catch {\n // Ignore errors\n }\n };\n\n await walk(dirPath);\n return { files, size };\n }\n}\n\nexport const uninstallCommand = new Command('uninstall')\n .description('Remove tbd from this repository')\n .option('--confirm', 'Confirm removal (required to proceed)')\n .option('--keep-branch', 'Keep the local sync branch')\n .option('--remove-remote', 'Also remove the remote sync branch')\n .action(async (options, command) => {\n const handler = new UninstallHandler(command);\n await handler.run(options);\n });\n","/**\n * DocCache - Path-ordered markdown document cache with lookup.\n *\n * Provides document lookups for the `tbd shortcut` command, supporting\n * both exact matching by filename and fuzzy matching against metadata.\n *\n * Also provides auto-sync functionality when docs are stale (per spec).\n *\n * See: docs/project/specs/active/plan-2026-01-22-doc-cache-abstraction.md\n * See: docs/project/specs/active/plan-2026-01-26-configurable-doc-cache-sync.md\n */\n\nimport { readdir, readFile } from 'node:fs/promises';\nimport { join, basename } from 'node:path';\nimport matter from 'gray-matter';\n\nimport { readConfig, readLocalState, findTbdRoot } from './config.js';\nimport { isDocsStale, syncDocsWithDefaults } from './doc-sync.js';\nimport { estimateTokens } from '../lib/format-utils.js';\n\n// =============================================================================\n// Scoring Constants\n// =============================================================================\n\n/** Score for exact filename match (with or without .md extension) */\nexport const SCORE_EXACT_MATCH = 1.0;\n\n/** Score when query is a prefix of the filename */\nexport const SCORE_PREFIX_MATCH = 0.9;\n\n/** Score when filename contains all query words */\nexport const SCORE_CONTAINS_ALL = 0.8;\n\n/** Base score for partial word matches (multiplied by matched/total ratio) */\nexport const SCORE_PARTIAL_BASE = 0.7;\n\n/** Minimum score threshold to return a fuzzy match result */\nexport const SCORE_MIN_THRESHOLD = 0.5;\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * Frontmatter fields used for shortcut documents.\n * These are the expected fields in YAML frontmatter for searchability.\n */\nexport interface DocFrontmatter {\n /** Display title for the shortcut */\n title?: string;\n /** Brief description for fuzzy matching and listing */\n description?: string;\n /** Category for filtering (e.g., planning, review, git) */\n category?: string;\n /** Optional categorization tags */\n tags?: string[];\n}\n\n/**\n * A cached document loaded from the doc path.\n */\nexport interface CachedDoc {\n /** Full filesystem path to the document */\n path: string;\n /** Filename without extension (used for lookups) */\n name: string;\n /** Parsed YAML frontmatter, if present */\n frontmatter?: DocFrontmatter;\n /** Full file content (including frontmatter for output) */\n content: string;\n /** Which directory in the path this doc came from */\n sourceDir: string;\n /** File size in bytes */\n sizeBytes: number;\n /** Estimated token count (based on ~3.5 chars/token) */\n approxTokens: number;\n}\n\n/**\n * A document match with relevance score.\n */\nexport interface DocMatch {\n /** The matched document */\n doc: CachedDoc;\n /** Match score: 1.0 = exact, lower = fuzzier */\n score: number;\n}\n\n// =============================================================================\n// DocCache Class\n// =============================================================================\n\n/**\n * Options for loading the doc cache.\n */\nexport interface DocCacheLoadOptions {\n /** If true, suppress auto-sync output (default: false) */\n quiet?: boolean;\n}\n\n/**\n * Path-ordered markdown document cache.\n *\n * Loads all .md files from configured paths in order, with earlier paths\n * taking precedence (like shell $PATH). Supports exact lookup by filename\n * and fuzzy search across filename + frontmatter metadata.\n */\nexport class DocCache {\n /** Active docs (first occurrence of each name) */\n private docs: CachedDoc[] = [];\n\n /** All docs including shadowed ones */\n private allDocs: CachedDoc[] = [];\n\n /** Track names we've seen for shadow detection */\n private seenNames = new Set<string>();\n\n /** Whether the cache has been loaded */\n private loaded = false;\n\n /**\n * Create a new DocCache.\n *\n * @param paths - Ordered array of directory paths to search (relative to baseDir)\n * @param baseDir - Base directory for resolving relative paths (default: cwd)\n */\n constructor(\n private readonly paths: string[],\n private readonly baseDir: string = process.cwd(),\n ) {}\n\n /**\n * Load all documents from configured paths.\n *\n * Reads all .md files from each path in order. Documents with the same\n * name in later paths are shadowed (tracked but not returned by default).\n *\n * If auto-sync is enabled and docs are stale, triggers a sync first.\n *\n * @param options - Load options (quiet: suppress auto-sync output)\n */\n async load(options?: DocCacheLoadOptions): Promise<void> {\n if (this.loaded) return;\n\n // Check for auto-sync before loading\n await this.checkAutoSync(options?.quiet ?? false);\n\n for (const relativePath of this.paths) {\n const dirPath = join(this.baseDir, relativePath);\n await this.loadDirectory(dirPath, relativePath);\n }\n\n this.loaded = true;\n }\n\n /**\n * Check if docs are stale and auto-sync if needed.\n * Respects the quiet option - only silent when explicitly requested.\n *\n * Uses syncDocsWithDefaults() to ensure auto-sync also picks up new bundled\n * docs from tbd upgrades, not just existing config entries.\n *\n * @param quiet - If true, suppress sync output\n */\n private async checkAutoSync(quiet: boolean): Promise<void> {\n try {\n // Find tbd root\n const tbdRoot = await findTbdRoot(this.baseDir);\n if (!tbdRoot) return;\n\n // Read config and state\n const config = await readConfig(tbdRoot);\n const state = await readLocalState(tbdRoot);\n\n // Check if auto-sync is enabled and docs are stale\n const autoSyncHours = config.settings?.doc_auto_sync_hours ?? 24;\n if (!isDocsStale(state.last_doc_sync_at, autoSyncHours)) {\n return;\n }\n\n // Use syncDocsWithDefaults to merge bundled defaults with user config\n // This ensures new bundled docs from tbd upgrades are picked up by auto-sync\n await syncDocsWithDefaults(tbdRoot, { quiet });\n } catch {\n // Auto-sync errors are silent - don't interrupt the user\n }\n }\n\n /**\n * Load documents from a single directory.\n */\n private async loadDirectory(dirPath: string, sourceDir: string): Promise<void> {\n let entries: string[];\n\n try {\n entries = await readdir(dirPath);\n } catch {\n // Directory doesn't exist or isn't readable - skip silently\n // This is expected when paths haven't been initialized yet\n return;\n }\n\n for (const entry of entries) {\n if (!entry.endsWith('.md')) continue;\n\n const filePath = join(dirPath, entry);\n const name = basename(entry, '.md');\n\n try {\n const content = await readFile(filePath, 'utf-8');\n const frontmatter = this.parseFrontmatterData(content);\n const sizeBytes = Buffer.byteLength(content, 'utf-8');\n const approxTokens = estimateTokens(content);\n\n const doc: CachedDoc = {\n path: filePath,\n name,\n frontmatter,\n content,\n sourceDir,\n sizeBytes,\n approxTokens,\n };\n\n // Track all docs\n this.allDocs.push(doc);\n\n // Only add to active docs if not shadowed\n if (!this.seenNames.has(name)) {\n this.docs.push(doc);\n this.seenNames.add(name);\n }\n } catch (error) {\n // Failed to read or parse file - skip with warning context\n console.warn(`Failed to load shortcut ${filePath}: ${(error as Error).message}`);\n }\n }\n }\n\n /**\n * Parse YAML frontmatter from content and return typed data.\n * Uses gray-matter for consistent frontmatter parsing.\n */\n private parseFrontmatterData(content: string): DocFrontmatter | undefined {\n if (!matter.test(content)) {\n return undefined;\n }\n\n try {\n const parsed = matter(content).data as Record<string, unknown>;\n return {\n title: typeof parsed.title === 'string' ? parsed.title : undefined,\n description: typeof parsed.description === 'string' ? parsed.description : undefined,\n category: typeof parsed.category === 'string' ? parsed.category : undefined,\n tags: Array.isArray(parsed.tags)\n ? parsed.tags.filter((t) => typeof t === 'string')\n : undefined,\n };\n } catch {\n // Invalid YAML in frontmatter - return undefined\n return undefined;\n }\n }\n\n /**\n * Get a document by exact name match.\n *\n * @param name - Filename to match (with or without .md extension)\n * @returns Match with score SCORE_EXACT_MATCH, or null if not found\n */\n get(name: string): DocMatch | null {\n // Strip .md extension if present\n const lookupName = name.endsWith('.md') ? name.slice(0, -3) : name;\n\n const doc = this.docs.find((d) => d.name === lookupName);\n if (!doc) return null;\n\n return { doc, score: SCORE_EXACT_MATCH };\n }\n\n /**\n * Search for documents matching a query.\n *\n * Performs fuzzy matching against filename, title, and description.\n * Returns matches sorted by score descending.\n *\n * @param query - Search query string\n * @param limit - Maximum number of results (default: 10)\n * @returns Array of matches sorted by score descending\n */\n search(query: string, limit = 10): DocMatch[] {\n const matches: DocMatch[] = [];\n\n for (const doc of this.docs) {\n const score = this.calculateScore(doc, query);\n if (score >= SCORE_MIN_THRESHOLD) {\n matches.push({ doc, score });\n }\n }\n\n // Sort by score descending, then by name for stability\n matches.sort((a, b) => {\n if (b.score !== a.score) return b.score - a.score;\n return a.doc.name.localeCompare(b.doc.name);\n });\n\n return matches.slice(0, limit);\n }\n\n /**\n * Calculate relevance score for a document against a query.\n */\n private calculateScore(doc: CachedDoc, query: string): number {\n const queryLower = query.toLowerCase().trim();\n\n // Empty query matches nothing\n if (queryLower.length === 0) {\n return 0;\n }\n\n const nameLower = doc.name.toLowerCase();\n const titleLower = doc.frontmatter?.title?.toLowerCase() ?? '';\n const descLower = doc.frontmatter?.description?.toLowerCase() ?? '';\n\n // Exact match on name\n if (nameLower === queryLower) {\n return SCORE_EXACT_MATCH;\n }\n\n // Prefix match on name\n if (nameLower.startsWith(queryLower)) {\n return SCORE_PREFIX_MATCH;\n }\n\n // Split query into words for multi-word matching\n const queryWords = queryLower.split(/\\s+/).filter((w) => w.length > 0);\n const searchableText = `${nameLower} ${titleLower} ${descLower}`;\n\n // Check if all query words are contained\n const allWordsMatch = queryWords.every((word) => searchableText.includes(word));\n if (allWordsMatch && queryWords.length > 0) {\n return SCORE_CONTAINS_ALL;\n }\n\n // Partial match - count how many words match\n const matchedWords = queryWords.filter((word) => searchableText.includes(word));\n if (matchedWords.length > 0) {\n const ratio = matchedWords.length / queryWords.length;\n return SCORE_PARTIAL_BASE * ratio;\n }\n\n return 0;\n }\n\n /**\n * List all documents.\n *\n * @param includeAll - If true, include shadowed documents\n * @returns Array of cached documents\n */\n list(includeAll = false): CachedDoc[] {\n return includeAll ? this.allDocs : this.docs;\n }\n\n /**\n * Check if a document is shadowed by an earlier path.\n *\n * @param doc - Document to check\n * @returns True if this doc is shadowed (not the first with this name)\n */\n isShadowed(doc: CachedDoc): boolean {\n const firstDoc = this.docs.find((d) => d.name === doc.name);\n return firstDoc !== doc;\n }\n\n /**\n * Check if the cache has been loaded.\n */\n isLoaded(): boolean {\n return this.loaded;\n }\n}\n\n// =============================================================================\n// Shortcut Directory Generation\n// =============================================================================\n\n/**\n * Marker comments for shortcut directory section in skill files.\n * Used for incremental updates without overwriting user content.\n */\nconst SHORTCUT_DIRECTORY_BEGIN = '<!-- BEGIN SHORTCUT DIRECTORY -->';\nconst SHORTCUT_DIRECTORY_END = '<!-- END SHORTCUT DIRECTORY -->';\n\n/**\n * Build table rows from docs (shared helper for shortcuts and guidelines).\n */\nfunction buildTableRows(docs: CachedDoc[], skipNames: string[] = []): string[] {\n const sortedDocs = [...docs].sort((a, b) => a.name.localeCompare(b.name));\n const rows: string[] = [];\n\n for (const doc of sortedDocs) {\n if (skipNames.includes(doc.name)) {\n continue;\n }\n\n const name = doc.name;\n const description = doc.frontmatter?.description ?? '';\n const escapedDescription = description.replace(/\\|/g, '\\\\|');\n\n rows.push(`| ${name} | ${escapedDescription} |`);\n }\n\n return rows;\n}\n\n/**\n * Generate a formatted markdown directory of shortcuts and guidelines.\n *\n * The output includes:\n * 1. Marker comments for incremental updates\n * 2. Available Shortcuts section with name and description\n * 3. Available Guidelines section with name and description (if provided)\n *\n * @param shortcuts - Array of shortcut CachedDoc objects\n * @param guidelines - Optional array of guideline CachedDoc objects\n * @returns Formatted markdown string with shortcuts and guidelines directory\n *\n * @example\n * const directory = generateShortcutDirectory(shortcutDocs, guidelineDocs);\n * // Returns:\n * // <!-- BEGIN SHORTCUT DIRECTORY -->\n * // ## Available Shortcuts\n * // | Name | Description |\n * // | --- | --- |\n * // | code-review-and-commit | Run pre-commit checks, review changes, and commit code |\n * // ...\n * // ## Available Guidelines\n * // | Name | Description |\n * // | --- | --- |\n * // | typescript-rules | TypeScript coding rules and best practices |\n * // ...\n * // <!-- END SHORTCUT DIRECTORY -->\n */\nexport function generateShortcutDirectory(\n shortcuts: CachedDoc[],\n guidelines: CachedDoc[] = [],\n): string {\n const lines: string[] = [SHORTCUT_DIRECTORY_BEGIN];\n\n // Shortcuts section\n const shortcutRows = buildTableRows(shortcuts, [\n 'skill-baseline',\n 'skill-brief',\n 'skill-minimal',\n 'shortcut-explanation',\n ]);\n\n lines.push('## Available Shortcuts');\n lines.push('');\n lines.push('Run `tbd shortcut <name>` to use any of these shortcuts:');\n lines.push('');\n\n if (shortcutRows.length === 0) {\n lines.push('No shortcuts available. Create shortcuts in `.tbd/docs/shortcuts/standard/`.');\n } else {\n lines.push('| Name | Description |');\n lines.push('| --- | --- |');\n lines.push(...shortcutRows);\n }\n\n // Guidelines section (if provided)\n if (guidelines.length > 0) {\n const guidelineRows = buildTableRows(guidelines);\n\n if (guidelineRows.length > 0) {\n lines.push('');\n lines.push('## Available Guidelines');\n lines.push('');\n lines.push('Run `tbd guidelines <name>` to apply any of these guidelines:');\n lines.push('');\n lines.push('| Name | Description |');\n lines.push('| --- | --- |');\n lines.push(...guidelineRows);\n }\n }\n\n lines.push('');\n lines.push(SHORTCUT_DIRECTORY_END);\n\n return lines.join('\\n');\n}\n","/**\n * `tbd prime` - Output dashboard and workflow context for AI agents.\n *\n * Designed to be called by hooks at session start and before context compaction\n * to ensure agents remember the tbd workflow.\n *\n * See: tbd-design.md §6.4.3 The tbd prime Command\n */\n\nimport { Command } from 'commander';\nimport { readFile, access } from 'node:fs/promises';\nimport { join, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { shouldUseInteractiveOutput } from '../lib/context.js';\nimport { renderMarkdown, paginateOutput } from '../lib/output.js';\nimport { findTbdRoot, readConfig, hasSeenWelcome, markWelcomeSeen } from '../../file/config.js';\nimport { stripFrontmatter } from '../../utils/markdown-utils.js';\nimport { VERSION } from '../lib/version.js';\nimport { listIssues } from '../../file/storage.js';\nimport {\n resolveDataSyncDir,\n DEFAULT_SHORTCUT_PATHS,\n DEFAULT_GUIDELINES_PATHS,\n} from '../../lib/paths.js';\nimport { getClaudePaths } from '../../lib/integration-paths.js';\nimport type { Issue } from '../../lib/types.js';\nimport { DocCache, generateShortcutDirectory } from '../../file/doc-cache.js';\n\ninterface PrimeOptions {\n export?: boolean;\n brief?: boolean;\n}\n\n/**\n * Get the path to the bundled SKILL.md file.\n */\nfunction getSkillPath(): string {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n // When bundled, runs from dist/bin.mjs or dist/cli.mjs\n // Docs are at dist/docs/SKILL.md (same level as the bundle)\n return join(__dirname, 'docs', 'SKILL.md');\n}\n\n/**\n * Load the skill content from the bundled SKILL.md file with fallbacks.\n * This is exported for use by setup.ts for skill installation.\n */\nexport async function loadSkillContent(): Promise<string> {\n // Try bundled location first (dist/docs/SKILL.md)\n try {\n return await readFile(getSkillPath(), 'utf-8');\n } catch {\n // Fallback: compose from source files during development\n }\n\n // Dev fallback: compose SKILL.md from source files on-the-fly\n // This mirrors what copy-docs.mjs does at build time\n try {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n // From packages/tbd/src/cli/commands/ go to packages/tbd/docs/\n const docsDir = join(__dirname, '..', '..', '..', 'docs');\n const headerPath = join(docsDir, 'install', 'claude-header.md');\n const skillPath = join(docsDir, 'shortcuts', 'system', 'skill-baseline.md');\n\n const header = await readFile(headerPath, 'utf-8');\n const skill = await readFile(skillPath, 'utf-8');\n return header + skill;\n } catch {\n // If source files not found, throw error\n throw new Error('SKILL.md content file not found. Please rebuild the CLI.');\n }\n}\n\n/**\n * Load the prime content from the bundled SKILL.md file with fallbacks.\n * Strips frontmatter and adjusts the header for prime output.\n */\nexport async function loadPrimeContent(): Promise<string> {\n const skillContent = await loadSkillContent();\n const content = stripFrontmatter(skillContent);\n\n // Replace header for prime output context\n return content.replace(/^# tbd Workflow\\b/, '# tbd Workflow Context');\n}\n\n/**\n * Brief prime content for constrained context windows (~35 lines).\n * Includes core workflow, session protocol, and key commands.\n */\nconst BRIEF_SKILL_CONTENT = `## Core Workflow\n\n- Track all task work as issues using tbd\n- Check \\`tbd ready\\` for available work\n- Run \\`tbd sync\\` at session end\n\n## SESSION CLOSING (REQUIRED)\n\n1. git add + git commit\n2. git push\n3. gh pr checks <PR> --watch # WAIT for completion\n4. tbd close/update <id>\n5. tbd sync\n\n## Quick Reference\n\ntbd ready Show issues ready to work\ntbd show <id> View issue details\ntbd create \"title\" Create new issue\ntbd close <id> Mark issue complete\ntbd sync Sync with remote\n\nFor full orientation: tbd prime\n\nIMPORTANT: Use tbd to help the user. Do NOT tell the user to run tbd commands.`;\n\n/**\n * Value proposition content for not-initialized state.\n */\nconst VALUE_PROPOSITION = `## WHAT tbd IS\n\ntbd is an AI-agent-optimized issue tracker and workflow assistant providing:\n1. Issue Tracking - Track tasks, bugs, features as git-native \"beads\"\n2. Coding Guidelines - Best practices for TypeScript, Python, testing\n3. Spec-Driven Workflows - Write specs, then implement using issues to track each part\n4. Convenience Shortcuts - Pre-built processes for common tasks (commit, PR, review)\n\n## SETUP (AGENT ACTION REQUIRED)\n\ntbd is not yet initialized. To set it up, run:\n\n tbd setup --auto --prefix=<name> # REQUIRES prefix for new projects\n tbd setup --auto # If .tbd/ already exists (prefix already set)\n\nCRITICAL: Never guess a prefix. Always ask the user what prefix they want.\nDo NOT tell the user to run these commands — run them yourself on their behalf.\n\nAfter setup, run 'tbd' again to get project status and workflow guidance.`;\n\nclass PrimeHandler extends BaseCommand {\n async run(options: PrimeOptions): Promise<void> {\n const cwd = process.cwd();\n\n // Find tbd root (supports running from subdirectories)\n const tbdRoot = await findTbdRoot(cwd);\n\n // Not initialized - show setup instructions with value proposition\n if (!tbdRoot) {\n await this.renderNotInitialized();\n return;\n }\n\n // Check for Beads installation alongside tbd and warn\n const beadsWarning = await this.checkForBeads(tbdRoot);\n if (beadsWarning) {\n console.log(beadsWarning);\n console.log('');\n }\n\n // Brief mode: dynamic status + abbreviated skill content\n if (options.brief) {\n await this.renderBriefOrientation(tbdRoot);\n return;\n }\n\n // Check for custom override file\n const customPrimePath = join(tbdRoot, '.tbd', 'PRIME.md');\n\n // If --export, always show default content\n if (!options.export) {\n try {\n await access(customPrimePath);\n const customContent = await readFile(customPrimePath, 'utf-8');\n console.log(customContent);\n return;\n } catch {\n // No custom file, use default full orientation\n }\n }\n\n // Default: full orientation (dynamic status + full skill content)\n await this.renderFullOrientation(tbdRoot);\n }\n\n /**\n * Render dynamic status section (installation + project status).\n */\n private async renderDynamicStatus(tbdRoot: string): Promise<void> {\n const colors = this.output.getColors();\n\n console.log(`${colors.bold('tbd')} v${VERSION}`);\n console.log('');\n\n // === INSTALLATION ===\n console.log(colors.bold('=== INSTALLATION ==='));\n console.log(`${colors.success('✓')} tbd installed (v${VERSION})`);\n console.log(`${colors.success('✓')} Initialized in this repo`);\n\n // Check if hooks are installed\n const hooksInstalled = await this.checkHooksInstalled(tbdRoot);\n if (hooksInstalled) {\n console.log(`${colors.success('✓')} Hooks installed`);\n } else {\n console.log(`${colors.dim('✗')} Hooks not installed (run: tbd setup --auto)`);\n }\n console.log('');\n\n // === PROJECT STATUS ===\n console.log(colors.bold('=== PROJECT STATUS ==='));\n try {\n const config = await readConfig(tbdRoot);\n console.log(`Repository: ${config.display.id_prefix || 'unknown'}`);\n } catch {\n console.log('Repository: unknown');\n }\n\n // Get issue stats\n const stats = await this.getIssueStats(tbdRoot);\n if (stats) {\n const statusInfo = `${stats.open} open (${stats.inProgress} in_progress)`;\n const blockedInfo = stats.blocked > 0 ? ` | ${stats.blocked} blocked` : '';\n console.log(`Issues: ${statusInfo}${blockedInfo}`);\n } else {\n console.log('Issues: (none)');\n }\n console.log('');\n }\n\n /**\n * Render full orientation: dynamic status + full skill content.\n * If the user hasn't seen the welcome message, add a welcome banner.\n */\n private async renderFullOrientation(tbdRoot: string): Promise<void> {\n // Dynamic status\n await this.renderDynamicStatus(tbdRoot);\n\n // Check if this is the user's first time\n const isNewUser = !(await hasSeenWelcome(tbdRoot));\n if (isNewUser) {\n await this.renderWelcomeBanner(tbdRoot);\n }\n\n // Full skill content\n const primeContent = await loadPrimeContent();\n\n // Shortcut directory\n const shortcutDir = await this.getShortcutDirectory(tbdRoot);\n\n // Build the full markdown content\n let markdownContent = primeContent;\n if (shortcutDir) {\n markdownContent += '\\n\\n' + shortcutDir;\n }\n markdownContent +=\n '\\n\\nIMPORTANT: Use tbd to help the user — do NOT tell the user to run tbd commands.\\n' +\n 'When the user asks for help or orientation, run `tbd shortcut welcome-user`.';\n\n // Output with interactive formatting (colors, pagination) when appropriate\n if (shouldUseInteractiveOutput(this.ctx)) {\n const rendered = renderMarkdown(markdownContent, this.ctx.color);\n await paginateOutput(rendered, true);\n } else {\n console.log(markdownContent);\n }\n }\n\n /**\n * Render brief orientation: dynamic status + abbreviated skill content.\n */\n private async renderBriefOrientation(tbdRoot: string): Promise<void> {\n // Dynamic status\n await this.renderDynamicStatus(tbdRoot);\n\n // Abbreviated skill content with interactive formatting\n if (shouldUseInteractiveOutput(this.ctx)) {\n console.log(renderMarkdown(BRIEF_SKILL_CONTENT, this.ctx.color));\n } else {\n console.log(BRIEF_SKILL_CONTENT);\n }\n }\n\n /**\n * Render output for not initialized state with value proposition.\n */\n private async renderNotInitialized(): Promise<void> {\n const colors = this.output.getColors();\n\n console.log(`${colors.bold('tbd')} v${VERSION}`);\n console.log('');\n console.log(colors.bold('=== NOT INITIALIZED ==='));\n console.log(`${colors.warn('✗')} tbd not initialized in this repository`);\n console.log('');\n\n // Value proposition with interactive formatting\n if (shouldUseInteractiveOutput(this.ctx)) {\n console.log(renderMarkdown(VALUE_PROPOSITION, this.ctx.color));\n } else {\n console.log(VALUE_PROPOSITION);\n }\n }\n\n /**\n * Render a welcome banner for first-time users.\n * Instructs the agent to show the welcome message.\n */\n private async renderWelcomeBanner(tbdRoot: string): Promise<void> {\n const colors = this.output.getColors();\n\n console.log(colors.bold('=== WELCOME (FIRST TIME) ==='));\n console.log('');\n console.log('AGENT INSTRUCTION:');\n console.log('1. Welcome the user! Run `tbd shortcut welcome-user` and follow its');\n console.log(' instructions to give the user a warm orientation.');\n console.log('2. Show the user examples of what THEY can say (e.g. \"there\\'s a bug\",');\n console.log(' \"let\\'s plan a feature\") — do NOT show them tbd CLI commands.');\n console.log(\"3. You use tbd on the user's behalf. The user talks to you naturally;\");\n console.log(' you translate their requests into tbd actions.');\n console.log('');\n\n // Mark welcome as seen\n try {\n await markWelcomeSeen(tbdRoot);\n } catch {\n // Non-critical\n }\n }\n\n /**\n * Check if Claude Code hooks are installed in project-local .claude/settings.json.\n */\n private async checkHooksInstalled(projectRoot: string): Promise<boolean> {\n const { settings } = getClaudePaths(projectRoot);\n try {\n const content = await readFile(settings, 'utf-8');\n return content.includes('tbd');\n } catch {\n return false;\n }\n }\n\n /**\n * Get issue statistics.\n */\n private async getIssueStats(tbdRoot: string): Promise<{\n open: number;\n inProgress: number;\n blocked: number;\n } | null> {\n try {\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n const issues: Issue[] = await listIssues(dataSyncDir);\n\n let open = 0;\n let inProgress = 0;\n const blockedIds = new Set<string>();\n\n // Find blocked issues\n // \"blocks\" dependency: issue A with {type: 'blocks', target: B} means A blocks B\n // B is blocked only if A (the blocker) is not closed\n for (const issue of issues) {\n for (const dep of issue.dependencies) {\n if (dep.type === 'blocks') {\n // Only count target as blocked if the blocker (this issue) is not closed\n if (issue.status !== 'closed') {\n blockedIds.add(dep.target);\n }\n }\n }\n }\n\n // Count by status\n for (const issue of issues) {\n if (issue.status === 'open') {\n open++;\n } else if (issue.status === 'in_progress') {\n inProgress++;\n }\n }\n\n return { open, inProgress, blocked: blockedIds.size };\n } catch {\n return null;\n }\n }\n\n /**\n * Check if Beads is installed alongside tbd and return a warning message.\n * This helps users who are migrating from Beads to tbd.\n */\n private async checkForBeads(cwd: string): Promise<string | null> {\n const beadsDir = join(cwd, '.beads');\n try {\n await access(beadsDir);\n // .beads/ exists - warn the agent\n return `⚠️ WARNING: A .beads/ directory was detected alongside .tbd/\n When asked to use beads, use \\`tbd\\` commands, NOT \\`bd\\` commands.\n To complete migration: tbd setup beads --disable --confirm`;\n } catch {\n // No .beads/ directory, no warning needed\n return null;\n }\n }\n\n /**\n * Generate the shortcut and guidelines directory on-the-fly.\n */\n private async getShortcutDirectory(tbdRoot: string): Promise<string | null> {\n // Load shortcuts\n const shortcutCache = new DocCache(DEFAULT_SHORTCUT_PATHS, tbdRoot);\n await shortcutCache.load({ quiet: this.ctx.quiet });\n const shortcuts = shortcutCache.list();\n\n // Load guidelines\n const guidelinesCache = new DocCache(DEFAULT_GUIDELINES_PATHS, tbdRoot);\n await guidelinesCache.load({ quiet: this.ctx.quiet });\n const guidelines = guidelinesCache.list();\n\n // If no docs loaded, skip directory\n if (shortcuts.length === 0 && guidelines.length === 0) {\n return null;\n }\n\n return generateShortcutDirectory(shortcuts, guidelines);\n }\n}\n\nexport const primeCommand = new Command('prime')\n .description('Show full orientation with workflow context')\n .option('--export', 'Output default content (ignores PRIME.md override)')\n .option('--brief', 'Output abbreviated orientation (~35 lines) for constrained contexts')\n .action(async (options: PrimeOptions, command) => {\n const handler = new PrimeHandler(command);\n await handler.run(options);\n });\n","/**\n * `tbd skill` - Output AI agent skill file content.\n *\n * See: tbd-design.md §Prime-First Design\n */\n\nimport { Command } from 'commander';\nimport { readFile } from 'node:fs/promises';\nimport { join, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { shouldUseInteractiveOutput } from '../lib/context.js';\nimport { renderMarkdownWithFrontmatter, paginateOutput } from '../lib/output.js';\nimport { findTbdRoot } from '../../file/config.js';\nimport { DocCache, generateShortcutDirectory } from '../../file/doc-cache.js';\nimport { DEFAULT_SHORTCUT_PATHS, DEFAULT_GUIDELINES_PATHS } from '../../lib/paths.js';\n\ninterface SkillOptions {\n brief?: boolean;\n}\n\n/**\n * Get the path to a bundled doc file.\n */\nfunction getDocPath(filename: string): string {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n // When bundled, runs from dist/bin.mjs or dist/cli.mjs\n // Docs are at dist/docs/ (same level as the bundle)\n return join(__dirname, 'docs', filename);\n}\n\n/**\n * Load a doc file content.\n */\nasync function loadDocContent(filename: string): Promise<string> {\n // Try bundled location first\n try {\n return await readFile(getDocPath(filename), 'utf-8');\n } catch {\n // Fallback for development\n }\n\n // Fallback: try to read from source location during development\n try {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n // During development: src/cli/commands -> packages/tbd/docs\n const devPath = join(__dirname, '..', '..', '..', 'docs', filename);\n return await readFile(devPath, 'utf-8');\n } catch {\n throw new Error(`${filename} not found. Please rebuild the CLI.`);\n }\n}\n\nclass SkillHandler extends BaseCommand {\n async run(options: SkillOptions): Promise<void> {\n await this.execute(async () => {\n let content: string;\n if (options.brief) {\n // Brief mode: just output skill-brief.md\n content = await loadDocContent('skill-brief.md');\n } else {\n // Full mode: compose header + skill-baseline.md + shortcut directory\n content = await this.composeFullSkill();\n }\n\n // Output with interactive formatting (colors, pagination) when appropriate\n if (shouldUseInteractiveOutput(this.ctx)) {\n const rendered = renderMarkdownWithFrontmatter(content, this.ctx.color);\n await paginateOutput(rendered, true);\n } else {\n console.log(content);\n }\n }, 'Failed to output skill content');\n }\n\n /**\n * Compose the full skill output by combining:\n * 1. Claude header (YAML frontmatter)\n * 2. Base skill content (skill-baseline.md from shortcuts/system)\n * 3. Shortcut directory (from cache or generated on-the-fly)\n */\n private async composeFullSkill(): Promise<string> {\n // Load header (YAML frontmatter for Claude)\n const header = await loadDocContent('install/claude-header.md');\n\n // Load base skill content\n const baseSkill = await loadDocContent('shortcuts/system/skill-baseline.md');\n\n // Get shortcut directory\n const directory = await this.getShortcutDirectory();\n\n // Compose: header + base skill + (optional) shortcut directory\n let result = header + baseSkill;\n if (directory) {\n result = result.trimEnd() + '\\n\\n' + directory;\n }\n\n return result;\n }\n\n /**\n * Generate the shortcut directory on-the-fly.\n */\n private async getShortcutDirectory(): Promise<string | null> {\n // Try to find tbd root (may not be initialized)\n const tbdRoot = await findTbdRoot(process.cwd());\n if (!tbdRoot) {\n return null;\n }\n\n // Load shortcuts\n const shortcutCache = new DocCache(DEFAULT_SHORTCUT_PATHS, tbdRoot);\n await shortcutCache.load({ quiet: this.ctx.quiet });\n const shortcuts = shortcutCache.list();\n\n // Load guidelines\n const guidelinesCache = new DocCache(DEFAULT_GUIDELINES_PATHS, tbdRoot);\n await guidelinesCache.load({ quiet: this.ctx.quiet });\n const guidelines = guidelinesCache.list();\n\n // If no docs loaded, skip directory\n if (shortcuts.length === 0 && guidelines.length === 0) {\n return null;\n }\n\n return generateShortcutDirectory(shortcuts, guidelines);\n }\n}\n\nexport const skillCommand = new Command('skill')\n .description('Output AI agent skill file content')\n .option('--brief', 'Output condensed workflow rules only')\n .action(async (options: SkillOptions, command) => {\n const handler = new SkillHandler(command);\n await handler.run(options);\n });\n","/**\n * Agent instruction prompts for document commands.\n *\n * These prompts are displayed when tbd outputs a document to help\n * the agent understand how to use the content.\n */\n\n/**\n * Header shown when outputting a shortcut document.\n */\nexport const SHORTCUT_AGENT_HEADER =\n 'Agent instructions: You have activated a shortcut with task instructions. If a user has asked you to do a task that requires this work, follow the instructions below carefully.';\n\n/**\n * Header shown when outputting a guidelines document.\n */\nexport const GUIDELINES_AGENT_HEADER =\n 'Agent instructions: You have activated a guidelines document. If a user has asked you to apply these rules, read them carefully and apply them. Use beads to track each step.';\n","/**\n * Add external documentation to the tbd doc cache.\n *\n * Uses the shared github-fetch utility for URL conversion and fetching.\n * Handles doc-specific concerns: content validation, file writing, and\n * atomic config updates.\n */\n\nimport { join, dirname } from 'node:path';\nimport { mkdir } from 'node:fs/promises';\nimport { writeFile } from 'atomically';\n\nimport { readConfig, writeConfig } from './config.js';\nimport { githubBlobToRawUrl, fetchWithGhFallback } from './github-fetch.js';\nimport { TBD_DOCS_DIR } from '../lib/paths.js';\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * The type of document being added.\n */\nexport type DocType = 'guideline' | 'shortcut' | 'template';\n\n/**\n * Options for adding a document.\n */\nexport interface AddDocOptions {\n /** URL to fetch the document from */\n url: string;\n /** Name for the document (without .md extension) */\n name: string;\n /** Type of document */\n docType: DocType;\n}\n\n/**\n * Result of adding a document.\n */\nexport interface AddDocResult {\n /** The destination path relative to .tbd/docs/ */\n destPath: string;\n /** The raw URL used to fetch the content */\n rawUrl: string;\n /** Whether gh CLI was used as fallback */\n usedGhCli: boolean;\n}\n\n// =============================================================================\n// Content Validation\n// =============================================================================\n\n/**\n * Validate that fetched content looks like a reasonable markdown document.\n *\n * @throws If content fails sanity checks\n */\nexport function validateDocContent(content: string, name: string): void {\n if (!content || content.trim().length === 0) {\n throw new Error(`Fetched content for \"${name}\" is empty`);\n }\n\n if (content.length < 10) {\n throw new Error(`Fetched content for \"${name}\" is too short (${content.length} chars)`);\n }\n\n // Check for HTML error pages\n if (content.trimStart().startsWith('<!DOCTYPE') || content.trimStart().startsWith('<html')) {\n throw new Error(\n `Fetched content for \"${name}\" appears to be an HTML page, not a markdown document`,\n );\n }\n\n // Check for binary content (high ratio of non-printable characters)\n const nonPrintable = content.split('').filter((c) => {\n const code = c.charCodeAt(0);\n return code < 32 && code !== 9 && code !== 10 && code !== 13;\n }).length;\n if (nonPrintable / content.length > 0.1) {\n throw new Error(`Fetched content for \"${name}\" appears to be binary, not a text document`);\n }\n}\n\n// =============================================================================\n// Doc Type to Path Mapping\n// =============================================================================\n\n/**\n * Get the destination subdirectory for a doc type.\n */\nexport function getDocTypeSubdir(docType: DocType): string {\n switch (docType) {\n case 'guideline':\n return 'guidelines';\n case 'shortcut':\n return 'shortcuts/custom';\n case 'template':\n return 'templates';\n }\n}\n\n// =============================================================================\n// Main Add Function\n// =============================================================================\n\n/**\n * Add an external document to the tbd doc cache.\n *\n * This function:\n * 1. Converts GitHub blob URLs to raw URLs\n * 2. Fetches the content (with gh CLI fallback on 403)\n * 3. Validates the content looks like markdown\n * 4. Writes the file to .tbd/docs/{type}/{name}.md\n * 5. Atomically updates the config to include the new source\n *\n * @throws If fetching, validation, or config update fails\n */\nexport async function addDoc(tbdRoot: string, options: AddDocOptions): Promise<AddDocResult> {\n const { url, name, docType } = options;\n\n // Normalize name (strip .md if provided)\n const cleanName = name.endsWith('.md') ? name.slice(0, -3) : name;\n const filename = `${cleanName}.md`;\n const subdir = getDocTypeSubdir(docType);\n const destPath = `${subdir}/${filename}`;\n const rawUrl = githubBlobToRawUrl(url);\n\n // Fetch content\n const { content, usedGhCli } = await fetchWithGhFallback(url);\n\n // Validate content\n validateDocContent(content, cleanName);\n\n // Write file to .tbd/docs/{subdir}/{name}.md\n const fullPath = join(tbdRoot, TBD_DOCS_DIR, destPath);\n await mkdir(dirname(fullPath), { recursive: true });\n await writeFile(fullPath, content);\n\n // Atomically update config\n const config = await readConfig(tbdRoot);\n config.docs_cache ??= { files: {}, lookup_path: [] };\n config.docs_cache.files ??= {};\n config.docs_cache.files[destPath] = rawUrl;\n\n // Ensure the lookup_path includes the subdir\n const lookupDir = `.tbd/docs/${subdir}`;\n if (!config.docs_cache.lookup_path.includes(lookupDir)) {\n config.docs_cache.lookup_path.push(lookupDir);\n }\n\n await writeConfig(tbdRoot, config);\n\n return { destPath, rawUrl, usedGhCli };\n}\n","/**\n * `tbd shortcut` - Find and output documentation shortcuts.\n *\n * Shortcuts are reusable instruction templates for common tasks.\n * Give a name or description and tbd will find the matching shortcut.\n *\n * See: docs/project/specs/active/plan-2026-01-22-doc-cache-abstraction.md\n */\n\nimport { Command } from 'commander';\nimport pc from 'picocolors';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { SHORTCUT_AGENT_HEADER } from '../lib/doc-prompts.js';\nimport { requireInit, CLIError } from '../lib/errors.js';\nimport { DocCache, SCORE_PREFIX_MATCH } from '../../file/doc-cache.js';\nimport { addDoc } from '../../file/doc-add.js';\nimport { readConfig } from '../../file/config.js';\nimport { DEFAULT_SHORTCUT_PATHS } from '../../lib/paths.js';\nimport { truncate } from '../../lib/truncate.js';\nimport { formatDocSize } from '../../lib/format-utils.js';\nimport { getTerminalWidth } from '../lib/output.js';\n\ninterface ShortcutOptions {\n list?: boolean;\n all?: boolean;\n refresh?: boolean;\n quiet?: boolean;\n category?: string;\n add?: string;\n name?: string;\n}\n\n/**\n * Shortcut categories for filtering.\n * Categories are read from frontmatter.\n */\ntype ShortcutCategory =\n | 'planning'\n | 'documentation'\n | 'review'\n | 'git'\n | 'cleanup'\n | 'session'\n | 'meta';\n\nclass ShortcutHandler extends BaseCommand {\n async run(query: string | undefined, options: ShortcutOptions): Promise<void> {\n await this.execute(async () => {\n // Add mode\n if (options.add) {\n if (!options.name) {\n throw new CLIError('--name is required when using --add');\n }\n const tbdRoot = await requireInit();\n console.log(`Adding shortcut: ${options.name}`);\n console.log(` URL: ${options.add}`);\n const result = await addDoc(tbdRoot, {\n url: options.add,\n name: options.name,\n docType: 'shortcut',\n });\n if (result.usedGhCli) {\n console.log(pc.dim(' (fetched via gh CLI due to direct access restriction)'));\n }\n console.log(pc.green(` Added to ${result.destPath}`));\n console.log(pc.green(` Config updated with source: ${result.rawUrl}`));\n console.log('');\n console.log('Run `tbd shortcut --list` to verify.');\n return;\n }\n\n // Get tbd root (supports running from subdirectories)\n const tbdRoot = await requireInit();\n\n // Read config to get lookup paths (fall back to defaults)\n const config = await readConfig(tbdRoot);\n const lookupPaths = config.docs_cache?.lookup_path ?? DEFAULT_SHORTCUT_PATHS;\n\n // Create and load the doc cache with proper base directory\n const cache = new DocCache(lookupPaths, tbdRoot);\n await cache.load({ quiet: this.ctx.quiet });\n\n // Refresh mode: regenerate cache and update skill files\n if (options.refresh) {\n await this.handleRefresh(cache, tbdRoot, options.quiet);\n return;\n }\n\n // List mode\n if (options.list || options.category) {\n await this.handleList(cache, options.all, options.category);\n return;\n }\n\n // No query: show explanation + help\n if (!query) {\n await this.handleNoQuery(cache);\n return;\n }\n\n // Query provided: try exact match first, then fuzzy\n await this.handleQuery(cache, query);\n }, 'Failed to find shortcut');\n }\n\n /**\n * Handle --refresh mode: no-op since shortcuts are now generated on-the-fly.\n * Kept for backward compatibility.\n */\n private async handleRefresh(cache: DocCache, _tbdRoot: string, quiet?: boolean): Promise<void> {\n const docs = cache.list();\n\n // Count shortcuts (excluding system docs)\n const shortcutCount = docs.filter(\n (d) =>\n d.name !== 'skill-baseline' &&\n d.name !== 'skill-brief' &&\n d.name !== 'skill-minimal' &&\n d.name !== 'shortcut-explanation',\n ).length;\n\n if (!quiet) {\n if (this.ctx.json) {\n this.output.data({\n refreshed: true,\n shortcutCount,\n message: 'Shortcuts are now generated on-the-fly (no cache)',\n });\n } else {\n console.log(`${shortcutCount} shortcut(s) available (generated on-the-fly)`);\n }\n }\n }\n\n /**\n * Handle --list mode: show all available shortcuts.\n */\n private async handleList(\n cache: DocCache,\n includeAll?: boolean,\n category?: string,\n ): Promise<void> {\n let docs = cache.list(includeAll);\n\n // Filter by category if specified (read from frontmatter)\n if (category) {\n docs = docs.filter((d) => {\n const docCategory = d.frontmatter?.category as ShortcutCategory | undefined;\n return docCategory === category;\n });\n }\n\n if (this.ctx.json) {\n this.output.data(\n docs.map((d) => ({\n name: d.name,\n title: d.frontmatter?.title,\n description: d.frontmatter?.description,\n category: d.frontmatter?.category,\n path: d.path,\n sourceDir: d.sourceDir,\n sizeBytes: d.sizeBytes,\n approxTokens: d.approxTokens,\n shadowed: cache.isShadowed(d),\n })),\n );\n return;\n }\n\n if (docs.length === 0) {\n console.log('No shortcuts found.');\n console.log('Run `tbd setup --auto` to install built-in shortcuts.');\n return;\n }\n\n const maxWidth = getTerminalWidth();\n\n for (const doc of docs) {\n const shadowed = cache.isShadowed(doc);\n const name = doc.name;\n const title = doc.frontmatter?.title;\n const description = doc.frontmatter?.description ?? this.extractFallbackText(doc.content);\n\n if (shadowed) {\n // Muted style for shadowed entries\n const line = `${name} (${doc.sourceDir}) [shadowed]`;\n console.log(pc.dim(truncate(line, maxWidth)));\n } else {\n // Line 1: name (bold) + size/token info (dimmed)\n const sizeInfo = formatDocSize(doc.sizeBytes, doc.approxTokens);\n console.log(`${pc.bold(name)} ${pc.dim(sizeInfo)}`);\n\n // Line 2+: Indented \"Title: Description\"\n // Only truncate fallback body text; never truncate actual title/description\n const hasFrontmatter = title ?? doc.frontmatter?.description;\n const content =\n title && description ? `${title}: ${description}` : (title ?? description ?? '');\n if (content) {\n this.printWrappedDescription(content, maxWidth, !hasFrontmatter);\n }\n }\n }\n }\n\n /**\n * Extract fallback text from content when no frontmatter description exists.\n * Strips frontmatter and markdown syntax, takes first text, condenses whitespace.\n */\n private extractFallbackText(content: string): string | undefined {\n // Strip YAML frontmatter if present\n let text = content;\n if (text.startsWith('---')) {\n const endIndex = text.indexOf('---', 3);\n if (endIndex !== -1) {\n text = text.slice(endIndex + 3);\n }\n }\n\n // Strip markdown headers (# Title -> Title)\n text = text.replace(/^#+\\s*/gm, '');\n // Strip bold/italic markers\n text = text.replace(/\\*\\*|__|\\*|_/g, '');\n // Strip code blocks\n text = text.replace(/```[\\s\\S]*?```/g, '');\n // Strip inline code\n text = text.replace(/`[^`]+`/g, '');\n // Strip blockquotes\n text = text.replace(/^>\\s*/gm, '');\n\n // Condense all whitespace to single spaces and trim\n text = text.replace(/\\s+/g, ' ').trim();\n\n // Return first chunk of text (up to ~200 chars for reasonable fallback)\n if (text.length === 0) return undefined;\n return text.slice(0, 200);\n }\n\n /**\n * Print description indented, wrapped across lines.\n * @param text - Text to print\n * @param maxWidth - Terminal width\n * @param shouldTruncate - If true, truncate to two lines; if false, wrap all lines\n */\n private printWrappedDescription(text: string, maxWidth: number, shouldTruncate: boolean): void {\n const indent = ' ';\n const availableWidth = maxWidth - indent.length;\n\n if (text.length <= availableWidth) {\n // Fits on one line\n console.log(`${indent}${text}`);\n return;\n }\n\n if (shouldTruncate) {\n // Truncate to two lines max (for fallback body text)\n const firstLine = this.wrapAtWord(text, availableWidth);\n const remainder = text.slice(firstLine.length).trimStart();\n console.log(`${indent}${firstLine}`);\n if (remainder) {\n console.log(`${indent}${truncate(remainder, availableWidth)}`);\n }\n } else {\n // Wrap all lines without truncation (for title/description)\n let remaining = text;\n while (remaining.length > 0) {\n if (remaining.length <= availableWidth) {\n console.log(`${indent}${remaining}`);\n break;\n }\n const line = this.wrapAtWord(remaining, availableWidth);\n console.log(`${indent}${line}`);\n remaining = remaining.slice(line.length).trimStart();\n }\n }\n }\n\n /**\n * Wrap text at word boundary to fit within maxWidth.\n */\n private wrapAtWord(text: string, maxWidth: number): string {\n if (text.length <= maxWidth) return text;\n const lastSpace = text.lastIndexOf(' ', maxWidth);\n if (lastSpace > 0) {\n return text.slice(0, lastSpace);\n }\n return text.slice(0, maxWidth);\n }\n\n /**\n * Handle no query: show explanation + help.\n */\n private async handleNoQuery(cache: DocCache): Promise<void> {\n // Try to find the shortcut-explanation.md\n const explanation = cache.get('shortcut-explanation');\n if (explanation) {\n console.log(explanation.doc.content);\n } else {\n // Fallback explanation\n console.log('tbd shortcut - Find and output documentation shortcuts');\n console.log('');\n console.log('Usage:');\n console.log(' tbd shortcut <name> Find shortcut by exact name');\n console.log(' tbd shortcut <description> Find shortcut by fuzzy match');\n console.log(' tbd shortcut --list List all available shortcuts');\n console.log(' tbd shortcut --list --all Include shadowed shortcuts');\n console.log('');\n console.log('No shortcuts found. Run `tbd setup --auto` to install built-in shortcuts.');\n }\n }\n\n /**\n * Handle query: exact match first, then fuzzy.\n */\n private async handleQuery(cache: DocCache, query: string): Promise<void> {\n // Try exact match first\n const exactMatch = cache.get(query);\n if (exactMatch) {\n if (this.ctx.json) {\n this.output.data({\n name: exactMatch.doc.name,\n title: exactMatch.doc.frontmatter?.title,\n score: exactMatch.score,\n content: exactMatch.doc.content,\n });\n } else {\n console.log(SHORTCUT_AGENT_HEADER + '\\n');\n console.log(exactMatch.doc.content);\n }\n return;\n }\n\n // Fuzzy match\n const matches = cache.search(query, 5);\n if (matches.length === 0) {\n console.log(`No shortcut found matching: ${query}`);\n console.log('Run `tbd shortcut --list` to see available shortcuts.');\n return;\n }\n\n const best = matches[0]!;\n // Use PREFIX_MATCH (0.9) as threshold for high confidence\n // Below this, show suggestions instead of auto-selecting\n if (best.score < SCORE_PREFIX_MATCH) {\n // Low confidence - show suggestions instead\n console.log(`No exact match for \"${query}\". Did you mean:`);\n for (const m of matches) {\n const name = m.doc.frontmatter?.title ?? m.doc.name;\n console.log(` ${name} ${pc.dim(`(score: ${m.score.toFixed(2)})`)}`);\n }\n return;\n }\n\n // Good fuzzy match - output it\n if (this.ctx.json) {\n this.output.data({\n name: best.doc.name,\n title: best.doc.frontmatter?.title,\n score: best.score,\n content: best.doc.content,\n });\n } else {\n console.log(SHORTCUT_AGENT_HEADER + '\\n');\n console.log(best.doc.content);\n }\n }\n}\n\nexport const shortcutCommand = new Command('shortcut')\n .description('Find and output documentation shortcuts')\n .argument('[query]', 'Shortcut name or description to search for')\n .option('--list', 'List all available shortcuts')\n .option('--all', 'Include shadowed shortcuts (use with --list)')\n .option(\n '--category <category>',\n 'Filter by category: planning, documentation, review, git, cleanup, session, meta',\n )\n .option('--refresh', 'Refresh the cached shortcut directory')\n .option('--quiet', 'Suppress output (use with --refresh)')\n .option('--add <url>', 'Add a shortcut from a URL')\n .option('--name <name>', 'Name for the added shortcut (required with --add)')\n .action(async (query: string | undefined, options: ShortcutOptions, command) => {\n const handler = new ShortcutHandler(command);\n await handler.run(query, options);\n });\n","/**\n * Shared base class for document listing/lookup commands.\n *\n * Used by shortcuts, guidelines, and templates commands to provide\n * consistent behavior for --list, fuzzy search, and exact match.\n */\n\nimport type { Command } from 'commander';\nimport pc from 'picocolors';\n\nimport { BaseCommand } from './base-command.js';\nimport { shouldUseInteractiveOutput } from './context.js';\nimport { GUIDELINES_AGENT_HEADER } from './doc-prompts.js';\nimport { requireInit } from './errors.js';\nimport { DocCache, SCORE_PREFIX_MATCH } from '../../file/doc-cache.js';\nimport { addDoc, type DocType } from '../../file/doc-add.js';\nimport { truncate } from '../../lib/truncate.js';\nimport { formatDocSize } from '../../lib/format-utils.js';\nimport { getTerminalWidth, renderMarkdownWithFrontmatter, paginateOutput } from './output.js';\n\n/**\n * Configuration for a doc command handler.\n */\nexport interface DocCommandConfig {\n /** Display name for the doc type (e.g., \"shortcut\", \"guideline\", \"template\") */\n typeName: string;\n /** Plural display name (e.g., \"shortcuts\", \"guidelines\", \"templates\") */\n typeNamePlural: string;\n /** Paths to search for documents (relative to tbd root) */\n paths: string[];\n /** Names to exclude from listings (e.g., system docs) */\n excludeFromList?: string[];\n /** Content to show when no query is provided (optional) */\n noQueryDocName?: string;\n /** The doc type for --add operations */\n docType: DocType;\n}\n\n/**\n * Common options for doc commands.\n */\nexport interface DocCommandOptions {\n list?: boolean;\n all?: boolean;\n refresh?: boolean;\n quiet?: boolean;\n add?: string;\n name?: string;\n}\n\n/**\n * Base handler for document commands (shortcuts, guidelines, templates).\n *\n * Provides shared functionality for:\n * - Listing documents with --list\n * - Exact name lookup\n * - Fuzzy search\n * - Wrapped description output\n */\nexport abstract class DocCommandHandler extends BaseCommand {\n protected cache: DocCache | null = null;\n protected tbdRoot = '';\n\n constructor(\n command: Command,\n protected readonly config: DocCommandConfig,\n ) {\n super(command);\n }\n\n /**\n * Initialize the doc cache. Must be called before other operations.\n */\n protected async initCache(): Promise<void> {\n this.tbdRoot = await requireInit();\n this.cache = new DocCache(this.config.paths, this.tbdRoot);\n await this.cache.load({ quiet: this.ctx.quiet });\n }\n\n /**\n * Handle --list mode: show all available documents.\n */\n protected async handleList(includeAll?: boolean): Promise<void> {\n if (!this.cache) throw new Error('Cache not initialized');\n\n const docs = this.cache.list(includeAll);\n\n if (this.ctx.json) {\n this.output.data(\n docs.map((d) => ({\n name: d.name,\n title: d.frontmatter?.title,\n description: d.frontmatter?.description,\n path: d.path,\n sourceDir: d.sourceDir,\n sizeBytes: d.sizeBytes,\n approxTokens: d.approxTokens,\n shadowed: this.cache!.isShadowed(d),\n })),\n );\n return;\n }\n\n if (docs.length === 0) {\n console.log(`No ${this.config.typeNamePlural} found.`);\n console.log(`Run \\`tbd setup --auto\\` to install built-in ${this.config.typeNamePlural}.`);\n return;\n }\n\n const maxWidth = getTerminalWidth();\n\n for (const doc of docs) {\n const shadowed = this.cache.isShadowed(doc);\n const name = doc.name;\n const title = doc.frontmatter?.title;\n const description = doc.frontmatter?.description ?? this.extractFallbackText(doc.content);\n\n if (shadowed) {\n // Muted style for shadowed entries\n const line = `${name} (${doc.sourceDir}) [shadowed]`;\n console.log(pc.dim(truncate(line, maxWidth)));\n } else {\n // Line 1: name (bold) + size/token info (dimmed)\n const sizeInfo = formatDocSize(doc.sizeBytes, doc.approxTokens);\n console.log(`${pc.bold(name)} ${pc.dim(sizeInfo)}`);\n\n // Line 2+: Indented \"Title: Description\"\n const hasFrontmatter = title ?? doc.frontmatter?.description;\n const content =\n title && description ? `${title}: ${description}` : (title ?? description ?? '');\n if (content) {\n this.printWrappedDescription(content, maxWidth, !hasFrontmatter);\n }\n }\n }\n }\n\n /**\n * Handle no query: show explanation + help.\n */\n protected async handleNoQuery(): Promise<void> {\n if (!this.cache) throw new Error('Cache not initialized');\n\n // Try to find the explanation doc if configured\n if (this.config.noQueryDocName) {\n const explanation = this.cache.get(this.config.noQueryDocName);\n if (explanation) {\n console.log(explanation.doc.content);\n return;\n }\n }\n\n // Fallback explanation\n const { typeName, typeNamePlural } = this.config;\n console.log(`tbd ${typeNamePlural} - Find and output ${typeNamePlural}`);\n console.log('');\n console.log('Usage:');\n console.log(` tbd ${typeNamePlural} <name> Find ${typeName} by exact name`);\n console.log(` tbd ${typeNamePlural} <description> Find ${typeName} by fuzzy match`);\n console.log(` tbd ${typeNamePlural} --list List all available ${typeNamePlural}`);\n console.log(` tbd ${typeNamePlural} --list --all Include shadowed ${typeNamePlural}`);\n console.log('');\n console.log(\n `No ${typeNamePlural} found. Run \\`tbd setup --auto\\` to install built-in ${typeNamePlural}.`,\n );\n }\n\n /**\n * Get the agent instruction header for the doc type.\n * Returns undefined if no header should be shown.\n */\n protected getAgentHeader(): string | undefined {\n if (this.config.typeName === 'guideline') {\n return GUIDELINES_AGENT_HEADER;\n }\n // Templates and other types don't need a header\n return undefined;\n }\n\n /**\n * Handle query: exact match first, then fuzzy.\n */\n protected async handleQuery(query: string): Promise<void> {\n if (!this.cache) throw new Error('Cache not initialized');\n\n // Try exact match first\n const exactMatch = this.cache.get(query);\n if (exactMatch) {\n if (this.ctx.json) {\n this.output.data({\n name: exactMatch.doc.name,\n title: exactMatch.doc.frontmatter?.title,\n score: exactMatch.score,\n content: exactMatch.doc.content,\n });\n } else {\n await this.outputDocContent(exactMatch.doc.content);\n }\n return;\n }\n\n // Fuzzy match\n const matches = this.cache.search(query, 5);\n if (matches.length === 0) {\n console.log(`No ${this.config.typeName} found matching: ${query}`);\n console.log(\n `Run \\`tbd ${this.config.typeNamePlural} --list\\` to see available ${this.config.typeNamePlural}.`,\n );\n return;\n }\n\n const best = matches[0]!;\n // Use PREFIX_MATCH (0.9) as threshold for high confidence\n if (best.score < SCORE_PREFIX_MATCH) {\n // Low confidence - show suggestions instead\n console.log(`No exact match for \"${query}\". Did you mean:`);\n for (const m of matches) {\n const name = m.doc.frontmatter?.title ?? m.doc.name;\n console.log(` ${name} ${pc.dim(`(score: ${m.score.toFixed(2)})`)}`);\n }\n return;\n }\n\n // Good fuzzy match - output it\n if (this.ctx.json) {\n this.output.data({\n name: best.doc.name,\n title: best.doc.frontmatter?.title,\n score: best.score,\n content: best.doc.content,\n });\n } else {\n await this.outputDocContent(best.doc.content);\n }\n }\n\n /**\n * Output document content with interactive formatting (colors, pagination) when appropriate.\n * For non-interactive output (pipes, agents), outputs plain text.\n *\n * Handles YAML frontmatter properly by rendering it separately with YAML syntax highlighting.\n */\n protected async outputDocContent(content: string): Promise<void> {\n const header = this.getAgentHeader();\n\n // Use interactive formatting (colors, pagination) only for TTY\n if (shouldUseInteractiveOutput(this.ctx)) {\n // Render content with proper frontmatter handling (YAML gets syntax highlighting)\n let output = renderMarkdownWithFrontmatter(content, this.ctx.color);\n\n // Prepend header if present (after rendering so it doesn't interfere with frontmatter)\n if (header) {\n output = header + '\\n\\n' + output;\n }\n\n await paginateOutput(output, true);\n } else {\n // Plain text output - prepend header to raw content\n let output = content;\n if (header) {\n output = header + '\\n\\n' + output;\n }\n console.log(output);\n }\n }\n\n /**\n * Handle --add mode: add an external document by URL.\n */\n protected async handleAdd(url: string, name: string): Promise<void> {\n if (!this.tbdRoot) {\n this.tbdRoot = await requireInit();\n }\n\n const { typeName, docType } = this.config;\n\n console.log(`Adding ${typeName}: ${name}`);\n console.log(` URL: ${url}`);\n\n const result = await addDoc(this.tbdRoot, { url, name, docType });\n\n if (result.usedGhCli) {\n console.log(pc.dim(' (fetched via gh CLI due to direct access restriction)'));\n }\n\n console.log(pc.green(` Added to ${result.destPath}`));\n console.log(pc.green(` Config updated with source: ${result.rawUrl}`));\n console.log('');\n console.log(`Run \\`tbd ${this.config.typeNamePlural} --list\\` to verify.`);\n }\n\n /**\n * Extract fallback text from content when no frontmatter description exists.\n */\n protected extractFallbackText(content: string): string | undefined {\n // Strip YAML frontmatter if present\n let text = content;\n if (text.startsWith('---')) {\n const endIndex = text.indexOf('---', 3);\n if (endIndex !== -1) {\n text = text.slice(endIndex + 3);\n }\n }\n\n // Strip markdown headers (# Title -> Title)\n text = text.replace(/^#+\\s*/gm, '');\n // Strip bold/italic markers\n text = text.replace(/\\*\\*|__|\\*|_/g, '');\n // Strip code blocks\n text = text.replace(/```[\\s\\S]*?```/g, '');\n // Strip inline code\n text = text.replace(/`[^`]+`/g, '');\n // Strip blockquotes\n text = text.replace(/^>\\s*/gm, '');\n\n // Condense all whitespace to single spaces and trim\n text = text.replace(/\\s+/g, ' ').trim();\n\n // Return first chunk of text (up to ~200 chars for reasonable fallback)\n if (text.length === 0) return undefined;\n return text.slice(0, 200);\n }\n\n /**\n * Print description indented, wrapped across lines.\n */\n protected printWrappedDescription(text: string, maxWidth: number, shouldTruncate: boolean): void {\n const indent = ' ';\n const availableWidth = maxWidth - indent.length;\n\n if (text.length <= availableWidth) {\n console.log(`${indent}${text}`);\n return;\n }\n\n if (shouldTruncate) {\n // Truncate to two lines max (for fallback body text)\n const firstLine = this.wrapAtWord(text, availableWidth);\n const remainder = text.slice(firstLine.length).trimStart();\n console.log(`${indent}${firstLine}`);\n if (remainder) {\n console.log(`${indent}${truncate(remainder, availableWidth)}`);\n }\n } else {\n // Wrap all lines without truncation (for title/description)\n let remaining = text;\n while (remaining.length > 0) {\n if (remaining.length <= availableWidth) {\n console.log(`${indent}${remaining}`);\n break;\n }\n const line = this.wrapAtWord(remaining, availableWidth);\n console.log(`${indent}${line}`);\n remaining = remaining.slice(line.length).trimStart();\n }\n }\n }\n\n /**\n * Wrap text at word boundary to fit within maxWidth.\n */\n protected wrapAtWord(text: string, maxWidth: number): string {\n if (text.length <= maxWidth) return text;\n const lastSpace = text.lastIndexOf(' ', maxWidth);\n if (lastSpace > 0) {\n return text.slice(0, lastSpace);\n }\n return text.slice(0, maxWidth);\n }\n}\n","/**\n * `tbd guidelines` - Find and output coding guidelines.\n *\n * Guidelines are reusable coding rules and best practices documents.\n * Give a name or description and tbd will find the matching guideline.\n */\n\nimport { Command } from 'commander';\nimport pc from 'picocolors';\n\nimport { DocCommandHandler, type DocCommandOptions } from '../lib/doc-command-handler.js';\nimport { CLIError } from '../lib/errors.js';\nimport { DEFAULT_GUIDELINES_PATHS } from '../../lib/paths.js';\nimport { truncate } from '../../lib/truncate.js';\nimport { formatDocSize } from '../../lib/format-utils.js';\nimport { getTerminalWidth } from '../lib/output.js';\n\n/**\n * Guideline categories for filtering.\n */\ntype GuidelineCategory = 'typescript' | 'python' | 'testing' | 'general';\n\n/**\n * Infer category from guideline name.\n */\nfunction inferGuidelineCategory(name: string): GuidelineCategory | undefined {\n // TypeScript guidelines\n if (name.startsWith('typescript-')) {\n return 'typescript';\n }\n\n // Python guidelines\n if (name.startsWith('python-')) {\n return 'python';\n }\n\n // Testing guidelines\n if (name.includes('tdd') || name.includes('testing') || name.includes('golden')) {\n return 'testing';\n }\n\n // General guidelines (everything else starting with general- or other general rules)\n if (\n name.startsWith('general-') ||\n name.includes('rules') ||\n name.includes('patterns') ||\n name.startsWith('backward-') ||\n name.startsWith('convex-') ||\n name.startsWith('release-') ||\n name.startsWith('writing-')\n ) {\n return 'general';\n }\n\n return undefined;\n}\n\ninterface GuidelinesOptions extends DocCommandOptions {\n category?: string;\n add?: string;\n name?: string;\n}\n\nclass GuidelinesHandler extends DocCommandHandler {\n constructor(command: Command) {\n super(command, {\n typeName: 'guideline',\n typeNamePlural: 'guidelines',\n paths: DEFAULT_GUIDELINES_PATHS,\n docType: 'guideline',\n });\n }\n\n async run(query: string | undefined, options: GuidelinesOptions): Promise<void> {\n await this.execute(async () => {\n // Add mode\n if (options.add) {\n if (!options.name) {\n throw new CLIError('--name is required when using --add');\n }\n await this.handleAdd(options.add, options.name);\n return;\n }\n\n await this.initCache();\n\n // List mode (also triggered by --category)\n if (options.list || options.category) {\n await this.handleListWithCategory(options.all, options.category);\n return;\n }\n\n // No query: show help\n if (!query) {\n await this.handleNoQuery();\n return;\n }\n\n // Query provided: try exact match first, then fuzzy\n await this.handleQuery(query);\n }, 'Failed to find guideline');\n }\n\n /**\n * Handle --list mode with optional category filtering.\n */\n private async handleListWithCategory(includeAll?: boolean, category?: string): Promise<void> {\n if (!this.cache) throw new Error('Cache not initialized');\n\n let docs = this.cache.list(includeAll);\n\n // Filter by category if specified\n if (category) {\n docs = docs.filter((d) => {\n const docCategory = inferGuidelineCategory(d.name);\n return docCategory === category;\n });\n }\n\n if (this.ctx.json) {\n this.output.data(\n docs.map((d) => ({\n name: d.name,\n title: d.frontmatter?.title,\n description: d.frontmatter?.description,\n category: inferGuidelineCategory(d.name),\n path: d.path,\n sourceDir: d.sourceDir,\n sizeBytes: d.sizeBytes,\n approxTokens: d.approxTokens,\n shadowed: this.cache!.isShadowed(d),\n })),\n );\n return;\n }\n\n if (docs.length === 0) {\n if (category) {\n console.log(`No guidelines found in category: ${category}`);\n console.log('Valid categories: typescript, python, testing, general');\n } else {\n console.log('No guidelines found.');\n console.log('Run `tbd setup --auto` to install built-in guidelines.');\n }\n return;\n }\n\n const maxWidth = getTerminalWidth();\n\n for (const doc of docs) {\n const shadowed = this.cache.isShadowed(doc);\n const name = doc.name;\n const title = doc.frontmatter?.title;\n const description = doc.frontmatter?.description ?? this.extractFallbackText(doc.content);\n\n if (shadowed) {\n const line = `${name} (${doc.sourceDir}) [shadowed]`;\n console.log(pc.dim(truncate(line, maxWidth)));\n } else {\n const sizeInfo = formatDocSize(doc.sizeBytes, doc.approxTokens);\n console.log(`${pc.bold(name)} ${pc.dim(sizeInfo)}`);\n const hasFrontmatter = title ?? doc.frontmatter?.description;\n const content =\n title && description ? `${title}: ${description}` : (title ?? description ?? '');\n if (content) {\n this.printWrappedDescription(content, maxWidth, !hasFrontmatter);\n }\n }\n }\n }\n}\n\nexport const guidelinesCommand = new Command('guidelines')\n .description('Find and output coding guidelines')\n .argument('[query]', 'Guideline name or description to search for')\n .option('--list', 'List all available guidelines')\n .option('--all', 'Include shadowed guidelines (use with --list)')\n .option('--category <category>', 'Filter by category: typescript, python, testing, general')\n .option('--add <url>', 'Add a guideline from a URL')\n .option('--name <name>', 'Name for the added guideline (required with --add)')\n .action(async (query: string | undefined, options: GuidelinesOptions, command) => {\n const handler = new GuidelinesHandler(command);\n await handler.run(query, options);\n });\n","/**\n * `tbd template` - Find and output document templates.\n *\n * Templates are reusable document templates for specs, research briefs, etc.\n * Give a name or description and tbd will find the matching template.\n */\n\nimport { Command } from 'commander';\n\nimport { DocCommandHandler, type DocCommandOptions } from '../lib/doc-command-handler.js';\nimport { CLIError } from '../lib/errors.js';\nimport { DEFAULT_TEMPLATE_PATHS } from '../../lib/paths.js';\n\nclass TemplateHandler extends DocCommandHandler {\n constructor(command: Command) {\n super(command, {\n typeName: 'template',\n typeNamePlural: 'templates',\n paths: DEFAULT_TEMPLATE_PATHS,\n docType: 'template',\n });\n }\n\n async run(query: string | undefined, options: DocCommandOptions): Promise<void> {\n await this.execute(async () => {\n // Add mode\n if (options.add) {\n if (!options.name) {\n throw new CLIError('--name is required when using --add');\n }\n await this.handleAdd(options.add, options.name);\n return;\n }\n\n await this.initCache();\n\n // List mode\n if (options.list) {\n await this.handleList(options.all);\n return;\n }\n\n // No query: show help\n if (!query) {\n await this.handleNoQuery();\n return;\n }\n\n // Query provided: try exact match first, then fuzzy\n await this.handleQuery(query);\n }, 'Failed to find template');\n }\n}\n\nexport const templateCommand = new Command('template')\n .description('Find and output document templates')\n .argument('[query]', 'Template name or description to search for')\n .option('--list', 'List all available templates')\n .option('--all', 'Include shadowed templates (use with --list)')\n .option('--add <url>', 'Add a template from a URL')\n .option('--name <name>', 'Name for the added template (required with --add)')\n .action(async (query: string | undefined, options: DocCommandOptions, command) => {\n const handler = new TemplateHandler(command);\n await handler.run(query, options);\n });\n","/**\n * `tbd setup` - Configure tbd integration with editors and tools.\n *\n * Requires a git repository. All setup artifacts (.tbd/, .claude/) are placed\n * at the git root, adjacent to .git/. Installation is always project-local —\n * there is no global/user-level install.\n *\n * Options:\n * - `tbd setup --auto` - Non-interactive setup (for agents/scripts)\n * - `tbd setup --interactive` - Interactive setup with prompts (for humans)\n * - `tbd setup --from-beads` - Migrate from Beads to tbd\n *\n * See: tbd-design.md §6.4.2 Claude Code Integration\n */\n\nimport { Command } from 'commander';\nimport { readFile, mkdir, access, rm, rename, chmod, readdir } from 'node:fs/promises';\nimport { spawnSync } from 'node:child_process';\nimport { join, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { writeFile } from 'atomically';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { CLIError } from '../lib/errors.js';\nimport { loadSkillContent } from './prime.js';\nimport { stripFrontmatter, insertAfterFrontmatter } from '../../utils/markdown-utils.js';\nimport { pathExists } from '../../utils/file-utils.js';\nimport { ensureGitignorePatterns } from '../../utils/gitignore-utils.js';\nimport { type DiagnosticResult, renderDiagnostics } from '../lib/diagnostics.js';\nimport { isValidPrefix, isRecommendedPrefix, getBeadsPrefix } from '../lib/prefix-detection.js';\nimport {\n initConfig,\n isInitialized,\n readConfig,\n readConfigWithMigration,\n findTbdRoot,\n writeConfig,\n markWelcomeSeen,\n} from '../../file/config.js';\nimport { syncDocsWithDefaults } from '../../file/doc-sync.js';\nimport { VERSION } from '../lib/version.js';\nimport {\n TBD_DIR,\n TBD_DOCS_DIR,\n WORKTREE_DIR_NAME,\n DATA_SYNC_DIR_NAME,\n DEFAULT_SHORTCUT_PATHS,\n DEFAULT_GUIDELINES_PATHS,\n TBD_SHORTCUTS_SYSTEM,\n TBD_SHORTCUTS_STANDARD,\n TBD_GUIDELINES_DIR,\n TBD_TEMPLATES_DIR,\n} from '../../lib/paths.js';\nimport { getClaudePaths, getAgentsMdPath, GLOBAL_CLAUDE_DIR } from '../../lib/integration-paths.js';\nimport { initWorktree, isInGitRepo, findGitRoot, checkWorktreeHealth } from '../../file/git.js';\nimport { DocCache, generateShortcutDirectory } from '../../file/doc-cache.js';\n\n/**\n * Get the shortcut and guidelines directory content for appending to installed skill files.\n * Always generates on-the-fly from installed shortcuts and guidelines.\n *\n * @param quiet - If true, suppress auto-sync output (default: false)\n */\nasync function getShortcutDirectory(quiet = false): Promise<string | null> {\n const cwd = process.cwd();\n\n // Try to find tbd root (may not be initialized)\n const tbdRoot = await findTbdRoot(cwd);\n if (!tbdRoot) {\n return null;\n }\n\n // Load shortcuts\n const shortcutCache = new DocCache(DEFAULT_SHORTCUT_PATHS, tbdRoot);\n await shortcutCache.load({ quiet });\n const shortcuts = shortcutCache.list();\n\n // Load guidelines\n const guidelinesCache = new DocCache(DEFAULT_GUIDELINES_PATHS, tbdRoot);\n await guidelinesCache.load({ quiet });\n const guidelines = guidelinesCache.list();\n\n // If no docs loaded, skip directory\n if (shortcuts.length === 0 && guidelines.length === 0) {\n return null;\n }\n\n return generateShortcutDirectory(shortcuts, guidelines);\n}\n\n/**\n * Get the tbd section content for AGENTS.md (Codex integration).\n * Loads from SKILL.md, strips frontmatter, and wraps in TBD INTEGRATION markers.\n *\n * @param quiet - If true, suppress auto-sync output (default: false)\n */\nasync function getCodexTbdSection(quiet = false): Promise<string> {\n const skillContent = await loadSkillContent();\n let content = stripFrontmatter(skillContent);\n const directory = await getShortcutDirectory(quiet);\n if (directory) {\n content = content.trimEnd() + '\\n\\n' + directory + '\\n';\n }\n return `<!-- BEGIN TBD INTEGRATION -->\\n${content}<!-- END TBD INTEGRATION -->\\n`;\n}\n\ninterface SetupClaudeOptions {\n check?: boolean;\n remove?: boolean;\n}\n\ninterface SetupCodexOptions {\n check?: boolean;\n remove?: boolean;\n}\n\n/**\n * Script to ensure tbd CLI is installed and run tbd prime.\n * Installed to project .claude/scripts/tbd-session.sh.\n * Runs on SessionStart and PreCompact to ensure tbd is available and provide orientation.\n *\n * Usage:\n * tbd-session.sh # Ensure tbd + run tbd prime\n * tbd-session.sh --brief # Ensure tbd + run tbd prime --brief (for PreCompact)\n */\nconst TBD_SESSION_SCRIPT = `#!/bin/bash\n# Ensure tbd CLI is installed and run tbd prime for Claude Code sessions\n# Installed by: tbd setup --auto\n# This script runs on SessionStart and PreCompact\n\n# Get npm global bin directory (if npm is available)\nNPM_GLOBAL_BIN=\"\"\nif command -v npm &> /dev/null; then\n NPM_PREFIX=$(npm config get prefix 2>/dev/null)\n if [ -n \"$NPM_PREFIX\" ] && [ -d \"$NPM_PREFIX/bin\" ]; then\n NPM_GLOBAL_BIN=\"$NPM_PREFIX/bin\"\n fi\nfi\n\n# Add common binary locations to PATH (persists for entire script)\n# Include npm global bin if found\nexport PATH=\"$NPM_GLOBAL_BIN:$HOME/.local/bin:$HOME/bin:/usr/local/bin:$PATH\"\n\n# Function to ensure tbd is available\nensure_tbd() {\n # Check if tbd is already installed\n if command -v tbd &> /dev/null; then\n return 0\n fi\n\n echo \"[tbd] CLI not found, installing...\"\n\n # Try npm first (most common for Node.js tools)\n if command -v npm &> /dev/null; then\n echo \"[tbd] Installing via npm...\"\n npm install -g get-tbd 2>/dev/null || {\n # If global install fails (permissions), try local install\n echo \"[tbd] Global npm install failed, trying user install...\"\n mkdir -p ~/.local/bin\n npm install --prefix ~/.local get-tbd\n # Create symlink if needed\n if [ -f ~/.local/node_modules/.bin/tbd ]; then\n ln -sf ~/.local/node_modules/.bin/tbd ~/.local/bin/tbd\n fi\n }\n elif command -v pnpm &> /dev/null; then\n echo \"[tbd] Installing via pnpm...\"\n pnpm add -g get-tbd\n elif command -v yarn &> /dev/null; then\n echo \"[tbd] Installing via yarn...\"\n yarn global add get-tbd\n else\n echo \"[tbd] ERROR: No package manager found (npm, pnpm, or yarn required)\"\n echo \"[tbd] Please install Node.js and npm, then run: npm install -g get-tbd\"\n return 1\n fi\n\n # Verify installation\n if command -v tbd &> /dev/null; then\n echo \"[tbd] Successfully installed to $(which tbd)\"\n return 0\n else\n echo \"[tbd] WARNING: tbd installed but not found in PATH\"\n echo \"[tbd] Checking common locations...\"\n # Try to find and add to path (include npm global bin)\n for dir in \"$NPM_GLOBAL_BIN\" ~/.local/bin ~/.local/node_modules/.bin /usr/local/bin; do\n if [ -n \"$dir\" ] && [ -x \"$dir/tbd\" ]; then\n export PATH=\"$dir:$PATH\"\n echo \"[tbd] Found at $dir/tbd\"\n return 0\n fi\n done\n echo \"[tbd] Could not locate tbd after installation\"\n return 1\n fi\n}\n\n# Main\nensure_tbd || exit 1\n\n# Run tbd prime with any passed arguments (e.g., --brief for PreCompact)\ntbd prime \"$@\"\n`;\n\n/**\n * Claude Code session hooks configuration.\n * Always uses project-relative paths so hooks work in any environment\n * (local dev, Claude Code Cloud, etc.).\n */\nconst CLAUDE_SESSION_HOOKS = {\n hooks: {\n SessionStart: [\n {\n matcher: '',\n hooks: [{ type: 'command', command: 'bash .claude/scripts/tbd-session.sh' }],\n },\n ],\n PreCompact: [\n {\n matcher: '',\n hooks: [{ type: 'command', command: 'bash .claude/scripts/tbd-session.sh --brief' }],\n },\n ],\n },\n};\n\n/**\n * Claude Code project-local hooks configuration (installed to .claude/settings.json)\n * PostToolUse hook reminds about tbd sync after git push\n */\nconst CLAUDE_PROJECT_HOOKS = {\n hooks: {\n PostToolUse: [\n {\n matcher: 'Bash',\n hooks: [\n {\n type: 'command',\n command: '\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/tbd-closing-reminder.sh',\n },\n ],\n },\n ],\n },\n};\n\n/**\n * Script to remind about close protocol after git push\n */\nconst TBD_CLOSE_PROTOCOL_SCRIPT = `#!/bin/bash\n# Remind about close protocol after git push\n# Installed by: tbd setup claude\n\ninput=$(cat)\ncommand=$(echo \"$input\" | jq -r '.tool_input.command // empty')\n\n# Check if this is a git push command and .tbd exists\nif [[ \"$command\" == git\\\\ push* ]] || [[ \"$command\" == *\"&& git push\"* ]] || [[ \"$command\" == *\"; git push\"* ]]; then\n if [ -d \".tbd\" ]; then\n tbd closing\n fi\nfi\n\nexit 0\n`;\n\n/**\n * SessionStart hook entry for the gh CLI ensure script.\n * Installed to project-local .claude/settings.json when use_gh_cli is true.\n */\nconst GH_CLI_HOOK_ENTRY = {\n matcher: '',\n hooks: [{ type: 'command', command: 'bash .claude/scripts/ensure-gh-cli.sh', timeout: 120 }],\n};\n\n/**\n * Command string used to identify the gh CLI hook entry in settings.json.\n */\nconst GH_CLI_HOOK_COMMAND_PATTERN = 'ensure-gh-cli';\n\n/**\n * Load a bundled script from dist/docs/install/ (or dev fallback).\n * Used to read real .sh files that are copied into the npm package at build time.\n */\nasync function loadBundledScript(name: string): Promise<string> {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n // Flat bundle: dist/docs/install/<name> (tsdown produces flat dist/)\n const flatBundlePath = join(__dirname, 'docs', 'install', name);\n // Nested bundle: dist/cli/commands/../../docs/install/<name>\n const nestedBundlePath = join(__dirname, '..', 'docs', 'install', name);\n // Dev fallback: packages/tbd/docs/install/<name>\n const devPath = join(__dirname, '..', '..', '..', 'docs', 'install', name);\n for (const p of [flatBundlePath, nestedBundlePath, devPath]) {\n try {\n return await readFile(p, 'utf-8');\n } catch {\n continue;\n }\n }\n throw new Error(`Bundled script not found: ${name}`);\n}\n\n/**\n * AGENTS.md integration markers for Codex/Factory.ai\n * Content is now generated dynamically from SKILL.md via getCodexTbdSection()\n */\nconst CODEX_BEGIN_MARKER = '<!-- BEGIN TBD INTEGRATION -->';\nconst CODEX_END_MARKER = '<!-- END TBD INTEGRATION -->';\n\n/**\n * Generate a new AGENTS.md file with tbd integration.\n *\n * @param quiet - If true, suppress auto-sync output (default: false)\n */\nasync function getCodexNewAgentsFile(quiet = false): Promise<string> {\n const tbdSection = await getCodexTbdSection(quiet);\n return `# Project Instructions for AI Agents\n\nThis file provides instructions and context for AI coding agents working on this project.\n\n${tbdSection}\n## Build & Test\n\n_Add your build and test commands here_\n\n\\`\\`\\`bash\n# Example:\n# npm install\n# npm test\n\\`\\`\\`\n\n## Architecture Overview\n\n_Add a brief overview of your project architecture_\n\n## Conventions & Patterns\n\n_Add your project-specific conventions here_\n`;\n}\n\n/**\n * Legacy script patterns to clean up from .claude/scripts/\n * These were used in older versions of tbd before hooks moved to `tbd prime`\n */\nconst LEGACY_TBD_SCRIPTS = ['setup-tbd.sh', 'ensure-tbd-cli.sh', 'ensure-tbd.sh', 'tbd-setup.sh'];\n\n/**\n * Patterns to identify legacy tbd hooks that should be removed.\n * These patterns match old-style commands that are no longer used.\n */\nconst LEGACY_TBD_HOOK_PATTERNS = [\n /\\.claude\\/scripts\\/.*tbd/i, // Any tbd-related script in .claude/scripts/\n /tbd\\s+setup\\s+claude/i, // Old command: tbd setup claude\n /setup-tbd\\.sh/i, // Old script name\n /ensure-tbd/i, // Old script names\n];\n\nclass SetupClaudeHandler extends BaseCommand {\n private projectDir: string | undefined;\n\n setProjectDir(dir: string): void {\n this.projectDir = dir;\n }\n\n async run(options: SetupClaudeOptions): Promise<void> {\n const cwd = this.projectDir ?? process.cwd();\n const claudePaths = getClaudePaths(cwd);\n\n if (options.check) {\n await this.checkClaudeSetup(claudePaths.skill);\n return;\n }\n\n if (options.remove) {\n await this.removeClaudeSetup(claudePaths.skill);\n return;\n }\n\n await this.installClaudeSetup(claudePaths.skill);\n }\n\n private async checkClaudeSetup(skillPath: string): Promise<void> {\n const cwd = this.projectDir ?? process.cwd();\n let sessionScriptInstalled = false;\n let sessionStartHook = false;\n let preCompactHook = false;\n let postToolUseHook = false;\n let hookScriptInstalled = false;\n let skillInstalled = false;\n\n // All hooks and scripts are project-local in .claude/\n const projectSettingsPath = join(cwd, '.claude', 'settings.json');\n const sessionScript = join(cwd, '.claude', 'scripts', 'tbd-session.sh');\n const hookScriptPath = join(cwd, '.claude', 'hooks', 'tbd-closing-reminder.sh');\n\n // Check for tbd-session.sh script\n try {\n await access(sessionScript);\n sessionScriptInstalled = true;\n } catch {\n // Script doesn't exist\n }\n\n // Check hooks in project settings\n try {\n await access(projectSettingsPath);\n const content = await readFile(projectSettingsPath, 'utf-8');\n const settings = JSON.parse(content) as Record<string, unknown>;\n const hooks = settings.hooks as Record<string, unknown> | undefined;\n\n if (hooks) {\n const sessionStart = hooks.SessionStart as { hooks?: { command?: string }[] }[];\n const preCompact = hooks.PreCompact as { hooks?: { command?: string }[] }[];\n const postToolUse = hooks.PostToolUse as { hooks?: { command?: string }[] }[];\n\n sessionStartHook =\n sessionStart?.some((h) =>\n h.hooks?.some(\n (hook) =>\n (hook.command?.includes('tbd prime') ?? false) ||\n (hook.command?.includes('tbd-session.sh') ?? false),\n ),\n ) ?? false;\n\n preCompactHook =\n preCompact?.some((h) =>\n h.hooks?.some(\n (hook) =>\n (hook.command?.includes('tbd prime') ?? false) ||\n (hook.command?.includes('tbd-session.sh') ?? false),\n ),\n ) ?? false;\n\n postToolUseHook =\n postToolUse?.some((h) =>\n h.hooks?.some((hook) => hook.command?.includes('tbd-closing-reminder')),\n ) ?? false;\n }\n } catch {\n // Project settings file doesn't exist\n }\n\n try {\n await access(hookScriptPath);\n hookScriptInstalled = true;\n } catch {\n // Hook script doesn't exist\n }\n\n const sessionHooksInstalled = sessionStartHook && preCompactHook && sessionScriptInstalled;\n const projectHooksInstalled = postToolUseHook && hookScriptInstalled;\n\n // Check skill file in project\n try {\n await access(skillPath);\n skillInstalled = true;\n } catch {\n // Skill file doesn't exist\n }\n\n const fullyInstalled = sessionHooksInstalled && projectHooksInstalled && skillInstalled;\n\n // Build diagnostic results for text output\n const diagnostics: DiagnosticResult[] = [];\n const settingsRelPath = '.claude/settings.json';\n\n // Session hooks diagnostic\n if (sessionHooksInstalled) {\n diagnostics.push({\n name: 'Session hooks',\n status: 'ok',\n message: 'SessionStart, PreCompact',\n path: settingsRelPath,\n });\n } else if (sessionStartHook || preCompactHook) {\n diagnostics.push({\n name: 'Session hooks',\n status: 'warn',\n message: 'partially configured',\n path: settingsRelPath,\n suggestion: 'Run: tbd setup --auto',\n });\n } else {\n diagnostics.push({\n name: 'Session hooks',\n status: 'warn',\n message: 'not configured',\n path: settingsRelPath,\n suggestion: 'Run: tbd setup --auto',\n });\n }\n\n // Project hooks diagnostic\n if (projectHooksInstalled) {\n diagnostics.push({\n name: 'Project hooks',\n status: 'ok',\n message: 'PostToolUse sync reminder',\n path: settingsRelPath,\n });\n } else if (postToolUseHook || hookScriptInstalled) {\n diagnostics.push({\n name: 'Project hooks',\n status: 'warn',\n message: 'partially configured',\n path: settingsRelPath,\n suggestion: 'Run: tbd setup --auto',\n });\n } else {\n diagnostics.push({\n name: 'Project hooks',\n status: 'warn',\n message: 'not configured',\n path: settingsRelPath,\n suggestion: 'Run: tbd setup --auto',\n });\n }\n\n // Skill file diagnostic\n const skillRelPath = '.claude/skills/tbd/SKILL.md';\n if (skillInstalled) {\n diagnostics.push({\n name: 'Skill file',\n status: 'ok',\n path: skillRelPath,\n });\n } else {\n diagnostics.push({\n name: 'Skill file',\n status: 'warn',\n message: 'not found',\n path: skillRelPath,\n suggestion: 'Run: tbd setup --auto',\n });\n }\n\n this.output.data(\n {\n installed: fullyInstalled,\n sessionHooks: {\n installed: sessionHooksInstalled,\n sessionStart: sessionStartHook,\n preCompact: preCompactHook,\n script: sessionScriptInstalled,\n path: projectSettingsPath,\n },\n projectHooks: {\n installed: projectHooksInstalled,\n postToolUse: postToolUseHook,\n hookScript: hookScriptInstalled,\n path: projectSettingsPath,\n },\n skill: { installed: skillInstalled, path: skillPath },\n },\n () => {\n const colors = this.output.getColors();\n renderDiagnostics(diagnostics, colors);\n },\n );\n }\n\n private async removeClaudeSetup(skillPath: string): Promise<void> {\n const cwd = this.projectDir ?? process.cwd();\n const claudePaths = getClaudePaths(cwd);\n let removedHooks = false;\n let removedScripts = false;\n let removedSkill = false;\n\n // Remove hooks from project .claude/settings.json\n try {\n await access(claudePaths.settings);\n const content = await readFile(claudePaths.settings, 'utf-8');\n const settings = JSON.parse(content) as Record<string, unknown>;\n\n if (settings.hooks) {\n const hooks = settings.hooks as Record<string, unknown>;\n\n // Remove all tbd hooks (SessionStart, PreCompact, PostToolUse)\n const filterTbdHooks = (arr: { hooks?: { command?: string }[] }[] | undefined) => {\n if (!arr) return undefined;\n return arr.filter(\n (h) =>\n !h.hooks?.some(\n (hook) =>\n (hook.command?.includes('tbd-closing-reminder') ?? false) ||\n (hook.command?.includes('tbd-session.sh') ?? false) ||\n (hook.command?.includes('tbd prime') ?? false),\n ),\n );\n };\n\n for (const hookType of ['PostToolUse', 'SessionStart', 'PreCompact'] as const) {\n const filtered = filterTbdHooks(hooks[hookType] as { hooks?: { command?: string }[] }[]);\n if (filtered?.length === 0) delete hooks[hookType];\n else if (filtered) hooks[hookType] = filtered;\n }\n\n if (Object.keys(hooks).length === 0) {\n delete settings.hooks;\n }\n\n await writeFile(claudePaths.settings, JSON.stringify(settings, null, 2) + '\\n');\n removedHooks = true;\n }\n } catch {\n // Project settings file doesn't exist\n }\n\n // Remove hook script\n try {\n await rm(claudePaths.closingReminder);\n removedHooks = true;\n } catch {\n // Hook script doesn't exist\n }\n\n // Remove tbd scripts from project\n try {\n await rm(claudePaths.sessionScript);\n removedScripts = true;\n } catch {\n // Script doesn't exist\n }\n\n // Remove skill file from project\n try {\n await rm(skillPath);\n removedSkill = true;\n } catch {\n // Skill file doesn't exist\n }\n\n // Report what was removed\n if (removedHooks || removedScripts) {\n this.output.success('Removed hooks and scripts');\n } else {\n this.output.info('No hooks to remove');\n }\n\n if (removedSkill) {\n this.output.success('Removed skill file');\n } else {\n this.output.info('No skill file to remove');\n }\n }\n\n private async installClaudeSetup(skillPath: string): Promise<void> {\n const cwd = this.projectDir ?? process.cwd();\n const claudePaths = getClaudePaths(cwd);\n\n if (\n this.checkDryRun('Would install Claude Code hooks and skill file', {\n settingsPath: claudePaths.settings,\n skillPath,\n })\n ) {\n return;\n }\n\n try {\n // Always install to project .claude/ directory (create if needed).\n // This avoids confusion between global vs project-level settings and\n // ensures hooks work in any environment (local dev, Claude Code Cloud, etc.).\n await mkdir(claudePaths.dir, { recursive: true });\n\n // Read existing project settings\n let settings: Record<string, unknown> = {};\n try {\n await access(claudePaths.settings);\n const content = await readFile(claudePaths.settings, 'utf-8');\n settings = JSON.parse(content) as Record<string, unknown>;\n } catch {\n // File doesn't exist, start fresh\n }\n\n // Merge session hooks (SessionStart, PreCompact) - append without overwriting\n const existingHooks = (settings.hooks as Record<string, unknown[]>) || {};\n const newHooks = CLAUDE_SESSION_HOOKS.hooks as Record<string, unknown[]>;\n const mergedHooks: Record<string, unknown[]> = { ...existingHooks };\n\n for (const [hookType, hookEntries] of Object.entries(newHooks)) {\n if (mergedHooks[hookType]) {\n // Filter out any existing tbd-session.sh hooks before adding new ones\n const filtered = (mergedHooks[hookType] as { hooks?: { command?: string }[] }[]).filter(\n (entry) => !entry.hooks?.some((h) => h.command?.includes('tbd-session.sh')),\n );\n mergedHooks[hookType] = [...filtered, ...hookEntries];\n } else {\n mergedHooks[hookType] = hookEntries;\n }\n }\n\n // Merge PostToolUse hooks\n const projectHooks = CLAUDE_PROJECT_HOOKS.hooks as Record<string, unknown[]>;\n for (const [hookType, hookEntries] of Object.entries(projectHooks)) {\n mergedHooks[hookType] ??= hookEntries;\n }\n\n settings.hooks = mergedHooks;\n\n // Manage gh CLI SessionStart hook based on use_gh_cli config setting\n const useGhCli = await this.getUseGhCliSetting();\n const finalHooks = settings.hooks as Record<string, unknown>;\n let sessionStartEntries = (finalHooks.SessionStart as Record<string, unknown>[]) || [];\n\n if (useGhCli) {\n // Add gh CLI hook if not already present\n const hasGhCliHook = sessionStartEntries.some((h: Record<string, unknown>) =>\n (h.hooks as { command?: string }[])?.some((hook) =>\n hook.command?.includes(GH_CLI_HOOK_COMMAND_PATTERN),\n ),\n );\n if (!hasGhCliHook) {\n sessionStartEntries = [...sessionStartEntries, GH_CLI_HOOK_ENTRY];\n }\n\n // Install the script file\n await mkdir(claudePaths.scriptsDir, { recursive: true });\n const ghScriptContent = await loadBundledScript('ensure-gh-cli.sh');\n await writeFile(claudePaths.ghCliScript, ghScriptContent);\n await chmod(claudePaths.ghCliScript, 0o755);\n this.output.success('Installed gh CLI setup script');\n } else {\n // Remove gh CLI hook entries\n sessionStartEntries = sessionStartEntries.filter(\n (h: Record<string, unknown>) =>\n !(h.hooks as { command?: string }[])?.some((hook) =>\n hook.command?.includes(GH_CLI_HOOK_COMMAND_PATTERN),\n ),\n );\n\n // Remove the script file\n try {\n await rm(claudePaths.ghCliScript);\n this.output.success('Removed gh CLI setup script');\n } catch {\n // Script doesn't exist, ignore\n }\n }\n\n if (sessionStartEntries.length > 0) {\n finalHooks.SessionStart = sessionStartEntries;\n } else {\n delete finalHooks.SessionStart;\n }\n\n // Write all hooks to project settings in a single write\n await writeFile(claudePaths.settings, JSON.stringify(settings, null, 2) + '\\n');\n this.output.success('Installed hooks to .claude/settings.json');\n\n // Install tbd-session.sh script\n await mkdir(claudePaths.scriptsDir, { recursive: true });\n await writeFile(claudePaths.sessionScript, TBD_SESSION_SCRIPT);\n await chmod(claudePaths.sessionScript, 0o755);\n\n // Clean up legacy scripts in project\n const legacyScripts = ['ensure-tbd-cli.sh', 'setup-tbd.sh', 'ensure-tbd.sh'];\n for (const script of legacyScripts) {\n try {\n await rm(join(claudePaths.scriptsDir, script));\n } catch {\n // Script doesn't exist, ignore\n }\n }\n\n this.output.success('Installed tbd session script to .claude/scripts/');\n\n // Add .claude/.gitignore to ignore backup files\n // NOTE: Pattern re-addition is intentional - see comment in initializeTbd\n const claudeGitignorePath = join(claudePaths.dir, '.gitignore');\n const claudeGitignoreResult = await ensureGitignorePatterns(claudeGitignorePath, [\n '# Backup files',\n '*.bak',\n ]);\n if (claudeGitignoreResult.created) {\n this.output.success('Created .claude/.gitignore');\n } else if (claudeGitignoreResult.added.length > 0) {\n this.output.success('Updated .claude/.gitignore');\n }\n // else: file is up-to-date, no message needed\n\n // Install hook script\n await mkdir(claudePaths.hooksDir, { recursive: true });\n await writeFile(claudePaths.closingReminder, TBD_CLOSE_PROTOCOL_SCRIPT);\n await chmod(claudePaths.closingReminder, 0o755);\n this.output.success('Installed sync reminder hook script');\n\n // Install skill file in project (with shortcut directory appended)\n await mkdir(dirname(skillPath), { recursive: true });\n let skillContent = await loadSkillContent();\n const directory = await getShortcutDirectory(this.ctx.quiet);\n if (directory) {\n skillContent = skillContent.trimEnd() + '\\n\\n' + directory;\n }\n // Insert DO NOT EDIT marker after frontmatter (formatted to match flowmark output)\n const markerComment =\n \"<!-- DO NOT EDIT: Generated by tbd setup.\\nRun 'tbd setup' to update.\\n-->\";\n skillContent = insertAfterFrontmatter(skillContent, markerComment);\n // Ensure file ends with newline\n skillContent = skillContent.trimEnd() + '\\n';\n await writeFile(skillPath, skillContent);\n this.output.success('Installed skill file');\n this.output.info(` ${skillPath}`);\n\n this.output.info('');\n this.output.info('What was installed:');\n this.output.info(' - Session hooks: SessionStart and PreCompact run `tbd prime`');\n this.output.info(' - Session script: .claude/scripts/tbd-session.sh');\n this.output.info(' - Project hooks: PostToolUse reminds about `tbd sync` after git push');\n this.output.info(' - Project skill: .claude/skills/tbd/SKILL.md');\n } catch (error) {\n throw new CLIError(`Failed to install: ${(error as Error).message}`);\n }\n }\n\n /**\n * Read the use_gh_cli setting from config. Defaults to true if not set or if\n * tbd is not yet initialized (so fresh setup installs gh CLI by default).\n */\n private async getUseGhCliSetting(): Promise<boolean> {\n try {\n const tbdRoot = await findTbdRoot(process.cwd());\n if (!tbdRoot) return true;\n const config = await readConfig(tbdRoot);\n return config.settings.use_gh_cli ?? true;\n } catch {\n return true;\n }\n }\n}\n\nclass SetupCodexHandler extends BaseCommand {\n private projectDir: string | undefined;\n\n setProjectDir(dir: string): void {\n this.projectDir = dir;\n }\n\n async run(options: SetupCodexOptions): Promise<void> {\n const cwd = this.projectDir ?? process.cwd();\n const agentsPath = join(cwd, 'AGENTS.md');\n\n if (options.check) {\n await this.checkCodexSetup(agentsPath);\n return;\n }\n\n if (options.remove) {\n await this.removeCodexSection(agentsPath);\n return;\n }\n\n await this.installCodexSection(agentsPath);\n }\n\n private async checkCodexSetup(agentsPath: string): Promise<void> {\n const agentsRelPath = './AGENTS.md';\n try {\n await access(agentsPath);\n const content = await readFile(agentsPath, 'utf-8');\n\n if (content.includes(CODEX_BEGIN_MARKER)) {\n const diagnostic: DiagnosticResult = {\n name: 'AGENTS.md',\n status: 'ok',\n message: 'tbd section found',\n path: agentsRelPath,\n };\n this.output.data({ installed: true, path: agentsPath, hastbdSection: true }, () => {\n const colors = this.output.getColors();\n renderDiagnostics([diagnostic], colors);\n });\n } else {\n const diagnostic: DiagnosticResult = {\n name: 'AGENTS.md',\n status: 'warn',\n message: 'exists but no tbd section',\n path: agentsRelPath,\n suggestion: 'Run: tbd setup --auto',\n };\n this.output.data({ installed: false, path: agentsPath, hastbdSection: false }, () => {\n const colors = this.output.getColors();\n renderDiagnostics([diagnostic], colors);\n });\n }\n } catch {\n const diagnostic: DiagnosticResult = {\n name: 'AGENTS.md',\n status: 'warn',\n message: 'not found',\n path: agentsRelPath,\n suggestion: 'Run: tbd setup --auto',\n };\n this.output.data({ installed: false, expectedPath: agentsPath }, () => {\n const colors = this.output.getColors();\n renderDiagnostics([diagnostic], colors);\n });\n }\n }\n\n private async removeCodexSection(agentsPath: string): Promise<void> {\n try {\n await access(agentsPath);\n const content = await readFile(agentsPath, 'utf-8');\n\n if (!content.includes(CODEX_BEGIN_MARKER)) {\n this.output.info('No tbd section found in AGENTS.md');\n return;\n }\n\n const newContent = this.removetbdSection(content);\n const trimmed = newContent.trim();\n\n if (trimmed === '' || trimmed === '# Project Instructions for AI Agents') {\n // File is empty or only has the default header, remove it\n await rm(agentsPath);\n this.output.success('Removed AGENTS.md (file was empty after removing tbd section)');\n } else {\n await writeFile(agentsPath, newContent);\n this.output.success('Removed tbd section from AGENTS.md');\n }\n } catch {\n this.output.info('AGENTS.md not found');\n }\n }\n\n private async installCodexSection(agentsPath: string): Promise<void> {\n if (this.checkDryRun('Would create/update AGENTS.md', { path: agentsPath })) {\n return;\n }\n\n try {\n let existingContent = '';\n try {\n await access(agentsPath);\n existingContent = await readFile(agentsPath, 'utf-8');\n } catch {\n // File doesn't exist\n }\n\n let newContent: string;\n\n const tbdSection = await getCodexTbdSection(this.ctx.quiet);\n\n if (existingContent) {\n if (existingContent.includes(CODEX_BEGIN_MARKER)) {\n // Update existing section\n newContent = this.updatetbdSection(existingContent, tbdSection);\n await writeFile(agentsPath, newContent);\n this.output.success('Updated existing tbd section in AGENTS.md');\n } else {\n // Append section to existing file\n newContent = existingContent + '\\n\\n' + tbdSection;\n await writeFile(agentsPath, newContent);\n this.output.success('Added tbd section to existing AGENTS.md');\n }\n } else {\n // Create new file\n const newAgentsFile = await getCodexNewAgentsFile(this.ctx.quiet);\n await writeFile(agentsPath, newAgentsFile);\n this.output.success('Created new AGENTS.md with tbd integration');\n }\n\n this.output.info(` File: ${agentsPath}`);\n this.output.info('');\n this.output.info('Codex and other AGENTS.md-compatible tools will automatically');\n this.output.info('read this file on session start.');\n } catch (error) {\n throw new CLIError(`Failed to update AGENTS.md: ${(error as Error).message}`);\n }\n }\n\n private updatetbdSection(content: string, tbdSection: string): string {\n const startIdx = content.indexOf(CODEX_BEGIN_MARKER);\n const endIdx = content.indexOf(CODEX_END_MARKER);\n\n if (startIdx === -1 || endIdx === -1 || startIdx > endIdx) {\n // Markers not found or invalid, append instead\n return content + '\\n\\n' + tbdSection;\n }\n\n // Find the end of the end marker line\n let endOfEndMarker = endIdx + CODEX_END_MARKER.length;\n const nextNewline = content.indexOf('\\n', endOfEndMarker);\n if (nextNewline !== -1) {\n endOfEndMarker = nextNewline + 1;\n }\n\n return content.slice(0, startIdx) + tbdSection + content.slice(endOfEndMarker);\n }\n\n private removetbdSection(content: string): string {\n const startIdx = content.indexOf(CODEX_BEGIN_MARKER);\n const endIdx = content.indexOf(CODEX_END_MARKER);\n\n if (startIdx === -1 || endIdx === -1 || startIdx > endIdx) {\n return content;\n }\n\n // Find the end of the end marker line\n let endOfEndMarker = endIdx + CODEX_END_MARKER.length;\n const nextNewline = content.indexOf('\\n', endOfEndMarker);\n if (nextNewline !== -1) {\n endOfEndMarker = nextNewline + 1;\n }\n\n // Also remove leading blank lines before the section\n let trimStart = startIdx;\n while (trimStart > 0 && (content[trimStart - 1] === '\\n' || content[trimStart - 1] === '\\r')) {\n trimStart--;\n }\n\n return content.slice(0, trimStart) + content.slice(endOfEndMarker);\n }\n}\n\n// ============================================================================\n// Setup Default Handler (for --auto and --interactive modes)\n// ============================================================================\n\ninterface SetupDefaultOptions {\n auto?: boolean;\n interactive?: boolean;\n fromBeads?: boolean;\n prefix?: string;\n force?: boolean;\n ghCli?: boolean; // Commander sets to false when --no-gh-cli is passed\n}\n\n/**\n * Default handler for `tbd setup` with --auto or --interactive flags.\n *\n * This implements the unified onboarding flow:\n * - `tbd setup --auto`: Non-interactive setup with smart defaults (for agents)\n * - `tbd setup --interactive`: Interactive setup with prompts (for humans)\n *\n * Decision tree:\n * 1. Not in git repo → Error (git init first)\n * 2. Resolve to git root → All paths relative to .git/ parent\n * 3. Has .tbd/ → Already initialized, check/update integrations\n * 4. Has .beads/ → Beads migration flow\n * 5. Fresh repo → Initialize + configure integrations\n */\nclass SetupDefaultHandler extends BaseCommand {\n private cmd: Command;\n\n constructor(command: Command) {\n super(command);\n this.cmd = command;\n }\n\n async run(options: SetupDefaultOptions): Promise<void> {\n const colors = this.output.getColors();\n const cwd = process.cwd();\n\n // Determine mode\n const isAutoMode = options.auto === true;\n // Note: options.interactive will be used when we add interactive prompts\n\n // Header\n console.log(colors.bold('tbd: Git-native issue tracking for AI agents and humans'));\n console.log('');\n\n // Check if in git repo and resolve to git root\n const inGitRepo = await isInGitRepo(cwd);\n if (!inGitRepo) {\n throw new CLIError('Not a git repository. Run `git init` first.');\n }\n\n // Resolve to git root so .tbd/ and .claude/ are always adjacent to .git/\n const gitRoot = await findGitRoot(cwd);\n if (!gitRoot) {\n throw new CLIError('Could not determine git repository root.');\n }\n\n // Use git root as the working directory for all setup operations\n const projectDir = gitRoot;\n\n // Check current state\n const hasTbd = await isInitialized(projectDir);\n const hasBeads = await pathExists(join(projectDir, '.beads'));\n\n // Validate --from-beads flag requires .beads/ directory\n if (options.fromBeads && !hasBeads) {\n throw new CLIError(\n 'The --from-beads flag requires a .beads/ directory to migrate from.\\n' +\n 'For fresh setup, use: tbd setup --auto --prefix=<name>',\n );\n }\n\n console.log('Checking repository...');\n console.log(` ${colors.success('✓')} Git repository detected`);\n\n if (hasTbd) {\n // Already initialized flow - check for migrations\n const { config, migrated, changes } = await readConfigWithMigration(projectDir);\n console.log(` ${colors.success('✓')} tbd initialized (prefix: ${config.display.id_prefix})`);\n\n // Apply --no-gh-cli flag to config if specified\n let needsConfigWrite = migrated;\n if (options.ghCli === false && config.settings.use_gh_cli !== false) {\n config.settings.use_gh_cli = false;\n needsConfigWrite = true;\n }\n\n // Persist config if migrated or --no-gh-cli was applied\n if (needsConfigWrite) {\n await writeConfig(projectDir, config);\n if (migrated) {\n console.log(` ${colors.success('✓')} Config migrated to latest format`);\n for (const change of changes) {\n console.log(` ${colors.dim(change)}`);\n }\n }\n if (options.ghCli === false) {\n console.log(` ${colors.success('✓')} Disabled gh CLI auto-setup`);\n }\n }\n\n console.log('');\n await this.handleAlreadyInitialized(projectDir, isAutoMode);\n } else if (hasBeads || options.fromBeads) {\n // Beads migration flow\n console.log(` ${colors.dim('✗')} tbd not initialized`);\n console.log(` ${colors.warn('!')} Beads detected (.beads/ directory found)`);\n console.log('');\n await this.handleBeadsMigration(projectDir, isAutoMode, options);\n } else {\n // Fresh setup flow\n console.log(` ${colors.dim('✗')} tbd not initialized`);\n console.log('');\n await this.handleFreshSetup(projectDir, isAutoMode, options);\n }\n }\n\n private async handleAlreadyInitialized(projectDir: string, _isAutoMode: boolean): Promise<void> {\n const colors = this.output.getColors();\n\n // Ensure .tbd/.gitignore is up-to-date (may have new patterns from newer versions)\n const tbdGitignoreResult = await ensureGitignorePatterns(\n join(projectDir, TBD_DIR, '.gitignore'),\n [\n '# Synced documentation cache (regenerated by tbd sync --docs)',\n 'docs/',\n '',\n '# Hidden worktree for tbd-sync branch',\n `${WORKTREE_DIR_NAME}/`,\n '',\n '# Data sync directory (only exists in worktree)',\n `${DATA_SYNC_DIR_NAME}/`,\n '',\n '# Local state',\n 'state.yml',\n '',\n '# Migration backups (local only, not synced)',\n 'backups/',\n '',\n '# Temporary files',\n '*.tmp',\n '*.temp',\n '',\n '# workspaces/ stores state (including outbox) committed to the working branch',\n '!workspaces/',\n ],\n );\n if (tbdGitignoreResult.created) {\n console.log(` ${colors.success('✓')} Created .tbd/.gitignore`);\n } else if (tbdGitignoreResult.added.length > 0) {\n console.log(` ${colors.success('✓')} Updated .tbd/.gitignore with new patterns`);\n }\n\n // Ensure .tbd/.gitattributes has merge protection for outbox ids.yml\n // Placed inside .tbd/ so all tbd settings are self-contained in one directory.\n // Git supports .gitattributes in subdirectories — patterns are relative to that directory.\n const gitattributesResult = await ensureGitignorePatterns(\n join(projectDir, TBD_DIR, '.gitattributes'),\n [\n '# Protect ID mappings from merge deletion (always keep all rows)',\n '**/mappings/ids.yml merge=union',\n ],\n );\n if (gitattributesResult.created) {\n console.log(` ${colors.success('✓')} Created .tbd/.gitattributes (merge protection)`);\n } else if (gitattributesResult.added.length > 0) {\n console.log(` ${colors.success('✓')} Updated .tbd/.gitattributes (merge protection)`);\n }\n\n console.log('Checking integrations...');\n\n // Use SetupAutoHandler to configure integrations\n const autoHandler = new SetupAutoHandler(this.cmd);\n await autoHandler.run(projectDir);\n\n console.log('');\n console.log(colors.success('All set!'));\n }\n\n private async handleBeadsMigration(\n cwd: string,\n isAutoMode: boolean,\n options: SetupDefaultOptions,\n ): Promise<void> {\n const colors = this.output.getColors();\n\n if (isAutoMode) {\n console.log(` ${colors.warn('!')} Beads detected - auto-migrating`);\n console.log('');\n }\n\n // Get prefix from beads config or use provided --prefix\n const beadsPrefix = await getBeadsPrefix(cwd);\n const prefix = options.prefix ?? beadsPrefix;\n\n if (!prefix) {\n throw new CLIError(\n 'Could not read prefix from beads config.\\n' +\n 'Please specify a prefix (2-8 letters recommended):\\n' +\n ' tbd setup --auto --prefix=tbd',\n );\n }\n\n // Hard validation: always enforced\n if (!isValidPrefix(prefix)) {\n throw new CLIError(\n 'Invalid prefix format.\\n' +\n 'Prefix must be 1-20 lowercase characters:\\n' +\n ' - Must start with a letter (a-z)\\n' +\n ' - Must end with alphanumeric (a-z, 0-9)\\n' +\n ' - Middle characters can include dots (.) and underscores (_)\\n' +\n ' - No dashes allowed (breaks ID syntax)\\n\\n' +\n 'Please specify a valid prefix:\\n' +\n ' tbd setup --from-beads --prefix=tbd',\n );\n }\n\n // Soft validation: only for user-provided prefixes (not from beads config)\n // If prefix came from beads config, we accept it to ease migration\n const prefixFromBeads = beadsPrefix && prefix === beadsPrefix;\n if (!prefixFromBeads && !isRecommendedPrefix(prefix) && !options.force) {\n throw new CLIError(\n `Prefix \"${prefix}\" is not recommended.\\n` +\n 'Recommended prefixes are 2-8 alphabetic characters (e.g., \"tbd\", \"myp\", \"proj\").\\n\\n' +\n 'If you really want to use this prefix, add --force to override.\\n\\n' +\n 'Example:\\n' +\n ` tbd setup --from-beads --prefix=${prefix} --force`,\n );\n }\n\n // Initialize tbd first\n await this.initializeTbd(cwd, prefix);\n\n // Apply --no-gh-cli flag to newly created config\n if (options.ghCli === false) {\n const config = await readConfig(cwd);\n config.settings.use_gh_cli = false;\n await writeConfig(cwd, config);\n console.log(` ${colors.success('✓')} Disabled gh CLI auto-setup`);\n }\n\n // Import beads issues from the JSONL file\n console.log('Importing from Beads...');\n const beadsDir = join(cwd, '.beads');\n const jsonlPath = join(beadsDir, 'issues.jsonl');\n\n try {\n await access(jsonlPath);\n // Import directly from the JSONL file (tbd is already initialized)\n const result = spawnSync('tbd', ['import', jsonlPath, '--verbose'], {\n cwd,\n stdio: 'inherit',\n });\n if (result.status !== 0) {\n console.log(colors.warn('Warning: Some issues may not have imported correctly'));\n }\n } catch {\n console.log(colors.dim(' No issues.jsonl found - skipping import'));\n }\n\n // Disable beads\n await this.disableBeads(cwd);\n\n console.log('');\n console.log('Configuring integrations...');\n\n // Configure integrations\n const autoHandler = new SetupAutoHandler(this.cmd);\n await autoHandler.run(cwd);\n\n console.log('');\n console.log(colors.success('Setup complete!'));\n\n this.showWhatsNext(colors);\n\n // Show dashboard after setup\n spawnSync('tbd', ['prime'], { stdio: 'inherit' });\n\n // Mark welcome as seen since the user got the full onboarding experience\n try {\n await markWelcomeSeen(cwd);\n } catch {\n // Non-critical: don't fail setup if state write fails\n }\n }\n\n private async handleFreshSetup(\n cwd: string,\n isAutoMode: boolean,\n options: SetupDefaultOptions,\n ): Promise<void> {\n const colors = this.output.getColors();\n\n // Require --prefix for fresh setup (no auto-detection)\n const prefix = options.prefix;\n\n if (!prefix) {\n throw new CLIError(\n '--prefix is required for tbd setup --auto\\n\\n' +\n 'The --prefix flag specifies your project name for issue IDs.\\n' +\n 'Use a short 2-4 letter prefix so issue IDs stand out clearly.\\n\\n' +\n 'Example:\\n' +\n ' tbd setup --auto --prefix=tbd # Issues: tbd-a1b2\\n' +\n ' tbd setup --auto --prefix=myp # Issues: myp-c3d4\\n\\n' +\n 'Note: If migrating from beads, the prefix is automatically read from your beads config.',\n );\n }\n\n // Hard validation: always enforced\n if (!isValidPrefix(prefix)) {\n throw new CLIError(\n 'Invalid prefix format.\\n' +\n 'Prefix must be 1-20 lowercase characters:\\n' +\n ' - Must start with a letter (a-z)\\n' +\n ' - Must end with alphanumeric (a-z, 0-9)\\n' +\n ' - Middle characters can include dots (.) and underscores (_)\\n' +\n ' - No dashes allowed (breaks ID syntax)\\n\\n' +\n 'Example:\\n' +\n ' tbd setup --auto --prefix=tbd',\n );\n }\n\n // Soft validation: recommended format (2-8 alphabetic)\n if (!isRecommendedPrefix(prefix) && !options.force) {\n throw new CLIError(\n `Prefix \"${prefix}\" is not recommended.\\n` +\n 'Recommended prefixes are 2-8 alphabetic characters (e.g., \"tbd\", \"myp\", \"proj\").\\n\\n' +\n 'If you really want to use this prefix, add --force to override.\\n\\n' +\n 'Example:\\n' +\n ` tbd setup --auto --prefix=${prefix} --force`,\n );\n }\n\n console.log(`Initializing with prefix \"${prefix}\"...`);\n\n await this.initializeTbd(cwd, prefix);\n\n // Apply --no-gh-cli flag to newly created config\n if (options.ghCli === false) {\n const config = await readConfig(cwd);\n config.settings.use_gh_cli = false;\n await writeConfig(cwd, config);\n console.log(` ${colors.success('✓')} Disabled gh CLI auto-setup`);\n }\n\n console.log('');\n console.log('Configuring integrations...');\n\n // Configure integrations\n const autoHandler = new SetupAutoHandler(this.cmd);\n await autoHandler.run(cwd);\n\n console.log('');\n console.log(colors.success('Setup complete!'));\n\n this.showWhatsNext(colors);\n\n // Show dashboard after setup\n spawnSync('tbd', ['prime'], { stdio: 'inherit' });\n\n // Mark welcome as seen since the user got the full onboarding experience\n try {\n await markWelcomeSeen(cwd);\n } catch {\n // Non-critical: don't fail setup if state write fails\n }\n }\n\n /**\n * Show \"What's Next\" guidance after setup completion.\n * Framed as what users can SAY to get help, not as CLI commands to run.\n */\n private showWhatsNext(colors: ReturnType<typeof this.output.getColors>): void {\n console.log('');\n console.log(colors.bold(\"WHAT'S NEXT\"));\n console.log('');\n console.log(' Try saying things like:');\n console.log(' \"There\\'s a bug where ...\" → Creates and tracks a bug');\n console.log(' \"Let\\'s plan a new feature\" → Walks through a planning spec');\n console.log(' \"Let\\'s work on current issues\" → Shows ready issues to tackle');\n console.log(' \"Commit this code\" → Reviews and commits properly');\n console.log(' \"Review for best practices\" → Code review with guidelines');\n console.log('');\n }\n\n private async initializeTbd(cwd: string, prefix: string): Promise<void> {\n const colors = this.output.getColors();\n\n // 1. Create .tbd/ directory with config.yml\n await initConfig(cwd, VERSION, prefix);\n console.log(` ${colors.success('✓')} Created .tbd/config.yml`);\n\n // 2. Create/update .tbd/.gitignore (idempotent)\n // NOTE: Pattern re-addition is intentional - these are tool-managed files\n // that are regenerated from the npm package on every setup. If a user removes\n // a pattern, we re-add it because tracking these directories in git would\n // cause noise on every tbd upgrade.\n const tbdGitignoreResult = await ensureGitignorePatterns(join(cwd, TBD_DIR, '.gitignore'), [\n '# Synced documentation cache (regenerated by tbd sync --docs)',\n 'docs/',\n '',\n '# Hidden worktree for tbd-sync branch',\n `${WORKTREE_DIR_NAME}/`,\n '',\n '# Data sync directory (only exists in worktree)',\n `${DATA_SYNC_DIR_NAME}/`,\n '',\n '# Local state',\n 'state.yml',\n '',\n '# Migration backups (local only, not synced)',\n 'backups/',\n '',\n '# Temporary files',\n '*.tmp',\n '*.temp',\n '',\n '# workspaces/ stores state (including outbox) committed to the working branch',\n '!workspaces/',\n ]);\n if (tbdGitignoreResult.created) {\n console.log(` ${colors.success('✓')} Created .tbd/.gitignore`);\n } else if (tbdGitignoreResult.added.length > 0) {\n console.log(` ${colors.success('✓')} Updated .tbd/.gitignore`);\n }\n // else: file is up-to-date, no message needed\n\n // 2b. Create/update .tbd/.gitattributes with merge strategies for tbd files.\n // Placed inside .tbd/ so all tbd settings are self-contained in one directory.\n // Git supports .gitattributes in subdirectories — patterns are relative to that directory.\n // The outbox ids.yml must use \"merge=union\" so git never drops rows during merge.\n // Without this, AI agents resolving merge conflicts can delete the mapping file\n // (main has no outbox, so the merge considers \"no file\" as the correct version),\n // causing all tbd commands to crash with \"No short ID mapping found\".\n // See: https://github.com/jlevy/tbd/issues/99\n const gitattributesResult = await ensureGitignorePatterns(\n join(cwd, TBD_DIR, '.gitattributes'),\n [\n '# Protect ID mappings from merge deletion (always keep all rows)',\n '**/mappings/ids.yml merge=union',\n ],\n );\n if (gitattributesResult.created) {\n console.log(` ${colors.success('✓')} Created .tbd/.gitattributes (merge protection)`);\n } else if (gitattributesResult.added.length > 0) {\n console.log(` ${colors.success('✓')} Updated .tbd/.gitattributes (merge protection)`);\n }\n\n // 3. Initialize worktree for sync branch\n try {\n await initWorktree(cwd);\n\n // Verify worktree health after creation (prevents silent failures)\n const health = await checkWorktreeHealth(cwd);\n if (health.valid) {\n console.log(` ${colors.success('✓')} Initialized sync branch`);\n } else {\n console.log(\n ` ${colors.warn('!')} Sync branch created but verification failed (status: ${health.status})`,\n );\n console.log(` Run 'tbd doctor' to diagnose`);\n }\n } catch {\n // Non-fatal - sync will work, just not optimally\n console.log(` ${colors.dim('○')} Sync branch will be created on first sync`);\n }\n }\n\n private async disableBeads(cwd: string): Promise<void> {\n const colors = this.output.getColors();\n\n // Move .beads to .beads-disabled\n const beadsDir = join(cwd, '.beads');\n const disabledDir = join(cwd, '.beads-disabled');\n\n try {\n await rename(beadsDir, disabledDir);\n console.log(` ${colors.success('✓')} Disabled beads (moved to .beads-disabled/)`);\n } catch {\n console.log(` ${colors.dim('○')} Could not move .beads directory`);\n }\n }\n}\n\n// ============================================================================\n// Auto Setup Command\n// ============================================================================\n\ninterface AutoSetupResult {\n name: string;\n detected: boolean;\n installed: boolean;\n alreadyInstalled: boolean;\n error?: string;\n}\n\nclass SetupAutoHandler extends BaseCommand {\n private cmd: Command;\n\n constructor(command: Command) {\n super(command);\n this.cmd = command;\n }\n\n /**\n * Clean up legacy scripts from project .claude/scripts/ directory.\n * This runs during any setup, regardless of whether Claude Code is detected,\n * since we want to clean up old project-level scripts that are no longer needed.\n */\n private async cleanupLegacyProjectScripts(cwd: string): Promise<string[]> {\n const scriptsDir = join(cwd, '.claude', 'scripts');\n const scriptsRemoved: string[] = [];\n\n try {\n await access(scriptsDir);\n const entries = await readdir(scriptsDir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.isFile()) {\n const filename = entry.name;\n // Check against known legacy script names\n if (LEGACY_TBD_SCRIPTS.includes(filename)) {\n try {\n await rm(join(scriptsDir, filename));\n scriptsRemoved.push(filename);\n } catch {\n // Ignore removal errors\n }\n }\n }\n }\n } catch {\n // Scripts directory doesn't exist, nothing to clean\n }\n\n return scriptsRemoved;\n }\n\n /**\n * Filter out hook entries that match legacy tbd patterns from project settings.\n */\n private filterLegacyHooks(\n hookList: { hooks?: { command?: string }[] }[],\n ): { hooks?: { command?: string }[] }[] {\n return hookList.filter((entry) => {\n // Check if any hook command matches legacy patterns\n const hasLegacyCommand = entry.hooks?.some((hook) => {\n if (!hook.command) return false;\n return LEGACY_TBD_HOOK_PATTERNS.some((pattern) => pattern.test(hook.command!));\n });\n // Keep entries that DON'T have legacy commands\n return !hasLegacyCommand;\n });\n }\n\n /**\n * Clean up legacy hooks from project .claude/settings.json.\n * This runs during any setup, regardless of whether Claude Code is detected.\n */\n private async cleanupLegacyProjectHooks(cwd: string): Promise<number> {\n const projectSettingsPath = join(cwd, '.claude', 'settings.json');\n let hooksRemoved = 0;\n\n try {\n await access(projectSettingsPath);\n const content = await readFile(projectSettingsPath, 'utf-8');\n const settings = JSON.parse(content) as Record<string, unknown>;\n\n if (settings.hooks) {\n const hooks = settings.hooks as Record<string, unknown>;\n let modified = false;\n\n for (const hookType of ['SessionStart', 'PreCompact', 'PostToolUse']) {\n if (hooks[hookType]) {\n const hookList = hooks[hookType] as { hooks?: { command?: string }[] }[];\n const filtered = this.filterLegacyHooks(hookList);\n if (filtered.length !== hookList.length) {\n hooksRemoved += hookList.length - filtered.length;\n hooks[hookType] = filtered.length > 0 ? filtered : undefined;\n if (!hooks[hookType]) delete hooks[hookType];\n modified = true;\n }\n }\n }\n\n if (modified) {\n if (Object.keys(hooks).length === 0) {\n delete settings.hooks;\n }\n await writeFile(projectSettingsPath, JSON.stringify(settings, null, 2) + '\\n');\n }\n }\n } catch {\n // Project settings file doesn't exist, nothing to clean\n }\n\n return hooksRemoved;\n }\n\n async run(projectDir?: string): Promise<void> {\n const colors = this.output.getColors();\n const cwd = projectDir ?? process.cwd();\n const results: AutoSetupResult[] = [];\n\n // Clean up legacy project-level scripts and hooks FIRST,\n // regardless of whether any coding agent is detected.\n // This ensures old tbd scripts are removed even if user switches tools.\n const scriptsRemoved = await this.cleanupLegacyProjectScripts(cwd);\n const hooksRemoved = await this.cleanupLegacyProjectHooks(cwd);\n if (scriptsRemoved.length > 0 || hooksRemoved > 0) {\n const parts = [];\n if (scriptsRemoved.length > 0) parts.push(`${scriptsRemoved.length} script(s)`);\n if (hooksRemoved > 0) parts.push(`${hooksRemoved} hook(s)`);\n console.log(colors.dim(`Cleaned up legacy ${parts.join(' and ')}`));\n }\n\n // Sync docs using DocSync\n await this.syncDocs(cwd);\n\n // Detect and set up Claude Code\n const claudeResult = await this.setupClaudeIfDetected(cwd);\n results.push(claudeResult);\n\n // Detect and set up Codex/AGENTS.md (also used by Cursor since v1.6)\n const codexResult = await this.setupCodexIfDetected(cwd);\n results.push(codexResult);\n\n // Report results\n const installed = results.filter((r) => r.installed && !r.alreadyInstalled);\n const alreadyInstalled = results.filter((r) => r.alreadyInstalled);\n const skipped = results.filter((r) => !r.detected);\n\n if (installed.length > 0) {\n console.log(colors.bold('Configured integrations:'));\n for (const r of installed) {\n console.log(` ${colors.success('✓')} ${r.name}`);\n }\n }\n\n if (alreadyInstalled.length > 0) {\n console.log(colors.dim('Already configured:'));\n for (const r of alreadyInstalled) {\n console.log(` ${colors.dim('✓')} ${r.name}`);\n }\n }\n\n if (skipped.length > 0 && (installed.length > 0 || alreadyInstalled.length > 0)) {\n console.log(colors.dim('Not detected (skipped):'));\n for (const r of skipped) {\n console.log(` ${colors.dim('-')} ${r.name}`);\n }\n }\n\n if (installed.length === 0 && alreadyInstalled.length === 0) {\n console.log(colors.dim('No coding agents detected.'));\n console.log('');\n console.log(\n 'Install a coding agent (Claude Code, Codex, or any AGENTS.md-compatible tool) and re-run:',\n );\n console.log(' tbd setup --auto');\n }\n }\n\n /**\n * Sync docs using syncDocsWithDefaults.\n * Uses the shared function that merges bundled defaults, prunes stale entries,\n * syncs files, and updates config.\n */\n private async syncDocs(cwd: string): Promise<void> {\n const colors = this.output.getColors();\n\n // Ensure docs directories exist\n await mkdir(join(cwd, TBD_SHORTCUTS_SYSTEM), { recursive: true });\n await mkdir(join(cwd, TBD_SHORTCUTS_STANDARD), { recursive: true });\n await mkdir(join(cwd, TBD_GUIDELINES_DIR), { recursive: true });\n await mkdir(join(cwd, TBD_TEMPLATES_DIR), { recursive: true });\n\n // Use shared sync function that handles:\n // - Merging bundled defaults with user config\n // - Pruning stale internal entries\n // - Syncing files to .tbd/docs/\n // - Writing config if changed\n // - Updating last_doc_sync_at in state\n const result = await syncDocsWithDefaults(cwd);\n\n // Report sync results\n if (result.configChanged) {\n console.log(colors.dim('Updated docs_cache config'));\n }\n\n const total = result.added.length + result.updated.length;\n if (total > 0) {\n console.log(colors.dim(`Synced ${total} doc(s) to ${TBD_DOCS_DIR}/`));\n }\n if (result.removed.length > 0) {\n console.log(colors.dim(`Removed ${result.removed.length} outdated doc(s)`));\n }\n if (result.pruned.length > 0) {\n console.log(colors.dim(`Pruned ${result.pruned.length} stale config entry/entries`));\n }\n if (result.errors.length > 0) {\n for (const { path, error } of result.errors) {\n console.log(colors.warn(`Warning: ${path}: ${error}`));\n }\n }\n }\n\n private async setupClaudeIfDetected(cwd: string): Promise<AutoSetupResult> {\n const result: AutoSetupResult = {\n name: 'Claude Code',\n detected: false,\n installed: false,\n alreadyInstalled: false,\n };\n\n // Detect Claude Code: check for ~/.claude/ directory or CLAUDE_* env vars\n // Note: We check global dir for DETECTION only, not for installation\n const hasClaudeDir = await pathExists(GLOBAL_CLAUDE_DIR);\n const hasClaudeEnv = Object.keys(process.env).some((k) => k.startsWith('CLAUDE_'));\n\n if (!hasClaudeDir && !hasClaudeEnv) {\n return result;\n }\n\n result.detected = true;\n\n // Check if already installed (project-local settings - all installs are project-local)\n const claudePaths = getClaudePaths(cwd);\n\n try {\n if (await pathExists(claudePaths.settings)) {\n const content = await readFile(claudePaths.settings, 'utf-8');\n const settings = JSON.parse(content) as Record<string, unknown>;\n const hooks = settings.hooks as Record<string, unknown> | undefined;\n if (hooks) {\n const sessionStart = hooks.SessionStart as { hooks?: { command?: string }[] }[];\n const hasTbdHook = sessionStart?.some((h) =>\n h.hooks?.some(\n (hook) =>\n (hook.command?.includes('tbd prime') ?? false) ||\n (hook.command?.includes('tbd-session.sh') ?? false),\n ),\n );\n if (hasTbdHook && (await pathExists(claudePaths.skill))) {\n result.alreadyInstalled = true;\n // Note: We still run the handler to update the skill file content\n // even if hooks are already installed. This ensures users get the\n // latest skill file when running `tbd setup --auto`.\n }\n }\n }\n\n // Install/update Claude Code setup (always runs to update skill file)\n const handler = new SetupClaudeHandler(this.cmd);\n handler.setProjectDir(cwd);\n await handler.run({});\n result.installed = true;\n } catch (error) {\n result.error = (error as Error).message;\n }\n\n return result;\n }\n\n private async setupCodexIfDetected(cwd: string): Promise<AutoSetupResult> {\n const result: AutoSetupResult = {\n name: 'Codex/AGENTS.md',\n detected: false,\n installed: false,\n alreadyInstalled: false,\n };\n\n // Detect Codex: check for existing AGENTS.md or CODEX_* env vars\n const agentsPath = getAgentsMdPath(cwd);\n const hasAgentsMd = await pathExists(agentsPath);\n const hasCodexEnv = Object.keys(process.env).some((k) => k.startsWith('CODEX_'));\n\n if (!hasAgentsMd && !hasCodexEnv) {\n return result;\n }\n\n result.detected = true;\n\n // Check if already has tbd section\n if (hasAgentsMd) {\n const content = await readFile(agentsPath, 'utf-8');\n if (content.includes('BEGIN TBD INTEGRATION')) {\n result.alreadyInstalled = true;\n // Note: We still run the handler to update the AGENTS.md content\n // even if tbd section exists. This ensures users get the latest\n // content when running `tbd setup --auto`.\n }\n }\n\n try {\n // Install/update Codex AGENTS.md (always runs to update content)\n const handler = new SetupCodexHandler(this.cmd);\n handler.setProjectDir(cwd);\n await handler.run({});\n result.installed = true;\n } catch (error) {\n result.error = (error as Error).message;\n }\n\n return result;\n }\n}\n\n// Main setup command\nexport const setupCommand = new Command('setup')\n .description('Configure tbd integration with editors and tools')\n .option('--auto', 'Non-interactive mode with smart defaults (for agents/scripts)')\n .option('--interactive', 'Interactive mode with prompts (for humans)')\n .option('--from-beads', 'Migrate from Beads to tbd')\n .option('--prefix <name>', 'Project prefix for issue IDs (required for fresh setup)')\n .option('--force', 'Allow non-recommended prefix format (not 2-8 alphabetic)')\n .option('--no-gh-cli', 'Disable automatic GitHub CLI installation hook')\n .action(async (options: SetupDefaultOptions, command) => {\n // If --auto or --interactive flag is set, run the default handler\n if (options.auto || options.interactive) {\n const handler = new SetupDefaultHandler(command);\n await handler.run(options);\n return;\n }\n\n // If --from-beads is set without --auto/--interactive, treat as --auto\n if (options.fromBeads) {\n const handler = new SetupDefaultHandler(command);\n await handler.run({ ...options, auto: true });\n return;\n }\n\n // No flags provided - show help\n console.log('Usage: tbd setup [options]');\n console.log('');\n console.log('Initialize tbd and configure agent integrations.');\n console.log('Must be run inside a git repository. Installs .tbd/ and .claude/');\n console.log('at the git root (adjacent to .git/).');\n console.log('');\n console.log('Modes (one required):');\n console.log(\n ' --auto Non-interactive mode with smart defaults (for agents/scripts)',\n );\n console.log(' --interactive Interactive mode with prompts (for humans)');\n console.log(' --from-beads Migrate from Beads to tbd (implies --auto)');\n console.log('');\n console.log('Options:');\n console.log(' --prefix <name> Project prefix for issue IDs (2-8 alphabetic recommended)');\n console.log(' --force Allow non-recommended prefix format');\n console.log(' --no-gh-cli Disable automatic GitHub CLI installation hook');\n console.log('');\n console.log('Examples:');\n console.log(' tbd setup --auto --prefix=tbd # Full automatic setup with prefix');\n console.log(' tbd setup --from-beads # Migrate from Beads (uses beads prefix)');\n console.log(' tbd setup --interactive # Interactive setup with prompts');\n console.log('');\n console.log('For surgical initialization without integrations, see: tbd init --help');\n });\n","/**\n * `tbd save` - Save issues to a workspace or directory.\n *\n * Saves issues from the data-sync worktree to a named workspace or directory.\n * Used for sync failure recovery, backups, and bulk editing workflows.\n *\n * See: plan-2026-01-30-workspace-sync-alt.md\n */\n\nimport { Command } from 'commander';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { requireInit, ValidationError } from '../lib/errors.js';\nimport { resolveDataSyncDir } from '../../lib/paths.js';\nimport { saveToWorkspace, type SaveOptions } from '../../file/workspace.js';\n\ninterface SaveCommandOptions {\n workspace?: string;\n dir?: string;\n outbox?: boolean;\n updatesOnly?: boolean;\n}\n\nclass SaveHandler extends BaseCommand {\n async run(options: SaveCommandOptions): Promise<void> {\n const tbdRoot = await requireInit();\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n\n // Validate that at least one target is specified\n if (!options.workspace && !options.dir && !options.outbox) {\n throw new ValidationError('One of --workspace, --dir, or --outbox is required');\n }\n\n // Build save options\n const saveOptions: SaveOptions = {\n workspace: options.workspace,\n dir: options.dir,\n outbox: options.outbox,\n updatesOnly: options.updatesOnly,\n };\n\n if (this.checkDryRun('Would save issues to workspace', saveOptions)) {\n return;\n }\n\n const spinner = this.output.spinner('Saving issues...');\n saveOptions.logger = this.output.logger(spinner);\n\n const result = await this.execute(async () => {\n return await saveToWorkspace(tbdRoot, dataSyncDir, saveOptions);\n }, 'Failed to save issues');\n\n spinner.stop();\n\n if (!result) {\n return;\n }\n\n // Format output\n const targetName = options.outbox ? 'outbox' : (options.workspace ?? options.dir ?? 'unknown');\n\n this.output.data(\n {\n saved: result.saved,\n conflicts: result.conflicts,\n target: targetName,\n totalSource: result.totalSource,\n filtered: result.filtered,\n },\n () => {\n if (result.saved === 0) {\n if (result.filtered) {\n this.output.info(`No issues to save (0 of ${result.totalSource} issues have updates)`);\n } else {\n this.output.info('No issues to save');\n }\n } else {\n if (result.filtered) {\n this.output.success(\n `Saved ${result.saved} issue(s) to ${targetName} (${result.saved} of ${result.totalSource} filtered)`,\n );\n } else {\n this.output.success(`Saved ${result.saved} issue(s) to ${targetName}`);\n }\n if (result.conflicts > 0) {\n this.output.warn(`${result.conflicts} conflict(s) moved to attic`);\n }\n }\n },\n );\n\n // Remind user to commit if saving to workspace\n if (options.workspace || options.outbox) {\n const colors = this.output.getColors();\n console.log(\n colors.dim(\n `\\nRemember to commit: git add .tbd/workspaces && git commit -m \"tbd: save workspace\"`,\n ),\n );\n }\n }\n}\n\nexport const saveCommand = new Command('save')\n .description('Save issues to a workspace or directory')\n .option('--workspace <name>', 'Save to named workspace under .tbd/workspaces/')\n .option('--dir <path>', 'Save to arbitrary directory')\n .option('--outbox', 'Shortcut for --workspace=outbox --updates-only')\n .option('--updates-only', 'Only save issues modified since last sync')\n .action(async (options, command) => {\n const handler = new SaveHandler(command);\n await handler.run(options);\n });\n","/**\n * `tbd workspace` - Workspace management commands.\n *\n * Workspaces are named directories for sync failure recovery, backups,\n * and bulk editing. Issues can be saved to a workspace and imported back.\n *\n * See: plan-2026-01-30-workspace-sync-alt.md\n */\n\nimport { Command } from 'commander';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { requireInit, NotFoundError, ValidationError } from '../lib/errors.js';\nimport {\n listWorkspacesWithCounts,\n deleteWorkspace,\n workspaceExists,\n} from '../../file/workspace.js';\nimport { isValidWorkspaceName } from '../../lib/paths.js';\n\n/**\n * List all workspaces with issue counts.\n */\nclass WorkspaceListHandler extends BaseCommand {\n async run(): Promise<void> {\n const tbdRoot = await requireInit();\n\n const workspaces = await listWorkspacesWithCounts(tbdRoot);\n\n this.output.data(workspaces, () => {\n const colors = this.output.getColors();\n if (workspaces.length === 0) {\n console.log('No workspaces');\n return;\n }\n\n // Calculate column widths\n const maxNameLen = Math.max(9, ...workspaces.map((ws) => ws.name.length)); // 9 = \"WORKSPACE\".length\n const countWidth = 6;\n\n // Header\n const header = `${colors.dim('WORKSPACE'.padEnd(maxNameLen))} ${colors.dim('open'.padStart(countWidth))} ${colors.dim('in_progress'.padStart(11))} ${colors.dim('closed'.padStart(countWidth))} ${colors.dim('total'.padStart(countWidth))}`;\n console.log(header);\n\n // Rows\n for (const ws of workspaces) {\n const { name, counts } = ws;\n const row = `${name.padEnd(maxNameLen)} ${String(counts.open).padStart(countWidth)} ${String(counts.in_progress).padStart(11)} ${String(counts.closed).padStart(countWidth)} ${String(counts.total).padStart(countWidth)}`;\n console.log(row);\n }\n });\n }\n}\n\n/**\n * Delete a workspace.\n */\nclass WorkspaceDeleteHandler extends BaseCommand {\n async run(name: string, options: { force?: boolean }): Promise<void> {\n const tbdRoot = await requireInit();\n\n // Validate workspace name\n if (!isValidWorkspaceName(name)) {\n throw new ValidationError(\n `Invalid workspace name: \"${name}\". Use lowercase alphanumeric characters, hyphens, and underscores.`,\n );\n }\n\n // Check if workspace exists\n const exists = await workspaceExists(tbdRoot, name);\n if (!exists && !options.force) {\n throw new NotFoundError('Workspace', name);\n }\n\n if (this.checkDryRun('Would delete workspace', { name })) {\n return;\n }\n\n await this.execute(async () => {\n await deleteWorkspace(tbdRoot, name);\n }, 'Failed to delete workspace');\n\n this.output.success(`Deleted workspace \"${name}\"`);\n }\n}\n\nconst listWorkspaceCommand = new Command('list')\n .description('List all workspaces')\n .action(async (_options, command) => {\n const handler = new WorkspaceListHandler(command);\n await handler.run();\n });\n\nconst deleteWorkspaceCommand = new Command('delete')\n .description('Delete a workspace')\n .argument('<name>', 'Workspace name to delete')\n .option('--force', 'Delete without error if workspace does not exist')\n .action(async (name, options, command) => {\n const handler = new WorkspaceDeleteHandler(command);\n await handler.run(name, options);\n });\n\nexport const workspaceCommand = new Command('workspace')\n .description('Manage workspaces for sync recovery and backups')\n .addCommand(listWorkspaceCommand)\n .addCommand(deleteWorkspaceCommand);\n","/**\n * CLI program setup using Commander.js\n *\n * See: research-modern-typescript-cli-patterns.md\n */\n\nimport { Command } from 'commander';\n\nimport { VERSION } from './lib/version.js';\nimport {\n configureColoredHelp,\n createColoredHelpConfig,\n createHelpEpilog,\n getColorOptionFromArgv,\n} from './lib/output.js';\nimport { initCommand } from './commands/init.js';\nimport { createCommand } from './commands/create.js';\nimport { listCommand } from './commands/list.js';\nimport { showCommand } from './commands/show.js';\nimport { updateCommand } from './commands/update.js';\nimport { closeCommand } from './commands/close.js';\nimport { reopenCommand } from './commands/reopen.js';\nimport { readyCommand } from './commands/ready.js';\nimport { blockedCommand } from './commands/blocked.js';\nimport { staleCommand } from './commands/stale.js';\nimport { labelCommand } from './commands/label.js';\nimport { depCommand } from './commands/dep.js';\nimport { syncCommand } from './commands/sync.js';\nimport { searchCommand } from './commands/search.js';\nimport { statusCommand } from './commands/status.js';\nimport { statsCommand } from './commands/stats.js';\nimport { doctorCommand } from './commands/doctor.js';\nimport { configCommand } from './commands/config.js';\nimport { atticCommand } from './commands/attic.js';\nimport { importCommand } from './commands/import.js';\nimport { docsCommand } from './commands/docs.js';\nimport { closeProtocolCommand } from './commands/closing.js';\nimport { designCommand } from './commands/design.js';\nimport { readmeCommand } from './commands/readme.js';\nimport { uninstallCommand } from './commands/uninstall.js';\nimport { primeCommand } from './commands/prime.js';\nimport { skillCommand } from './commands/skill.js';\nimport { shortcutCommand } from './commands/shortcut.js';\nimport { guidelinesCommand } from './commands/guidelines.js';\nimport { templateCommand } from './commands/template.js';\nimport { setupCommand } from './commands/setup.js';\nimport { saveCommand } from './commands/save.js';\nimport { workspaceCommand } from './commands/workspace.js';\nimport { CLIError } from './lib/errors.js';\n\n/**\n * Create and configure the CLI program.\n */\nfunction createProgram(): Command {\n const program = new Command()\n .name('tbd')\n .description('Git-native issue tracking for AI agents and humans')\n .version(VERSION, '--version', 'Show version number')\n .helpOption('--help', 'Display help for command')\n .showHelpAfterError('(add --help for additional information)');\n\n // Configure colored help output (respects --color option)\n configureColoredHelp(program);\n\n // Global options\n program\n .option('--dry-run', 'Show what would be done without making changes')\n .option('--verbose', 'Enable verbose output')\n .option('--quiet', 'Suppress non-essential output')\n .option('--json', 'Output as JSON')\n .option('--color <when>', 'Colorize output: auto, always, never', 'auto')\n .option('--no-sync', 'Skip automatic sync after write operations')\n .option('--debug', 'Show internal IDs alongside public IDs for debugging');\n\n // Add commands in logical groups\n // Note: commandsGroup() sets the heading for all following addCommand() calls\n\n program.commandsGroup('Documentation:');\n program.addCommand(readmeCommand);\n program.addCommand(primeCommand);\n program.addCommand(skillCommand);\n program.addCommand(shortcutCommand);\n program.addCommand(guidelinesCommand);\n program.addCommand(templateCommand);\n program.addCommand(closeProtocolCommand);\n program.addCommand(docsCommand);\n program.addCommand(designCommand);\n\n program.commandsGroup('Setup & Configuration:');\n program.addCommand(initCommand);\n program.addCommand(configCommand);\n program.addCommand(setupCommand);\n\n program.commandsGroup('Working With Issues:');\n\n program.addCommand(createCommand);\n program.addCommand(showCommand);\n program.addCommand(updateCommand);\n program.addCommand(closeCommand);\n program.addCommand(reopenCommand);\n program.addCommand(searchCommand);\n\n program.commandsGroup('Views and Filtering:');\n program.addCommand(readyCommand);\n program.addCommand(listCommand);\n program.addCommand(blockedCommand);\n program.addCommand(staleCommand);\n\n program.commandsGroup('Labels and Dependencies:');\n program.addCommand(depCommand);\n program.addCommand(labelCommand);\n\n program.commandsGroup('Sync and Status:');\n program.addCommand(syncCommand);\n program.addCommand(saveCommand);\n program.addCommand(statusCommand);\n program.addCommand(statsCommand);\n\n program.commandsGroup('Maintenance:');\n program.addCommand(doctorCommand);\n program.addCommand(atticCommand);\n program.addCommand(workspaceCommand);\n program.addCommand(importCommand);\n program.addCommand(uninstallCommand);\n\n // Apply colored help to all commands recursively\n // Note: addCommand() does NOT inherit parent's configureHelp settings,\n // unlike command() which does inherit. So we must apply manually.\n applyColoredHelpToAllCommands(program);\n\n return program;\n}\n\n/**\n * Apply colored help configuration and epilog to all commands recursively.\n * This is needed because Commander.js's addCommand() does not inherit\n * configureHelp settings from the parent command.\n */\nfunction applyColoredHelpToAllCommands(program: Command): void {\n const colorOption = getColorOptionFromArgv();\n const helpConfig = createColoredHelpConfig(colorOption);\n const epilog = createHelpEpilog(colorOption);\n\n // Add epilog to main program only - it shows for all help including subcommands\n program.addHelpText('afterAll', `\\n${epilog}`);\n\n const applyRecursively = (cmd: Command) => {\n cmd.configureHelp(helpConfig);\n for (const sub of cmd.commands) {\n applyRecursively(sub);\n }\n };\n\n for (const cmd of program.commands) {\n applyRecursively(cmd);\n }\n}\n\n/**\n * Check if --json flag is present in argv.\n */\nfunction isJsonMode(): boolean {\n return process.argv.includes('--json');\n}\n\n/**\n * Check if --debug flag is present in argv.\n */\nfunction isDebugMode(): boolean {\n return process.argv.includes('--debug');\n}\n\n/**\n * Output error in the appropriate format (JSON or text).\n * In debug mode, shows full error details, stack trace, and cause chain.\n */\nfunction outputError(message: string, error?: Error): void {\n const debugMode = isDebugMode();\n\n if (isJsonMode()) {\n const errorObj: {\n error: string;\n type?: string;\n details?: string;\n stack?: string;\n cause?: string;\n } = {\n error: message,\n };\n if (error instanceof CLIError) {\n errorObj.type = error.name;\n }\n if (error && error.message !== message) {\n errorObj.details = error.message;\n }\n if (debugMode && error?.stack) {\n errorObj.stack = error.stack;\n }\n if (error?.cause instanceof Error) {\n errorObj.cause = error.cause.message;\n }\n console.error(JSON.stringify(errorObj));\n } else {\n console.error(`Error: ${message}`);\n if (debugMode && error?.stack) {\n console.error('');\n console.error('Stack trace:');\n console.error(error.stack);\n // Walk the cause chain to show underlying errors\n let cause = error.cause;\n while (cause instanceof Error) {\n console.error('');\n console.error(`Caused by: ${cause.message}`);\n if (cause.stack) {\n console.error(cause.stack);\n }\n cause = cause.cause;\n }\n }\n }\n}\n\n/**\n * Check if running with no command (just options or nothing).\n * Returns true if: `tbd`, `tbd --help`, `tbd --version`, `tbd --color never`\n * Returns false if there's a command: `tbd list`, `tbd show foo`\n */\nfunction hasNoCommand(): boolean {\n // process.argv is: [node, script, ...args]\n const rawArgs = process.argv.slice(2);\n\n // Global options that take a value (space-separated form)\n const optionsWithValues = new Set(['--color']);\n\n const nonOptionArgs: string[] = [];\n let skipNext = false;\n\n for (const arg of rawArgs) {\n if (skipNext) {\n // This arg is a value for the previous option, skip it\n skipNext = false;\n continue;\n }\n\n if (arg.startsWith('-')) {\n // Check if this option takes a value (and doesn't use = syntax)\n const optionName = arg.includes('=') ? arg.split('=')[0] : arg;\n if (optionsWithValues.has(optionName!) && !arg.includes('=')) {\n skipNext = true;\n }\n continue;\n }\n\n // This is a non-option argument (potential command)\n nonOptionArgs.push(arg);\n }\n\n return nonOptionArgs.length === 0;\n}\n\n/**\n * Run the CLI. This is the main entry point.\n */\nexport async function runCli(): Promise<void> {\n const program = createProgram();\n\n // If no command specified (and not help/version), run prime by default\n // But only if no --help or --version flags\n const isHelpOrVersion =\n process.argv.includes('--help') ||\n process.argv.includes('-h') ||\n process.argv.includes('--version') ||\n process.argv.includes('-V');\n\n // Show help by default when no command given (changed from prime)\n // The help epilog guides agents to run `tbd prime` for full context\n if (hasNoCommand() && !isHelpOrVersion) {\n // Show help instead of defaulting to prime\n process.argv.splice(2, 0, '--help');\n }\n\n try {\n await program.parseAsync(process.argv);\n } catch (error) {\n if (error instanceof CLIError) {\n outputError(error.message, error);\n process.exit(error.exitCode);\n }\n // Unexpected error\n const message = error instanceof Error ? error.message : String(error);\n outputError(message, error instanceof Error ? error : undefined);\n process.exit(1);\n }\n}\n\n// Handle SIGINT (Ctrl+C)\nprocess.on('SIGINT', () => {\n console.error('\\nInterrupted');\n process.exit(130); // 128 + SIGINT(2)\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.mjs","names":["BUILD_VERSION","parseYaml","execFileAsync","normalizePath","addCommand","removeCommand"],"sources":["../src/cli/lib/version.ts","../src/cli/lib/context.ts","../src/cli/lib/output.ts","../src/cli/lib/errors.ts","../src/cli/lib/base-command.ts","../src/utils/file-utils.ts","../src/utils/gitignore-utils.ts","../src/cli/lib/prefix-detection.ts","../src/utils/time-utils.ts","../src/file/git.ts","../src/cli/commands/init.ts","../src/utils/zod-error-utils.ts","../src/file/storage.ts","../src/lib/priority.ts","../src/lib/project-paths.ts","../src/cli/lib/issue-input-validation.ts","../src/cli/commands/create.ts","../src/cli/lib/limit-utils.ts","../src/cli/lib/data-context.ts","../src/lib/status.ts","../src/lib/truncate.ts","../src/cli/lib/issue-format.ts","../src/cli/lib/tree-view.ts","../src/lib/spec-matching.ts","../src/cli/commands/list.ts","../src/cli/commands/show.ts","../src/cli/commands/update.ts","../src/cli/commands/close.ts","../src/cli/commands/reopen.ts","../src/cli/commands/ready.ts","../src/cli/commands/blocked.ts","../src/cli/commands/stale.ts","../src/cli/commands/label.ts","../src/cli/commands/dep.ts","../src/lib/sync-summary.ts","../src/file/github-fetch.ts","../src/file/doc-sync.ts","../src/file/workspace.ts","../src/cli/commands/sync.ts","../src/lib/format-utils.ts","../src/cli/commands/search.ts","../src/cli/lib/sections.ts","../src/lib/integration-paths.ts","../src/cli/commands/status.ts","../src/cli/commands/stats.ts","../src/cli/lib/diagnostics.ts","../src/cli/commands/doctor.ts","../src/cli/commands/config.ts","../src/cli/commands/attic.ts","../src/cli/commands/import.ts","../src/cli/commands/docs.ts","../src/cli/commands/closing.ts","../src/cli/commands/design.ts","../src/cli/commands/readme.ts","../src/cli/commands/uninstall.ts","../src/file/doc-cache.ts","../src/cli/commands/prime.ts","../src/cli/commands/skill.ts","../src/cli/lib/doc-prompts.ts","../src/file/doc-add.ts","../src/cli/commands/shortcut.ts","../src/cli/lib/doc-command-handler.ts","../src/cli/commands/guidelines.ts","../src/cli/commands/template.ts","../src/cli/commands/setup.ts","../src/cli/commands/save.ts","../src/cli/commands/workspace.ts","../src/cli/cli.ts"],"sourcesContent":["/**\n * CLI version detection\n *\n * Priority:\n * 1. Build-time injected __TBD_VERSION__ (production builds)\n * 2. TBD_DEV_VERSION env var (dev mode, set by pnpm tbd script)\n * 3. package.json version (fallback)\n *\n * No git dependency at runtime - git version is computed at build time\n * or by the dev script wrapper.\n */\n\nimport { createRequire } from 'node:module';\n\nimport { VERSION as BUILD_VERSION } from '../../index.js';\n\n/**\n * Get the CLI version.\n */\nfunction getVersion(): string {\n // 1. Build-time injected version (production)\n if (BUILD_VERSION !== 'development') {\n return BUILD_VERSION;\n }\n\n // 2. Dev mode env var (set by pnpm tbd script)\n if (process.env.TBD_DEV_VERSION) {\n return process.env.TBD_DEV_VERSION;\n }\n\n // 3. Fallback to package.json version\n const require = createRequire(import.meta.url);\n const pkg = require('../../../package.json') as { version: string };\n return pkg.version;\n}\n\n/**\n * CLI version - use this instead of importing VERSION directly from index.ts\n */\nexport const VERSION = getVersion();\n","/**\n * Command context and global options management.\n *\n * See: research-modern-typescript-cli-patterns.md#9-global-options\n */\n\nimport type { Command } from 'commander';\n\n/**\n * Output format options.\n */\nexport type OutputFormat = 'text' | 'json';\n\n/**\n * Color output options.\n */\nexport type ColorOption = 'auto' | 'always' | 'never';\n\n/**\n * Global command context extracted from Commander options.\n */\nexport interface CommandContext {\n dryRun: boolean;\n verbose: boolean;\n quiet: boolean;\n json: boolean;\n color: ColorOption;\n sync: boolean;\n /** Debug mode: shows internal IDs alongside public IDs */\n debug: boolean;\n}\n\n/**\n * Extract command context from a Commander command.\n * Handles inheritance of global options through the command hierarchy.\n */\nexport function getCommandContext(command: Command): CommandContext {\n const opts = command.optsWithGlobals();\n\n return {\n dryRun: opts.dryRun ?? false,\n verbose: opts.verbose ?? false,\n quiet: opts.quiet ?? false,\n json: opts.json ?? false,\n color: (opts.color as ColorOption) ?? 'auto',\n sync: opts.sync !== false, // --no-sync sets this to false\n debug: opts.debug ?? false,\n };\n}\n\n/**\n * Determine if output should be colorized based on options and environment.\n */\nexport function shouldColorize(colorOption: ColorOption): boolean {\n // NO_COLOR takes precedence (unless --color=always explicitly set)\n if (process.env.NO_COLOR && colorOption !== 'always') {\n return false;\n }\n if (colorOption === 'always') {\n return true;\n }\n if (colorOption === 'never') {\n return false;\n }\n return process.stdout.isTTY ?? false;\n}\n\n/**\n * Check if we should use interactive output features (colors, pagination).\n * Returns true when: TTY output, not JSON mode, not quiet mode.\n *\n * This is specifically for human-readable output formatting. Agents typically\n * capture output via pipes (isTTY=false) or use --json mode, so they get\n * clean plain text without ANSI codes or pagination.\n */\nexport function shouldUseInteractiveOutput(ctx: CommandContext): boolean {\n return !ctx.json && !ctx.quiet && shouldColorize(ctx.color) && process.stdout.isTTY === true;\n}\n","/**\n * OutputManager for dual-mode output (text + JSON).\n *\n * See: research-modern-typescript-cli-patterns.md#4-dual-output-mode-text--json\n */\n\nimport pc from 'picocolors';\nimport type { Command } from 'commander';\nimport { marked } from 'marked';\nimport { markedTerminal } from 'marked-terminal';\nimport { spawn } from 'node:child_process';\n\nimport type { CommandContext, ColorOption } from './context.js';\nimport { shouldColorize } from './context.js';\nimport { PAGINATION_LINE_THRESHOLD } from '../../lib/settings.js';\nimport { parseMarkdown } from '../../utils/markdown-utils.js';\nimport type { OperationLogger } from '../../lib/types.js';\n\n/**\n * Standard icons for CLI output. Use these constants instead of hardcoded characters.\n *\n * Message icons (prefix messages with these):\n * - SUCCESS_ICON: ✓ for completed operations\n * - ERROR_ICON: ✗ for failures\n * - WARN_ICON: ⚠ for warnings\n * - NOTICE_ICON: • for notices/bullets\n *\n * Status icons (show issue status):\n * - OPEN_ICON: ○ for open issues\n * - IN_PROGRESS_ICON: ◐ for in-progress issues\n * - BLOCKED_ICON: ● for blocked issues\n * - CLOSED_ICON: ✓ for closed issues (same as SUCCESS_ICON)\n */\n\n/**\n * Format a section heading - ALL CAPS for consistent CLI output.\n * Per arch-cli-interface-design-system.md, section headings should be\n * ALL CAPS, bold, followed by blank line before content.\n *\n * @param text - The heading text to format\n * @returns Uppercase heading string\n */\nexport function formatHeading(text: string): string {\n return text.toUpperCase();\n}\n\nexport const ICONS = {\n // Message icons\n SUCCESS: '✓', // U+2713\n ERROR: '✗', // U+2717\n WARN: '⚠', // U+26A0\n NOTICE: '•', // U+2022\n\n // Status icons\n OPEN: '○', // U+25CB\n IN_PROGRESS: '◐', // U+25D0\n BLOCKED: '●', // U+25CF\n CLOSED: '✓', // U+2713 (same as SUCCESS)\n DEFERRED: '○', // U+25CB (same as OPEN)\n} as const;\n\n/**\n * Pre-parse argv to determine color setting before Commander parses options.\n * This is needed because help output happens before full option parsing.\n */\nexport function getColorOptionFromArgv(): ColorOption {\n const colorArg = process.argv.find((arg) => arg.startsWith('--color='));\n if (colorArg) {\n const value = colorArg.split('=')[1];\n if (value === 'always' || value === 'never' || value === 'auto') {\n return value;\n }\n }\n // Check for --color followed by value\n const colorIdx = process.argv.indexOf('--color');\n if (colorIdx !== -1 && process.argv[colorIdx + 1]) {\n const value = process.argv[colorIdx + 1];\n if (value === 'always' || value === 'never' || value === 'auto') {\n return value;\n }\n }\n return 'auto';\n}\n\n/**\n * Maximum width for help text and formatted output. We cap at 88 characters\n * for readability, but use narrower if the terminal is smaller.\n */\nexport const MAX_HELP_WIDTH = 88;\n\n/**\n * Get terminal width capped at MAX_HELP_WIDTH.\n * Use this for all formatted CLI output to ensure consistent width handling.\n */\nexport function getTerminalWidth(): number {\n return Math.min(MAX_HELP_WIDTH, process.stdout.columns ?? 80);\n}\n\n/**\n * Create colored help configuration for Commander.js.\n * Uses Commander's built-in configureHelp() style functions (requires v14+).\n *\n * @param colorOption - Color option to determine if colors should be enabled\n * @returns Help configuration object for program.configureHelp()\n */\nexport function createColoredHelpConfig(colorOption: ColorOption = 'auto') {\n const colors = pc.createColors(shouldColorize(colorOption));\n\n return {\n helpWidth: getTerminalWidth(),\n styleTitle: (str: string) => colors.bold(colors.cyan(str)),\n styleCommandText: (str: string) => colors.green(str),\n styleOptionText: (str: string) => colors.yellow(str),\n showGlobalOptions: true,\n };\n}\n\n/**\n * Create the help epilog text with color.\n * Includes \"Getting Started\" section and prominent agent guidance per spec.\n *\n * @param colorOption - Color option to determine if colors should be enabled\n * @returns Colored epilog string\n */\nexport function createHelpEpilog(colorOption: ColorOption = 'auto'): string {\n const colors = pc.createColors(shouldColorize(colorOption));\n const lines = [\n colors.bold(colors.yellow('IMPORTANT:')),\n ` Agents unfamiliar with tbd should run ${colors.green('`tbd prime`')} for full workflow context.`,\n '',\n colors.bold('Getting Started:'),\n ` ${colors.green('npm install -g get-tbd@latest && tbd setup --auto --prefix=<name>')}`,\n '',\n ' This initializes tbd and configures your coding agents automatically.',\n ` To refresh setup (idempotent, safe anytime): ${colors.green('`tbd setup --auto`')}`,\n ` For interactive setup: ${colors.dim('`tbd setup --interactive`')}`,\n '',\n colors.blue('For more on tbd, see: https://github.com/jlevy/tbd'),\n ];\n return lines.join('\\n');\n}\n\n/**\n * Configure Commander.js with colored help text.\n * Call this on the program before adding commands.\n */\nexport function configureColoredHelp(program: Command): Command {\n const colorOption = getColorOptionFromArgv();\n return program.configureHelp(createColoredHelpConfig(colorOption));\n}\n\n/**\n * Color utilities with conditional colorization.\n *\n * Uses picocolors' createColors() for manual color support control,\n * which is the recommended approach per picocolors documentation.\n * This allows --color=always to work even when stdout is not a TTY.\n */\nexport function createColors(colorOption: ColorOption) {\n const enabled = shouldColorize(colorOption);\n\n // Use picocolors' createColors() for proper manual control\n // This overrides picocolors' automatic TTY detection\n const colors = pc.createColors(enabled);\n\n return {\n // Status colors\n success: colors.green,\n error: colors.red,\n warn: colors.yellow,\n info: colors.blue,\n\n // Text formatting\n bold: colors.bold,\n dim: colors.dim,\n italic: colors.italic,\n underline: colors.underline,\n\n // Semantic colors\n id: colors.cyan,\n label: colors.magenta,\n path: colors.blue,\n };\n}\n\n/**\n * Render Markdown to colorized terminal output.\n *\n * Uses marked-terminal for colorized output when colors are enabled,\n * falls back to plain Markdown when colors are disabled or piped.\n * Respects the --color option and TTY detection.\n *\n * @param content - Markdown string to render\n * @param colorOption - Color option to determine if colors should be enabled\n * @returns Rendered string (colorized or plain)\n */\nexport function renderMarkdown(content: string, colorOption: ColorOption = 'auto'): string {\n const useColors = shouldColorize(colorOption);\n\n if (!useColors) {\n // Return plain markdown when colors are disabled\n return content;\n }\n\n // Configure marked with terminal renderer for this parse\n // Note: @types/marked-terminal is outdated; markedTerminal returns MarkedExtension in v7+\n // but types still claim it returns TerminalRenderer. Cast to work around this.\n marked.use(\n markedTerminal({\n width: getTerminalWidth(),\n reflowText: true,\n }) as unknown as Parameters<typeof marked.use>[0],\n );\n\n // marked.parse returns string with sync renderer\n return marked.parse(content) as string;\n}\n\n/**\n * Render YAML frontmatter with custom styling.\n * Keys are dimmed, values are bold.\n *\n * @param frontmatter - Raw YAML frontmatter string (without --- delimiters)\n * @returns Styled frontmatter string\n */\nfunction renderYamlFrontmatter(frontmatter: string): string {\n const lines = frontmatter.split('\\n');\n const styledLines = lines.map((line) => {\n // Match YAML key: value pattern\n const match = /^(\\s*)([^:]+:)(.*)$/.exec(line);\n if (match) {\n const [, indent, key, value] = match;\n // Key (including colon) is dim, value is bold\n return indent + pc.dim(key) + pc.bold(value);\n }\n // Lines without key: pattern (e.g., continuation lines) stay as-is but bold\n return pc.bold(line);\n });\n return styledLines.join('\\n');\n}\n\n/**\n * Render markdown with proper YAML frontmatter handling.\n *\n * Separates YAML frontmatter from markdown body and renders them appropriately:\n * - Frontmatter keys are dimmed, values are bold (no indentation)\n * - Body is rendered as regular markdown\n *\n * Works with or without frontmatter - if no frontmatter exists, renders as plain markdown.\n *\n * @param content - Markdown string (possibly with YAML frontmatter) to render\n * @param colorOption - Color option to determine if colors should be enabled\n * @returns Rendered string (colorized or plain)\n */\nexport function renderMarkdownWithFrontmatter(\n content: string,\n colorOption: ColorOption = 'auto',\n): string {\n const useColors = shouldColorize(colorOption);\n\n if (!useColors) {\n // Return plain markdown when colors are disabled\n return content;\n }\n\n const { frontmatter, body } = parseMarkdown(content);\n\n let result = '';\n\n // Render frontmatter with custom YAML styling if present\n if (frontmatter !== null && frontmatter.length > 0) {\n result += pc.dim('---') + '\\n';\n result += renderYamlFrontmatter(frontmatter) + '\\n';\n result += pc.dim('---') + '\\n\\n';\n }\n\n // Render body as markdown\n if (body) {\n result += renderMarkdown(body, colorOption);\n }\n\n return result;\n}\n\n/**\n * Output content with pagination if it exceeds threshold and TTY is interactive.\n * Uses PAGER env var or falls back to 'less -R' (supports colors).\n *\n * When output is piped or not a TTY, outputs directly without pagination.\n * This ensures agents and scripts get clean output.\n *\n * @param content - Content to output\n * @param hasColors - If true, content has ANSI colors (use -R flag for less)\n * @returns Promise that resolves when output is complete\n */\nexport async function paginateOutput(content: string, hasColors = false): Promise<void> {\n const lines = content.split('\\n').length;\n\n // Don't paginate short content or non-TTY\n if (lines < PAGINATION_LINE_THRESHOLD || !process.stdout.isTTY) {\n console.log(content);\n return;\n }\n\n const pager = process.env.PAGER ?? (hasColors ? 'less -R' : 'less');\n const [cmd, ...args] = pager.split(' ');\n\n return new Promise((resolve) => {\n const child = spawn(cmd!, args, {\n stdio: ['pipe', 'inherit', 'inherit'],\n });\n\n // Handle EPIPE error when user quits pager (e.g., pressing 'q' in less).\n // This is expected behavior - the pager closes stdin when the user exits early.\n child.stdin.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EPIPE') {\n // User quit the pager early - this is fine, not an error\n return;\n }\n // For other errors, log only in debug scenarios\n // (but don't throw - let the process exit cleanly)\n });\n\n child.stdin.write(content);\n child.stdin.end();\n\n child.on('close', () => {\n resolve();\n });\n child.on('error', () => {\n // Fall back to direct output if pager fails (e.g., less not installed)\n console.log(content);\n resolve();\n });\n });\n}\n\n/**\n * Spinner interface for progress indication.\n */\nexport interface Spinner {\n message(msg: string): void;\n stop(msg?: string): void;\n}\n\n/**\n * No-op spinner for non-TTY or quiet mode.\n */\nconst noopSpinner: Spinner = {\n message: () => {},\n stop: () => {},\n};\n\n/**\n * OutputManager handles all CLI output with format switching.\n */\nexport class OutputManager {\n private ctx: CommandContext;\n private colors: ReturnType<typeof createColors>;\n\n constructor(ctx: CommandContext) {\n this.ctx = ctx;\n this.colors = createColors(ctx.color);\n }\n\n /**\n * Output structured data - always goes to stdout.\n * In JSON mode, outputs JSON. In text mode, calls the formatter.\n */\n data<T>(data: T, textFormatter?: (data: T) => void): void {\n if (this.ctx.json) {\n console.log(JSON.stringify(data, null, 2));\n } else if (textFormatter) {\n textFormatter(data);\n }\n }\n\n /**\n * Output success message - text mode only, stdout.\n * Suppressed by --quiet and --json.\n */\n success(message: string): void {\n if (!this.ctx.json && !this.ctx.quiet) {\n console.log(this.colors.success(`${ICONS.SUCCESS} ${message}`));\n }\n }\n\n /**\n * Output notice message - noteworthy events during normal operation.\n * Blue bullet, shown at default level. Suppressed by --quiet and --json.\n */\n notice(message: string): void {\n if (!this.ctx.json && !this.ctx.quiet) {\n console.log(this.colors.info(`${ICONS.NOTICE} ${message}`));\n }\n }\n\n /**\n * Output info message - operational progress.\n * Requires --verbose or --debug. Suppressed by --json.\n */\n info(message: string): void {\n if (!this.ctx.json && (this.ctx.verbose || this.ctx.debug)) {\n console.error(this.colors.dim(message));\n }\n }\n\n /**\n * Output warning - issues that didn't stop operation.\n * Yellow warning icon, stderr. Suppressed by --quiet.\n */\n warn(message: string): void {\n if (this.ctx.json) {\n console.error(JSON.stringify({ warning: message }));\n } else if (!this.ctx.quiet) {\n console.error(this.colors.warn(`${ICONS.WARN} ${message}`));\n }\n }\n\n /**\n * Output error - failures that stop operation.\n * Red X icon, always shown (even in --quiet), stderr.\n */\n error(message: string, err?: Error): void {\n if (this.ctx.json) {\n console.error(JSON.stringify({ error: message, details: err?.message }));\n } else {\n console.error(this.colors.error(`${ICONS.ERROR} ${message}`));\n if (this.ctx.verbose && err?.stack) {\n console.error(this.colors.dim(err.stack));\n }\n }\n }\n\n /**\n * Output command being executed - shows external commands.\n * Requires --verbose or --debug. Suppressed by --json.\n */\n command(cmd: string, args?: string[]): void {\n if (!this.ctx.json && (this.ctx.verbose || this.ctx.debug)) {\n const fullCmd = args ? `${cmd} ${args.join(' ')}` : cmd;\n console.error(this.colors.dim(`> ${fullCmd}`));\n }\n }\n\n /**\n * Output debug message - internal state for troubleshooting.\n * Requires --debug only (not --verbose). Suppressed by --json.\n */\n debug(message: string): void {\n if (this.ctx.debug && !this.ctx.json) {\n console.error(this.colors.dim(`[debug] ${message}`));\n }\n }\n\n /**\n * Output dry-run indication.\n */\n dryRun(message: string, details?: object): void {\n if (this.ctx.json) {\n console.log(JSON.stringify({ dryRun: true, action: message, ...details }));\n } else {\n console.log(this.colors.warn(`[DRY-RUN] ${message}`));\n if (details && (this.ctx.verbose || this.ctx.debug)) {\n console.log(this.colors.dim(JSON.stringify(details, null, 2)));\n }\n }\n }\n\n /**\n * Output a table with headers and rows.\n * Headers are dimmed. Rows are formatted with consistent column widths.\n * Suppressed in JSON mode.\n *\n * @param headers - Array of column headers with widths\n * @param rows - Array of row data arrays (each row = array of strings)\n */\n table(\n headers: { label: string; width: number }[],\n rows: (string | { value: string; color?: (s: string) => string })[][],\n ): void {\n if (this.ctx.json) return;\n\n // Output header row\n const headerLine = headers.map((h) => h.label.padEnd(h.width)).join('');\n console.log(this.colors.dim(headerLine));\n\n // Output data rows\n for (const row of rows) {\n const cells = row.map((cell, i) => {\n const width = headers[i]?.width ?? 0;\n if (typeof cell === 'string') {\n return cell.padEnd(width);\n }\n // Cell with custom color\n const paddedValue = cell.value.padEnd(width);\n return cell.color ? cell.color(paddedValue) : paddedValue;\n });\n console.log(cells.join(''));\n }\n }\n\n /**\n * Output a bulleted list.\n * Uses NOTICE icon (•) as bullet. Suppressed in JSON mode.\n *\n * @param items - Array of items to list\n * @param options - Optional indent level (default 0)\n */\n list(items: string[], options?: { indent?: number }): void {\n if (this.ctx.json) return;\n\n const indent = ' '.repeat(options?.indent ?? 0);\n for (const item of items) {\n console.log(`${indent}${ICONS.NOTICE} ${item}`);\n }\n }\n\n /**\n * Output a count summary in standard format.\n * Format: \"N item(s)\" with dim color. Suppressed in JSON mode.\n *\n * @param count - The count to display\n * @param singular - Singular form of the item (e.g., \"issue\")\n * @param plural - Optional plural form (defaults to singular + \"s\")\n */\n count(count: number, singular: string, plural?: string): void {\n if (this.ctx.json) return;\n\n const pluralForm = plural ?? `${singular}s`;\n const label = count === 1 ? singular : pluralForm;\n console.log(this.colors.dim(`${count} ${label}`));\n }\n\n /**\n * Create a spinner for progress indication.\n * Returns no-op in JSON/quiet mode or non-TTY.\n */\n spinner(message: string): Spinner {\n // Never show spinners in JSON mode, quiet mode, or non-TTY\n if (this.ctx.json || this.ctx.quiet || !process.stderr.isTTY) {\n return noopSpinner;\n }\n\n // Simple inline spinner (no external dependency for now)\n let frame = 0;\n const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];\n let currentMessage = message;\n\n const spinnerColor = this.colors.info;\n const write = () => {\n process.stderr.write(`\\r${spinnerColor(frames[frame] ?? '⠋')} ${currentMessage}`);\n frame = (frame + 1) % frames.length;\n };\n\n write();\n const interval = setInterval(write, 80);\n\n return {\n message: (msg: string) => {\n currentMessage = msg;\n },\n stop: (msg?: string) => {\n clearInterval(interval);\n process.stderr.write('\\r' + ' '.repeat(currentMessage.length + 3) + '\\r');\n if (msg) {\n console.error(msg);\n }\n },\n };\n }\n\n /**\n * Create an OperationLogger wired to this OutputManager and a spinner.\n *\n * Eliminates the boilerplate of manually wiring spinner.message, output.info,\n * output.warn, and output.debug in every command that calls a core function.\n */\n logger(spinner: Spinner): OperationLogger {\n return {\n progress: (msg) => {\n spinner.message(msg);\n },\n info: (msg) => {\n this.info(msg);\n },\n warn: (msg) => {\n this.warn(msg);\n },\n debug: (msg) => {\n this.debug(msg);\n },\n };\n }\n\n /**\n * Get colors instance for direct use.\n */\n getColors() {\n return this.colors;\n }\n\n /**\n * Check if quiet mode is enabled.\n */\n isQuiet(): boolean {\n return this.ctx.quiet;\n }\n}\n\n// ============================================================================\n// Component Helper Functions\n// ============================================================================\n\n/**\n * Format command header with version.\n * Used at start of orientation commands (status, doctor, stats).\n *\n * @example formatCommandHeader('tbd', '0.1.9', colors) → \"tbd v0.1.9\" (bold name)\n */\nexport function formatCommandHeader(\n name: string,\n version: string,\n colors: ReturnType<typeof createColors>,\n): string {\n return `${colors.bold(name)} v${version}`;\n}\n\n/**\n * Format key-value line with dim key.\n * Used for configuration display.\n *\n * @example formatKeyValue('Sync branch', 'tbd-sync', colors) → \"Sync branch: tbd-sync\"\n */\nexport function formatKeyValue(\n key: string,\n value: string,\n colors: ReturnType<typeof createColors>,\n): string {\n return `${colors.dim(key + ':')} ${value}`;\n}\n\n/**\n * Format aligned statistic block.\n * Returns array of formatted lines with aligned values.\n *\n * @param stats - Array of {label, value} pairs\n * @param colors - Color functions (unused but kept for consistency)\n * @returns Array of formatted lines\n *\n * @example\n * formatStatBlock([{label: 'Ready', value: 12}, {label: 'In progress', value: 4}], colors)\n * → [' Ready: 12', ' In progress: 4']\n */\nexport function formatStatBlock(\n stats: { label: string; value: number | string }[],\n _colors: ReturnType<typeof createColors>,\n): string[] {\n const maxLabelLen = Math.max(...stats.map((s) => s.label.length));\n\n return stats.map((stat) => {\n const padding = ' '.repeat(maxLabelLen - stat.label.length + 1);\n return ` ${stat.label}:${padding}${stat.value}`;\n });\n}\n\n/**\n * Format multi-line warning block.\n * Returns array of lines for a warning with headline, details, and suggestion.\n *\n * @param headline - Warning headline (shown with ⚠ icon)\n * @param details - Detail lines\n * @param suggestion - Optional suggestion with command (bolded)\n * @param colors - Color functions\n * @returns Array of formatted lines\n */\nexport function formatWarningBlock(\n headline: string,\n details: string[],\n suggestion: { text: string; command: string } | undefined,\n colors: ReturnType<typeof createColors>,\n): string[] {\n const lines: string[] = [];\n\n // Headline with warning icon\n lines.push(`${colors.warn(ICONS.WARN)} ${headline}`);\n\n // Detail lines\n for (const detail of details) {\n lines.push(detail);\n }\n\n // Suggestion with bolded command\n if (suggestion) {\n lines.push(`${suggestion.text} ${colors.bold(suggestion.command)}`);\n }\n\n return lines;\n}\n\n/**\n * Format footer with command suggestions.\n * Returns a formatted string like \"Use 'tbd stats' for statistics, 'tbd doctor' for health checks.\"\n *\n * @param suggestions - Array of {command, description} pairs\n * @param colors - Color functions\n * @returns Formatted footer string\n */\nexport function formatFooter(\n suggestions: { command: string; description: string }[],\n colors: ReturnType<typeof createColors>,\n): string {\n if (suggestions.length === 0) {\n return '';\n }\n\n const parts = suggestions.map((s) => `${colors.bold(`'${s.command}'`)} for ${s.description}`);\n\n if (parts.length === 1) {\n return `Use ${parts[0]}.`;\n }\n\n return `Use ${parts.join(', ')}.`;\n}\n","/**\n * CLI error types and helpers for structured error handling.\n *\n * See: research-modern-typescript-cli-patterns.md#3-base-command-pattern\n */\n\nimport { findTbdRoot } from '../../file/config.js';\n\n/**\n * Find and return the tbd repository root, starting from the given directory.\n * Walks up the directory tree to find .tbd/.\n *\n * @param cwd - Working directory to start from (defaults to process.cwd())\n * @returns The tbd repository root path\n * @throws NotInitializedError if tbd is not initialized in any parent directory\n */\nexport async function requireInit(cwd: string = process.cwd()): Promise<string> {\n const tbdRoot = await findTbdRoot(cwd);\n if (!tbdRoot) {\n throw new NotInitializedError();\n }\n return tbdRoot;\n}\n\n/**\n * Base CLI error. Thrown for operational errors that should exit\n * with a specific code but don't need stack traces.\n */\nexport class CLIError extends Error {\n constructor(\n message: string,\n public exitCode = 1,\n ) {\n super(message);\n this.name = 'CLIError';\n }\n}\n\n/**\n * Validation error for usage/argument issues.\n * Exit code 2 follows Unix convention.\n */\nexport class ValidationError extends CLIError {\n constructor(message: string) {\n super(message, 2);\n this.name = 'ValidationError';\n }\n}\n\n/**\n * Not initialized error - tbd repository not found.\n * Uses the stable error message defined in design spec §5.6.\n */\nexport class NotInitializedError extends CLIError {\n constructor(message = \"Not a tbd repository (run 'tbd setup --auto --prefix=<name>' first)\") {\n super(message, 1);\n this.name = 'NotInitializedError';\n }\n}\n\n/**\n * Entity not found error (issue, config, etc.).\n */\nexport class NotFoundError extends CLIError {\n constructor(entityType: string, id: string) {\n super(`${entityType} not found: ${id}`, 1);\n this.name = 'NotFoundError';\n }\n}\n\n/**\n * Sync/conflict error.\n */\nexport class SyncError extends CLIError {\n constructor(message: string) {\n super(message, 1);\n this.name = 'SyncError';\n }\n}\n\n/**\n * Worktree missing error - the data-sync-worktree directory doesn't exist.\n * This indicates the worktree was never created or was deleted.\n * See: tbd-design.md §2.3.6 Worktree Error Classes\n */\nexport class WorktreeMissingError extends CLIError {\n constructor(\n message = \"Worktree not found at .tbd/data-sync-worktree/. Run 'tbd doctor --fix' to repair.\",\n ) {\n super(message, 1);\n this.name = 'WorktreeMissingError';\n }\n}\n\n/**\n * Worktree corrupted error - the worktree exists but is invalid.\n * This can occur when the .git file is missing or points to an invalid location.\n * See: tbd-design.md §2.3.6 Worktree Error Classes\n */\nexport class WorktreeCorruptedError extends CLIError {\n constructor(\n message = \"Worktree at .tbd/data-sync-worktree/ is corrupted. Run 'tbd doctor --fix' to repair.\",\n ) {\n super(message, 1);\n this.name = 'WorktreeCorruptedError';\n }\n}\n\n/**\n * Sync branch error - issues with the tbd-sync branch.\n * This can indicate the branch is missing, orphaned, or has diverged.\n * See: tbd-design.md §2.3.6 Worktree Error Classes\n */\nexport class SyncBranchError extends CLIError {\n constructor(message: string) {\n super(message, 1);\n this.name = 'SyncBranchError';\n }\n}\n\n/**\n * Classification of sync errors for auto-save/retry decisions.\n * - 'permanent': Error indicates push is blocked (e.g., 403, protected branch).\n * Auto-save to outbox is appropriate.\n * - 'transient': Error is likely temporary (e.g., network timeout).\n * User should retry.\n * - 'unknown': Cannot determine error type.\n * Treat conservatively (suggest retry, mention save option).\n */\nexport type SyncErrorType = 'permanent' | 'transient' | 'unknown';\n\n/**\n * Classify a sync error to determine appropriate recovery action.\n *\n * Used by `tbd sync` to decide whether to:\n * - Auto-save to outbox (permanent failure - push is blocked)\n * - Suggest retry (transient failure - might work next time)\n * - Offer both options (unknown - let user decide)\n *\n * @param error - Error message or Error object from git push\n * @returns Classification of the error type\n */\nexport function classifySyncError(error: Error | string): SyncErrorType {\n const msg = typeof error === 'string' ? error : error.message;\n const lower = msg.toLowerCase();\n\n // Permanent indicators - push is blocked by policy/permissions\n const permanentPatterns = [\n /403/, // HTTP 403 Forbidden\n /forbidden/,\n /permission denied/,\n /401/, // HTTP 401 Unauthorized\n /unauthorized/,\n /protected branch/,\n /remote rejected/,\n /pre-receive hook declined/,\n /push declined/,\n /not allowed to push/,\n ];\n\n for (const pattern of permanentPatterns) {\n if (pattern.test(lower)) return 'permanent';\n }\n\n // Transient indicators - likely temporary, should retry\n const transientPatterns = [\n /timeout/,\n /timed out/,\n /connection refused/,\n /connection reset/,\n /network/,\n /dns/,\n /5\\d\\d/, // HTTP 5xx server errors\n /server error/,\n /temporarily/,\n /try again/,\n /could not resolve/,\n /no route to host/,\n /connection closed/,\n ];\n\n for (const pattern of transientPatterns) {\n if (pattern.test(lower)) return 'transient';\n }\n\n return 'unknown';\n}\n","/**\n * Base command class for CLI handlers.\n *\n * See: research-modern-typescript-cli-patterns.md#3-base-command-pattern\n */\n\nimport type { Command } from 'commander';\n\nimport type { CommandContext, OutputFormat } from './context.js';\nimport { getCommandContext } from './context.js';\nimport { OutputManager } from './output.js';\nimport { CLIError } from './errors.js';\n\n/**\n * Base class for all CLI command handlers.\n * Provides common functionality for context, output, and error handling.\n */\nexport abstract class BaseCommand {\n protected ctx: CommandContext;\n protected output: OutputManager;\n\n constructor(command: Command) {\n this.ctx = getCommandContext(command);\n this.output = new OutputManager(this.ctx);\n }\n\n /**\n * Execute an async action with error handling.\n * Catches errors and formats them consistently.\n * Preserves original error as `cause` for debugging.\n */\n protected async execute<T>(action: () => Promise<T>, errorMessage: string): Promise<T> {\n try {\n return await action();\n } catch (error) {\n if (error instanceof CLIError) {\n this.output.error(error.message);\n throw error;\n }\n const originalError = error instanceof Error ? error : undefined;\n const detail = originalError?.message;\n const fullMessage =\n detail && detail !== errorMessage ? `${errorMessage}: ${detail}` : errorMessage;\n this.output.error(fullMessage, originalError);\n const wrapped = new CLIError(fullMessage);\n if (originalError) {\n wrapped.cause = originalError;\n }\n throw wrapped;\n }\n }\n\n /**\n * Check if dry-run mode is enabled and log the action.\n * Returns true if in dry-run mode (caller should skip the actual action).\n */\n protected checkDryRun(message: string, details?: object): boolean {\n if (this.ctx.dryRun) {\n this.output.dryRun(message, details);\n return true;\n }\n return false;\n }\n\n /**\n * Abstract method that subclasses must implement.\n * Signature varies by command (positional args + options object).\n */\n abstract run(...args: unknown[]): Promise<void>;\n}\n\n/**\n * Helper to get output format from context.\n */\nexport function getOutputFormat(ctx: CommandContext): OutputFormat {\n return ctx.json ? 'json' : 'text';\n}\n","/**\n * File system utility functions.\n */\n\nimport { access } from 'node:fs/promises';\n\n/**\n * Check if a path exists on the filesystem.\n */\nexport async function pathExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n","/**\n * Gitignore file utilities for idempotent pattern management.\n */\n\nimport { readFile } from 'node:fs/promises';\nimport { writeFile } from 'atomically';\nimport { pathExists } from './file-utils.js';\n\n/**\n * Check if a pattern exists in gitignore content.\n * Matches exact lines, normalizing trailing slashes for directories.\n */\nexport function hasGitignorePattern(content: string, pattern: string): boolean {\n const normalizedPattern = pattern.replace(/\\/+$/, '');\n const lines = content.split('\\n');\n\n for (const line of lines) {\n const trimmed = line.trim();\n // Skip comments and empty lines\n if (trimmed === '' || trimmed.startsWith('#')) continue;\n\n const normalizedLine = trimmed.replace(/\\/+$/, '');\n if (normalizedLine === normalizedPattern) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Ensure patterns exist in a .gitignore file.\n * Creates file if missing. Appends only missing patterns.\n * Always uses atomic write.\n *\n * @param gitignorePath - Path to .gitignore file\n * @param patterns - Patterns to ensure exist (can include comments)\n * @param header - Optional header comment for new patterns section\n */\nexport async function ensureGitignorePatterns(\n gitignorePath: string,\n patterns: string[],\n header?: string,\n): Promise<{ added: string[]; skipped: string[]; created: boolean }> {\n // Read existing content or empty string\n let content = '';\n let created = false;\n\n if (await pathExists(gitignorePath)) {\n content = await readFile(gitignorePath, 'utf-8');\n } else {\n created = true;\n }\n\n // Group patterns into entries: each entry is leading comments/blanks followed by\n // one or more actual patterns. Comments are only included if their associated\n // pattern(s) are new, preventing orphaned comment duplication on upgrades.\n const entries: { preamble: string[]; patterns: string[] }[] = [];\n let currentPreamble: string[] = [];\n\n for (const pattern of patterns) {\n const trimmed = pattern.trim();\n if (trimmed === '' || trimmed.startsWith('#')) {\n currentPreamble.push(pattern);\n } else {\n entries.push({ preamble: currentPreamble, patterns: [trimmed] });\n currentPreamble = [];\n }\n }\n // Trailing comments/blanks (no associated pattern) form their own entry\n if (currentPreamble.length > 0) {\n entries.push({ preamble: currentPreamble, patterns: [] });\n }\n\n // Determine which entries have new patterns to add\n const added: string[] = [];\n const skipped: string[] = [];\n const linesToAppend: string[] = [];\n\n for (const entry of entries) {\n const newPatterns = entry.patterns.filter((p) => !hasGitignorePattern(content, p));\n const existingPatterns = entry.patterns.filter((p) => hasGitignorePattern(content, p));\n skipped.push(...existingPatterns);\n\n if (newPatterns.length > 0) {\n added.push(...newPatterns);\n // Include preamble comments only when their associated pattern is new\n linesToAppend.push(...entry.preamble, ...newPatterns);\n }\n }\n\n // If nothing new to add, return early\n if (added.length === 0) {\n return { added: [], skipped, created: false };\n }\n\n // Build new content\n let newContent = content;\n\n // Ensure content ends with newline before appending\n if (newContent && !newContent.endsWith('\\n')) {\n newContent += '\\n';\n }\n\n // Add blank line separator if file has existing content\n if (newContent && !newContent.endsWith('\\n\\n')) {\n newContent += '\\n';\n }\n\n // Add header if provided\n if (header) {\n newContent += header + '\\n';\n }\n\n // Add only entries with new patterns\n newContent += linesToAppend.join('\\n') + '\\n';\n\n // Atomic write\n await writeFile(gitignorePath, newContent);\n\n return { added, skipped, created };\n}\n","/**\n * Prefix validation and beads prefix extraction module.\n *\n * Provides functions to validate prefixes and extract prefix from beads config.\n * Used by setup commands to validate user-provided prefixes and migrate from beads.\n */\n\nimport { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { parse as parseYaml } from 'yaml';\n\n/** Maximum length for a valid prefix */\nconst MAX_PREFIX_LENGTH = 20;\n\n/** Minimum length for a valid prefix */\nconst MIN_PREFIX_LENGTH = 1;\n\n/** Recommended minimum length */\nconst RECOMMENDED_MIN_LENGTH = 2;\n\n/** Recommended maximum length */\nconst RECOMMENDED_MAX_LENGTH = 8;\n\n/**\n * Normalize a prefix string.\n * - Lowercases\n * - Removes invalid characters (keeps alphanumeric, dot, underscore)\n * - Truncates to max length\n */\nexport function normalizePrefix(s: string): string {\n if (!s) return '';\n\n // Lowercase and remove invalid characters (keep alphanumeric, dot, underscore)\n const normalized = s.toLowerCase().replace(/[^a-z0-9._]/g, '');\n\n // Truncate to max length\n return normalized.slice(0, MAX_PREFIX_LENGTH);\n}\n\n/**\n * Check if a prefix is valid (hard rules, always enforced).\n * - Must be 1-20 characters\n * - Must start with a letter (a-z)\n * - Must end with alphanumeric (a-z0-9)\n * - Middle characters can be alphanumeric, dot, or underscore\n * - No dashes allowed (breaks ID syntax)\n */\nexport function isValidPrefix(s: string): boolean {\n if (!s) return false;\n if (s.length < MIN_PREFIX_LENGTH || s.length > MAX_PREFIX_LENGTH) return false;\n\n // First char must be a letter\n if (!/^[a-z]/.test(s)) return false;\n\n // Last char must be alphanumeric (for length > 1)\n if (s.length > 1 && !/[a-z0-9]$/.test(s)) return false;\n\n // All chars must be alphanumeric, dot, or underscore (no dashes!)\n return /^[a-z][a-z0-9._]*$/.test(s);\n}\n\n/**\n * Check if a prefix follows recommended format (soft rules).\n * - Must be 2-8 characters\n * - Must be alphabetic only (a-z)\n *\n * Prefixes that don't match this can still be used with --force.\n */\nexport function isRecommendedPrefix(s: string): boolean {\n if (!s) return false;\n if (s.length < RECOMMENDED_MIN_LENGTH || s.length > RECOMMENDED_MAX_LENGTH) return false;\n return /^[a-z]+$/.test(s);\n}\n\n/**\n * Get prefix from existing beads config.\n *\n * Looks for .beads/config.yaml and extracts display.id_prefix\n *\n * @param cwd Current working directory\n * @returns The beads prefix, or null if not found\n */\nexport async function getBeadsPrefix(cwd: string): Promise<string | null> {\n try {\n const configPath = join(cwd, '.beads', 'config.yaml');\n const content = await readFile(configPath, 'utf-8');\n const config = parseYaml(content) as Record<string, unknown>;\n\n const display = config?.display as Record<string, unknown> | undefined;\n const prefix = display?.id_prefix;\n\n if (typeof prefix === 'string' && isValidPrefix(prefix)) {\n return prefix;\n }\n\n return null;\n } catch {\n return null;\n }\n}\n","/**\n * Time utilities for tbd.\n *\n * All timestamps should be ISO 8601 UTC format with Z suffix.\n * Use these functions instead of raw Date calls for consistency.\n */\n\n/**\n * Get current time as ISO 8601 UTC string.\n * Use this when recording timestamps for new/updated issues.\n */\nexport function now(): string {\n return new Date().toISOString();\n}\n\n/**\n * Get current time as Date object.\n * Use this when you need date arithmetic or comparisons.\n */\nexport function nowDate(): Date {\n return new Date();\n}\n\n/**\n * Parse a timestamp string to Date object.\n * Returns null if the timestamp is invalid.\n */\nexport function parseDate(timestamp: string | undefined | null): Date | null {\n if (!timestamp) return null;\n try {\n const date = new Date(timestamp);\n if (isNaN(date.getTime())) return null;\n return date;\n } catch {\n return null;\n }\n}\n\n/**\n * Normalize any timestamp to ISO 8601 UTC format with Z suffix.\n * Handles various formats including timezone offsets like -08:00.\n * Returns null if the timestamp is invalid or missing.\n */\nexport function normalizeTimestamp(timestamp: string | undefined | null): string | null {\n const date = parseDate(timestamp);\n return date?.toISOString() ?? null;\n}\n\n/**\n * Check if a timestamp string is valid.\n */\nexport function isValidTimestamp(timestamp: string | undefined | null): boolean {\n return parseDate(timestamp) !== null;\n}\n\n/**\n * Get current time as a filename-safe timestamp.\n * Replaces colons with dashes and removes milliseconds for filesystem compatibility.\n */\nexport function nowFilenameTimestamp(): string {\n return new Date()\n .toISOString()\n .replace(/:/g, '-')\n .replace(/\\.\\d+Z$/, 'Z');\n}\n","/**\n * Git utilities for sync operations.\n *\n * Provides:\n * - Isolated index operations (protect user's staging area)\n * - Field-level merge algorithm\n * - Push retry with exponential backoff\n *\n * See: tbd-design.md §3.3 Sync Operations\n */\n\nimport { execFile } from 'node:child_process';\nimport { mkdir } from 'node:fs/promises';\nimport { promisify } from 'node:util';\nimport { join } from 'node:path';\n\nimport { writeFile } from 'atomically';\n\nimport type { Issue } from '../lib/types.js';\nimport { now, nowFilenameTimestamp } from '../utils/time-utils.js';\n\nconst execFileAsync = promisify(execFile);\n\n/**\n * Maximum buffer size for git command output.\n *\n * Node.js child_process.execFile() defaults to 1MB (1024 * 1024 bytes).\n * When exceeded, the child process is terminated with \"stdout maxBuffer length exceeded\".\n * Git commands like push/fetch with verbose output or diff on large changesets can exceed 1MB.\n *\n * See: https://nodejs.org/api/child_process.html#child_processexecfilefile-args-options-callback\n */\nconst GIT_MAX_BUFFER = 50 * 1024 * 1024; // 50MB\n\n/**\n * Execute a git command and return stdout.\n * Uses execFile for security - prevents shell injection attacks.\n */\nexport async function git(...args: string[]): Promise<string> {\n const { stdout } = await execFileAsync('git', args, { maxBuffer: GIT_MAX_BUFFER });\n return stdout.trim();\n}\n\n// =============================================================================\n// Git Version Detection\n// See: plan spec §3.4 Git Integration Architecture\n// =============================================================================\n\n/**\n * Minimum Git version required.\n * Git 2.42 (August 2023) introduced `git worktree add --orphan` which tbd requires.\n */\nexport const MIN_GIT_VERSION = '2.42.0';\n\n/**\n * Parsed Git version information.\n */\n/**\n * Find the git repository root directory.\n * Uses `git rev-parse --show-toplevel` which returns the absolute path.\n *\n * @param cwd - Directory to start from (default: process.cwd())\n * @returns Absolute path to the git root, or null if not in a git repo\n */\nexport async function findGitRoot(cwd?: string): Promise<string | null> {\n try {\n const args = ['rev-parse', '--show-toplevel'];\n if (cwd) {\n args.unshift('-C', cwd);\n }\n return await git(...args);\n } catch {\n return null;\n }\n}\n\n/**\n * Check if the current directory is inside a git repository.\n */\nexport async function isInGitRepo(cwd?: string): Promise<boolean> {\n try {\n const args = ['rev-parse', '--is-inside-work-tree'];\n if (cwd) {\n args.unshift('-C', cwd);\n }\n const result = await git(...args);\n return result === 'true';\n } catch {\n return false;\n }\n}\n\nexport interface GitVersion {\n major: number;\n minor: number;\n patch: number;\n raw: string;\n}\n\n/**\n * Get the installed Git version.\n *\n * @returns Parsed version information\n * @throws Error if git is not installed or version cannot be parsed\n */\nexport async function getGitVersion(): Promise<GitVersion> {\n const versionOutput = await git('--version');\n // Output format: \"git version 2.42.0\" or \"git version 2.42.0.windows.1\"\n const versionRegex = /git version (\\d+)\\.(\\d+)\\.(\\d+)/;\n const match = versionRegex.exec(versionOutput);\n\n const major = match?.[1];\n const minor = match?.[2];\n const patch = match?.[3];\n\n if (!major || !minor || !patch) {\n throw new Error(`Unable to parse git version from: ${versionOutput}`);\n }\n\n return {\n major: parseInt(major, 10),\n minor: parseInt(minor, 10),\n patch: parseInt(patch, 10),\n raw: versionOutput,\n };\n}\n\n/**\n * Compare two version strings.\n *\n * @returns -1 if a < b, 0 if a === b, 1 if a > b\n */\nexport function compareVersions(a: GitVersion, b: string): number {\n const parts = b.split('.');\n const bMajor = parseInt(parts[0] ?? '0', 10);\n const bMinor = parseInt(parts[1] ?? '0', 10);\n const bPatch = parseInt(parts[2] ?? '0', 10);\n\n if (a.major !== bMajor) return a.major < bMajor ? -1 : 1;\n if (a.minor !== bMinor) return a.minor < bMinor ? -1 : 1;\n if (a.patch !== bPatch) return a.patch < bPatch ? -1 : 1;\n return 0;\n}\n\n/**\n * Check if the installed Git version meets minimum requirements.\n *\n * @returns Object with version info and whether it meets requirements\n * @throws Error with upgrade instructions if Git version is too old\n */\nexport async function checkGitVersion(): Promise<{\n version: GitVersion;\n supported: boolean;\n}> {\n const version = await getGitVersion();\n const supported = compareVersions(version, MIN_GIT_VERSION) >= 0;\n return { version, supported };\n}\n\n/**\n * Require minimum Git version, throwing an error if not met.\n */\nexport async function requireGitVersion(): Promise<GitVersion> {\n const { version, supported } = await checkGitVersion();\n if (!supported) {\n throw new Error(getUpgradeInstructions(version));\n }\n return version;\n}\n\n/**\n * Get platform-specific upgrade instructions.\n * Points to official documentation rather than detailed commands for easier maintenance.\n */\nfunction getUpgradeInstructions(currentVersion: GitVersion): string {\n const platform = process.platform;\n const versionStr = `${currentVersion.major}.${currentVersion.minor}.${currentVersion.patch}`;\n\n let upgradeUrl: string;\n switch (platform) {\n case 'darwin':\n upgradeUrl = 'https://git-scm.com/download/mac';\n break;\n case 'linux':\n upgradeUrl = 'https://git-scm.com/download/linux';\n break;\n case 'win32':\n upgradeUrl = 'https://git-scm.com/download/win';\n break;\n default:\n upgradeUrl = 'https://git-scm.com/downloads';\n }\n\n return `Git ${versionStr} detected. Git ${MIN_GIT_VERSION}+ required for tbd.\\nUpgrade: ${upgradeUrl}`;\n}\n\n/**\n * Execute a git command with isolated index.\n * This protects the user's staging area during sync operations.\n *\n * See: tbd-design.md §3.3.2 Writing to Sync Branch\n */\nexport async function withIsolatedIndex<T>(fn: () => Promise<T>): Promise<T> {\n const gitDir = await git('rev-parse', '--git-dir');\n const isolatedIndex = join(gitDir, 'tbd-index');\n const originalIndex = process.env.GIT_INDEX_FILE;\n\n try {\n process.env.GIT_INDEX_FILE = isolatedIndex;\n return await fn();\n } finally {\n if (originalIndex) {\n process.env.GIT_INDEX_FILE = originalIndex;\n } else {\n delete process.env.GIT_INDEX_FILE;\n }\n }\n}\n\n/**\n * Commit changes to sync branch using isolated index.\n */\nexport async function commitToSyncBranch(\n syncBranch: string,\n message: string,\n files: string[],\n): Promise<string> {\n return withIsolatedIndex(async () => {\n // Try to read existing tree from sync branch\n try {\n await git('read-tree', syncBranch);\n } catch {\n // Branch doesn't exist - start fresh\n }\n\n // Add changed files to index\n for (const file of files) {\n await git('add', file);\n }\n\n // Write tree object\n const tree = await git('write-tree');\n\n // Get parent commit if exists\n let parent: string | null = null;\n try {\n parent = await git('rev-parse', syncBranch);\n } catch {\n // No parent - orphan commit\n }\n\n // Create commit\n // Note: With execFile, we pass the message directly without shell quoting\n const commitArgs = ['commit-tree', tree, '-m', message];\n if (parent) {\n commitArgs.push('-p', parent);\n }\n\n const commit = await git(...commitArgs);\n\n // Update branch ref\n await git('update-ref', `refs/heads/${syncBranch}`, commit);\n\n return commit;\n });\n}\n\n/**\n * Field-level merge strategy types.\n */\ntype MergeStrategy = 'lww' | 'union' | 'max' | 'immutable';\n\n/**\n * Field-level merge strategies for Issue fields.\n * See: tbd-design.md §3.5 Merge Rules\n */\nconst FIELD_STRATEGIES: Record<keyof Issue, MergeStrategy> = {\n // Immutable - never change after creation\n type: 'immutable',\n id: 'immutable',\n created_at: 'immutable',\n created_by: 'immutable',\n\n // LWW (Last-Write-Wins) - compare updated_at\n version: 'max',\n kind: 'lww',\n title: 'lww',\n description: 'lww',\n notes: 'lww',\n status: 'lww',\n priority: 'lww',\n assignee: 'lww',\n parent_id: 'lww',\n child_order_hints: 'lww',\n updated_at: 'max',\n closed_at: 'lww',\n close_reason: 'lww',\n due_date: 'lww',\n deferred_until: 'lww',\n spec_path: 'lww',\n\n // Union - combine arrays, deduplicate\n labels: 'union',\n dependencies: 'union',\n\n // Extensions - LWW for whole object\n extensions: 'lww',\n};\n\n/**\n * Conflict entry for attic storage.\n */\nexport interface ConflictEntry {\n issue_id: string;\n field: string;\n timestamp: string;\n lost_value: unknown;\n winner_value: unknown;\n local_version: number;\n remote_version: number;\n resolution: 'lww' | 'union' | 'manual';\n}\n\n/**\n * Merge result with merged issue and any conflicts.\n */\nexport interface MergeResult {\n merged: Issue;\n conflicts: ConflictEntry[];\n}\n\n/**\n * Fields that are metadata-only and should be ignored when checking\n * for substantive changes between issues. These fields change on every\n * merge operation and don't represent meaningful content changes.\n */\nconst METADATA_ONLY_FIELDS: ReadonlySet<keyof Issue> = new Set(['version', 'updated_at']);\n\n/**\n * Check if two issues are substantively equal, ignoring metadata fields\n * (version, updated_at) that change on every merge.\n *\n * This prevents trivial timestamp/version bumps from being treated as\n * real changes during outbox saves and sync operations.\n */\nexport function issuesSubstantivelyEqual(a: Issue, b: Issue): boolean {\n for (const key of Object.keys(FIELD_STRATEGIES) as (keyof Issue)[]) {\n if (METADATA_ONLY_FIELDS.has(key)) continue;\n if (!deepEqual(a[key], b[key])) return false;\n }\n return true;\n}\n\n/**\n * Deep equality check for values.\n */\nexport function deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a === null || b === null) return a === b;\n if (typeof a !== typeof b) return false;\n\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n return a.every((item, i) => deepEqual(item, b[i]));\n }\n\n if (typeof a === 'object' && typeof b === 'object') {\n const aKeys = Object.keys(a);\n const bKeys = Object.keys(b);\n if (aKeys.length !== bKeys.length) return false;\n return aKeys.every((key) =>\n deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]),\n );\n }\n\n return false;\n}\n\n/**\n * Union arrays with deduplication.\n */\nfunction unionArrays<T>(a: T[], b: T[]): T[] {\n const result = [...a];\n for (const item of b) {\n if (!result.some((existing) => deepEqual(existing, item))) {\n result.push(item);\n }\n }\n return result;\n}\n\n/**\n * Create an attic entry for a conflict.\n */\nfunction createConflictEntry(\n issueId: string,\n field: string,\n lostValue: unknown,\n winnerValue: unknown,\n localVersion: number,\n remoteVersion: number,\n resolution: 'lww' | 'union' | 'manual',\n): ConflictEntry {\n const timestamp = nowFilenameTimestamp();\n\n return {\n issue_id: issueId,\n field,\n timestamp,\n lost_value: lostValue,\n winner_value: winnerValue,\n local_version: localVersion,\n remote_version: remoteVersion,\n resolution,\n };\n}\n\n/**\n * Three-way merge algorithm for issues.\n * See: tbd-design.md §3.4 Conflict Detection and Resolution\n *\n * @param base - Common ancestor (null if new issue)\n * @param local - Local version\n * @param remote - Remote version\n */\nexport function mergeIssues(base: Issue | null, local: Issue, remote: Issue): MergeResult {\n const conflicts: ConflictEntry[] = [];\n\n // If no base, check if these are versions of the same issue or independent creations\n if (!base) {\n const localTime = new Date(local.created_at).getTime();\n const remoteTime = new Date(remote.created_at).getTime();\n\n // Same created_at means same original creation - these are versions of the same issue\n // Use field-by-field merge instead of whole_issue conflict\n if (localTime === remoteTime) {\n // Use the one with the lower version as a synthetic base\n // This forces field-by-field comparison\n base = local.version <= remote.version ? local : remote;\n // Fall through to field-by-field merge below\n } else {\n // Different creation times - truly independent issues\n // Use whole_issue conflict (original behavior)\n if (localTime < remoteTime) {\n // Local was created first - it wins\n if (!deepEqual(local, remote)) {\n conflicts.push(\n createConflictEntry(\n remote.id,\n 'whole_issue',\n remote,\n local,\n remote.version,\n local.version,\n 'lww',\n ),\n );\n }\n return { merged: local, conflicts };\n } else {\n // Remote was created first - it wins\n if (!deepEqual(local, remote)) {\n conflicts.push(\n createConflictEntry(\n local.id,\n 'whole_issue',\n local,\n remote,\n local.version,\n remote.version,\n 'lww',\n ),\n );\n }\n return { merged: remote, conflicts };\n }\n }\n }\n\n // Field-by-field merge\n const merged = { ...base } as Issue;\n\n for (const [field, strategy] of Object.entries(FIELD_STRATEGIES)) {\n const key = field as keyof Issue;\n const localVal = local[key];\n const remoteVal = remote[key];\n const baseVal = base[key];\n\n // Skip if both unchanged from base\n if (deepEqual(localVal, baseVal) && deepEqual(remoteVal, baseVal)) {\n continue;\n }\n\n // Only one changed - take changed value\n if (deepEqual(localVal, baseVal)) {\n (merged as Record<string, unknown>)[key] = remoteVal;\n continue;\n }\n if (deepEqual(remoteVal, baseVal)) {\n (merged as Record<string, unknown>)[key] = localVal;\n continue;\n }\n\n // Both changed - apply strategy\n switch (strategy) {\n case 'immutable':\n // Keep base value (shouldn't change)\n break;\n\n case 'lww': {\n // Only create conflicts when values actually differ (data is discarded)\n // See: tbd-design.md §3.5 \"Attic entries are created only when a merge strategy discards data\"\n if (deepEqual(localVal, remoteVal)) {\n // Values are identical - no conflict, use either one\n (merged as Record<string, unknown>)[key] = localVal;\n break;\n }\n\n // Values differ - apply LWW based on updated_at timestamps\n const localTime = new Date(local.updated_at).getTime();\n const remoteTime = new Date(remote.updated_at).getTime();\n\n if (localTime >= remoteTime) {\n (merged as Record<string, unknown>)[key] = localVal;\n conflicts.push(\n createConflictEntry(\n local.id,\n field,\n remoteVal,\n localVal,\n local.version,\n remote.version,\n 'lww',\n ),\n );\n } else {\n (merged as Record<string, unknown>)[key] = remoteVal;\n conflicts.push(\n createConflictEntry(\n local.id,\n field,\n localVal,\n remoteVal,\n local.version,\n remote.version,\n 'lww',\n ),\n );\n }\n break;\n }\n\n case 'union':\n // Combine arrays and deduplicate\n (merged as Record<string, unknown>)[key] = unionArrays(\n localVal as unknown[],\n remoteVal as unknown[],\n );\n break;\n\n case 'max':\n // Take maximum value\n (merged as Record<string, unknown>)[key] = Math.max(\n localVal as number,\n remoteVal as number,\n );\n break;\n }\n }\n\n // Check if the merge produced any substantive changes compared to the\n // highest-versioned input. If not, return that input as-is to avoid\n // gratuitous version/timestamp bumps that cause bulk outbox saves.\n const latest = local.version >= remote.version ? local : remote;\n if (issuesSubstantivelyEqual(merged, latest)) {\n // No substantive change - return the latest version without bumping\n return { merged: { ...latest }, conflicts };\n }\n\n // Actual substantive changes from merge - bump version\n merged.version = Math.max(local.version, remote.version) + 1;\n merged.updated_at = now();\n\n return { merged, conflicts };\n}\n\n/**\n * Maximum retry attempts for push operations.\n */\nconst MAX_PUSH_RETRIES = 3;\n\n/**\n * Check if error is a non-fast-forward rejection.\n */\nfunction isNonFastForward(error: unknown): boolean {\n const msg = error instanceof Error ? error.message : String(error);\n return (\n msg.includes('non-fast-forward') || msg.includes('fetch first') || msg.includes('rejected')\n );\n}\n\n/**\n * Push result with retry information.\n */\nexport interface PushResult {\n success: boolean;\n attempt: number;\n conflicts?: ConflictEntry[];\n error?: string;\n}\n\n/**\n * Push with retry and merge on conflict.\n * See: tbd-design.md §3.3.3 Sync Algorithm\n *\n * @param syncBranch - The sync branch name\n * @param remote - The remote name\n * @param onMergeNeeded - Callback to merge remote changes\n * @param baseDir - Repository root directory (uses process.cwd() if not provided)\n */\nexport async function pushWithRetry(\n syncBranch: string,\n remote: string,\n onMergeNeeded: () => Promise<ConflictEntry[]>,\n baseDir?: string,\n): Promise<PushResult> {\n // Use explicit refspec to avoid ambiguity with tags or other refs\n const refspec = `refs/heads/${syncBranch}:refs/heads/${syncBranch}`;\n // Build -C prefix args when baseDir is provided\n const dirArgs = baseDir ? ['-C', baseDir] : [];\n\n for (let attempt = 1; attempt <= MAX_PUSH_RETRIES; attempt++) {\n try {\n // Try to push\n await git(...dirArgs, 'push', remote, refspec);\n return { success: true, attempt };\n } catch (error) {\n if (!isNonFastForward(error)) {\n // Unrecoverable error\n return {\n success: false,\n attempt,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n\n if (attempt === MAX_PUSH_RETRIES) {\n return {\n success: false,\n attempt,\n error: `Push failed after ${MAX_PUSH_RETRIES} attempts. Remote has conflicting changes.`,\n };\n }\n\n // Fetch and merge remote changes\n await git(...dirArgs, 'fetch', remote, syncBranch);\n const conflicts = await onMergeNeeded();\n\n if (conflicts.length > 0) {\n // Return conflicts but continue trying\n return { success: false, attempt, conflicts };\n }\n\n // Loop to retry push\n }\n }\n\n return { success: false, attempt: MAX_PUSH_RETRIES, error: 'Unexpected error in push retry' };\n}\n\n/**\n * Get the current branch name.\n */\nexport async function getCurrentBranch(): Promise<string> {\n return git('rev-parse', '--abbrev-ref', 'HEAD');\n}\n\n/**\n * Check if a branch exists locally.\n */\nexport async function branchExists(branch: string): Promise<boolean> {\n try {\n await git('rev-parse', '--verify', `refs/heads/${branch}`);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Check if a remote branch exists.\n */\nexport async function remoteBranchExists(remote: string, branch: string): Promise<boolean> {\n try {\n await git('ls-remote', '--exit-code', remote, `refs/heads/${branch}`);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Get the remote URL.\n */\nexport async function getRemoteUrl(remote: string): Promise<string | null> {\n try {\n return await git('remote', 'get-url', remote);\n } catch {\n return null;\n }\n}\n\n// =============================================================================\n// Worktree Management\n// See: tbd-design.md §2.3 Hidden Worktree Model\n// =============================================================================\n\nimport { access, rm, cp } from 'node:fs/promises';\nimport {\n WORKTREE_DIR,\n WORKTREE_DIR_NAME,\n TBD_DIR,\n DATA_SYNC_DIR_NAME,\n SYNC_BRANCH,\n} from '../lib/paths.js';\n\n/**\n * Check if the hidden worktree exists and is valid.\n */\nexport async function worktreeExists(baseDir: string): Promise<boolean> {\n const worktreePath = join(baseDir, WORKTREE_DIR);\n try {\n await access(worktreePath);\n // Also verify it's a valid git worktree by checking for .git file\n await access(join(worktreePath, '.git'));\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Worktree health status values.\n * See: tbd-design.md §2.3.4 Worktree Health States\n */\nexport type WorktreeStatus = 'valid' | 'missing' | 'prunable' | 'corrupted';\n\n/**\n * Worktree health status.\n */\nexport interface WorktreeHealth {\n /** Whether the worktree directory exists on disk */\n exists: boolean;\n /** Whether the worktree is valid and functional */\n valid: boolean;\n /** Detailed status: valid, missing, prunable, or corrupted */\n status: WorktreeStatus;\n /** The branch checked out in the worktree */\n branch: string | null;\n /** The commit HEAD points to */\n commit: string | null;\n /** Error message if status is not 'valid' */\n error?: string;\n}\n\n/**\n * Check worktree health and return status.\n * See: tbd-design.md §2.3 Worktree Lifecycle\n * See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md §3\n */\nexport async function checkWorktreeHealth(baseDir: string): Promise<WorktreeHealth> {\n const worktreePath = join(baseDir, WORKTREE_DIR);\n\n // First check if git reports the worktree as prunable\n // This catches the case where worktree directory was deleted but git still tracks it\n try {\n const worktreeList = await git('-C', baseDir, 'worktree', 'list', '--porcelain');\n\n // Check if our worktree path appears in the list as prunable\n const lines = worktreeList.split('\\n');\n let foundWorktree = false;\n let isPrunable = false;\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n // Check if this entry is for our worktree path\n if (line?.startsWith('worktree ') && line.includes(WORKTREE_DIR_NAME)) {\n foundWorktree = true;\n // Look for prunable marker in subsequent lines until next worktree entry\n for (let j = i + 1; j < lines.length && !lines[j]?.startsWith('worktree '); j++) {\n if (lines[j]?.startsWith('prunable')) {\n isPrunable = true;\n break;\n }\n }\n break;\n }\n }\n\n if (isPrunable) {\n return {\n exists: false,\n valid: false,\n status: 'prunable',\n branch: null,\n commit: null,\n error: 'Worktree directory was deleted but git still tracks it. Run: git worktree prune',\n };\n }\n\n // If git doesn't know about the worktree, check if directory exists\n if (!foundWorktree) {\n try {\n await access(worktreePath);\n // Directory exists but git doesn't know about it - corrupted\n return {\n exists: true,\n valid: false,\n status: 'corrupted',\n branch: null,\n commit: null,\n error: 'Worktree directory exists but is not registered with git',\n };\n } catch {\n // Directory doesn't exist and git doesn't know about it - missing\n return {\n exists: false,\n valid: false,\n status: 'missing',\n branch: null,\n commit: null,\n };\n }\n }\n } catch {\n // git worktree list failed - likely not in a git repo\n // Fall through to directory-based checks\n }\n\n // Check if worktree directory exists\n try {\n await access(worktreePath);\n } catch {\n return {\n exists: false,\n valid: false,\n status: 'missing',\n branch: null,\n commit: null,\n };\n }\n\n // Check if it's a valid git worktree\n try {\n await access(join(worktreePath, '.git'));\n } catch {\n return {\n exists: true,\n valid: false,\n status: 'corrupted',\n branch: null,\n commit: null,\n error: 'Worktree directory exists but is not a valid git worktree (missing .git)',\n };\n }\n\n // Get current commit and branch info\n try {\n const commit = await git('-C', worktreePath, 'rev-parse', 'HEAD');\n let branch: string | null = null;\n\n try {\n // Check if we're on detached HEAD pointing to tbd-sync\n const refName = await git('-C', worktreePath, 'symbolic-ref', '-q', 'HEAD');\n branch = refName.replace('refs/heads/', '');\n } catch {\n // Detached HEAD - expected state\n branch = null;\n }\n\n return { exists: true, valid: true, status: 'valid', branch, commit };\n } catch (error) {\n return {\n exists: true,\n valid: false,\n status: 'corrupted',\n branch: null,\n commit: null,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\n/**\n * Initialize the hidden worktree for the tbd-sync branch.\n * Follows the decision tree from tbd-design.md §2.3.\n *\n * @param baseDir - The base directory of the repository\n * @param remote - The remote name (default: 'origin')\n * @param syncBranch - The sync branch name (default: 'tbd-sync')\n * @returns Path to the worktree or error message\n */\nexport async function initWorktree(\n baseDir: string,\n remote = 'origin',\n syncBranch: string = SYNC_BRANCH,\n): Promise<{ success: boolean; path?: string; created?: boolean; error?: string }> {\n const worktreePath = join(baseDir, WORKTREE_DIR);\n\n // Check if worktree already exists and is valid\n if (await worktreeExists(baseDir)) {\n return { success: true, path: worktreePath, created: false };\n }\n\n // Remove any stale worktree directory\n try {\n await rm(worktreePath, { recursive: true, force: true });\n } catch {\n // Ignore errors - directory might not exist\n }\n\n try {\n // Check if local branch exists\n const localExists = await branchExists(syncBranch);\n if (localExists) {\n // Create worktree on local branch (no --detach, so commits update the branch)\n // Note: Don't use --detach here - we want commits to update tbd-sync branch\n await git('-C', baseDir, 'worktree', 'add', worktreePath, syncBranch);\n return { success: true, path: worktreePath, created: true };\n }\n\n // Check if remote branch exists\n const remoteExists = await remoteBranchExists(remote, syncBranch);\n if (remoteExists) {\n // Fetch and create worktree from remote branch with local tracking branch\n await git('-C', baseDir, 'fetch', remote, syncBranch);\n // Use -b to create local branch tracking remote, not --detach\n // This ensures commits update the local branch which can then be pushed\n await git(\n '-C',\n baseDir,\n 'worktree',\n 'add',\n '-b',\n syncBranch,\n worktreePath,\n `${remote}/${syncBranch}`,\n );\n return { success: true, path: worktreePath, created: true };\n }\n\n // No branch exists - create orphan worktree (requires Git 2.42+)\n // Syntax: git worktree add --orphan -b <branch> <path>\n await requireGitVersion();\n await git('-C', baseDir, 'worktree', 'add', '--orphan', '-b', syncBranch, worktreePath);\n\n // Initialize the data-sync directory structure in the worktree\n const dataSyncPath = join(worktreePath, TBD_DIR, DATA_SYNC_DIR_NAME);\n await mkdir(join(dataSyncPath, 'issues'), { recursive: true });\n await mkdir(join(dataSyncPath, 'mappings'), { recursive: true });\n await mkdir(join(dataSyncPath, 'attic', 'conflicts'), { recursive: true });\n\n // Create initial commit in worktree\n await writeFile(join(dataSyncPath, 'meta.yml'), 'schema_version: 1\\n');\n await writeFile(join(dataSyncPath, 'issues', '.gitkeep'), '');\n await writeFile(join(dataSyncPath, 'mappings', '.gitkeep'), '');\n\n // Add .gitattributes for merge=union on ids.yml so concurrent additions\n // (both sides add non-overlapping keys) merge cleanly instead of conflicting.\n // This must be inside the worktree — .gitattributes on the main branch has\n // no effect on merges happening on the tbd-sync branch.\n await writeFile(join(dataSyncPath, 'mappings', '.gitattributes'), 'ids.yml merge=union\\n');\n\n // Stage and commit the initial structure\n // Use --no-verify to bypass parent repo hooks (lefthook, husky, etc.)\n await git('-C', worktreePath, 'add', '.');\n await git('-C', worktreePath, 'commit', '--no-verify', '-m', 'Initialize tbd-sync branch');\n\n return { success: true, path: worktreePath, created: true };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\n/**\n * Update the hidden worktree to latest sync branch state.\n * Called after sync operations to ensure worktree reflects current state.\n *\n * @param baseDir - The base directory of the repository\n * @param remote - The remote name (default: 'origin')\n * @param syncBranch - The sync branch name (default: 'tbd-sync')\n */\nexport async function updateWorktree(\n baseDir: string,\n remote = 'origin',\n syncBranch: string = SYNC_BRANCH,\n): Promise<{ success: boolean; error?: string }> {\n const worktreePath = join(baseDir, WORKTREE_DIR);\n\n // Ensure worktree exists\n if (!(await worktreeExists(baseDir))) {\n const initResult = await initWorktree(baseDir, remote, syncBranch);\n if (!initResult.success) {\n return { success: false, error: initResult.error };\n }\n }\n\n try {\n // Fetch latest from remote\n try {\n await git('-C', baseDir, 'fetch', remote, syncBranch);\n } catch {\n // Remote fetch may fail if offline - that's ok\n }\n\n // Get the latest commit on the sync branch\n let targetCommit: string;\n try {\n // Try local branch first\n targetCommit = await git('-C', baseDir, 'rev-parse', `refs/heads/${syncBranch}`);\n } catch {\n try {\n // Fall back to remote tracking branch\n targetCommit = await git(\n '-C',\n baseDir,\n 'rev-parse',\n `refs/remotes/${remote}/${syncBranch}`,\n );\n } catch {\n // No remote either - worktree is already at latest\n return { success: true };\n }\n }\n\n // Update worktree to that commit (detached HEAD)\n await git('-C', worktreePath, 'checkout', '--detach', targetCommit);\n\n return { success: true };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\n// =============================================================================\n// Branch Health Checks\n// See: tbd-design.md §2.3 Hidden Worktree Model, plan spec §4b\n// =============================================================================\n\n/**\n * Local branch health status.\n */\nexport interface LocalBranchHealth {\n exists: boolean;\n orphaned: boolean;\n head?: string;\n}\n\n/**\n * Check local sync branch health.\n * See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md §4b\n *\n * @param syncBranch - The sync branch name (default: 'tbd-sync')\n * @returns Health status indicating if branch exists and has commits\n */\nexport async function checkLocalBranchHealth(\n syncBranch: string = SYNC_BRANCH,\n): Promise<LocalBranchHealth> {\n try {\n const head = await git('rev-parse', `refs/heads/${syncBranch}`);\n return { exists: true, orphaned: false, head: head.trim() };\n } catch {\n // Check if branch ref exists but is orphaned (no commits)\n try {\n await git('show-ref', '--verify', `refs/heads/${syncBranch}`);\n return { exists: true, orphaned: true };\n } catch {\n return { exists: false, orphaned: false };\n }\n }\n}\n\n/**\n * Remote branch health status.\n */\nexport interface RemoteBranchHealth {\n exists: boolean;\n diverged: boolean;\n head?: string;\n}\n\n/**\n * Check remote sync branch health.\n * See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md §4b\n *\n * @param remote - The remote name (default: 'origin')\n * @param syncBranch - The sync branch name (default: 'tbd-sync')\n * @returns Health status indicating if remote branch exists and divergence state\n */\nexport async function checkRemoteBranchHealth(\n remote = 'origin',\n syncBranch: string = SYNC_BRANCH,\n): Promise<RemoteBranchHealth> {\n try {\n await git('fetch', remote, syncBranch);\n const head = await git('rev-parse', `refs/remotes/${remote}/${syncBranch}`);\n const remoteHead = head.trim();\n\n // Check for divergence (only if local branch exists)\n let diverged = false;\n try {\n const mergeBase = await git('merge-base', syncBranch, `${remote}/${syncBranch}`);\n const localHead = await git('rev-parse', syncBranch);\n\n // Diverged if merge-base is neither local nor remote HEAD\n diverged = mergeBase.trim() !== localHead.trim() && mergeBase.trim() !== remoteHead;\n } catch {\n // Local branch doesn't exist - can't be diverged\n diverged = false;\n }\n\n return { exists: true, diverged, head: remoteHead };\n } catch {\n return { exists: false, diverged: false };\n }\n}\n\n/**\n * Sync consistency status.\n */\nexport interface SyncConsistency {\n /** Worktree HEAD commit SHA */\n worktreeHead: string;\n /** Local branch HEAD commit SHA */\n localHead: string;\n /** Remote branch HEAD commit SHA */\n remoteHead: string;\n /** Whether worktree HEAD matches local branch HEAD */\n worktreeMatchesLocal: boolean;\n /** Number of commits local is ahead of remote */\n localAhead: number;\n /** Number of commits local is behind remote */\n localBehind: number;\n}\n\n/**\n * Check consistency between worktree, local branch, and remote.\n * See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md §4b\n *\n * @param baseDir - The base directory of the repository\n * @param syncBranch - The sync branch name (default: 'tbd-sync')\n * @param remote - The remote name (default: 'origin')\n * @returns Consistency status with HEAD comparisons and ahead/behind counts\n */\nexport async function checkSyncConsistency(\n baseDir: string,\n syncBranch: string = SYNC_BRANCH,\n remote = 'origin',\n): Promise<SyncConsistency> {\n const worktreePath = join(baseDir, WORKTREE_DIR);\n\n // Get worktree HEAD\n const worktreeHead = await git('-C', worktreePath, 'rev-parse', 'HEAD').catch(() => '');\n\n // Get local branch HEAD\n const localHead = await git('-C', baseDir, 'rev-parse', syncBranch).catch(() => '');\n\n // Get remote branch HEAD\n const remoteHead = await git('-C', baseDir, 'rev-parse', `${remote}/${syncBranch}`).catch(\n () => '',\n );\n\n // Calculate ahead/behind counts\n let localAhead = 0;\n let localBehind = 0;\n\n if (localHead && remoteHead) {\n try {\n const aheadOutput = await git(\n '-C',\n baseDir,\n 'rev-list',\n '--count',\n `${remote}/${syncBranch}..${syncBranch}`,\n );\n localAhead = parseInt(aheadOutput.trim(), 10) || 0;\n } catch {\n // Ignore errors\n }\n\n try {\n const behindOutput = await git(\n '-C',\n baseDir,\n 'rev-list',\n '--count',\n `${syncBranch}..${remote}/${syncBranch}`,\n );\n localBehind = parseInt(behindOutput.trim(), 10) || 0;\n } catch {\n // Ignore errors\n }\n }\n\n return {\n worktreeHead: worktreeHead.trim(),\n localHead: localHead.trim(),\n remoteHead: remoteHead.trim(),\n worktreeMatchesLocal: worktreeHead.trim() === localHead.trim(),\n localAhead,\n localBehind,\n };\n}\n\n/**\n * Count issues on a remote sync branch without creating a worktree.\n * Used by doctor to show accurate statistics on fresh clones.\n *\n * @param remote - The remote name (default: 'origin')\n * @param syncBranch - The sync branch name (default: 'tbd-sync')\n * @returns Number of issue files on the remote branch, or null if branch doesn't exist\n */\nexport async function countRemoteIssues(\n remote = 'origin',\n syncBranch: string = SYNC_BRANCH,\n): Promise<number | null> {\n try {\n // Fetch the remote branch first\n await git('fetch', remote, syncBranch);\n\n // List all files in the remote branch\n const remoteBranch = `${remote}/${syncBranch}`;\n const output = await git('ls-tree', '-r', '--name-only', remoteBranch);\n\n // Count issue files in the issues directory\n // Uses path constants to avoid hardcoded paths\n const issuesDir = `${TBD_DIR}/${DATA_SYNC_DIR_NAME}/issues/`;\n const lines = output.split('\\n').filter(Boolean);\n const issueCount = lines.filter(\n (line) => line.startsWith(issuesDir) && line.endsWith('.md'),\n ).length;\n\n return issueCount;\n } catch {\n // Remote branch doesn't exist or fetch failed\n return null;\n }\n}\n\n/**\n * Remove the hidden worktree.\n * Used by doctor --fix when worktree is corrupted.\n */\nexport async function removeWorktree(\n baseDir: string,\n): Promise<{ success: boolean; error?: string }> {\n const worktreePath = join(baseDir, WORKTREE_DIR);\n\n try {\n // First try to properly remove via git\n try {\n await git('-C', baseDir, 'worktree', 'remove', worktreePath, '--force');\n } catch {\n // If git worktree remove fails, just delete the directory\n await rm(worktreePath, { recursive: true, force: true });\n }\n\n // Prune stale worktree references\n await git('-C', baseDir, 'worktree', 'prune');\n\n return { success: true };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\n/**\n * Repair an unhealthy worktree.\n *\n * Follows decision tree from spec Appendix E:\n * - PRUNABLE: git worktree prune, then recreate\n * - CORRUPTED: backup to .tbd/backups/, remove, then recreate\n * - MISSING: just create\n *\n * See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md\n *\n * @param baseDir - The base directory of the repository\n * @param status - Current worktree health status\n * @param remote - The remote name (default: 'origin')\n * @param syncBranch - The sync branch name (default: 'tbd-sync')\n */\nexport async function repairWorktree(\n baseDir: string,\n status: 'missing' | 'prunable' | 'corrupted',\n remote = 'origin',\n syncBranch: string = SYNC_BRANCH,\n): Promise<{ success: boolean; path?: string; backedUp?: string; error?: string }> {\n const worktreePath = join(baseDir, WORKTREE_DIR);\n\n try {\n // Always prune stale worktree entries first for missing and prunable states\n // This ensures git's worktree list is clean before creating a new worktree\n if (status === 'missing' || status === 'prunable') {\n await git('-C', baseDir, 'worktree', 'prune');\n }\n\n // Handle corrupted status: backup before removal\n if (status === 'corrupted') {\n const backupsDir = join(baseDir, TBD_DIR, 'backups');\n await mkdir(backupsDir, { recursive: true });\n\n const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);\n const backupPath = join(backupsDir, `corrupted-worktree-backup-${timestamp}`);\n\n // Copy corrupted worktree to backup before removal\n try {\n await cp(worktreePath, backupPath, { recursive: true });\n } catch {\n // If copy fails, the directory might not exist or be accessible\n // Continue with repair anyway\n }\n\n // Remove the corrupted worktree\n await rm(worktreePath, { recursive: true, force: true });\n await git('-C', baseDir, 'worktree', 'prune');\n\n // Initialize fresh worktree\n const result = await initWorktree(baseDir, remote, syncBranch);\n return { ...result, backedUp: backupPath };\n }\n\n // For missing or prunable (after prune), just initialize\n const result = await initWorktree(baseDir, remote, syncBranch);\n return result;\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\n/**\n * Ensure worktree is attached to sync branch, not detached HEAD.\n * Old tbd versions (pre-v0.1.9) created worktrees with --detach flag.\n * This repairs them automatically.\n *\n * @param worktreePath - Path to the worktree directory\n * @returns true if worktree was detached and repaired, false if already attached\n */\nexport async function ensureWorktreeAttached(worktreePath: string): Promise<boolean> {\n try {\n const currentBranch = await git('-C', worktreePath, 'branch', '--show-current').catch(() => '');\n\n if (!currentBranch) {\n // Detached HEAD - re-attach to sync branch\n // This is a one-time repair for repos created with old tbd versions\n await git('-C', worktreePath, 'checkout', SYNC_BRANCH);\n return true; // Was detached, now repaired\n }\n\n return false; // Already attached\n } catch (error) {\n // If we can't check/fix, that's a problem but don't fail the operation\n console.warn('Warning: Could not check worktree HEAD status:', error);\n return false;\n }\n}\n\n/**\n * Migrate data from wrong location (.tbd/data-sync/) to worktree.\n *\n * Used when data was incorrectly written to the direct path instead of the worktree.\n * Per spec Appendix E:\n * 1. Backup to .tbd/backups/\n * 2. Copy issues/mappings from .tbd/data-sync/ to worktree\n * 3. Commit in worktree\n * 4. Optionally remove wrong location data\n *\n * See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md\n *\n * @param baseDir - The base directory of the repository\n * @param removeSource - Whether to remove data from wrong location after migration\n */\nexport async function migrateDataToWorktree(\n baseDir: string,\n removeSource = false,\n): Promise<{\n success: boolean;\n migratedCount: number;\n backupPath?: string;\n error?: string;\n}> {\n const wrongPath = join(baseDir, TBD_DIR, DATA_SYNC_DIR_NAME);\n const correctPath = join(baseDir, WORKTREE_DIR, TBD_DIR, DATA_SYNC_DIR_NAME);\n const worktreePath = join(baseDir, WORKTREE_DIR);\n\n try {\n // Ensure worktree is attached to sync branch (repair old tbd repos)\n await ensureWorktreeAttached(worktreePath);\n // Check if there's data in the wrong location\n const wrongIssuesPath = join(wrongPath, 'issues');\n const wrongMappingsPath = join(wrongPath, 'mappings');\n\n let issueFiles: string[] = [];\n let mappingFiles: string[] = [];\n\n try {\n const { readdir } = await import('node:fs/promises');\n issueFiles = await readdir(wrongIssuesPath).catch(() => []);\n mappingFiles = await readdir(wrongMappingsPath).catch(() => []);\n } catch {\n // Directory doesn't exist\n }\n\n // Filter out .gitkeep files\n issueFiles = issueFiles.filter((f) => f !== '.gitkeep');\n mappingFiles = mappingFiles.filter((f) => f !== '.gitkeep');\n\n if (issueFiles.length === 0 && mappingFiles.length === 0) {\n return { success: true, migratedCount: 0 };\n }\n\n // Step 1: Backup to .tbd/backups/\n const backupsDir = join(baseDir, TBD_DIR, 'backups');\n await mkdir(backupsDir, { recursive: true });\n\n const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);\n const backupPath = join(backupsDir, `data-sync-backup-${timestamp}`);\n\n await cp(wrongPath, backupPath, { recursive: true });\n\n // Step 2: Copy issues and mappings to worktree\n const correctIssuesPath = join(correctPath, 'issues');\n const correctMappingsPath = join(correctPath, 'mappings');\n\n await mkdir(correctIssuesPath, { recursive: true });\n await mkdir(correctMappingsPath, { recursive: true });\n\n for (const file of issueFiles) {\n await cp(join(wrongIssuesPath, file), join(correctIssuesPath, file));\n }\n\n // Merge ID mappings instead of overwriting — ids.yml is append-only, so a\n // raw cp would destroy existing entries in the worktree. Import and use the\n // merge utilities so both source and destination entries are preserved.\n for (const file of mappingFiles) {\n if (file === 'ids.yml') {\n const { readFile } = await import('node:fs/promises');\n const { loadIdMapping, mergeIdMappings, saveIdMapping, resolveIdMappingConflicts } =\n await import('./id-mapping.js');\n const sourceContent = await readFile(join(wrongMappingsPath, file), 'utf-8');\n const sourceMapping = resolveIdMappingConflicts(sourceContent);\n\n let targetMapping;\n try {\n const targetContent = await readFile(join(correctMappingsPath, file), 'utf-8');\n targetMapping = resolveIdMappingConflicts(targetContent);\n } catch {\n targetMapping = await loadIdMapping(correctPath);\n }\n\n const merged = mergeIdMappings(targetMapping, sourceMapping);\n await saveIdMapping(correctPath, merged);\n } else {\n await cp(join(wrongMappingsPath, file), join(correctMappingsPath, file));\n }\n }\n\n // Step 3: Commit in worktree (if there are changes)\n // Use --no-verify to bypass parent repo hooks (lefthook, husky, etc.)\n const totalFiles = issueFiles.length + mappingFiles.length;\n await git('-C', worktreePath, 'add', '-A');\n\n // Check if there are staged changes before committing\n const hasChanges = await git('-C', worktreePath, 'diff', '--cached', '--quiet')\n .then(() => false)\n .catch(() => true);\n\n if (hasChanges) {\n await git(\n '-C',\n worktreePath,\n 'commit',\n '--no-verify',\n '-m',\n `tbd: migrate ${totalFiles} file(s) from incorrect location`,\n );\n }\n // If no changes, files were already migrated - that's fine\n\n // Step 4: Optionally remove wrong location data\n if (removeSource) {\n // Remove issue and mapping files, but keep directory structure\n for (const file of issueFiles) {\n await rm(join(wrongIssuesPath, file));\n }\n for (const file of mappingFiles) {\n await rm(join(wrongMappingsPath, file));\n }\n }\n\n return {\n success: true,\n migratedCount: totalFiles,\n backupPath,\n };\n } catch (error) {\n return {\n success: false,\n migratedCount: 0,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n","/**\n * `tbd init` - Initialize tbd in a repository.\n *\n * See: tbd-design.md §4.3 Initialization\n */\n\nimport { Command } from 'commander';\nimport { mkdir, stat } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { ensureGitignorePatterns } from '../../utils/gitignore-utils.js';\nimport { CLIError, ValidationError } from '../lib/errors.js';\nimport { isValidPrefix, isRecommendedPrefix } from '../lib/prefix-detection.js';\nimport { VERSION } from '../lib/version.js';\nimport { initConfig } from '../../file/config.js';\nimport {\n TBD_DIR,\n WORKTREE_DIR_NAME,\n DATA_SYNC_DIR_NAME,\n SYNC_BRANCH,\n TBD_SHORTCUTS_SYSTEM,\n TBD_SHORTCUTS_STANDARD,\n TBD_GUIDELINES_DIR,\n TBD_TEMPLATES_DIR,\n TBD_DOCS_DIR,\n} from '../../lib/paths.js';\nimport {\n initWorktree,\n checkGitVersion,\n checkWorktreeHealth,\n findGitRoot,\n isInGitRepo,\n MIN_GIT_VERSION,\n} from '../../file/git.js';\n\ninterface InitOptions {\n prefix?: string;\n force?: boolean;\n syncBranch?: string;\n remote?: string;\n}\n\nclass InitHandler extends BaseCommand {\n async run(options: InitOptions): Promise<void> {\n // Require git repository and resolve to git root\n const inGitRepo = await isInGitRepo();\n if (!inGitRepo) {\n throw new CLIError('Not a git repository. Run `git init` first.');\n }\n\n const gitRoot = await findGitRoot();\n if (!gitRoot) {\n throw new CLIError('Could not determine git repository root.');\n }\n\n // Use git root as the working directory (ensures .tbd/ is always at repo root)\n const cwd = gitRoot;\n\n // Check if already initialized\n try {\n await stat(join(cwd, TBD_DIR));\n throw new CLIError('tbd is already initialized in this directory');\n } catch (error) {\n // Not initialized - continue (unless it's our CLIError)\n if (error instanceof CLIError) throw error;\n }\n\n // Validate prefix is provided\n if (!options.prefix) {\n throw new ValidationError(\n 'The --prefix option is required\\n\\n' +\n 'Usage: tbd init --prefix=<name>\\n\\n' +\n 'The prefix is used for display IDs (e.g., proj-a7k2, myapp-b3m9)\\n' +\n 'Choose a short 2-8 letter prefix for your project (e.g., tbd, myp, proj).\\n\\n' +\n 'For full setup with integrations: tbd setup --auto --prefix=<name>',\n );\n }\n\n const prefix = options.prefix;\n\n // Hard validation: always enforced\n if (!isValidPrefix(prefix)) {\n throw new ValidationError(\n 'Invalid prefix format.\\n' +\n 'Prefix must be 1-20 lowercase characters:\\n' +\n ' - Must start with a letter (a-z)\\n' +\n ' - Must end with alphanumeric (a-z, 0-9)\\n' +\n ' - Middle characters can include dots (.) and underscores (_)\\n' +\n ' - No dashes allowed (breaks ID syntax)\\n\\n' +\n 'Example:\\n' +\n ' tbd init --prefix=tbd',\n );\n }\n\n // Soft validation: recommended format (2-8 alphabetic)\n if (!isRecommendedPrefix(prefix) && !options.force) {\n throw new ValidationError(\n `Prefix \"${prefix}\" is not recommended.\\n` +\n 'Recommended prefixes are 2-8 alphabetic characters (e.g., \"tbd\", \"myp\", \"proj\").\\n\\n' +\n 'If you really want to use this prefix, add --force to override.\\n\\n' +\n 'Example:\\n' +\n ` tbd init --prefix=${prefix} --force`,\n );\n }\n\n if (this.checkDryRun('Would initialize tbd repository', options)) {\n return;\n }\n\n await this.execute(async () => {\n // 1. Create .tbd/ directory with config.yml\n // Note: options.prefix is validated to be non-null above\n await initConfig(cwd, VERSION, options.prefix!);\n this.output.debug(`Created ${TBD_DIR}/config.yml with prefix '${options.prefix}'`);\n\n // 2. Create .tbd/.gitignore (idempotent)\n // Per spec: Must ignore docs/, data-sync-worktree/, and data-sync/\n await ensureGitignorePatterns(join(cwd, TBD_DIR, '.gitignore'), [\n '# Installed documentation (regenerated on setup)',\n 'docs/',\n '',\n '# Hidden worktree for tbd-sync branch',\n `${WORKTREE_DIR_NAME}/`,\n '',\n '# Data sync directory (only exists in worktree)',\n `${DATA_SYNC_DIR_NAME}/`,\n '',\n '# Local state',\n 'state.yml',\n '',\n '# Migration backups (local only, not synced)',\n 'backups/',\n '',\n '# Temporary files',\n '*.tmp',\n '*.temp',\n '',\n '# workspaces/ stores state (including outbox) committed to the working branch',\n '!workspaces/',\n ]);\n this.output.debug(`Created ${TBD_DIR}/.gitignore`);\n\n // 3. Create docs directories for shortcuts, guidelines, and templates\n await mkdir(join(cwd, TBD_SHORTCUTS_SYSTEM), { recursive: true });\n await mkdir(join(cwd, TBD_SHORTCUTS_STANDARD), { recursive: true });\n await mkdir(join(cwd, TBD_GUIDELINES_DIR), { recursive: true });\n await mkdir(join(cwd, TBD_TEMPLATES_DIR), { recursive: true });\n this.output.debug(`Created ${TBD_DOCS_DIR}/ directories`);\n\n // 4. Initialize the hidden worktree for tbd-sync branch\n // This creates .tbd/data-sync-worktree/ with the sync branch checkout\n const remote = options.remote ?? 'origin';\n const syncBranch = options.syncBranch ?? SYNC_BRANCH;\n\n // Check Git version before attempting worktree creation\n // Git 2.42+ is required for --orphan worktree support\n try {\n const { version, supported } = await checkGitVersion();\n if (!supported) {\n const versionStr = `${version.major}.${version.minor}.${version.patch}`;\n throw new CLIError(\n `Git ${versionStr} detected. Git ${MIN_GIT_VERSION}+ is required for tbd.\\n\\n` +\n `tbd requires Git 2.42+ for orphan worktree support.\\n` +\n `Please upgrade Git: https://git-scm.com/downloads`,\n );\n }\n this.output.debug(`Git version ${version.major}.${version.minor}.${version.patch} OK`);\n } catch (error) {\n // If git is not installed at all, let worktree init handle it\n if (error instanceof CLIError) throw error;\n this.output.debug(`Git version check skipped: ${(error as Error).message}`);\n }\n\n const worktreeResult = await initWorktree(cwd, remote, syncBranch);\n\n if (worktreeResult.success) {\n if (worktreeResult.created) {\n this.output.debug(`Created hidden worktree at ${TBD_DIR}/${WORKTREE_DIR_NAME}/`);\n } else {\n this.output.debug(`Worktree already exists at ${TBD_DIR}/${WORKTREE_DIR_NAME}/`);\n }\n\n // Verify worktree health after creation (prevents silent failures)\n const health = await checkWorktreeHealth(cwd);\n if (!health.valid) {\n this.output.warn(\n `Worktree created but failed verification (status: ${health.status}). ` +\n `Run 'tbd doctor' to diagnose.`,\n );\n }\n } else {\n // Worktree creation failed - this is ok if not in a git repo\n // Log warning but don't fail init (supports non-git usage)\n this.output.debug(`Note: Worktree not created (${worktreeResult.error})`);\n }\n }, 'Failed to initialize tbd');\n\n this.output.data({ initialized: true, version: VERSION, prefix: options.prefix }, () => {\n this.output.success(`Initialized tbd repository (prefix: ${options.prefix})`);\n // Only show next steps if not in quiet mode\n if (!this.output.isQuiet()) {\n console.log('');\n console.log('Next steps:');\n console.log(' git add .tbd/ && git commit -m \"Initialize tbd\"');\n console.log(' tbd setup --auto # Optional: configure agent integrations');\n }\n });\n }\n}\n\nexport const initCommand = new Command('init')\n .description('Initialize tbd in a git repository')\n .option('--prefix <name>', 'Project prefix for display IDs (2-8 alphabetic recommended)')\n .option('--force', 'Allow non-recommended prefix format')\n .option('--sync-branch <name>', 'Sync branch name (default: tbd-sync)')\n .option('--remote <name>', 'Remote name (default: origin)')\n .action(async (options, command) => {\n const handler = new InitHandler(command);\n await handler.run(options);\n });\n","/**\n * Helpers for rendering Zod errors without relying on object inspection.\n */\n\nimport { ZodError } from 'zod';\n\n/**\n * Format a ZodError as concise path-qualified messages for CLI output.\n */\nexport function formatZodError(error: ZodError): string {\n const messages = error.issues.map((issue) => {\n const path = issue.path.length > 0 ? issue.path.join('.') : '<root>';\n return `${path}: ${issue.message}`;\n });\n\n return messages.length > 0 ? messages.join('; ') : error.message;\n}\n\n/**\n * Format unknown thrown values as safe strings for warnings and diagnostics.\n */\nexport function formatUnknownError(error: unknown): string {\n if (error instanceof ZodError) {\n return formatZodError(error);\n }\n if (error instanceof Error) {\n return error.message;\n }\n return String(error);\n}\n","/**\n * Storage layer for issue files.\n *\n * Provides atomic file operations and issue CRUD operations.\n * All operations work on the hidden worktree at .tbd/data-sync/issues/.\n *\n * See: tbd-design.md §3.2 Storage Layer\n */\n\nimport { readFile, unlink, readdir } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { writeFile } from 'atomically';\n\nimport type { Issue } from '../lib/types.js';\nimport { IssueSchema } from '../lib/schemas.js';\nimport { formatUnknownError } from '../utils/zod-error-utils.js';\nimport { parseIssue, serializeIssue } from './parser.js';\n\n/**\n * Maximum issue files read concurrently to avoid exhausting file descriptors in large repos.\n */\nconst ISSUE_READ_BATCH_SIZE = 200;\n\n/**\n * Parse or read failure for an issue file.\n */\nexport interface InvalidIssueFile {\n /** Issue filename relative to the issues directory. */\n file: string;\n /** Safe, human-readable read or parse failure reason. */\n reason: string;\n}\n\ninterface ListIssuesOptions {\n /** When true, emit skipped-file warnings to stderr. */\n warnOnInvalid?: boolean;\n /** Optional callback for callers that need structured skipped-file diagnostics. */\n onInvalidIssue?: (invalidIssue: InvalidIssueFile) => void;\n}\n\n/**\n * Get the path to an issue file.\n */\nfunction getIssuePath(baseDir: string, id: string): string {\n return join(baseDir, 'issues', `${id}.md`);\n}\n\n/**\n * Read an issue from the worktree.\n * @throws If the issue file doesn't exist or is invalid.\n */\nexport async function readIssue(baseDir: string, id: string): Promise<Issue> {\n const filePath = getIssuePath(baseDir, id);\n const content = await readFile(filePath, 'utf-8');\n return parseIssue(content);\n}\n\n/**\n * Write an issue to the worktree.\n * Uses atomic write to prevent corruption.\n */\nexport async function writeIssue(baseDir: string, issue: Issue): Promise<void> {\n const validIssue = IssueSchema.parse(issue);\n const filePath = getIssuePath(baseDir, validIssue.id);\n const content = serializeIssue(validIssue);\n await writeFile(filePath, content);\n}\n\n/**\n * List all issues in the worktree.\n * Returns empty array if issues directory doesn't exist.\n *\n * Uses parallel file reading for better performance with many issues.\n */\nexport async function listIssues(\n baseDir: string,\n options: ListIssuesOptions = {},\n): Promise<Issue[]> {\n const warnOnInvalid = options.warnOnInvalid ?? true;\n const issuesDir = join(baseDir, 'issues');\n\n let files: string[];\n try {\n files = await readdir(issuesDir);\n } catch {\n // Directory doesn't exist - return empty\n return [];\n }\n\n // Filter to only .md files\n const mdFiles = files.filter((f) => f.endsWith('.md'));\n\n const issues: Issue[] = [];\n\n for (let i = 0; i < mdFiles.length; i += ISSUE_READ_BATCH_SIZE) {\n const batch = mdFiles.slice(i, i + ISSUE_READ_BATCH_SIZE);\n const fileContents = await Promise.all(\n batch.map(async (file) => {\n const filePath = join(issuesDir, file);\n try {\n const content = await readFile(filePath, 'utf-8');\n return { file, content };\n } catch (error) {\n return { file, error: formatUnknownError(error) };\n }\n }),\n );\n\n for (const result of fileContents) {\n if ('error' in result) {\n reportInvalidIssueFile(\n { file: result.file, reason: `failed to read file: ${result.error}` },\n warnOnInvalid,\n options.onInvalidIssue,\n );\n continue;\n }\n try {\n const issue = parseIssue(result.content);\n issues.push(issue);\n } catch (error) {\n reportInvalidIssueFile(\n { file: result.file, reason: formatUnknownError(error) },\n warnOnInvalid,\n options.onInvalidIssue,\n );\n }\n }\n }\n\n return issues;\n}\n\nfunction reportInvalidIssueFile(\n invalidIssue: InvalidIssueFile,\n warnOnInvalid: boolean,\n onInvalidIssue?: (invalidIssue: InvalidIssueFile) => void,\n): void {\n onInvalidIssue?.(invalidIssue);\n if (warnOnInvalid) {\n console.warn(`Skipping invalid issue file: ${invalidIssue.file}: ${invalidIssue.reason}`);\n }\n}\n\n/**\n * Delete an issue from the worktree.\n * Does not throw if issue doesn't exist.\n */\nexport async function deleteIssue(baseDir: string, id: string): Promise<void> {\n const filePath = getIssuePath(baseDir, id);\n try {\n await unlink(filePath);\n } catch (error) {\n // Ignore ENOENT (file doesn't exist)\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {\n throw error;\n }\n }\n}\n","/**\n * Priority formatting and parsing utilities.\n *\n * Priority values range from 0 (critical) to 4 (backlog).\n * Display format uses \"P\" prefix: P0, P1, P2, P3, P4.\n */\n\nimport type { createColors } from '../cli/lib/output.js';\n\n/**\n * Valid priority values.\n */\nexport const MIN_PRIORITY = 0;\nexport const MAX_PRIORITY = 4;\n\n/**\n * Format a priority number for display.\n * Always uses \"P\" prefix format.\n *\n * @example formatPriority(0) → \"P0\"\n * @example formatPriority(2) → \"P2\"\n */\nexport function formatPriority(priority: number): string {\n return `P${priority}`;\n}\n\n/**\n * Parse a priority string to a number.\n * Accepts both numeric (\"1\") and prefixed (\"P1\") formats.\n * Case-insensitive for prefixed format.\n *\n * @example parsePriority(\"P1\") → 1\n * @example parsePriority(\"p0\") → 0\n * @example parsePriority(\"2\") → 2\n * @example parsePriority(\"invalid\") → undefined\n *\n * @returns The priority number, or undefined if invalid.\n */\nexport function parsePriority(input: string): number | undefined {\n const trimmed = input.trim().toUpperCase();\n if (!trimmed) return undefined;\n\n let numStr: string;\n if (trimmed.startsWith('P')) {\n // Prefixed format: P0, P1, etc.\n if (trimmed.length !== 2) return undefined;\n numStr = trimmed.slice(1);\n } else {\n // Numeric format: 0, 1, etc.\n numStr = trimmed;\n }\n\n const num = parseInt(numStr, 10);\n if (isNaN(num) || num < MIN_PRIORITY || num > MAX_PRIORITY) {\n return undefined;\n }\n\n return num;\n}\n\n/**\n * Get the color function for a priority value.\n *\n * - P0 (critical): red\n * - P1 (high): yellow\n * - P2-P4: no color (identity function)\n *\n * @param priority - The priority number (0-4)\n * @param colors - The colors object from createColors()\n * @returns A function that applies the appropriate color\n */\nexport function getPriorityColor(\n priority: number,\n colors: ReturnType<typeof createColors>,\n): (s: string) => string {\n switch (priority) {\n case 0:\n return colors.error;\n case 1:\n return colors.warn;\n default:\n return (s) => s;\n }\n}\n","/**\n * Generic project path utilities for handling user-provided paths.\n *\n * This module provides reusable functions for resolving, validating, and\n * normalizing paths relative to a project root. It is designed to be\n * general-purpose and not tied to any specific use case (e.g., specs).\n *\n * Key features:\n * - Resolves absolute, relative, and subdirectory paths to project-relative paths\n * - Validates that paths stay within project boundaries\n * - Normalizes paths (removes ./, converts backslashes, etc.)\n * - Validates file existence\n */\n\nimport { resolve, relative, isAbsolute, normalize, sep } from 'node:path';\nimport { access, stat } from 'node:fs/promises';\n\n/**\n * Result of resolving a path relative to the project root.\n */\nexport interface ResolvedProjectPath {\n /** Path relative to project root (always uses forward slashes) */\n relativePath: string;\n /** Absolute path to the file */\n absolutePath: string;\n}\n\n/**\n * Error thrown when a path operation fails.\n * The message is user-friendly and can be displayed directly.\n */\nexport class ProjectPathError extends Error {\n constructor(\n message: string,\n public readonly code: 'OUTSIDE_PROJECT' | 'NOT_FOUND' | 'NOT_A_FILE',\n ) {\n super(message);\n this.name = 'ProjectPathError';\n }\n}\n\n/**\n * Converts a ProjectPathError to a user-friendly error message for CLI display.\n * Returns the error message if it's a ProjectPathError, otherwise re-throws.\n */\nexport function getPathErrorMessage(error: unknown): string {\n if (error instanceof ProjectPathError) {\n return error.message;\n }\n throw error;\n}\n\n/**\n * Normalizes a path by:\n * - Removing leading ./\n * - Converting backslashes to forward slashes (Windows compatibility)\n * - Removing redundant slashes\n * - Resolving . and .. components\n *\n * @param inputPath - The path to normalize\n * @returns Normalized path string\n */\nexport function normalizePath(inputPath: string): string {\n if (!inputPath) {\n return '';\n }\n\n // First, convert all backslashes to forward slashes (Windows compatibility)\n // This must happen BEFORE normalize() since backslashes aren't path separators on Linux\n let normalized = inputPath.replace(/\\\\/g, '/');\n\n // Use Node's normalize to handle . and .. components\n // (normalize on Linux won't touch forward slashes)\n normalized = normalize(normalized);\n\n // Ensure we still have forward slashes after normalize (in case of mixed separators)\n normalized = normalized.split(sep).join('/');\n\n // Remove leading ./\n while (normalized.startsWith('./')) {\n normalized = normalized.slice(2);\n }\n\n // Remove trailing slash (unless it's just \"/\")\n if (normalized.length > 1 && normalized.endsWith('/')) {\n normalized = normalized.slice(0, -1);\n }\n\n // Handle edge case where normalize returns '.'\n if (normalized === '.') {\n return '';\n }\n\n return normalized;\n}\n\n/**\n * Checks if an absolute path is within the project root.\n *\n * @param absolutePath - The absolute path to check\n * @param projectRoot - The project root directory\n * @returns true if the path is within or at the project root\n */\nexport function isPathWithinProject(absolutePath: string, projectRoot: string): boolean {\n // Normalize both paths for consistent comparison\n const normalizedPath = resolve(absolutePath);\n const normalizedRoot = resolve(projectRoot);\n\n // The path is within the project if it starts with the project root\n // We need to handle the case where the path IS the project root\n // or is a subdirectory of it\n if (normalizedPath === normalizedRoot) {\n return true;\n }\n\n // Check if path starts with root + separator\n // This prevents false positives like /project-backup matching /project\n return normalizedPath.startsWith(normalizedRoot + sep);\n}\n\n/**\n * Resolves any path (absolute, relative, or from subdirectory) to a project-relative path.\n *\n * Resolution rules:\n * 1. Absolute paths within project → convert to relative\n * 2. Absolute paths outside project → error\n * 3. Relative paths from subdirectory → resolve to project root\n * 4. Already project-relative → pass through with normalization\n * 5. Path escaping project (../../) → error\n *\n * @param inputPath - The path provided by the user (can be absolute or relative)\n * @param projectRoot - The project root directory (parent of .tbd/)\n * @param cwd - Current working directory (where command was run)\n * @returns Resolved paths object with both relative and absolute paths\n * @throws ProjectPathError if path is outside project\n */\nexport function resolveProjectPath(\n inputPath: string,\n projectRoot: string,\n cwd: string,\n): ResolvedProjectPath {\n // Normalize inputs\n const normalizedProjectRoot = resolve(projectRoot);\n const normalizedCwd = resolve(cwd);\n\n let absolutePath: string;\n\n if (isAbsolute(inputPath)) {\n // Input is already absolute\n absolutePath = resolve(inputPath);\n } else {\n // Input is relative - resolve from current working directory\n absolutePath = resolve(normalizedCwd, inputPath);\n }\n\n // Check if path is within project\n if (!isPathWithinProject(absolutePath, normalizedProjectRoot)) {\n throw new ProjectPathError(`Path is outside project root: ${inputPath}`, 'OUTSIDE_PROJECT');\n }\n\n // Calculate relative path from project root\n const relativePath = relative(normalizedProjectRoot, absolutePath);\n\n // Normalize the relative path (remove ./, convert separators, etc.)\n const normalizedRelative = normalizePath(relativePath);\n\n return {\n relativePath: normalizedRelative,\n absolutePath,\n };\n}\n\n/**\n * Validates that a file exists at the resolved path.\n *\n * @param resolvedPath - Path already resolved via resolveProjectPath\n * @returns true if file exists\n * @throws ProjectPathError if file does not exist or is not a file\n */\nexport async function validateFileExists(resolvedPath: ResolvedProjectPath): Promise<boolean> {\n try {\n await access(resolvedPath.absolutePath);\n } catch {\n throw new ProjectPathError(`File not found: ${resolvedPath.relativePath}`, 'NOT_FOUND');\n }\n\n // Also verify it's a file, not a directory\n try {\n const stats = await stat(resolvedPath.absolutePath);\n if (!stats.isFile()) {\n throw new ProjectPathError(`Path is not a file: ${resolvedPath.relativePath}`, 'NOT_A_FILE');\n }\n } catch (error) {\n if (error instanceof ProjectPathError) {\n throw error;\n }\n throw new ProjectPathError(`File not found: ${resolvedPath.relativePath}`, 'NOT_FOUND');\n }\n\n return true;\n}\n\n/**\n * Convenience function that resolves and validates a path in one call.\n *\n * @param inputPath - The path provided by the user\n * @param projectRoot - The project root directory\n * @param cwd - Current working directory\n * @returns Resolved and validated path\n * @throws ProjectPathError if path is outside project or file doesn't exist\n */\nexport async function resolveAndValidatePath(\n inputPath: string,\n projectRoot: string,\n cwd: string,\n): Promise<ResolvedProjectPath> {\n const resolved = resolveProjectPath(inputPath, projectRoot, cwd);\n await validateFileExists(resolved);\n return resolved;\n}\n","/**\n * CLI validation helpers for user-provided issue fields.\n */\n\nimport { ISSUE_TITLE_MAX_LENGTH, IssueTitle } from '../../lib/schemas.js';\nimport { formatZodError } from '../../utils/zod-error-utils.js';\nimport { ValidationError } from './errors.js';\n\ninterface IssueTitleValidationOptions {\n /** Error message used when the title is empty. */\n emptyMessage: string;\n /** When true, whitespace-only titles are treated as empty. */\n rejectBlank?: boolean;\n}\n\n/**\n * Validate a CLI-provided issue title with actionable user-facing errors.\n */\nexport function validateIssueTitle(title: string, options: IssueTitleValidationOptions): string {\n const isEmpty = options.rejectBlank ? title.trim().length === 0 : title.length === 0;\n if (isEmpty) {\n throw new ValidationError(options.emptyMessage);\n }\n\n if (title.length > ISSUE_TITLE_MAX_LENGTH) {\n throw new ValidationError(\n `Title is too long (${title.length} chars, max ${ISSUE_TITLE_MAX_LENGTH}). Move detail into the description body.`,\n );\n }\n\n const result = IssueTitle.safeParse(title);\n if (!result.success) {\n throw new ValidationError(`Invalid title: ${formatZodError(result.error)}`);\n }\n\n return title;\n}\n","/**\n * `tbd create` - Create a new issue.\n *\n * See: tbd-design.md §4.4 Create\n */\n\nimport { Command } from 'commander';\nimport { readFile } from 'node:fs/promises';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { requireInit, ValidationError, CLIError } from '../lib/errors.js';\nimport type { Issue, IssueKindType, PriorityType } from '../../lib/types.js';\nimport { generateInternalId, extractUlidFromInternalId } from '../../lib/ids.js';\nimport { readIssue, writeIssue } from '../../file/storage.js';\nimport {\n loadIdMapping,\n saveIdMapping,\n generateUniqueShortId,\n addIdMapping,\n resolveToInternalId,\n} from '../../file/id-mapping.js';\nimport { IssueKind } from '../../lib/schemas.js';\nimport { parsePriority } from '../../lib/priority.js';\nimport { resolveDataSyncDir } from '../../lib/paths.js';\nimport { now } from '../../utils/time-utils.js';\nimport { readConfig } from '../../file/config.js';\nimport { resolveAndValidatePath, getPathErrorMessage } from '../../lib/project-paths.js';\nimport { validateIssueTitle } from '../lib/issue-input-validation.js';\n\ninterface CreateOptions {\n fromFile?: string;\n type?: string;\n priority?: string;\n description?: string;\n file?: string;\n assignee?: string;\n due?: string;\n defer?: string;\n parent?: string;\n label?: string[];\n spec?: string;\n}\n\nclass CreateHandler extends BaseCommand {\n async run(title: string | undefined, options: CreateOptions): Promise<void> {\n const tbdRoot = await requireInit();\n\n // Validate title is provided (unless --from-file)\n if (!title && !options.fromFile) {\n throw new ValidationError('Title is required. Use: tbd create \"Issue title\"');\n }\n const validatedTitle =\n title === undefined\n ? undefined\n : validateIssueTitle(title, {\n emptyMessage: 'Title is required. Use: tbd create \"Issue title\"',\n rejectBlank: true,\n });\n\n // Parse and validate options\n const kind = this.parseKind(options.type ?? 'task');\n const priority = this.validatePriority(options.priority ?? '2');\n\n // Read description from file if specified\n let description = options.description;\n if (options.file) {\n try {\n description = await readFile(options.file, 'utf-8');\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new CLIError(`Failed to read description from file '${options.file}': ${message}`);\n }\n }\n\n // Validate and normalize spec path if provided\n let specPath: string | undefined;\n if (options.spec) {\n try {\n const resolved = await resolveAndValidatePath(options.spec, tbdRoot, process.cwd());\n specPath = resolved.relativePath;\n } catch (error) {\n throw new ValidationError(getPathErrorMessage(error));\n }\n }\n\n if (\n this.checkDryRun('Would create issue', {\n title: validatedTitle,\n kind,\n priority,\n spec: specPath,\n ...options,\n })\n ) {\n return;\n }\n\n const timestamp = now();\n const id = generateInternalId();\n const ulid = extractUlidFromInternalId(id);\n\n let shortId: string;\n let prefix: string;\n let issue: Issue;\n await this.execute(async () => {\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n\n // Read config for display prefix\n const config = await readConfig(tbdRoot);\n prefix = config.display.id_prefix;\n\n // Load mapping, generate unique short ID, and save\n const mapping = await loadIdMapping(dataSyncDir);\n shortId = generateUniqueShortId(mapping);\n addIdMapping(mapping, ulid, shortId);\n\n // Resolve parent ID if provided (convert display ID to internal ID)\n let parentId: string | undefined;\n if (options.parent) {\n try {\n parentId = resolveToInternalId(options.parent, mapping);\n } catch {\n throw new ValidationError(`Invalid parent ID: ${options.parent}`);\n }\n }\n\n // Inherit spec_path from parent if not explicitly provided\n if (!specPath && parentId) {\n const parentIssue = await readIssue(dataSyncDir, parentId);\n if (parentIssue.spec_path) {\n specPath = parentIssue.spec_path;\n }\n }\n\n issue = {\n type: 'is',\n id,\n version: 1,\n title: validatedTitle!,\n kind,\n status: 'open',\n priority,\n labels: options.label ?? [],\n dependencies: [],\n created_at: timestamp,\n updated_at: timestamp,\n description: description ?? undefined,\n assignee: options.assignee ?? undefined,\n due_date: options.due ?? undefined,\n deferred_until: options.defer ?? undefined,\n parent_id: parentId,\n spec_path: specPath,\n };\n\n // Write both the issue and the mapping\n await writeIssue(dataSyncDir, issue);\n await saveIdMapping(dataSyncDir, mapping);\n\n // When creating with a parent, append child to parent's child_order_hints\n if (parentId) {\n try {\n const parentIssue = await readIssue(dataSyncDir, parentId);\n const hints = parentIssue.child_order_hints ?? [];\n\n // Only append if not already in hints (shouldn't happen for new issue, but safe)\n if (!hints.includes(id)) {\n parentIssue.child_order_hints = [...hints, id];\n parentIssue.version += 1;\n parentIssue.updated_at = timestamp;\n await writeIssue(dataSyncDir, parentIssue);\n }\n } catch {\n // Parent not found or other error - skip order hint update\n }\n }\n }, 'Failed to create issue');\n\n // Output with display ID (prefix + short ID)\n const displayId = `${prefix!}-${shortId!}`;\n this.output.data({ id: displayId, internalId: id, title: validatedTitle }, () => {\n this.output.success(`Created ${displayId}: ${validatedTitle}`);\n });\n }\n\n private parseKind(value: string): IssueKindType {\n const result = IssueKind.safeParse(value);\n if (!result.success) {\n throw new ValidationError(`Invalid type: ${value}. Must be: bug, feature, task, epic, chore`);\n }\n return result.data;\n }\n\n private validatePriority(value: string): PriorityType {\n // Use shared parsePriority which accepts both \"P1\" and \"1\" formats\n const num = parsePriority(value);\n if (num === undefined) {\n throw new ValidationError(`Invalid priority: ${value}. Use P0-P4 or 0-4.`);\n }\n return num;\n }\n}\n\nexport const createCommand = new Command('create')\n .description('Create a new issue')\n .argument('[title]', 'Issue title')\n .option('--from-file <path>', 'Create from YAML+Markdown file')\n .option('-t, --type <type>', 'Issue type: bug, feature, task, epic, chore', 'task')\n .option('-p, --priority <0-4>', 'Priority (0=critical, 4=lowest)', '2')\n .option('-d, --description <text>', 'Description')\n .option('-f, --file <path>', 'Read description from file')\n .option('--assignee <name>', 'Assignee')\n .option('--due <date>', 'Due date (ISO8601)')\n .option('--defer <date>', 'Defer until date (ISO8601)')\n .option('--parent <id>', 'Parent issue ID')\n .option('--spec <path>', 'Link to spec document (relative path)')\n .option('-l, --label <label>', 'Add label (repeatable)', (val, prev: string[] = []) => [\n ...prev,\n val,\n ])\n .action(async (title, options, command) => {\n const handler = new CreateHandler(command);\n await handler.run(title, options);\n });\n","/**\n * Utility for parsing limit options in CLI commands.\n */\n\n/**\n * Parse a limit option and apply it to an array.\n *\n * @param items - Array to limit\n * @param limitOption - String limit option from CLI (may be undefined)\n * @returns Limited array (or original if no valid limit)\n */\nexport function applyLimit<T>(items: T[], limitOption: string | undefined): T[] {\n if (!limitOption) {\n return items;\n }\n const limit = parseInt(limitOption, 10);\n if (isNaN(limit) || limit <= 0) {\n return items;\n }\n return items.slice(0, limit);\n}\n","/**\n * Shared data context for tbd commands.\n *\n * Provides a single point to load common data needed by most commands:\n * - dataSyncDir: the path to the data sync directory\n * - mapping: the ID mapping (ULID to short ID)\n * - config: the project configuration\n * - prefix: the display prefix (from config.display.id_prefix)\n *\n * This eliminates the repetitive pattern of loading these individually in each command.\n *\n * For unified CLI + data context with helper methods, use FullCommandContext and\n * loadFullContext() which adds displayId() and other conveniences.\n */\n\nimport type { Command } from 'commander';\nimport type { IdMapping } from '../../file/id-mapping.js';\nimport { loadIdMapping, resolveToInternalId } from '../../file/id-mapping.js';\nimport { readConfig } from '../../file/config.js';\nimport type { Config } from '../../lib/types.js';\nimport { resolveDataSyncDir } from '../../lib/paths.js';\nimport { formatDisplayId, formatDebugId } from '../../lib/ids.js';\nimport type { CommandContext } from './context.js';\nimport { getCommandContext } from './context.js';\nimport { requireInit, NotFoundError } from './errors.js';\n\n/**\n * Data context containing commonly needed data for tbd commands.\n */\nexport interface TbdDataContext {\n /** Path to the data sync directory */\n dataSyncDir: string;\n /** ID mapping (ULID to short ID and vice versa) */\n mapping: IdMapping;\n /** Project configuration */\n config: Config;\n /** Display prefix from config (convenience accessor) */\n prefix: string;\n}\n\n/**\n * Full command context combining CLI options with data context.\n * Provides unified access to all command needs with helper methods.\n */\nexport interface FullCommandContext extends TbdDataContext {\n /** CLI options (dryRun, verbose, json, debug, etc.) */\n cli: CommandContext;\n /**\n * Format an internal issue ID for display.\n * Automatically respects debug mode to show full internal ID.\n */\n displayId(internalId: string): string;\n /**\n * Resolve user input ID to internal ID.\n * @throws NotFoundError if the ID cannot be resolved\n */\n resolveId(inputId: string): string;\n}\n\n/**\n * Load all common data context needed by tbd commands.\n *\n * This loads:\n * - dataSyncDir from resolveDataSyncDir()\n * - mapping from loadIdMapping()\n * - config from readConfig()\n * - prefix from config.display.id_prefix\n *\n * Call this once at the start of a command handler instead of\n * loading each piece separately.\n *\n * @param tbdRoot - The tbd repository root directory (from requireInit or findTbdRoot)\n * @throws Error if any of the resources fail to load\n */\nexport async function loadDataContext(tbdRoot: string): Promise<TbdDataContext> {\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n const [mapping, config] = await Promise.all([loadIdMapping(dataSyncDir), readConfig(tbdRoot)]);\n\n return {\n dataSyncDir,\n mapping,\n config,\n prefix: config.display.id_prefix,\n };\n}\n\n/**\n * Load unified command context with CLI options, data, and helper methods.\n *\n * This is the recommended way to initialize command context. It:\n * 1. Checks that tbd is initialized (calls requireInit)\n * 2. Loads data context (dataSyncDir, mapping, config, prefix)\n * 3. Extracts CLI context from Commander\n * 4. Provides helper methods like displayId() and resolveId()\n *\n * Usage:\n * ```ts\n * class MyHandler extends BaseCommand {\n * async run(id: string): Promise<void> {\n * const ctx = await loadFullContext(this.command);\n * const internalId = ctx.resolveId(id);\n * const issue = await readIssue(ctx.dataSyncDir, internalId);\n * console.log(ctx.displayId(issue.id));\n * }\n * }\n * ```\n *\n * @param command - The Commander command instance\n * @throws Error if tbd is not initialized or resources fail to load\n */\nexport async function loadFullContext(command: Command): Promise<FullCommandContext> {\n const tbdRoot = await requireInit();\n\n const cli = getCommandContext(command);\n const dataCtx = await loadDataContext(tbdRoot);\n\n return {\n ...dataCtx,\n cli,\n displayId(internalId: string): string {\n return cli.debug\n ? formatDebugId(internalId, dataCtx.mapping, dataCtx.prefix)\n : formatDisplayId(internalId, dataCtx.mapping, dataCtx.prefix);\n },\n resolveId(inputId: string): string {\n try {\n return resolveToInternalId(inputId, dataCtx.mapping);\n } catch {\n throw new NotFoundError('Issue', inputId);\n }\n },\n };\n}\n","/**\n * Status formatting utilities.\n *\n * Status values: open, in_progress, blocked, deferred, closed.\n */\n\nimport { ICONS, type createColors } from '../cli/lib/output.js';\nimport type { IssueStatusType } from './types.js';\n\n/**\n * Get the icon for a status value.\n *\n * - open: ○ (empty circle)\n * - in_progress: ◐ (half-filled circle)\n * - blocked: ● (filled circle)\n * - deferred: ○ (empty circle, same as open)\n * - closed: ✓ (checkmark)\n */\nexport function getStatusIcon(status: IssueStatusType): string {\n switch (status) {\n case 'open':\n return ICONS.OPEN;\n case 'in_progress':\n return ICONS.IN_PROGRESS;\n case 'blocked':\n return ICONS.BLOCKED;\n case 'deferred':\n return ICONS.DEFERRED;\n case 'closed':\n return ICONS.CLOSED;\n default:\n return '';\n }\n}\n\n/**\n * Format a status for display with icon prefix.\n * Format: \"icon status\" (e.g., \"○ open\", \"◐ in_progress\")\n *\n * @example formatStatus('open') → \"○ open\"\n * @example formatStatus('blocked') → \"● blocked\"\n */\nexport function formatStatus(status: IssueStatusType): string {\n const icon = getStatusIcon(status);\n return `${icon} ${status}`;\n}\n\n/**\n * Get the color function for a status value.\n *\n * - open: blue (info)\n * - in_progress: green (success)\n * - blocked: red (error)\n * - deferred: dim\n * - closed: dim\n *\n * @param status - The status value\n * @param colors - The colors object from createColors()\n * @returns A function that applies the appropriate color\n */\nexport function getStatusColor(\n status: IssueStatusType,\n colors: ReturnType<typeof createColors>,\n): (s: string) => string {\n switch (status) {\n case 'open':\n return colors.info;\n case 'in_progress':\n return colors.success;\n case 'blocked':\n return colors.error;\n case 'deferred':\n return colors.dim;\n case 'closed':\n return colors.dim;\n default:\n return (s) => s;\n }\n}\n","/**\n * Text truncation utilities for CLI output.\n *\n * Provides consistent truncation with Unicode ellipsis character.\n */\n\n/**\n * Unicode ellipsis character (U+2026). Use this instead of '...'.\n */\nexport const ELLIPSIS = '…';\n\n/**\n * Options for truncate function.\n */\nexport interface TruncateOptions {\n /** If true, truncate at last word boundary before maxLength. */\n wordBoundary?: boolean;\n}\n\n/**\n * Truncate text to maxLength, appending ellipsis if truncated.\n *\n * @param text - Text to truncate\n * @param maxLength - Maximum length including ellipsis\n * @param options - Truncation options\n * @returns Truncated text with ellipsis, or original if short enough\n */\nexport function truncate(text: string, maxLength: number, options?: TruncateOptions): string {\n if (maxLength <= 0) {\n return '';\n }\n\n if (text.length <= maxLength) {\n return text;\n }\n\n if (maxLength === 1) {\n return ELLIPSIS;\n }\n\n const truncatedLength = maxLength - 1; // Reserve space for ellipsis\n\n if (options?.wordBoundary) {\n // Find last space within the truncation limit\n const lastSpace = text.lastIndexOf(' ', truncatedLength);\n if (lastSpace > 0) {\n return text.slice(0, lastSpace) + ELLIPSIS;\n }\n }\n\n // Character-level truncation\n return text.slice(0, truncatedLength) + ELLIPSIS;\n}\n\n/**\n * Truncate text from the middle, preserving start and end.\n * Useful for paths and IDs where both prefix and suffix are meaningful.\n *\n * @param text - Text to truncate\n * @param maxLength - Maximum length including ellipsis\n * @returns Truncated text with ellipsis in middle, or original if short enough\n */\nexport function truncateMiddle(text: string, maxLength: number): string {\n if (maxLength <= 0) {\n return '';\n }\n\n if (text.length <= maxLength) {\n return text;\n }\n\n if (maxLength === 1) {\n return ELLIPSIS;\n }\n\n if (maxLength === 2) {\n // Only room for one char + ellipsis\n return text[0] + ELLIPSIS;\n }\n\n // Calculate how many characters to keep on each side\n // Subtract 1 for the ellipsis\n const availableChars = maxLength - 1;\n const endLength = Math.floor(availableChars / 2);\n const startLength = availableChars - endLength;\n\n const start = text.slice(0, startLength);\n const end = text.slice(text.length - endLength);\n\n return start + ELLIPSIS + end;\n}\n","/**\n * Issue formatting utilities for consistent CLI output.\n *\n * Provides standardized formatting for issue display across all commands.\n */\n\nimport { formatPriority, getPriorityColor } from '../../lib/priority.js';\nimport { getStatusIcon, getStatusColor } from '../../lib/status.js';\nimport { truncate, ELLIPSIS } from '../../lib/truncate.js';\nimport type { createColors } from './output.js';\nimport type { IssueKindType, IssueStatusType } from '../../lib/types.js';\n\n/**\n * Column width constants for issue tables.\n */\nexport const ISSUE_COLUMNS = {\n ID: 12,\n PRIORITY: 5,\n STATUS: 16,\n ASSIGNEE: 10,\n} as const;\n\n/**\n * Issue data structure for formatting.\n */\nexport interface IssueForDisplay {\n id: string;\n priority: number;\n status: IssueStatusType;\n kind: IssueKindType;\n title: string;\n description?: string;\n labels?: string[];\n assignee?: string;\n}\n\n/**\n * Format a kind in brackets.\n *\n * @example formatKind('bug') → \"[bug]\"\n * @example formatKind('feature') → \"[feature]\"\n */\nexport function formatKind(kind: IssueKindType): string {\n return `[${kind}]`;\n}\n\n/**\n * Format a standard issue line for table display.\n *\n * Format: {ID} {PRI} {STATUS} [kind] {TITLE}\n *\n * @example \"bd-a1b2 P0 ● blocked [bug] Fix authentication timeout\"\n */\nexport function formatIssueLine(\n issue: IssueForDisplay,\n colors: ReturnType<typeof createColors>,\n): string {\n const idCol = colors.id(issue.id.padEnd(ISSUE_COLUMNS.ID));\n const priCol = getPriorityColor(\n issue.priority,\n colors,\n )(formatPriority(issue.priority).padEnd(ISSUE_COLUMNS.PRIORITY));\n const statusText = `${getStatusIcon(issue.status)} ${issue.status}`;\n const statusCol = getStatusColor(issue.status, colors)(statusText.padEnd(ISSUE_COLUMNS.STATUS));\n const kindPrefix = colors.dim(formatKind(issue.kind));\n\n return `${idCol}${priCol}${statusCol}${kindPrefix} ${issue.title}`;\n}\n\n/**\n * Format an extended issue line with assignee column.\n *\n * Format: {ID} {PRI} {STATUS} {ASSIGNEE} [kind] {TITLE}\n */\nexport function formatIssueLineExtended(\n issue: IssueForDisplay,\n colors: ReturnType<typeof createColors>,\n): string {\n const idCol = colors.id(issue.id.padEnd(ISSUE_COLUMNS.ID));\n const priCol = getPriorityColor(\n issue.priority,\n colors,\n )(formatPriority(issue.priority).padEnd(ISSUE_COLUMNS.PRIORITY));\n const statusText = `${getStatusIcon(issue.status)} ${issue.status}`;\n const statusCol = getStatusColor(issue.status, colors)(statusText.padEnd(ISSUE_COLUMNS.STATUS));\n const assigneeText = issue.assignee ? `@${issue.assignee}` : '-';\n const assigneeCol = assigneeText.padEnd(ISSUE_COLUMNS.ASSIGNEE);\n const kindPrefix = colors.dim(formatKind(issue.kind));\n\n return `${idCol}${priCol}${statusCol}${assigneeCol}${kindPrefix} ${issue.title}`;\n}\n\n/**\n * Format an issue line with labels.\n *\n * Format: {ID} {PRI} {STATUS} [kind] {TITLE} [labels]\n */\nexport function formatIssueWithLabels(\n issue: IssueForDisplay,\n colors: ReturnType<typeof createColors>,\n): string {\n const baseLine = formatIssueLine(issue, colors);\n\n if (!issue.labels || issue.labels.length === 0) {\n return baseLine;\n }\n\n const labelsText = colors.label(`[${issue.labels.join(', ')}]`);\n return `${baseLine} ${labelsText}`;\n}\n\n/**\n * Format a compact issue reference.\n *\n * Format: {ID} {STATUS_ICON} {TITLE}\n * (No kind shown)\n *\n * @example \"bd-a1b2 ● Fix authentication timeout\"\n */\nexport function formatIssueCompact(\n issue: IssueForDisplay,\n colors: ReturnType<typeof createColors>,\n): string {\n const icon = getStatusIcon(issue.status);\n return `${colors.id(issue.id)} ${icon} ${issue.title}`;\n}\n\n/**\n * Format an inline issue mention.\n *\n * Format: {ID} ({TITLE})\n * (No kind shown)\n *\n * @example \"bd-a1b2 (Fix authentication timeout)\"\n */\nexport function formatIssueInline(issue: IssueForDisplay): string {\n return `${issue.id} (${issue.title})`;\n}\n\n/**\n * Format the table header row for issue listings.\n */\nexport function formatIssueHeader(colors: ReturnType<typeof createColors>): string {\n const idHeader = 'ID'.padEnd(ISSUE_COLUMNS.ID);\n const priHeader = 'PRI'.padEnd(ISSUE_COLUMNS.PRIORITY);\n const statusHeader = 'STATUS'.padEnd(ISSUE_COLUMNS.STATUS);\n const titleHeader = 'TITLE';\n\n return colors.dim(`${idHeader}${priHeader}${statusHeader}${titleHeader}`);\n}\n\n/**\n * Format the extended table header row with assignee column.\n */\nexport function formatIssueHeaderExtended(colors: ReturnType<typeof createColors>): string {\n const idHeader = 'ID'.padEnd(ISSUE_COLUMNS.ID);\n const priHeader = 'PRI'.padEnd(ISSUE_COLUMNS.PRIORITY);\n const statusHeader = 'STATUS'.padEnd(ISSUE_COLUMNS.STATUS);\n const assigneeHeader = 'ASSIGNEE'.padEnd(ISSUE_COLUMNS.ASSIGNEE);\n const titleHeader = 'TITLE';\n\n return colors.dim(`${idHeader}${priHeader}${statusHeader}${assigneeHeader}${titleHeader}`);\n}\n\n/**\n * Format an issue with long format (includes description on second line).\n *\n * Description is indented 6 spaces, dim color, max 2 lines.\n */\nexport function formatIssueLong(\n issue: IssueForDisplay,\n colors: ReturnType<typeof createColors>,\n maxWidth = 80,\n): string {\n const firstLine = formatIssueLine(issue, colors);\n\n if (!issue.description) {\n return firstLine;\n }\n\n const descLines = wrapDescription(issue.description, 6, 2, maxWidth);\n if (!descLines) {\n return firstLine;\n }\n\n return `${firstLine}\\n${colors.dim(descLines)}`;\n}\n\n/**\n * Word-wrap description text with indentation.\n *\n * @param text - The text to wrap\n * @param indent - Number of spaces to indent each line\n * @param maxLines - Maximum number of lines (truncates with ellipsis)\n * @param maxWidth - Maximum width per line (including indent)\n */\nexport function wrapDescription(\n text: string,\n indent: number,\n maxLines: number,\n maxWidth: number,\n): string {\n if (!text) return '';\n\n const indentStr = ' '.repeat(indent);\n const contentWidth = maxWidth - indent;\n\n // Split into words and wrap\n const words = text.split(/\\s+/);\n const lines: string[] = [];\n let currentLine = '';\n\n for (const word of words) {\n if (!currentLine) {\n currentLine = word;\n } else if (currentLine.length + 1 + word.length <= contentWidth) {\n currentLine += ' ' + word;\n } else {\n lines.push(currentLine);\n currentLine = word;\n\n // Stop if we've hit max lines\n if (lines.length >= maxLines) {\n break;\n }\n }\n }\n\n // Add remaining content\n if (currentLine && lines.length < maxLines) {\n lines.push(currentLine);\n }\n\n // Truncate last line if we have more content\n if (lines.length === maxLines && currentLine && !lines.includes(currentLine)) {\n const lastLine = lines[maxLines - 1];\n if (lastLine) {\n lines[maxLines - 1] = truncate(lastLine, contentWidth - 1) + ELLIPSIS;\n }\n }\n\n return lines.map((line) => indentStr + line).join('\\n');\n}\n\n/**\n * Format a spec path with the filename portion bolded.\n *\n * e.g. \"docs/project/specs/active/plan-2026-01-27-my-feature.md\"\n * → \"docs/project/specs/active/\" + bold(\"plan-2026-01-27-my-feature.md\")\n */\nexport function formatSpecName(specPath: string, colors: ReturnType<typeof createColors>): string {\n const lastSlash = specPath.lastIndexOf('/');\n if (lastSlash === -1) {\n return colors.bold(specPath);\n }\n const dir = specPath.slice(0, lastSlash + 1);\n const filename = specPath.slice(lastSlash + 1);\n return dir + colors.bold(filename);\n}\n\n/**\n * Format a spec group header for --specs output.\n *\n * Renders \"Spec: path/to/bold-filename.md (count)\".\n */\nexport function formatSpecGroupHeader(\n specPath: string,\n count: number,\n colors: ReturnType<typeof createColors>,\n): string {\n return 'Spec: ' + formatSpecName(specPath, colors) + colors.dim(` (${count})`);\n}\n\n/**\n * Format the \"No spec\" group header for beads without a linked spec.\n */\nexport function formatNoSpecGroupHeader(\n count: number,\n colors: ReturnType<typeof createColors>,\n): string {\n return colors.bold('(No spec)') + colors.dim(` (${count})`);\n}\n","/**\n * Tree view utilities for displaying issues with parent-child relationships.\n *\n * Used by `tbd list --pretty` to show hierarchical issue structure.\n */\n\nimport type { createColors } from './output.js';\nimport { formatPriority, getPriorityColor } from '../../lib/priority.js';\nimport { getStatusIcon, getStatusColor } from '../../lib/status.js';\nimport {\n formatKind,\n wrapDescription,\n ISSUE_COLUMNS,\n type IssueForDisplay,\n} from './issue-format.js';\nimport { comparisonChain, ordering } from '../../lib/comparison-chain.js';\nimport type { InternalIssueId } from '../../lib/ids.js';\n\n/**\n * Options for tree rendering.\n */\nexport interface TreeRenderOptions {\n /** Show descriptions (--long mode) */\n long?: boolean;\n /** Terminal width for description wrapping */\n maxWidth?: number;\n}\n\n/**\n * Tree node representing an issue with its children.\n */\nexport interface TreeNode {\n issue: IssueForDisplay;\n children: TreeNode[];\n}\n\n/**\n * Unicode box-drawing characters for tree display.\n */\nconst TREE_CHARS = {\n /** Middle child connector: ├── */\n BRANCH: '├── ',\n /** Last child connector: └── */\n LAST: '└── ',\n /** Vertical line continuation: │ */\n VERTICAL: '│ ',\n /** Empty space for alignment: */\n SPACE: ' ',\n} as const;\n\n/**\n * Issue input for tree building, with optional parent and ordering hints.\n */\nexport interface IssueForTree extends IssueForDisplay {\n parentId?: string;\n /** Internal ID for matching against order hints (optional, defaults to id) */\n internalId?: InternalIssueId;\n /** Ordered list of child internal IDs for preferred display order */\n child_order_hints?: InternalIssueId[];\n}\n\n/**\n * Get the internal ID for an issue (used for matching against order hints).\n * Falls back to display ID if internalId is not set.\n */\nfunction getInternalId(issue: IssueForTree): string {\n return issue.internalId ?? issue.id;\n}\n\n/**\n * Sort children using order hints from the parent.\n *\n * Children in hints appear first, in hints order.\n * Children not in hints appear after, sorted by ID for determinism.\n * Uses internalId for matching against hints (which contain internal IDs).\n */\nfunction sortChildren(children: TreeNode[], hints: InternalIssueId[] | undefined): void {\n if (!hints || hints.length === 0) {\n // No hints - sort by ID for determinism\n children.sort(\n comparisonChain<TreeNode>()\n .compare((n) => n.issue.id)\n .result(),\n );\n return;\n }\n\n // Sort using manual ordering: items in hints first, then by ID\n // Use internalId for matching since hints contain internal IDs\n // Cast to string[] since ordering.manual works with any strings\n children.sort(\n comparisonChain<TreeNode>()\n .compare((n) => getInternalId(n.issue as IssueForTree), ordering.manual(hints as string[]))\n .compare((n) => n.issue.id) // Secondary sort for items not in hints\n .result(),\n );\n}\n\n/**\n * Build a tree structure from a flat list of issues.\n *\n * Groups children under their parents based on parent_id.\n * Issues without a parent (or whose parent is not in the list) become root nodes.\n * Children are sorted according to parent's child_order_hints if available.\n *\n * @param issues - Flat list of issues with optional parent_id and child_order_hints\n * @returns Array of root tree nodes with nested children\n */\nexport function buildIssueTree(issues: IssueForTree[]): TreeNode[] {\n // Create a map for quick lookup by ID\n const issueMap = new Map<string, TreeNode>();\n // Store order hints per parent (internal IDs for child ordering)\n const orderHintsMap = new Map<string, InternalIssueId[]>();\n const roots: TreeNode[] = [];\n\n // First pass: create nodes for all issues and collect order hints\n for (const issue of issues) {\n issueMap.set(issue.id, { issue, children: [] });\n if (issue.child_order_hints) {\n orderHintsMap.set(issue.id, issue.child_order_hints);\n }\n }\n\n // Second pass: build parent-child relationships\n for (const issue of issues) {\n const node = issueMap.get(issue.id)!;\n\n if (issue.parentId && issueMap.has(issue.parentId)) {\n // Has a parent that's in our list - add as child\n const parentNode = issueMap.get(issue.parentId)!;\n parentNode.children.push(node);\n } else {\n // No parent or parent not in list - this is a root\n roots.push(node);\n }\n }\n\n // Third pass: sort children using parent's order hints\n for (const node of issueMap.values()) {\n if (node.children.length > 0) {\n const hints = orderHintsMap.get(node.issue.id);\n sortChildren(node.children, hints);\n }\n }\n\n // Root nodes preserve their input order (list command already sorts by priority)\n\n return roots;\n}\n\n/**\n * Format a single issue line for tree view (no header, compact format).\n *\n * Format: {ID} {PRI} {STATUS} [kind] {TITLE}\n *\n * ID column is padded to ISSUE_COLUMNS.ID width for consistent alignment.\n */\nfunction formatTreeIssueLine(\n issue: IssueForDisplay,\n colors: ReturnType<typeof createColors>,\n): string {\n const id = colors.id(issue.id.padEnd(ISSUE_COLUMNS.ID));\n const pri = getPriorityColor(issue.priority, colors)(formatPriority(issue.priority));\n const statusText = `${getStatusIcon(issue.status)} ${issue.status}`;\n const status = getStatusColor(issue.status, colors)(statusText);\n const kind = colors.dim(formatKind(issue.kind));\n\n return `${id} ${pri} ${status} ${kind} ${issue.title}`;\n}\n\n/**\n * Render a tree node and its children as formatted lines.\n *\n * @param node - The tree node to render\n * @param colors - Color functions for formatting\n * @param prefix - Current line prefix (for nested indentation)\n * @param options - Rendering options (long mode, max width)\n * @returns Array of formatted lines\n */\nfunction renderTreeNode(\n node: TreeNode,\n colors: ReturnType<typeof createColors>,\n prefix = '',\n options: TreeRenderOptions = {},\n): string[] {\n const lines: string[] = [];\n const { long = false, maxWidth = 80 } = options;\n\n // Render this node\n const issueLine = formatTreeIssueLine(node.issue, colors);\n lines.push(prefix + issueLine);\n\n // Render description if --long and description exists\n if (long && node.issue.description) {\n // Calculate indent: prefix length + 6 spaces for description alignment\n const descIndent = prefix.length + 6;\n const descWidth = maxWidth - descIndent;\n if (descWidth > 20) {\n const wrapped = wrapDescription(node.issue.description, 6, 2, descWidth + 6);\n if (wrapped) {\n // Add prefix to each description line\n const descLines = wrapped.split('\\n');\n for (const descLine of descLines) {\n lines.push(prefix + colors.dim(descLine));\n }\n }\n }\n }\n\n // Render children\n const childCount = node.children.length;\n node.children.forEach((child, index) => {\n const isLastChild = index === childCount - 1;\n\n // Determine the connector for this child\n const connector = isLastChild ? TREE_CHARS.LAST : TREE_CHARS.BRANCH;\n\n // Determine the prefix for continuation lines (descriptions, grandchildren)\n // If this child is not last, we need a vertical line; otherwise space\n const childPrefix = prefix + (isLastChild ? TREE_CHARS.SPACE : TREE_CHARS.VERTICAL);\n\n // Render child with childPrefix so it knows the correct indentation for descriptions\n const childLines = renderTreeNode(child, colors, childPrefix, options);\n\n // Process lines: first line gets connector, others keep childPrefix\n childLines.forEach((line, lineIndex) => {\n if (lineIndex === 0) {\n // Replace childPrefix with connector for the first line\n const lineWithoutPrefix = line.slice(childPrefix.length);\n lines.push(colors.dim(connector) + lineWithoutPrefix);\n } else {\n // Keep childPrefix for continuation lines (already included)\n lines.push(line);\n }\n });\n });\n\n return lines;\n}\n\n/**\n * Render a complete tree view of issues.\n *\n * @param roots - Array of root tree nodes\n * @param colors - Color functions for formatting\n * @param options - Rendering options (long mode, max width)\n * @returns Array of formatted lines (without header, count is separate)\n */\nexport function renderIssueTree(\n roots: TreeNode[],\n colors: ReturnType<typeof createColors>,\n options: TreeRenderOptions = {},\n): string[] {\n const lines: string[] = [];\n\n for (const root of roots) {\n const rootLines = renderTreeNode(root, colors, '', options);\n lines.push(...rootLines);\n }\n\n return lines;\n}\n\n/**\n * Count total issues in a tree (including all nested children).\n */\nexport function countTreeIssues(roots: TreeNode[]): number {\n let count = 0;\n\n function countNode(node: TreeNode): void {\n count++;\n for (const child of node.children) {\n countNode(child);\n }\n }\n\n for (const root of roots) {\n countNode(root);\n }\n\n return count;\n}\n","/**\n * Spec path matching utilities.\n *\n * Provides gradual path matching for linking beads to spec documents.\n * Supports matching by filename, partial path suffix, or full path.\n *\n * See: plan-2026-01-26-spec-linking.md §Gradual Path Matching Algorithm\n */\n\nimport { basename } from 'node:path';\n\n/**\n * Check if a stored spec path matches a query path using gradual matching.\n *\n * Matching rules (in order of precedence):\n * 1. Exact match after normalization\n * 2. Suffix match: stored path ends with query path at a path separator\n * 3. Filename match: query matches the filename portion of stored path\n *\n * @param storedPath - The spec_path stored in the issue (e.g., \"docs/specs/plan-feature.md\")\n * @param queryPath - The path to match against (e.g., \"plan-feature.md\" or \"specs/plan-feature.md\")\n * @returns true if the paths match\n *\n * @example\n * // All these queries match stored path \"docs/project/specs/active/plan-2026-01-26-feature.md\":\n * matchesSpecPath(stored, \"plan-2026-01-26-feature.md\") // filename match\n * matchesSpecPath(stored, \"feature.md\") // partial filename - NO MATCH (too ambiguous)\n * matchesSpecPath(stored, \"active/plan-2026-01-26-feature.md\") // suffix match\n * matchesSpecPath(stored, \"docs/project/specs/active/plan-2026-01-26-feature.md\") // exact match\n */\nexport function matchesSpecPath(storedPath: string, queryPath: string): boolean {\n // Handle empty/null cases\n if (!storedPath || !queryPath) {\n return false;\n }\n\n // Normalize paths: remove leading ./ and trailing /\n const normalizedStored = normalizePath(storedPath);\n const normalizedQuery = normalizePath(queryPath);\n\n // Empty after normalization\n if (!normalizedStored || !normalizedQuery) {\n return false;\n }\n\n // 1. Exact match\n if (normalizedStored === normalizedQuery) {\n return true;\n }\n\n // 2. Suffix match: stored path ends with /query\n // This handles partial path matches like \"active/plan.md\" matching \"docs/specs/active/plan.md\"\n if (normalizedStored.endsWith('/' + normalizedQuery)) {\n return true;\n }\n\n // 3. Filename match: query is just a filename that matches stored's filename\n // Only do filename match if query has no directory components\n // (otherwise it would have matched in suffix check above)\n if (!normalizedQuery.includes('/') && basename(normalizedStored) === normalizedQuery) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Normalize a path for comparison.\n * - Removes leading ./\n * - Removes trailing /\n * - Collapses multiple slashes\n */\nfunction normalizePath(path: string): string {\n return path\n .replace(/^\\.\\//, '') // Remove leading ./\n .replace(/\\/+$/, '') // Remove trailing /\n .replace(/\\/+/g, '/'); // Collapse multiple slashes\n}\n","/**\n * `tbd list` - List issues.\n *\n * See: tbd-design.md §4.4 List\n */\n\nimport { Command } from 'commander';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { applyLimit } from '../lib/limit-utils.js';\nimport { requireInit } from '../lib/errors.js';\nimport { loadDataContext } from '../lib/data-context.js';\nimport type { Issue, IssueStatusType, IssueKindType } from '../../lib/types.js';\nimport { listIssues } from '../../file/storage.js';\nimport { formatDisplayId, formatDebugId, extractUlidFromInternalId } from '../../lib/ids.js';\nimport type { IdMapping } from '../../file/id-mapping.js';\nimport { resolveToInternalId } from '../../file/id-mapping.js';\nimport { comparisonChain, ordering } from '../../lib/comparison-chain.js';\nimport {\n formatIssueLine,\n formatIssueLong,\n formatIssueHeader,\n formatSpecGroupHeader,\n formatNoSpecGroupHeader,\n type IssueForDisplay,\n} from '../lib/issue-format.js';\nimport { parsePriority } from '../../lib/priority.js';\nimport { buildIssueTree, renderIssueTree } from '../lib/tree-view.js';\nimport { getTerminalWidth, type createColors } from '../lib/output.js';\nimport { matchesSpecPath } from '../../lib/spec-matching.js';\n\ninterface ListOptions {\n status?: IssueStatusType;\n all?: boolean;\n type?: IssueKindType;\n priority?: string;\n assignee?: string;\n label?: string[];\n parent?: string;\n spec?: string;\n deferred?: boolean;\n deferBefore?: string;\n sort?: string;\n limit?: string;\n count?: boolean;\n long?: boolean;\n pretty?: boolean;\n specs?: boolean;\n}\n\nclass ListHandler extends BaseCommand {\n async run(options: ListOptions): Promise<void> {\n const tbdRoot = await requireInit();\n\n // Load shared data context (dataSyncDir, mapping, config, prefix)\n const dataCtx = await loadDataContext(tbdRoot);\n let issues = await listIssues(dataCtx.dataSyncDir);\n\n // Apply filters\n issues = this.filterIssues(issues, options, dataCtx.mapping);\n\n // Sort results (with secondary sort by short ID for stable ordering)\n issues = this.sortIssues(issues, options.sort ?? 'priority', dataCtx.mapping);\n\n // Apply limit\n issues = applyLimit(issues, options.limit);\n\n // Count-only mode for testing\n if (options.count) {\n this.output.data({ count: issues.length }, () => {\n console.log(issues.length);\n });\n return;\n }\n\n const showDebug = this.ctx.debug;\n const { mapping, prefix } = dataCtx;\n\n // Format output - use short display IDs instead of internal ULIDs\n const displayIssues = issues.map((i) => ({\n id: showDebug ? formatDebugId(i.id, mapping, prefix) : formatDisplayId(i.id, mapping, prefix),\n internalId: i.id,\n parentId: i.parent_id\n ? showDebug\n ? formatDebugId(i.parent_id, mapping, prefix)\n : formatDisplayId(i.parent_id, mapping, prefix)\n : undefined,\n priority: i.priority,\n status: i.status,\n kind: i.kind,\n title: i.title,\n description: i.description ?? undefined,\n assignee: i.assignee ?? undefined,\n labels: i.labels,\n spec_path: i.spec_path ?? undefined,\n // Use internal IDs for order hints (buildIssueTree compares against internal IDs)\n child_order_hints: i.child_order_hints ?? undefined,\n }));\n\n this.output.data(displayIssues, () => {\n if (issues.length === 0) {\n console.log('No issues found');\n return;\n }\n\n const colors = this.output.getColors();\n\n if (options.specs) {\n this.renderGroupedBySpec(displayIssues, options, colors);\n } else {\n this.renderFlat(displayIssues, options, colors);\n }\n\n console.log('');\n console.log(colors.dim(`${issues.length} issue(s)`));\n });\n }\n\n private renderFlat(\n displayIssues: (IssueForDisplay & { parentId?: string; spec_path?: string })[],\n options: ListOptions,\n colors: ReturnType<typeof createColors>,\n ): void {\n if (options.pretty) {\n const tree = buildIssueTree(displayIssues);\n const lines = renderIssueTree(tree, colors, {\n long: options.long,\n maxWidth: getTerminalWidth(),\n });\n for (const line of lines) {\n console.log(line);\n }\n } else {\n console.log(formatIssueHeader(colors));\n for (const issue of displayIssues) {\n if (options.long) {\n console.log(formatIssueLong(issue, colors));\n } else {\n console.log(formatIssueLine(issue, colors));\n }\n }\n }\n }\n\n private renderGroupedBySpec(\n displayIssues: (IssueForDisplay & { parentId?: string; spec_path?: string })[],\n options: ListOptions,\n colors: ReturnType<typeof createColors>,\n ): void {\n // Group issues by spec_path\n const specGroups = new Map<string, typeof displayIssues>();\n const noSpecIssues: typeof displayIssues = [];\n\n for (const issue of displayIssues) {\n if (issue.spec_path) {\n const group = specGroups.get(issue.spec_path);\n if (group) {\n group.push(issue);\n } else {\n specGroups.set(issue.spec_path, [issue]);\n }\n } else {\n noSpecIssues.push(issue);\n }\n }\n\n // Render each spec group\n let first = true;\n for (const [specPath, groupIssues] of specGroups) {\n if (!first) {\n console.log('');\n }\n first = false;\n\n console.log(formatSpecGroupHeader(specPath, groupIssues.length, colors));\n console.log('');\n this.renderFlat(groupIssues, options, colors);\n }\n\n // Render \"No spec\" group at the end\n if (noSpecIssues.length > 0) {\n if (!first) {\n console.log('');\n }\n console.log(formatNoSpecGroupHeader(noSpecIssues.length, colors));\n console.log('');\n this.renderFlat(noSpecIssues, options, colors);\n }\n }\n\n private filterIssues(issues: Issue[], options: ListOptions, mapping: IdMapping): Issue[] {\n // Resolve parent filter to internal ID if provided\n let resolvedParentId: string | undefined;\n if (options.parent) {\n try {\n resolvedParentId = resolveToInternalId(options.parent, mapping);\n } catch {\n // If parent ID cannot be resolved, no issues will match\n return [];\n }\n }\n\n return issues.filter((issue) => {\n // By default, exclude closed issues unless --all or --status closed\n if (!options.all && options.status !== 'closed' && issue.status === 'closed') {\n return false;\n }\n\n // Status filter\n if (options.status && issue.status !== options.status) {\n return false;\n }\n\n // Type filter\n if (options.type && issue.kind !== options.type) {\n return false;\n }\n\n // Priority filter - supports both numeric (1) and prefixed (P1) formats\n if (options.priority !== undefined) {\n const priority = parsePriority(options.priority);\n if (priority !== undefined && issue.priority !== priority) {\n return false;\n }\n }\n\n // Assignee filter\n if (options.assignee && issue.assignee !== options.assignee) {\n return false;\n }\n\n // Label filter (all must match)\n if (options.label && options.label.length > 0) {\n const hasAllLabels = options.label.every((l) => issue.labels.includes(l));\n if (!hasAllLabels) {\n return false;\n }\n }\n\n // Parent filter (compare resolved internal IDs)\n if (resolvedParentId && issue.parent_id !== resolvedParentId) {\n return false;\n }\n\n // Spec path filter (uses gradual matching)\n if (options.spec) {\n if (!issue.spec_path || !matchesSpecPath(issue.spec_path, options.spec)) {\n return false;\n }\n }\n\n // Deferred filter\n if (options.deferred && issue.status !== 'deferred') {\n return false;\n }\n\n return true;\n });\n }\n\n private sortIssues(issues: Issue[], sortField: string, _mapping: IdMapping): Issue[] {\n const primarySelector: (i: Issue) => number =\n sortField === 'created'\n ? (i) => new Date(i.created_at).getTime()\n : sortField === 'updated'\n ? (i) => new Date(i.updated_at).getTime()\n : (i) => i.priority;\n\n // For created/updated, reverse so newest comes first; for priority, ascending\n const primaryOrdering =\n sortField === 'created' || sortField === 'updated' ? ordering.reversed : ordering.default;\n\n return [...issues].sort(\n comparisonChain<Issue>()\n .compare(primarySelector, primaryOrdering)\n // Tiebreak by internal ULID (chronological and deterministic, unlike random short IDs)\n .compare((i) => extractUlidFromInternalId(i.id))\n .result(),\n );\n }\n}\n\nexport const listCommand = new Command('list')\n .description('List issues')\n .option('--status <status>', 'Filter: open, in_progress, blocked, deferred, closed')\n .option('--all', 'Include closed issues')\n .option('--type <type>', 'Filter: bug, feature, task, epic')\n .option('--priority <0-4>', 'Filter by priority')\n .option('--assignee <name>', 'Filter by assignee')\n .option('--label <label>', 'Filter by label (repeatable)', (val, prev: string[] = []) => [\n ...prev,\n val,\n ])\n .option('--parent <id>', 'List children of parent')\n .option(\n '--spec <path>',\n 'Filter by spec path (matches full path, partial path suffix, or filename)',\n )\n .option('--deferred', 'Show only deferred issues')\n .option('--defer-before <date>', 'Deferred before date')\n .option('--sort <field>', 'Sort by: priority, created, updated', 'priority')\n .option('--limit <n>', 'Limit results')\n .option('--count', 'Output only the count of matching issues')\n .option('--long', 'Show descriptions')\n .option('--pretty', 'Show tree view with parent-child relationships')\n .option('--specs', 'Group output by linked spec')\n .action(async (options, command) => {\n const handler = new ListHandler(command);\n await handler.run(options);\n });\n","/**\n * `tbd show` - Show issue details.\n *\n * See: tbd-design.md §4.4 Show\n */\n\nimport { Command } from 'commander';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { NotFoundError } from '../lib/errors.js';\nimport { loadFullContext } from '../lib/data-context.js';\nimport { readIssue } from '../../file/storage.js';\nimport { serializeIssue } from '../../file/parser.js';\nimport { formatPriority, getPriorityColor } from '../../lib/priority.js';\nimport { getStatusColor } from '../../lib/status.js';\nimport { PARENT_CONTEXT_MAX_LINES } from '../../lib/settings.js';\nimport type { createColors } from '../lib/output.js';\nimport type { Issue, IssueStatusType } from '../../lib/types.js';\n\ninterface ShowOptions {\n showOrder?: boolean;\n parent?: boolean; // Commander: --no-parent sets this to false (default: true)\n maxLines?: string;\n}\n\n/**\n * Render a serialized issue with colorized output, optionally truncated.\n *\n * @param issue - The issue to render\n * @param colors - Color functions\n * @param maxLines - If set, truncate output to this many lines with an omission notice\n * @returns Array of formatted lines\n */\nfunction renderIssueLines(issue: Issue, colors: ReturnType<typeof createColors>): string[] {\n const serialized = serializeIssue(issue);\n const output: string[] = [];\n\n for (const line of serialized.split('\\n')) {\n if (line === '---') {\n output.push(colors.dim(line));\n } else if (line.startsWith('id:')) {\n output.push(`${colors.dim('id:')} ${colors.id(line.slice(4))}`);\n } else if (line.startsWith('status:')) {\n const status = line.slice(8).trim() as IssueStatusType;\n const statusColor = getStatusColor(status, colors);\n output.push(`${colors.dim('status:')} ${statusColor(status)}`);\n } else if (line.startsWith('priority:')) {\n const priority = parseInt(line.slice(10).trim(), 10);\n const priorityColor = getPriorityColor(priority, colors);\n output.push(`${colors.dim('priority:')} ${priorityColor(formatPriority(priority))}`);\n } else if (line.startsWith('title:')) {\n output.push(`${colors.dim('title:')} ${colors.bold(line.slice(7))}`);\n } else if (line.startsWith('spec_path:')) {\n output.push(`${colors.dim('spec_path:')} ${colors.id(line.slice(11))}`);\n } else if (line.startsWith('## Notes')) {\n output.push(colors.bold(line));\n } else if (line.startsWith(' - ')) {\n output.push(` - ${colors.label(line.slice(4))}`);\n } else {\n output.push(line);\n }\n }\n\n return output;\n}\n\n/**\n * Print lines with optional max-lines truncation.\n * If maxLines is set and the output exceeds it, truncates and appends an omission notice.\n */\nfunction printWithTruncation(\n lines: string[],\n colors: ReturnType<typeof createColors>,\n maxLines?: number,\n): void {\n if (maxLines && lines.length > maxLines) {\n const omitted = lines.length - maxLines;\n for (const line of lines.slice(0, maxLines)) {\n console.log(line);\n }\n console.log(colors.dim(`… [${omitted} line${omitted === 1 ? '' : 's'} omitted]`));\n } else {\n for (const line of lines) {\n console.log(line);\n }\n }\n}\n\nclass ShowHandler extends BaseCommand {\n async run(id: string, command: Command, options: ShowOptions): Promise<void> {\n // Load unified context with data and helpers\n const ctx = await loadFullContext(command);\n\n // Resolve input ID to internal ID using helper\n const internalId = ctx.resolveId(id);\n\n let issue;\n try {\n issue = await readIssue(ctx.dataSyncDir, internalId);\n } catch {\n throw new NotFoundError('Issue', id);\n }\n\n // Load parent issue if this is a child and --no-parent not specified\n let parentIssue: Issue | undefined;\n if (issue.parent_id && options.parent !== false) {\n try {\n parentIssue = await readIssue(ctx.dataSyncDir, issue.parent_id);\n } catch {\n // Parent referenced but missing - silently skip\n }\n }\n\n // Parse --max-lines if provided\n const maxLines = options.maxLines ? parseInt(options.maxLines, 10) : undefined;\n\n // Format display ID using helper (respects debug mode automatically)\n const displayId = ctx.displayId(issue.id);\n\n // Create display version with short display ID\n const displayIssue = {\n ...issue,\n displayId,\n ...(parentIssue\n ? {\n parent: {\n ...parentIssue,\n displayId: ctx.displayId(parentIssue.id),\n },\n }\n : {}),\n };\n\n this.output.data(displayIssue, () => {\n const colors = this.output.getColors();\n\n // Render main issue first (what the user asked for)\n const issueLines = renderIssueLines(issue, colors);\n printWithTruncation(issueLines, colors, maxLines);\n\n // Show parent context below if this is a child issue\n if (parentIssue) {\n console.log('');\n console.log(colors.dim('The parent of this bead is:'));\n const parentLines = renderIssueLines(parentIssue, colors);\n printWithTruncation(parentLines, colors, PARENT_CONTEXT_MAX_LINES);\n }\n\n // Show child_order_hints if --show-order is specified\n if (options.showOrder) {\n console.log('');\n console.log(colors.dim('child_order_hints:'));\n if (issue.child_order_hints && issue.child_order_hints.length > 0) {\n for (const hintId of issue.child_order_hints) {\n const shortId = ctx.displayId(hintId);\n console.log(` - ${colors.id(shortId)}`);\n }\n } else {\n console.log(` ${colors.dim('(none)')}`);\n }\n }\n });\n }\n}\n\nexport const showCommand = new Command('show')\n .description('Show issue details')\n .argument('<id>', 'Issue ID')\n .option('--show-order', 'Display children ordering hints')\n .option('--no-parent', 'Suppress automatic parent context display')\n .option('--max-lines <n>', 'Truncate output to N lines')\n .action(async (id, options, command) => {\n const handler = new ShowHandler(command);\n await handler.run(id, command, options);\n });\n","/**\n * `tbd update` - Update an issue.\n *\n * See: tbd-design.md §4.4 Update\n */\n\nimport { Command } from 'commander';\nimport { readFile } from 'node:fs/promises';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { requireInit, NotFoundError, ValidationError, CLIError } from '../lib/errors.js';\nimport { readIssue, writeIssue, listIssues } from '../../file/storage.js';\nimport { parseMarkdownWithFrontmatter } from '../../file/parser.js';\nimport { formatDisplayId, formatDebugId } from '../../lib/ids.js';\nimport { IssueStatus, IssueKind } from '../../lib/schemas.js';\nimport { parsePriority } from '../../lib/priority.js';\nimport type { IssueStatusType, IssueKindType, PriorityType } from '../../lib/types.js';\nimport { resolveDataSyncDir } from '../../lib/paths.js';\nimport { now } from '../../utils/time-utils.js';\nimport { loadIdMapping, resolveToInternalId, type IdMapping } from '../../file/id-mapping.js';\nimport { readConfig } from '../../file/config.js';\nimport { resolveAndValidatePath, getPathErrorMessage } from '../../lib/project-paths.js';\nimport { validateIssueTitle } from '../lib/issue-input-validation.js';\n\ninterface UpdateOptions {\n fromFile?: string;\n title?: string;\n status?: string;\n type?: string;\n priority?: string;\n assignee?: string;\n description?: string;\n notes?: string;\n notesFile?: string;\n due?: string;\n defer?: string;\n addLabel?: string[];\n removeLabel?: string[];\n parent?: string;\n spec?: string;\n childOrder?: string;\n}\n\nclass UpdateHandler extends BaseCommand {\n async run(id: string, options: UpdateOptions): Promise<void> {\n const tbdRoot = await requireInit();\n\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n\n // Load ID mapping for resolution\n const mapping = await loadIdMapping(dataSyncDir);\n\n // Resolve input ID to internal ID\n let internalId: string;\n try {\n internalId = resolveToInternalId(id, mapping);\n } catch {\n throw new NotFoundError('Issue', id);\n }\n\n // Load existing issue\n let issue;\n try {\n issue = await readIssue(dataSyncDir, internalId);\n } catch {\n throw new NotFoundError('Issue', id);\n }\n\n // Parse and validate options\n const updates = await this.parseUpdates(options, mapping, tbdRoot);\n if (updates === null) return;\n\n if (this.checkDryRun('Would update issue', { id: internalId, ...updates })) {\n return;\n }\n\n // Capture old spec_path before applying updates (for propagation)\n const oldSpecPath = issue.spec_path;\n\n // Apply updates\n if (updates.title !== undefined) issue.title = updates.title;\n if (updates.status !== undefined) issue.status = updates.status;\n if (updates.kind !== undefined) issue.kind = updates.kind;\n if (updates.priority !== undefined) issue.priority = updates.priority;\n if (updates.assignee !== undefined) issue.assignee = updates.assignee;\n if (updates.description !== undefined) issue.description = updates.description;\n if (updates.notes !== undefined) issue.notes = updates.notes;\n if (updates.due_date !== undefined) issue.due_date = updates.due_date;\n if (updates.deferred_until !== undefined) issue.deferred_until = updates.deferred_until;\n if (updates.parent_id !== undefined) issue.parent_id = updates.parent_id;\n if (updates.spec_path !== undefined) issue.spec_path = updates.spec_path;\n if (updates.child_order_hints !== undefined)\n issue.child_order_hints = updates.child_order_hints;\n\n // Inherit spec_path from new parent when re-parenting without explicit --spec\n if (updates.parent_id && options.spec === undefined && !issue.spec_path) {\n try {\n const parentIssue = await readIssue(dataSyncDir, updates.parent_id);\n if (parentIssue.spec_path) {\n issue.spec_path = parentIssue.spec_path;\n }\n } catch {\n // Parent not found — skip inheritance\n }\n }\n\n // Handle full labels replacement (from --from-file)\n if (updates.labels !== undefined) {\n issue.labels = updates.labels;\n }\n\n // Handle label updates\n if (updates.addLabels && updates.addLabels.length > 0) {\n const labelsSet = new Set(issue.labels);\n for (const label of updates.addLabels) {\n labelsSet.add(label);\n }\n issue.labels = [...labelsSet];\n }\n if (updates.removeLabels && updates.removeLabels.length > 0) {\n const removeSet = new Set(updates.removeLabels);\n issue.labels = issue.labels.filter((l) => !removeSet.has(l));\n }\n\n // Update metadata\n issue.version += 1;\n issue.updated_at = now();\n\n // Save\n await this.execute(async () => {\n await writeIssue(dataSyncDir, issue);\n }, 'Failed to update issue');\n\n // When setting a new parent, append child to parent's child_order_hints\n if (updates.parent_id) {\n try {\n const parentIssue = await readIssue(dataSyncDir, updates.parent_id);\n const hints = parentIssue.child_order_hints ?? [];\n\n // Only append if not already in hints\n if (!hints.includes(internalId)) {\n parentIssue.child_order_hints = [...hints, internalId];\n parentIssue.version += 1;\n parentIssue.updated_at = now();\n await writeIssue(dataSyncDir, parentIssue);\n }\n } catch {\n // Parent not found or other error - skip order hint update\n }\n }\n\n // Propagate spec_path to children when parent's spec changes\n if (updates.spec_path !== undefined && issue.spec_path && issue.spec_path !== oldSpecPath) {\n const allIssues = await listIssues(dataSyncDir);\n const children = allIssues.filter((i) => i.parent_id === issue.id);\n const timestamp = now();\n for (const child of children) {\n if (!child.spec_path || child.spec_path === oldSpecPath) {\n child.spec_path = issue.spec_path;\n child.version += 1;\n child.updated_at = timestamp;\n await writeIssue(dataSyncDir, child);\n }\n }\n }\n\n // Use already loaded mapping for display\n const showDebug = this.ctx.debug;\n const config = await readConfig(tbdRoot);\n const prefix = config.display.id_prefix;\n const displayId = showDebug\n ? formatDebugId(issue.id, mapping, prefix)\n : formatDisplayId(issue.id, mapping, prefix);\n\n this.output.data({ id: displayId, updated: true }, () => {\n this.output.success(`Updated ${displayId}`);\n });\n }\n\n private async parseUpdates(\n options: UpdateOptions,\n mapping: IdMapping,\n tbdRoot: string,\n ): Promise<{\n title?: string;\n status?: IssueStatusType;\n kind?: IssueKindType;\n priority?: PriorityType;\n assignee?: string | null;\n description?: string | null;\n notes?: string | null;\n due_date?: string | null;\n deferred_until?: string | null;\n parent_id?: string | null;\n spec_path?: string | null;\n child_order_hints?: string[] | null;\n addLabels?: string[];\n removeLabels?: string[];\n labels?: string[];\n } | null> {\n const updates: {\n title?: string;\n status?: IssueStatusType;\n kind?: IssueKindType;\n priority?: PriorityType;\n assignee?: string | null;\n description?: string | null;\n notes?: string | null;\n due_date?: string | null;\n deferred_until?: string | null;\n parent_id?: string | null;\n spec_path?: string | null;\n child_order_hints?: string[] | null;\n addLabels?: string[];\n removeLabels?: string[];\n labels?: string[];\n } = {};\n\n // Handle --from-file: read all mutable fields from YAML+Markdown file\n if (options.fromFile) {\n let content: string;\n try {\n content = await readFile(options.fromFile, 'utf-8');\n } catch {\n throw new CLIError(`Failed to read file: ${options.fromFile}`);\n }\n\n try {\n const { frontmatter, description, notes } = parseMarkdownWithFrontmatter(content);\n\n // Extract mutable fields from frontmatter\n if (typeof frontmatter.title === 'string') {\n updates.title = validateIssueTitle(frontmatter.title, {\n emptyMessage: 'Title cannot be empty',\n rejectBlank: true,\n });\n }\n if (typeof frontmatter.status === 'string') {\n const result = IssueStatus.safeParse(frontmatter.status);\n if (result.success) {\n updates.status = result.data;\n }\n }\n if (typeof frontmatter.kind === 'string') {\n const result = IssueKind.safeParse(frontmatter.kind);\n if (result.success) {\n updates.kind = result.data;\n }\n }\n if (typeof frontmatter.priority === 'number') {\n const priority = parsePriority(String(frontmatter.priority));\n if (priority !== undefined) {\n updates.priority = priority;\n }\n }\n if (frontmatter.assignee !== undefined) {\n updates.assignee = typeof frontmatter.assignee === 'string' ? frontmatter.assignee : null;\n }\n if (frontmatter.due_date !== undefined) {\n updates.due_date = typeof frontmatter.due_date === 'string' ? frontmatter.due_date : null;\n }\n if (frontmatter.deferred_until !== undefined) {\n updates.deferred_until =\n typeof frontmatter.deferred_until === 'string' ? frontmatter.deferred_until : null;\n }\n if (frontmatter.parent_id !== undefined) {\n updates.parent_id =\n typeof frontmatter.parent_id === 'string' ? frontmatter.parent_id : null;\n }\n if (frontmatter.spec_path !== undefined) {\n if (typeof frontmatter.spec_path === 'string' && frontmatter.spec_path) {\n // Validate and normalize the spec path from file\n try {\n const resolved = await resolveAndValidatePath(\n frontmatter.spec_path,\n tbdRoot,\n process.cwd(),\n );\n updates.spec_path = resolved.relativePath;\n } catch (error) {\n throw new ValidationError(getPathErrorMessage(error));\n }\n } else {\n updates.spec_path = null;\n }\n }\n if (Array.isArray(frontmatter.labels)) {\n updates.labels = frontmatter.labels.filter((l): l is string => typeof l === 'string');\n }\n\n // Set description and notes from body\n updates.description = description || null;\n updates.notes = notes || null;\n } catch (error) {\n throw new CLIError(\n `Failed to parse file: ${options.fromFile}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n return updates;\n }\n\n if (options.title !== undefined) {\n updates.title = validateIssueTitle(options.title, {\n emptyMessage: 'Title cannot be empty',\n rejectBlank: true,\n });\n }\n\n if (options.status) {\n const result = IssueStatus.safeParse(options.status);\n if (!result.success) {\n throw new ValidationError(`Invalid status: ${options.status}`);\n }\n updates.status = result.data;\n }\n\n if (options.type) {\n const result = IssueKind.safeParse(options.type);\n if (!result.success) {\n throw new ValidationError(`Invalid type: ${options.type}`);\n }\n updates.kind = result.data;\n }\n\n if (options.priority) {\n // Use shared parsePriority which accepts both \"P1\" and \"1\" formats\n const priority = parsePriority(options.priority);\n if (priority === undefined) {\n throw new ValidationError(`Invalid priority: ${options.priority}. Use P0-P4 or 0-4.`);\n }\n updates.priority = priority;\n }\n\n if (options.assignee !== undefined) {\n updates.assignee = options.assignee || null;\n }\n\n if (options.description !== undefined) {\n updates.description = options.description || null;\n }\n\n if (options.notes !== undefined) {\n updates.notes = options.notes || null;\n }\n\n if (options.notesFile) {\n try {\n updates.notes = await readFile(options.notesFile, 'utf-8');\n } catch {\n throw new CLIError(`Failed to read notes from file: ${options.notesFile}`);\n }\n }\n\n if (options.due !== undefined) {\n updates.due_date = options.due || null;\n }\n\n if (options.defer !== undefined) {\n updates.deferred_until = options.defer || null;\n }\n\n if (options.parent !== undefined) {\n if (options.parent) {\n try {\n updates.parent_id = resolveToInternalId(options.parent, mapping);\n } catch {\n throw new ValidationError(`Invalid parent ID: ${options.parent}`);\n }\n } else {\n updates.parent_id = null;\n }\n }\n\n if (options.spec !== undefined) {\n if (options.spec) {\n // Non-empty spec path: validate and normalize\n try {\n const resolved = await resolveAndValidatePath(options.spec, tbdRoot, process.cwd());\n updates.spec_path = resolved.relativePath;\n } catch (error) {\n throw new ValidationError(getPathErrorMessage(error));\n }\n } else {\n // Empty string: clear the spec path (no validation needed)\n updates.spec_path = null;\n }\n }\n\n if (options.addLabel && options.addLabel.length > 0) {\n updates.addLabels = options.addLabel;\n }\n\n if (options.removeLabel && options.removeLabel.length > 0) {\n updates.removeLabels = options.removeLabel;\n }\n\n // Handle --child-order: set the ordering hints for children\n if (options.childOrder !== undefined) {\n if (options.childOrder === '' || options.childOrder === '\"\"') {\n // Empty string: clear the hints\n updates.child_order_hints = null;\n } else {\n // Parse comma-separated short IDs and resolve to internal IDs\n const shortIds = options.childOrder.split(',').map((s) => s.trim());\n const internalIds: string[] = [];\n for (const shortId of shortIds) {\n if (!shortId) continue; // Skip empty strings\n try {\n const internalId = resolveToInternalId(shortId, mapping);\n internalIds.push(internalId);\n } catch {\n throw new ValidationError(`Invalid ID in --child-order: ${shortId}`);\n }\n }\n updates.child_order_hints = internalIds.length > 0 ? internalIds : null;\n }\n }\n\n return updates;\n }\n}\n\nexport const updateCommand = new Command('update')\n .description('Update an issue')\n .argument('<id>', 'Issue ID')\n .option('--from-file <path>', 'Update all fields from YAML+Markdown file')\n .option('--title <text>', 'Set title')\n .option('--status <status>', 'Set status')\n .option('--type <type>', 'Set type')\n .option('--priority <0-4>', 'Set priority')\n .option('--assignee <name>', 'Set assignee')\n .option('--description <text>', 'Set description')\n .option('--notes <text>', 'Set working notes')\n .option('--notes-file <path>', 'Set notes from file')\n .option('--due <date>', 'Set due date')\n .option('--defer <date>', 'Set deferred until date')\n .option('--add-label <label>', 'Add label', (val, prev: string[] = []) => [...prev, val])\n .option('--remove-label <label>', 'Remove label', (val, prev: string[] = []) => [...prev, val])\n .option('--parent <id>', 'Set parent')\n .option('--spec <path>', 'Set or clear spec path (empty string clears)')\n .option('--child-order <ids>', 'Set child ordering hints (comma-separated IDs)')\n .action(async (id, options, command) => {\n const handler = new UpdateHandler(command);\n await handler.run(id, options);\n });\n","/**\n * `tbd close` - Close an issue.\n *\n * See: tbd-design.md §4.4 Close\n */\n\nimport { Command } from 'commander';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { requireInit, NotFoundError } from '../lib/errors.js';\nimport { readIssue, writeIssue } from '../../file/storage.js';\nimport { formatDisplayId, formatDebugId } from '../../lib/ids.js';\nimport { resolveDataSyncDir } from '../../lib/paths.js';\nimport { now } from '../../utils/time-utils.js';\nimport { loadIdMapping, resolveToInternalId } from '../../file/id-mapping.js';\nimport { readConfig } from '../../file/config.js';\n\ninterface CloseOptions {\n reason?: string;\n}\n\nclass CloseHandler extends BaseCommand {\n async run(id: string, options: CloseOptions): Promise<void> {\n const tbdRoot = await requireInit();\n\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n\n // Load ID mapping for resolution\n const mapping = await loadIdMapping(dataSyncDir);\n\n // Resolve input ID to internal ID\n let internalId: string;\n try {\n internalId = resolveToInternalId(id, mapping);\n } catch {\n throw new NotFoundError('Issue', id);\n }\n\n // Load existing issue\n let issue;\n try {\n issue = await readIssue(dataSyncDir, internalId);\n } catch {\n throw new NotFoundError('Issue', id);\n }\n\n // Get display ID for output\n const showDebug = this.ctx.debug;\n const config = await readConfig(tbdRoot);\n const prefix = config.display.id_prefix;\n const displayId = showDebug\n ? formatDebugId(issue.id, mapping, prefix)\n : formatDisplayId(issue.id, mapping, prefix);\n\n // Idempotent: if already closed, succeed silently without modification\n if (issue.status === 'closed') {\n this.output.data({ id: displayId, closed: true, alreadyClosed: true }, () => {\n this.output.success(`Closed ${displayId}`);\n });\n return;\n }\n\n if (this.checkDryRun('Would close issue', { id: internalId, reason: options.reason })) {\n return;\n }\n\n // Update issue\n issue.status = 'closed';\n issue.closed_at = now();\n issue.close_reason = options.reason ?? null;\n issue.version += 1;\n issue.updated_at = now();\n\n // Save\n await this.execute(async () => {\n await writeIssue(dataSyncDir, issue);\n }, 'Failed to close issue');\n\n this.output.data({ id: displayId, closed: true }, () => {\n this.output.success(`Closed ${displayId}`);\n });\n }\n}\n\nexport const closeCommand = new Command('close')\n .description('Close an issue')\n .argument('<id>', 'Issue ID')\n .option('--reason <text>', 'Close reason')\n .action(async (id, options, command) => {\n const handler = new CloseHandler(command);\n await handler.run(id, options);\n });\n","/**\n * `tbd reopen` - Reopen a closed issue.\n *\n * See: tbd-design.md §4.4 Reopen\n */\n\nimport { Command } from 'commander';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { requireInit, NotFoundError, CLIError } from '../lib/errors.js';\nimport { readIssue, writeIssue } from '../../file/storage.js';\nimport { formatDisplayId, formatDebugId } from '../../lib/ids.js';\nimport { resolveDataSyncDir } from '../../lib/paths.js';\nimport { now } from '../../utils/time-utils.js';\nimport { loadIdMapping, resolveToInternalId } from '../../file/id-mapping.js';\nimport { readConfig } from '../../file/config.js';\n\ninterface ReopenOptions {\n reason?: string;\n}\n\nclass ReopenHandler extends BaseCommand {\n async run(id: string, options: ReopenOptions): Promise<void> {\n const tbdRoot = await requireInit();\n\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n\n // Load ID mapping for resolution\n const mapping = await loadIdMapping(dataSyncDir);\n\n // Resolve input ID to internal ID\n let internalId: string;\n try {\n internalId = resolveToInternalId(id, mapping);\n } catch {\n throw new NotFoundError('Issue', id);\n }\n\n // Load existing issue\n let issue;\n try {\n issue = await readIssue(dataSyncDir, internalId);\n } catch {\n throw new NotFoundError('Issue', id);\n }\n\n // Check if not closed\n if (issue.status !== 'closed') {\n throw new CLIError(`Issue ${id} is not closed (status: ${issue.status})`);\n }\n\n if (this.checkDryRun('Would reopen issue', { id: internalId, reason: options.reason })) {\n return;\n }\n\n // Update issue\n issue.status = 'open';\n issue.closed_at = null;\n issue.close_reason = null;\n issue.version += 1;\n issue.updated_at = now();\n\n // Optionally store reopen reason in notes if provided\n if (options.reason) {\n const reopenNote = `Reopened: ${options.reason}`;\n issue.notes = issue.notes ? `${issue.notes}\\n\\n${reopenNote}` : reopenNote;\n }\n\n // Save\n await this.execute(async () => {\n await writeIssue(dataSyncDir, issue);\n }, 'Failed to reopen issue');\n\n // Use already loaded mapping for display\n const showDebug = this.ctx.debug;\n const config = await readConfig(tbdRoot);\n const prefix = config.display.id_prefix;\n const displayId = showDebug\n ? formatDebugId(issue.id, mapping, prefix)\n : formatDisplayId(issue.id, mapping, prefix);\n\n this.output.data({ id: displayId, reopened: true }, () => {\n this.output.success(`Reopened ${displayId}`);\n });\n }\n}\n\nexport const reopenCommand = new Command('reopen')\n .description('Reopen a closed issue')\n .argument('<id>', 'Issue ID')\n .option('--reason <text>', 'Reopen reason')\n .action(async (id, options, command) => {\n const handler = new ReopenHandler(command);\n await handler.run(id, options);\n });\n","/**\n * `tbd ready` - List issues ready to work on.\n *\n * See: tbd-design.md §4.4 Ready\n */\n\nimport { Command } from 'commander';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { applyLimit } from '../lib/limit-utils.js';\nimport { loadDataContext } from '../lib/data-context.js';\nimport { requireInit, NotInitializedError, ValidationError } from '../lib/errors.js';\nimport { listIssues } from '../../file/storage.js';\nimport { IssueKind } from '../../lib/schemas.js';\nimport type { Issue, IssueKindType } from '../../lib/types.js';\nimport { formatDisplayId, formatDebugId } from '../../lib/ids.js';\nimport { comparisonChain } from '../../lib/comparison-chain.js';\nimport {\n formatIssueLine,\n formatIssueLong,\n formatIssueHeader,\n type IssueForDisplay,\n} from '../lib/issue-format.js';\n\ninterface ReadyOptions {\n type?: string;\n limit?: string;\n long?: boolean;\n}\n\nclass ReadyHandler extends BaseCommand {\n async run(options: ReadyOptions): Promise<void> {\n const tbdRoot = await requireInit();\n\n // Load data context and issues\n let issues: Issue[];\n let dataCtx;\n try {\n dataCtx = await loadDataContext(tbdRoot);\n issues = await listIssues(dataCtx.dataSyncDir);\n } catch {\n throw new NotInitializedError('No issue store found. Run `tbd init` first.');\n }\n\n // Build lookup map for dependency resolution\n const issueMap = new Map(issues.map((i) => [i.id, i]));\n\n // Build reverse lookup: which issues are blocked by which\n // \"blocks\" dependency means \"this issue blocks target\"\n const blockedByMap = new Map<string, string[]>();\n for (const issue of issues) {\n for (const dep of issue.dependencies) {\n if (dep.type === 'blocks') {\n const existing = blockedByMap.get(dep.target) ?? [];\n existing.push(issue.id);\n blockedByMap.set(dep.target, existing);\n }\n }\n }\n\n // Filter for ready issues\n let readyIssues = issues.filter((issue) => {\n // Must be open (not in_progress, blocked, deferred, or closed)\n if (issue.status !== 'open') return false;\n\n // Must not have an assignee\n if (issue.assignee) return false;\n\n // Must not have unresolved blocking dependencies\n const blockers = blockedByMap.get(issue.id) ?? [];\n const hasUnresolvedBlocker = blockers.some((blockerId) => {\n const blocker = issueMap.get(blockerId);\n return blocker && blocker.status !== 'closed';\n });\n if (hasUnresolvedBlocker) return false;\n\n return true;\n });\n\n // Filter by type if specified\n if (options.type) {\n const result = IssueKind.safeParse(options.type);\n if (!result.success) {\n throw new ValidationError(`Invalid type: ${options.type}`);\n }\n const kind: IssueKindType = result.data;\n readyIssues = readyIssues.filter((i) => i.kind === kind);\n }\n\n // Sort by priority (lowest number = highest priority), then by ID for determinism\n readyIssues.sort(\n comparisonChain<Issue>()\n .compare((i) => i.priority)\n .compare((i) => i.id)\n .result(),\n );\n\n // Apply limit\n readyIssues = applyLimit(readyIssues, options.limit);\n\n const { mapping, prefix } = dataCtx;\n const showDebug = this.ctx.debug;\n\n // Format output\n const outputIssues = readyIssues.map((i) => ({\n id: showDebug ? formatDebugId(i.id, mapping, prefix) : formatDisplayId(i.id, mapping, prefix),\n priority: i.priority,\n status: i.status,\n kind: i.kind,\n title: i.title,\n description: i.description,\n }));\n\n this.output.data(outputIssues, () => {\n if (outputIssues.length === 0) {\n this.output.info('No ready issues found');\n return;\n }\n\n const colors = this.output.getColors();\n console.log(formatIssueHeader(colors));\n for (const issue of outputIssues) {\n if (options.long) {\n console.log(formatIssueLong(issue as IssueForDisplay, colors));\n } else {\n console.log(formatIssueLine(issue as IssueForDisplay, colors));\n }\n }\n });\n }\n}\n\nexport const readyCommand = new Command('ready')\n .description('List issues ready to work on (open, unblocked, unclaimed)')\n .option('--type <type>', 'Filter by type')\n .option('--limit <n>', 'Limit results')\n .option('--long', 'Show descriptions')\n .action(async (options, command) => {\n const handler = new ReadyHandler(command);\n await handler.run(options);\n });\n","/**\n * `tbd blocked` - List blocked issues.\n *\n * See: tbd-design.md §4.4 Blocked\n */\n\nimport { Command } from 'commander';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { loadDataContext, type TbdDataContext } from '../lib/data-context.js';\nimport { requireInit, NotInitializedError } from '../lib/errors.js';\nimport { applyLimit } from '../lib/limit-utils.js';\nimport { listIssues } from '../../file/storage.js';\nimport type { Issue } from '../../lib/types.js';\nimport { formatDisplayId, formatDebugId } from '../../lib/ids.js';\nimport { comparisonChain } from '../../lib/comparison-chain.js';\nimport {\n formatIssueLine,\n formatIssueLong,\n formatIssueHeader,\n formatIssueCompact,\n type IssueForDisplay,\n} from '../lib/issue-format.js';\n\ninterface BlockedOptions {\n limit?: string;\n long?: boolean;\n}\n\nclass BlockedHandler extends BaseCommand {\n async run(options: BlockedOptions): Promise<void> {\n const tbdRoot = await requireInit();\n\n // Load data context and issues\n let issues: Issue[];\n let dataCtx: TbdDataContext;\n try {\n dataCtx = await loadDataContext(tbdRoot);\n issues = await listIssues(dataCtx.dataSyncDir);\n } catch {\n throw new NotInitializedError('No issue store found. Run `tbd init` first.');\n }\n\n const { mapping, prefix } = dataCtx;\n const showDebug = this.ctx.debug;\n\n // Build lookup map for dependency resolution\n const issueMap = new Map(issues.map((i) => [i.id, i]));\n\n // Build reverse lookup: which issues are blocked by which\n // \"blocks\" dependency means \"this issue blocks target\"\n const blockedByMap = new Map<string, string[]>();\n for (const issue of issues) {\n for (const dep of issue.dependencies) {\n if (dep.type === 'blocks') {\n const existing = blockedByMap.get(dep.target) ?? [];\n existing.push(issue.id);\n blockedByMap.set(dep.target, existing);\n }\n }\n }\n\n // Find blocked issues (status=blocked OR has unresolved blocking dependencies)\n let blockedIssues: {\n issue: Issue;\n blockedBy: { id: string; issue: Issue }[];\n explicitlyBlocked?: boolean;\n }[] = [];\n\n for (const issue of issues) {\n // Skip closed issues\n if (issue.status === 'closed') continue;\n\n const unresolvedBlockers: { id: string; issue: Issue }[] = [];\n\n // Check if status is explicitly blocked\n const isExplicitlyBlocked = issue.status === 'blocked';\n\n // Check for unresolved blocking dependencies (from reverse lookup)\n const blockerIds = blockedByMap.get(issue.id) ?? [];\n for (const blockerId of blockerIds) {\n const blocker = issueMap.get(blockerId);\n if (blocker && blocker.status !== 'closed') {\n const blockerDisplayId = showDebug\n ? formatDebugId(blockerId, mapping, prefix)\n : formatDisplayId(blockerId, mapping, prefix);\n unresolvedBlockers.push({ id: blockerDisplayId, issue: blocker });\n }\n }\n\n if (isExplicitlyBlocked || unresolvedBlockers.length > 0) {\n blockedIssues.push({\n issue,\n blockedBy: unresolvedBlockers,\n explicitlyBlocked: isExplicitlyBlocked && unresolvedBlockers.length === 0,\n });\n }\n }\n\n // Sort by priority\n blockedIssues.sort(\n comparisonChain<{ issue: Issue }>()\n .compare((b) => b.issue.priority)\n .compare((b) => b.issue.id)\n .result(),\n );\n\n // Apply limit\n blockedIssues = applyLimit(blockedIssues, options.limit);\n\n // Format output\n const colors = this.output.getColors();\n const outputIssues = blockedIssues.map((b) => {\n const displayId = showDebug\n ? formatDebugId(b.issue.id, mapping, prefix)\n : formatDisplayId(b.issue.id, mapping, prefix);\n return {\n id: displayId,\n priority: b.issue.priority,\n status: b.issue.status,\n kind: b.issue.kind,\n title: b.issue.title,\n description: b.issue.description,\n blockedBy: b.explicitlyBlocked\n ? ['(explicitly blocked)']\n : b.blockedBy.map((blocker) =>\n formatIssueCompact(\n {\n id: blocker.id,\n priority: blocker.issue.priority,\n status: blocker.issue.status,\n kind: blocker.issue.kind,\n title: blocker.issue.title.slice(0, 20),\n },\n colors,\n ),\n ),\n };\n });\n\n this.output.data(outputIssues, () => {\n if (outputIssues.length === 0) {\n this.output.info('No blocked issues found');\n return;\n }\n\n console.log(formatIssueHeader(colors));\n for (const issue of outputIssues) {\n if (options.long) {\n console.log(formatIssueLong(issue as IssueForDisplay, colors));\n } else {\n console.log(formatIssueLine(issue as IssueForDisplay, colors));\n }\n // Show blockers on indented line\n console.log(` ${colors.dim('blocked by:')} ${issue.blockedBy.join(', ')}`);\n }\n });\n }\n}\n\nexport const blockedCommand = new Command('blocked')\n .description('List blocked issues')\n .option('--limit <n>', 'Limit results')\n .option('--long', 'Show descriptions')\n .action(async (options, command) => {\n const handler = new BlockedHandler(command);\n await handler.run(options);\n });\n","/**\n * `tbd stale` - List stale issues.\n *\n * See: tbd-design.md §4.4 Stale\n */\n\nimport { Command } from 'commander';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { applyLimit } from '../lib/limit-utils.js';\nimport { loadDataContext } from '../lib/data-context.js';\nimport { requireInit, NotInitializedError, ValidationError } from '../lib/errors.js';\nimport { listIssues } from '../../file/storage.js';\nimport { IssueStatus } from '../../lib/schemas.js';\nimport type { Issue, IssueStatusType } from '../../lib/types.js';\nimport { nowDate, parseDate } from '../../utils/time-utils.js';\nimport { formatDisplayId, formatDebugId } from '../../lib/ids.js';\nimport { comparisonChain, ordering } from '../../lib/comparison-chain.js';\n\ninterface StaleOptions {\n days?: string;\n status?: string;\n limit?: string;\n}\n\nclass StaleHandler extends BaseCommand {\n async run(options: StaleOptions): Promise<void> {\n const tbdRoot = await requireInit();\n\n // Load data context and issues\n let issues: Issue[];\n let dataCtx;\n try {\n dataCtx = await loadDataContext(tbdRoot);\n issues = await listIssues(dataCtx.dataSyncDir);\n } catch {\n throw new NotInitializedError('No issue store found. Run `tbd init` first.');\n }\n\n // Parse days threshold (default: 7)\n const daysThreshold = options.days ? parseInt(options.days, 10) : 7;\n if (isNaN(daysThreshold) || daysThreshold < 0) {\n throw new ValidationError('Invalid days value. Must be a positive number.');\n }\n\n // Parse status filter (default: open, in_progress)\n const allowedStatuses = new Set<IssueStatusType>();\n if (options.status) {\n const statuses = options.status.split(',').map((s) => s.trim());\n for (const s of statuses) {\n const result = IssueStatus.safeParse(s);\n if (!result.success) {\n throw new ValidationError(`Invalid status: ${s}`);\n }\n allowedStatuses.add(result.data);\n }\n } else {\n // Default: open and in_progress\n allowedStatuses.add('open');\n allowedStatuses.add('in_progress');\n }\n\n const currentTime = nowDate();\n const msPerDay = 24 * 60 * 60 * 1000;\n\n // Filter stale issues\n let staleIssues: { issue: Issue; daysSinceUpdate: number }[] = [];\n\n for (const issue of issues) {\n // Check status filter\n if (!allowedStatuses.has(issue.status)) continue;\n\n // Calculate days since last update\n const updatedAt = parseDate(issue.updated_at);\n if (!updatedAt) continue;\n const daysSinceUpdate = Math.floor((currentTime.getTime() - updatedAt.getTime()) / msPerDay);\n\n if (daysSinceUpdate >= daysThreshold) {\n staleIssues.push({ issue, daysSinceUpdate });\n }\n }\n\n // Sort by days since update (most stale first), then by ID for determinism\n staleIssues.sort(\n comparisonChain<{ issue: Issue; daysSinceUpdate: number }>()\n .compare((s) => s.daysSinceUpdate, ordering.reversed)\n .compare((s) => s.issue.id)\n .result(),\n );\n\n // Apply limit\n staleIssues = applyLimit(staleIssues, options.limit);\n\n const { mapping, prefix } = dataCtx;\n const showDebug = this.ctx.debug;\n\n // Format output\n const outputIssues = staleIssues.map((s) => ({\n id: showDebug\n ? formatDebugId(s.issue.id, mapping, prefix)\n : formatDisplayId(s.issue.id, mapping, prefix),\n days: s.daysSinceUpdate,\n status: s.issue.status,\n title: s.issue.title,\n }));\n\n this.output.data(outputIssues, () => {\n if (outputIssues.length === 0) {\n this.output.info(`No stale issues found (threshold: ${daysThreshold} days)`);\n return;\n }\n\n const colors = this.output.getColors();\n console.log(\n `${colors.dim('ISSUE'.padEnd(12))}${colors.dim('DAYS'.padEnd(6))}${colors.dim('STATUS'.padEnd(14))}${colors.dim('TITLE')}`,\n );\n for (const issue of outputIssues) {\n console.log(\n `${colors.id(issue.id.padEnd(12))}${String(issue.days).padEnd(6)}${issue.status.padEnd(14)}${issue.title}`,\n );\n }\n });\n }\n}\n\nexport const staleCommand = new Command('stale')\n .description('List issues not updated recently')\n .option('--days <n>', 'Days since last update (default: 7)')\n .option('--status <status>', 'Filter by status (default: open, in_progress)')\n .option('--limit <n>', 'Limit results')\n .action(async (options, command) => {\n const handler = new StaleHandler(command);\n await handler.run(options);\n });\n","/**\n * `tbd label` - Label management commands.\n *\n * See: tbd-design.md §4.5 Label Commands\n */\n\nimport { Command } from 'commander';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { requireInit, NotFoundError, NotInitializedError } from '../lib/errors.js';\nimport { readIssue, writeIssue, listIssues } from '../../file/storage.js';\nimport { formatDisplayId, formatDebugId } from '../../lib/ids.js';\nimport { resolveDataSyncDir } from '../../lib/paths.js';\nimport { now } from '../../utils/time-utils.js';\nimport { loadIdMapping, resolveToInternalId } from '../../file/id-mapping.js';\nimport { readConfig } from '../../file/config.js';\n\n// Add label\nclass LabelAddHandler extends BaseCommand {\n async run(id: string, labels: string[]): Promise<void> {\n const tbdRoot = await requireInit();\n\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n\n // Load ID mapping for resolution\n const mapping = await loadIdMapping(dataSyncDir);\n\n // Resolve input ID to internal ID\n let internalId: string;\n try {\n internalId = resolveToInternalId(id, mapping);\n } catch {\n throw new NotFoundError('Issue', id);\n }\n\n // Load existing issue\n let issue;\n try {\n issue = await readIssue(dataSyncDir, internalId);\n } catch {\n throw new NotFoundError('Issue', id);\n }\n\n if (this.checkDryRun('Would add labels', { id: internalId, labels })) {\n return;\n }\n\n // Add labels (avoiding duplicates)\n const labelsSet = new Set(issue.labels);\n let added = 0;\n for (const label of labels) {\n if (!labelsSet.has(label)) {\n labelsSet.add(label);\n added++;\n }\n }\n\n if (added === 0) {\n this.output.info('All labels already present');\n return;\n }\n\n issue.labels = [...labelsSet];\n issue.version += 1;\n issue.updated_at = now();\n\n await this.execute(async () => {\n await writeIssue(dataSyncDir, issue);\n }, 'Failed to update issue');\n\n // Use already loaded mapping for display\n const showDebug = this.ctx.debug;\n const config = await readConfig(tbdRoot);\n const prefix = config.display.id_prefix;\n const displayId = showDebug\n ? formatDebugId(issue.id, mapping, prefix)\n : formatDisplayId(issue.id, mapping, prefix);\n\n this.output.data({ id: displayId, addedLabels: labels }, () => {\n this.output.success(`Added labels to ${displayId}: ${labels.join(', ')}`);\n });\n }\n}\n\n// Remove label\nclass LabelRemoveHandler extends BaseCommand {\n async run(id: string, labels: string[]): Promise<void> {\n const tbdRoot = await requireInit();\n\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n\n // Load ID mapping for resolution\n const mapping = await loadIdMapping(dataSyncDir);\n\n // Resolve input ID to internal ID\n let internalId: string;\n try {\n internalId = resolveToInternalId(id, mapping);\n } catch {\n throw new NotFoundError('Issue', id);\n }\n\n // Load existing issue\n let issue;\n try {\n issue = await readIssue(dataSyncDir, internalId);\n } catch {\n throw new NotFoundError('Issue', id);\n }\n\n if (this.checkDryRun('Would remove labels', { id: internalId, labels })) {\n return;\n }\n\n // Remove labels\n const removeSet = new Set(labels);\n const originalCount = issue.labels.length;\n issue.labels = issue.labels.filter((l) => !removeSet.has(l));\n const removed = originalCount - issue.labels.length;\n\n if (removed === 0) {\n this.output.info('No matching labels found');\n return;\n }\n\n issue.version += 1;\n issue.updated_at = now();\n\n await this.execute(async () => {\n await writeIssue(dataSyncDir, issue);\n }, 'Failed to update issue');\n\n // Use already loaded mapping for display\n const showDebug = this.ctx.debug;\n const config = await readConfig(tbdRoot);\n const prefix = config.display.id_prefix;\n const displayId = showDebug\n ? formatDebugId(issue.id, mapping, prefix)\n : formatDisplayId(issue.id, mapping, prefix);\n\n this.output.data({ id: displayId, removedLabels: labels }, () => {\n this.output.success(`Removed labels from ${displayId}: ${labels.join(', ')}`);\n });\n }\n}\n\n// List labels\nclass LabelListHandler extends BaseCommand {\n async run(): Promise<void> {\n const tbdRoot = await requireInit();\n\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n\n // Load all issues and collect unique labels\n let issues;\n try {\n issues = await listIssues(dataSyncDir);\n } catch {\n throw new NotInitializedError('No issue store found. Run `tbd init` first.');\n }\n\n // Collect labels with counts\n const labelCounts = new Map<string, number>();\n for (const issue of issues) {\n for (const label of issue.labels) {\n labelCounts.set(label, (labelCounts.get(label) ?? 0) + 1);\n }\n }\n\n // Sort by count (descending), then alphabetically\n const sortedLabels = [...labelCounts.entries()].sort((a, b) => {\n if (b[1] !== a[1]) return b[1] - a[1];\n return a[0].localeCompare(b[0]);\n });\n\n const output = sortedLabels.map(([label, count]) => ({ label, count }));\n\n this.output.data(output, () => {\n if (output.length === 0) {\n this.output.info('No labels in use');\n return;\n }\n\n const colors = this.output.getColors();\n console.log(`${colors.dim('LABEL'.padEnd(24))}${colors.dim('COUNT')}`);\n for (const { label, count } of output) {\n console.log(`${colors.label(label.padEnd(24))}${count}`);\n }\n });\n }\n}\n\nconst addCommand = new Command('add')\n .description('Add labels to an issue')\n .argument('<id>', 'Issue ID')\n .argument('<labels...>', 'Labels to add')\n .action(async (id, labels, _options, command) => {\n const handler = new LabelAddHandler(command);\n await handler.run(id, labels);\n });\n\nconst removeCommand = new Command('remove')\n .description('Remove labels from an issue')\n .argument('<id>', 'Issue ID')\n .argument('<labels...>', 'Labels to remove')\n .action(async (id, labels, _options, command) => {\n const handler = new LabelRemoveHandler(command);\n await handler.run(id, labels);\n });\n\nconst listLabelCommand = new Command('list')\n .description('List all labels in use')\n .action(async (_options, command) => {\n const handler = new LabelListHandler(command);\n await handler.run();\n });\n\nexport const labelCommand = new Command('label')\n .description('Manage issue labels')\n .addCommand(addCommand)\n .addCommand(removeCommand)\n .addCommand(listLabelCommand);\n","/**\n * `tbd dep` - Dependency management commands.\n *\n * See: tbd-design.md §4.6 Dependency Commands\n */\n\nimport { Command } from 'commander';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { requireInit, NotFoundError, ValidationError } from '../lib/errors.js';\nimport { readIssue, writeIssue, listIssues } from '../../file/storage.js';\nimport { formatDisplayId, formatDebugId } from '../../lib/ids.js';\nimport type { Issue } from '../../lib/types.js';\nimport { resolveDataSyncDir } from '../../lib/paths.js';\nimport { now } from '../../utils/time-utils.js';\nimport { loadIdMapping, resolveToInternalId } from '../../file/id-mapping.js';\nimport { readConfig } from '../../file/config.js';\n\n// Add dependency: \"A depends on B\" means B blocks A\nclass DependsAddHandler extends BaseCommand {\n async run(issueId: string, dependsOnId: string): Promise<void> {\n const tbdRoot = await requireInit();\n\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n\n // Load ID mapping for resolution\n const mapping = await loadIdMapping(dataSyncDir);\n\n // Resolve both IDs to internal IDs\n // issueId = the issue that depends on something\n // dependsOnId = the issue it depends on (the blocker)\n let internalIssueId: string;\n let internalDependsOnId: string;\n try {\n internalIssueId = resolveToInternalId(issueId, mapping);\n } catch {\n throw new NotFoundError('Issue', issueId);\n }\n try {\n internalDependsOnId = resolveToInternalId(dependsOnId, mapping);\n } catch {\n throw new NotFoundError('Issue', dependsOnId);\n }\n\n // Verify issueId exists\n try {\n await readIssue(dataSyncDir, internalIssueId);\n } catch {\n throw new NotFoundError('Issue', issueId);\n }\n\n // Load the blocking issue (dependsOnId) - this is where we add the dependency\n let blockerIssue;\n try {\n blockerIssue = await readIssue(dataSyncDir, internalDependsOnId);\n } catch {\n throw new NotFoundError('Issue', dependsOnId);\n }\n\n // Check for self-reference\n if (internalIssueId === internalDependsOnId) {\n throw new ValidationError('Issue cannot depend on itself');\n }\n\n if (\n this.checkDryRun('Would add dependency', {\n issue: internalIssueId,\n dependsOn: internalDependsOnId,\n })\n ) {\n return;\n }\n\n // Check if dependency already exists (dependsOnId blocks issueId)\n const exists = blockerIssue.dependencies.some(\n (dep) => dep.type === 'blocks' && dep.target === internalIssueId,\n );\n if (exists) {\n this.output.info('Dependency already exists');\n return;\n }\n\n // Add the dependency: dependsOnId blocks issueId\n blockerIssue.dependencies.push({ type: 'blocks', target: internalIssueId });\n blockerIssue.version += 1;\n blockerIssue.updated_at = now();\n\n await this.execute(async () => {\n await writeIssue(dataSyncDir, blockerIssue);\n }, 'Failed to update issue');\n\n // Use already loaded mapping for display\n const showDebug = this.ctx.debug;\n const config = await readConfig(tbdRoot);\n const prefix = config.display.id_prefix;\n const displayIssueId = showDebug\n ? formatDebugId(internalIssueId, mapping, prefix)\n : formatDisplayId(internalIssueId, mapping, prefix);\n const displayDependsOnId = showDebug\n ? formatDebugId(internalDependsOnId, mapping, prefix)\n : formatDisplayId(internalDependsOnId, mapping, prefix);\n\n this.output.data({ issue: displayIssueId, dependsOn: displayDependsOnId }, () => {\n this.output.success(`${displayIssueId} now depends on ${displayDependsOnId}`);\n });\n }\n}\n\n// Remove dependency: \"A no longer depends on B\" means B no longer blocks A\nclass DependsRemoveHandler extends BaseCommand {\n async run(issueId: string, dependsOnId: string): Promise<void> {\n const tbdRoot = await requireInit();\n\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n\n // Load ID mapping for resolution\n const mapping = await loadIdMapping(dataSyncDir);\n\n // Resolve both IDs to internal IDs\n let internalIssueId: string;\n let internalDependsOnId: string;\n try {\n internalIssueId = resolveToInternalId(issueId, mapping);\n } catch {\n throw new NotFoundError('Issue', issueId);\n }\n try {\n internalDependsOnId = resolveToInternalId(dependsOnId, mapping);\n } catch {\n throw new NotFoundError('Issue', dependsOnId);\n }\n\n // Load the blocker issue (dependsOnId) - this is where the dependency is stored\n let blockerIssue;\n try {\n blockerIssue = await readIssue(dataSyncDir, internalDependsOnId);\n } catch {\n throw new NotFoundError('Issue', dependsOnId);\n }\n\n if (\n this.checkDryRun('Would remove dependency', {\n issue: internalIssueId,\n dependsOn: internalDependsOnId,\n })\n ) {\n return;\n }\n\n // Find and remove the dependency (dependsOnId blocks issueId)\n const initialLength = blockerIssue.dependencies.length;\n blockerIssue.dependencies = blockerIssue.dependencies.filter(\n (dep) => !(dep.type === 'blocks' && dep.target === internalIssueId),\n );\n\n if (blockerIssue.dependencies.length === initialLength) {\n this.output.info('Dependency not found');\n return;\n }\n\n blockerIssue.version += 1;\n blockerIssue.updated_at = now();\n\n await this.execute(async () => {\n await writeIssue(dataSyncDir, blockerIssue);\n }, 'Failed to update issue');\n\n // Use already loaded mapping for display\n const showDebug = this.ctx.debug;\n const config = await readConfig(tbdRoot);\n const prefix = config.display.id_prefix;\n const displayIssueId = showDebug\n ? formatDebugId(internalIssueId, mapping, prefix)\n : formatDisplayId(internalIssueId, mapping, prefix);\n const displayDependsOnId = showDebug\n ? formatDebugId(internalDependsOnId, mapping, prefix)\n : formatDisplayId(internalDependsOnId, mapping, prefix);\n\n this.output.data({ issue: displayIssueId, removed: displayDependsOnId }, () => {\n this.output.success(`${displayIssueId} no longer depends on ${displayDependsOnId}`);\n });\n }\n}\n\n// List dependencies\nclass DependsListHandler extends BaseCommand {\n async run(id: string): Promise<void> {\n const tbdRoot = await requireInit();\n\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n\n // Load ID mapping for resolution and display\n const mapping = await loadIdMapping(dataSyncDir);\n\n // Resolve input ID to internal ID\n let internalId: string;\n try {\n internalId = resolveToInternalId(id, mapping);\n } catch {\n throw new NotFoundError('Issue', id);\n }\n\n // Load the issue\n let issue;\n try {\n issue = await readIssue(dataSyncDir, internalId);\n } catch {\n throw new NotFoundError('Issue', id);\n }\n\n // Load all issues to find reverse dependencies\n let allIssues: Issue[];\n try {\n allIssues = await listIssues(dataSyncDir);\n } catch {\n allIssues = [];\n }\n\n const showDebug = this.ctx.debug;\n const config = await readConfig(tbdRoot);\n const prefix = config.display.id_prefix;\n\n // Find what this issue blocks (from its dependencies)\n const blocks = issue.dependencies\n .filter((dep) => dep.type === 'blocks')\n .map((dep) =>\n showDebug\n ? formatDebugId(dep.target, mapping, prefix)\n : formatDisplayId(dep.target, mapping, prefix),\n );\n\n // Find what blocks this issue (reverse lookup)\n const blockedBy: string[] = [];\n for (const other of allIssues) {\n for (const dep of other.dependencies) {\n if (dep.type === 'blocks' && dep.target === internalId) {\n blockedBy.push(\n showDebug\n ? formatDebugId(other.id, mapping, prefix)\n : formatDisplayId(other.id, mapping, prefix),\n );\n }\n }\n }\n\n const deps = { blocks, blockedBy };\n this.output.data(deps, () => {\n const colors = this.output.getColors();\n if (deps.blocks.length > 0) {\n console.log(`${colors.bold('Blocks:')} ${deps.blocks.join(', ')}`);\n }\n if (deps.blockedBy.length > 0) {\n console.log(`${colors.bold('Blocked by:')} ${deps.blockedBy.join(', ')}`);\n }\n if (deps.blocks.length === 0 && deps.blockedBy.length === 0) {\n console.log('No dependencies');\n }\n });\n }\n}\n\nconst addCommand = new Command('add')\n .description('Add dependency (issue depends on depends-on)')\n .argument('<issue>', 'Issue ID that depends on something')\n .argument('<depends-on>', 'Issue ID that must be completed first')\n .action(async (issue, dependsOn, _options, command) => {\n const handler = new DependsAddHandler(command);\n await handler.run(issue, dependsOn);\n });\n\nconst removeCommand = new Command('remove')\n .description('Remove dependency (issue no longer depends on depends-on)')\n .argument('<issue>', 'Issue ID')\n .argument('<depends-on>', 'Issue ID it depended on')\n .action(async (issue, dependsOn, _options, command) => {\n const handler = new DependsRemoveHandler(command);\n await handler.run(issue, dependsOn);\n });\n\nconst listDepsCommand = new Command('list')\n .description('List dependencies for an issue')\n .argument('<id>', 'Issue ID')\n .action(async (id, _options, command) => {\n const handler = new DependsListHandler(command);\n await handler.run(id);\n });\n\nexport const depCommand = new Command('dep')\n .description('Manage issue dependencies')\n .addCommand(addCommand)\n .addCommand(removeCommand)\n .addCommand(listDepsCommand);\n","/**\n * Sync summary formatting utilities.\n *\n * Provides consistent formatting for sync operation results.\n */\n\n/**\n * Tallies for a sync direction (sent or received).\n */\nexport interface SyncTallies {\n new: number;\n updated: number;\n deleted: number;\n}\n\n/**\n * Full sync summary with both directions.\n */\nexport interface SyncSummary {\n sent: SyncTallies;\n received: SyncTallies;\n conflicts: number;\n /** True if push to remote failed */\n pushFailed?: boolean;\n /** Error message if push failed */\n pushError?: string;\n}\n\n/**\n * Create empty sync tallies.\n */\nexport function emptyTallies(): SyncTallies {\n return { new: 0, updated: 0, deleted: 0 };\n}\n\n/**\n * Create empty sync summary.\n */\nexport function emptySummary(): SyncSummary {\n return {\n sent: emptyTallies(),\n received: emptyTallies(),\n conflicts: 0,\n };\n}\n\n/**\n * Check if tallies have any non-zero values.\n */\nexport function hasTallies(tallies: SyncTallies): boolean {\n return tallies.new > 0 || tallies.updated > 0 || tallies.deleted > 0;\n}\n\n/**\n * Format tallies for display (e.g., \"1 new, 2 updated\").\n * Omits zero counts.\n */\nexport function formatTallies(tallies: SyncTallies): string {\n const parts: string[] = [];\n\n if (tallies.new > 0) {\n parts.push(`${tallies.new} new`);\n }\n if (tallies.updated > 0) {\n parts.push(`${tallies.updated} updated`);\n }\n if (tallies.deleted > 0) {\n parts.push(`${tallies.deleted} deleted`);\n }\n\n return parts.join(', ');\n}\n\n/**\n * Format sync summary for display.\n *\n * Examples:\n * - \"sent 1 new\"\n * - \"sent 2 updated, received 1 new\"\n * - \"received 3 new, 1 updated\"\n * - \"\" (empty if nothing to report - caller should show \"Already in sync\")\n */\nexport function formatSyncSummary(summary: SyncSummary): string {\n const parts: string[] = [];\n\n const sentStr = formatTallies(summary.sent);\n const receivedStr = formatTallies(summary.received);\n\n if (sentStr) {\n parts.push(`sent ${sentStr}`);\n }\n if (receivedStr) {\n parts.push(`received ${receivedStr}`);\n }\n\n if (parts.length === 0) {\n return '';\n }\n\n let result = parts.join(', ');\n\n if (summary.conflicts > 0) {\n result += ` (${summary.conflicts} conflict${summary.conflicts === 1 ? '' : 's'} resolved)`;\n }\n\n return result;\n}\n\n/**\n * Parse git status --porcelain output to get tallies.\n *\n * @param statusOutput - Output from `git status --porcelain`\n * @returns Tallies for new, updated, deleted files\n */\nexport function parseGitStatus(statusOutput: string): SyncTallies {\n const tallies = emptyTallies();\n\n if (!statusOutput || statusOutput.trim() === '') {\n return tallies;\n }\n\n for (const line of statusOutput.split('\\n')) {\n if (!line) continue;\n\n const statusCode = line.slice(0, 2).trim();\n\n switch (statusCode) {\n case 'A':\n case '??':\n tallies.new++;\n break;\n case 'M':\n case 'MM':\n tallies.updated++;\n break;\n case 'D':\n tallies.deleted++;\n break;\n }\n }\n\n return tallies;\n}\n\n/**\n * Parse git diff --name-status output to get tallies.\n *\n * @param diffOutput - Output from `git diff --name-status`\n * @returns Tallies for new, updated, deleted files\n */\nexport function parseGitDiff(diffOutput: string): SyncTallies {\n const tallies = emptyTallies();\n\n if (!diffOutput || diffOutput.trim() === '') {\n return tallies;\n }\n\n for (const line of diffOutput.split('\\n')) {\n if (!line) continue;\n\n const statusCode = line[0];\n\n switch (statusCode) {\n case 'A':\n tallies.new++;\n break;\n case 'M':\n tallies.updated++;\n break;\n case 'D':\n tallies.deleted++;\n break;\n }\n }\n\n return tallies;\n}\n","/**\n * GitHub URL utilities and content fetching with `gh` CLI fallback.\n *\n * Consolidates all GitHub-specific URL handling and fetching logic:\n * - GitHub blob URL → raw URL conversion\n * - Direct HTTP fetch with timeout\n * - Fallback to `gh api` on 403 errors (common in restricted environments)\n *\n * This module is reusable across doc-sync, doc-add, and any future code\n * that needs to fetch content from GitHub.\n */\n\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\n\nconst execFileAsync = promisify(execFile);\n\n// =============================================================================\n// GitHub URL Conversion\n// =============================================================================\n\n/**\n * Regular expression to match GitHub blob URLs.\n *\n * Matches patterns like:\n * https://github.com/{owner}/{repo}/blob/{ref}/{path}\n */\nconst GITHUB_BLOB_RE = /^https?:\\/\\/github\\.com\\/([^/]+)\\/([^/]+)\\/blob\\/([^/]+)\\/(.+)$/;\n\n/**\n * Regular expression to match raw.githubusercontent.com URLs.\n *\n * Matches patterns like:\n * https://raw.githubusercontent.com/{owner}/{repo}/{ref}/{path}\n */\nconst RAW_GITHUB_RE = /^https?:\\/\\/raw\\.githubusercontent\\.com\\/([^/]+)\\/([^/]+)\\/([^/]+)\\/(.+)$/;\n\n/**\n * Convert a GitHub blob URL to a raw.githubusercontent.com URL.\n *\n * If the URL is already a raw URL or not a GitHub blob URL, returns it unchanged.\n *\n * @example\n * githubBlobToRawUrl('https://github.com/org/repo/blob/main/docs/file.md')\n * // => 'https://raw.githubusercontent.com/org/repo/main/docs/file.md'\n *\n * @example\n * githubBlobToRawUrl('https://raw.githubusercontent.com/org/repo/main/docs/file.md')\n * // => 'https://raw.githubusercontent.com/org/repo/main/docs/file.md' (unchanged)\n */\nexport function githubBlobToRawUrl(url: string): string {\n const match = GITHUB_BLOB_RE.exec(url);\n if (!match) {\n return url;\n }\n const [, owner, repo, ref, path] = match;\n return `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${path}`;\n}\n\n/**\n * Check if a URL is a GitHub-hosted URL (github.com or raw.githubusercontent.com).\n */\nexport function isGitHubUrl(url: string): boolean {\n return /^https?:\\/\\/(github\\.com|raw\\.githubusercontent\\.com)\\//.test(url);\n}\n\n/**\n * Parse a raw.githubusercontent.com URL into its components.\n *\n * @returns The parsed components, or null if not a raw GitHub URL\n */\nexport function parseRawGitHubUrl(\n url: string,\n): { owner: string; repo: string; ref: string; path: string } | null {\n const match = RAW_GITHUB_RE.exec(url);\n if (!match) {\n return null;\n }\n return {\n owner: match[1]!,\n repo: match[2]!,\n ref: match[3]!,\n path: match[4]!,\n };\n}\n\n// =============================================================================\n// HTTP Fetching\n// =============================================================================\n\n/** Default timeout for URL fetches in milliseconds */\nconst DEFAULT_FETCH_TIMEOUT = 30000;\n\n/**\n * Options for fetching content.\n */\nexport interface FetchOptions {\n /** Timeout in milliseconds (default: 30000) */\n timeout?: number;\n}\n\n/**\n * Result of a fetch operation.\n */\nexport interface FetchResult {\n /** The fetched content */\n content: string;\n /** Whether the gh CLI was used as a fallback */\n usedGhCli: boolean;\n}\n\n/**\n * Fetch content from a URL via direct HTTP.\n *\n * @throws If the request fails or returns a non-OK status\n */\nexport async function directFetch(url: string, options?: FetchOptions): Promise<string> {\n const timeout = options?.timeout ?? DEFAULT_FETCH_TIMEOUT;\n const controller = new AbortController();\n const timer = setTimeout(() => {\n controller.abort();\n }, timeout);\n\n try {\n const response = await fetch(url, {\n signal: controller.signal,\n headers: {\n 'User-Agent': 'get-tbd/1.0',\n Accept: 'text/plain, text/markdown, */*',\n },\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n return await response.text();\n } finally {\n clearTimeout(timer);\n }\n}\n\n// =============================================================================\n// gh CLI Fetching\n// =============================================================================\n\n/**\n * Fetch content from a GitHub raw URL using `gh api`.\n *\n * Converts raw.githubusercontent.com URLs to GitHub API content endpoints\n * and decodes the base64-encoded response.\n *\n * @throws If the URL is not a GitHub URL or gh CLI fails\n */\nexport async function ghCliFetch(rawUrl: string): Promise<string> {\n const parsed = parseRawGitHubUrl(rawUrl);\n\n if (parsed) {\n try {\n const { stdout } = await execFileAsync('gh', [\n 'api',\n `/repos/${parsed.owner}/${parsed.repo}/contents/${parsed.path}?ref=${parsed.ref}`,\n '--jq',\n '.content',\n '-H',\n 'Accept: application/vnd.github.v3+json',\n ]);\n\n // GitHub API returns base64-encoded content\n const base64Content = stdout.trim();\n return Buffer.from(base64Content, 'base64').toString('utf-8');\n } catch (error) {\n throw new Error(\n `Failed to fetch via gh CLI: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n // For non-raw-GitHub URLs, attempt a direct gh api call\n try {\n const { stdout } = await execFileAsync('gh', ['api', rawUrl]);\n return stdout;\n } catch (error) {\n throw new Error(\n `Failed to fetch via gh CLI: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n}\n\n// =============================================================================\n// Combined Fetch with Fallback\n// =============================================================================\n\n/**\n * Fetch content from a URL, falling back to `gh` CLI on 403 errors.\n *\n * GitHub.com and raw.githubusercontent.com may block requests from\n * certain environments (e.g., CI runners, corporate proxies). When\n * a 403 is received, we retry using `gh api` which authenticates\n * via the user's GitHub CLI token.\n *\n * This function also auto-converts GitHub blob URLs to raw URLs.\n *\n * @returns The fetched content and whether gh CLI was used\n * @throws If both direct fetch and gh CLI fallback fail\n */\nexport async function fetchWithGhFallback(\n url: string,\n options?: FetchOptions,\n): Promise<FetchResult> {\n const rawUrl = githubBlobToRawUrl(url);\n\n // Try direct fetch first\n try {\n const content = await directFetch(rawUrl, options);\n return { content, usedGhCli: false };\n } catch (error) {\n const is403 = error instanceof Error && error.message.includes('HTTP 403');\n if (!is403) {\n throw error;\n }\n }\n\n // 403 error - try gh CLI fallback\n const content = await ghCliFetch(rawUrl);\n return { content, usedGhCli: true };\n}\n","/**\n * DocSync - Sync documentation files from configured sources.\n *\n * Syncs docs from internal bundled sources and external URLs to .tbd/docs/.\n *\n * See: docs/project/specs/active/plan-2026-01-26-configurable-doc-cache-sync.md\n */\n\nimport { readdir, readFile, rm, mkdir, access } from 'node:fs/promises';\nimport { join, dirname } from 'node:path';\nimport { writeFile } from 'atomically';\nimport { fileURLToPath } from 'node:url';\n\nimport { TBD_DOCS_DIR } from '../lib/paths.js';\nimport { fetchWithGhFallback } from './github-fetch.js';\nimport { readConfig, writeConfig, updateLocalState } from './config.js';\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * A parsed document source.\n */\nexport interface DocSource {\n /** Source type: internal bundled or external URL */\n type: 'internal' | 'url';\n /** The source location - either a relative path or full URL */\n location: string;\n}\n\n/**\n * Result of a sync operation.\n */\nexport interface SyncResult {\n /** Paths of newly downloaded/copied docs */\n added: string[];\n /** Paths of updated docs (content changed) */\n updated: string[];\n /** Paths of removed docs (no longer in config) */\n removed: string[];\n /** Errors encountered during sync */\n errors: { path: string; error: string }[];\n /** Whether the sync was successful overall */\n success: boolean;\n}\n\n/**\n * Options for doc sync operations.\n */\nexport interface DocSyncOptions {\n /** If true, don't actually write/delete files (dry run) */\n dryRun?: boolean;\n /** If true, suppress normal output (only report errors) */\n silent?: boolean;\n}\n\n// =============================================================================\n// Constants\n// =============================================================================\n\n/** Prefix for internal bundled doc sources */\nconst INTERNAL_SOURCE_PREFIX = 'internal:';\n\n// =============================================================================\n// DocSync Class\n// =============================================================================\n\n/**\n * Syncs documentation files from configured sources.\n *\n * Supports:\n * - Internal bundled docs (using internal: prefix)\n * - External URLs (raw.githubusercontent.com, etc.)\n */\nexport class DocSync {\n private readonly docsDir: string;\n\n /**\n * Create a new DocSync instance.\n *\n * @param tbdRoot - The tbd project root directory (parent of .tbd/)\n * @param config - The doc_cache configuration mapping dest paths to sources\n */\n constructor(\n private readonly tbdRoot: string,\n private readonly config: Record<string, string>,\n ) {\n this.docsDir = join(tbdRoot, TBD_DOCS_DIR);\n }\n\n /**\n * Parse a source string into a DocSource.\n *\n * @example\n * parseSource('internal:shortcuts/standard/code-review-and-commit.md')\n * // => { type: 'internal', location: 'shortcuts/standard/code-review-and-commit.md' }\n *\n * @example\n * parseSource('https://raw.githubusercontent.com/org/repo/main/file.md')\n * // => { type: 'url', location: 'https://...' }\n */\n parseSource(source: string): DocSource {\n if (source.startsWith(INTERNAL_SOURCE_PREFIX)) {\n return {\n type: 'internal',\n location: source.slice(INTERNAL_SOURCE_PREFIX.length),\n };\n }\n\n // Anything else is treated as a URL\n return {\n type: 'url',\n location: source,\n };\n }\n\n /**\n * Fetch content from a source.\n *\n * @throws If the source cannot be fetched\n */\n async fetchContent(source: DocSource): Promise<string> {\n if (source.type === 'internal') {\n return this.fetchInternalContent(source.location);\n }\n return this.fetchUrlContent(source.location);\n }\n\n /**\n * Fetch content from an internal bundled doc.\n */\n private async fetchInternalContent(location: string): Promise<string> {\n const basePaths = getDocsBasePath();\n\n for (const basePath of basePaths) {\n const fullPath = join(basePath, location);\n try {\n await access(fullPath);\n return await readFile(fullPath, 'utf-8');\n } catch {\n // Try next path\n }\n }\n\n throw new Error(`Internal doc not found: ${location}`);\n }\n\n /**\n * Fetch content from a URL (with gh CLI fallback on 403).\n */\n private async fetchUrlContent(url: string): Promise<string> {\n const { content } = await fetchWithGhFallback(url);\n return content;\n }\n\n /**\n * Get the current state of the docs directory.\n * Returns a set of relative paths that exist in .tbd/docs/.\n */\n async getCurrentState(): Promise<Set<string>> {\n const paths = new Set<string>();\n\n try {\n await access(this.docsDir);\n } catch {\n // Directory doesn't exist yet\n return paths;\n }\n\n await this.scanDirectory(this.docsDir, '', paths);\n return paths;\n }\n\n /**\n * Recursively scan a directory and add all .md file paths to the set.\n */\n private async scanDirectory(baseDir: string, prefix: string, paths: Set<string>): Promise<void> {\n try {\n const dirEntries = await readdir(baseDir, { withFileTypes: true });\n\n for (const entry of dirEntries) {\n const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;\n\n if (entry.isDirectory()) {\n await this.scanDirectory(join(baseDir, entry.name), relativePath, paths);\n } else if (entry.isFile() && entry.name.endsWith('.md')) {\n paths.add(relativePath);\n }\n }\n } catch {\n // Directory doesn't exist or not readable\n }\n }\n\n /**\n * Sync all docs from config to .tbd/docs/.\n *\n * - Downloads/copies new docs\n * - Updates docs whose source has changed\n * - Removes docs no longer in config\n *\n * @param options - Sync options (dryRun, silent)\n */\n async sync(options: DocSyncOptions = {}): Promise<SyncResult> {\n const result: SyncResult = {\n added: [],\n updated: [],\n removed: [],\n errors: [],\n success: true,\n };\n\n // Get current state\n const currentPaths = await this.getCurrentState();\n const configPaths = new Set(Object.keys(this.config));\n\n // Process each doc in config\n for (const [destPath, sourceStr] of Object.entries(this.config)) {\n try {\n const source = this.parseSource(sourceStr);\n const content = await this.fetchContent(source);\n const fullPath = join(this.docsDir, destPath);\n\n // Check if file exists and compare content\n let exists = false;\n let existingContent = '';\n\n try {\n existingContent = await readFile(fullPath, 'utf-8');\n exists = true;\n } catch {\n // File doesn't exist\n }\n\n if (!exists) {\n // New file\n if (!options.dryRun) {\n await mkdir(dirname(fullPath), { recursive: true });\n await writeFile(fullPath, content);\n }\n result.added.push(destPath);\n } else if (existingContent !== content) {\n // Content changed\n if (!options.dryRun) {\n await writeFile(fullPath, content);\n }\n result.updated.push(destPath);\n }\n // else: unchanged, do nothing\n } catch (err) {\n result.errors.push({\n path: destPath,\n error: (err as Error).message,\n });\n result.success = false;\n }\n }\n\n // Remove docs not in config\n for (const existingPath of currentPaths) {\n if (!configPaths.has(existingPath)) {\n try {\n if (!options.dryRun) {\n await rm(join(this.docsDir, existingPath));\n }\n result.removed.push(existingPath);\n } catch (err) {\n result.errors.push({\n path: existingPath,\n error: `Failed to remove: ${(err as Error).message}`,\n });\n }\n }\n }\n\n return result;\n }\n\n /**\n * Get a status of what would change without actually making changes.\n */\n async status(): Promise<SyncResult> {\n return this.sync({ dryRun: true });\n }\n}\n\n// =============================================================================\n// Helper Functions\n// =============================================================================\n\n/**\n * Get base docs paths (with fallbacks for development).\n * Matches the logic in setup.ts.\n */\nfunction getDocsBasePath(): string[] {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n return [\n // Bundled location (dist/docs/)\n join(__dirname, 'docs'),\n // Development: packages/tbd/docs/\n join(__dirname, '..', '..', 'docs'),\n ];\n}\n\n/**\n * Generate default doc_cache config by scanning bundled docs.\n *\n * This creates a config entry for each .md file found in the bundled\n * docs directories (shortcuts, guidelines, templates).\n *\n * @returns A doc_cache config mapping destination paths to internal: sources\n */\nexport async function generateDefaultDocCacheConfig(): Promise<Record<string, string>> {\n const config: Record<string, string> = {};\n const basePaths = getDocsBasePath();\n\n // Find the first valid base path\n let docsDir: string | null = null;\n for (const path of basePaths) {\n try {\n await access(path);\n docsDir = path;\n break;\n } catch {\n // Try next path\n }\n }\n\n if (!docsDir) {\n return config;\n }\n\n // Directories to scan\n const scanDirs = [\n { subdir: 'shortcuts/system', prefix: 'shortcuts/system' },\n { subdir: 'shortcuts/standard', prefix: 'shortcuts/standard' },\n { subdir: 'guidelines', prefix: 'guidelines' },\n { subdir: 'templates', prefix: 'templates' },\n ];\n\n for (const { subdir, prefix } of scanDirs) {\n const fullDir = join(docsDir, subdir);\n try {\n const entries = await readdir(fullDir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isFile() && entry.name.endsWith('.md')) {\n const relativePath = `${prefix}/${entry.name}`;\n config[relativePath] = `${INTERNAL_SOURCE_PREFIX}${relativePath}`;\n }\n }\n } catch {\n // Directory doesn't exist, skip\n }\n }\n\n return config;\n}\n\n/**\n * Merge user's doc_cache config with default bundled docs.\n *\n * This ensures:\n * - New bundled docs from tbd updates are added to existing configs\n * - User's custom sources (URLs, etc.) are preserved\n * - User's overrides of bundled docs are respected\n *\n * @param userConfig - The user's existing doc_cache config (may be undefined/empty)\n * @param defaults - The default config from generateDefaultDocCacheConfig()\n * @returns Merged config with defaults as base, user config overlaid\n */\nexport function mergeDocCacheConfig(\n userConfig: Record<string, string> | undefined,\n defaults: Record<string, string>,\n): Record<string, string> {\n // Start with defaults, overlay user config (user takes precedence)\n return {\n ...defaults,\n ...userConfig,\n };\n}\n\n/**\n * Check if docs are stale based on last sync time and configured hours.\n *\n * @param lastSyncAt - ISO timestamp of last sync (or undefined if never synced)\n * @param autoSyncHours - Hours between auto-syncs (0 = disabled)\n * @returns true if docs should be synced\n */\nexport function isDocsStale(lastSyncAt: string | undefined, autoSyncHours: number): boolean {\n // Auto-sync disabled\n if (autoSyncHours <= 0) {\n return false;\n }\n\n // Never synced\n if (!lastSyncAt) {\n return true;\n }\n\n const lastSync = new Date(lastSyncAt).getTime();\n const now = Date.now();\n const hoursSinceSync = (now - lastSync) / (1000 * 60 * 60);\n\n return hoursSinceSync >= autoSyncHours;\n}\n\n// =============================================================================\n// Unified Doc Sync with Defaults\n// =============================================================================\n\n/**\n * Options for syncDocsWithDefaults.\n */\nexport interface SyncDocsOptions {\n /** If true, suppress output (for auto-sync) */\n quiet?: boolean;\n /** If true, don't write files or config (dry run for --status) */\n dryRun?: boolean;\n}\n\n/**\n * Result of syncDocsWithDefaults.\n */\nexport interface SyncDocsResult {\n /** Paths of newly downloaded/copied docs */\n added: string[];\n /** Paths of updated docs (content changed) */\n updated: string[];\n /** Paths of removed docs (no longer in config) */\n removed: string[];\n /** Entries removed due to missing internal sources */\n pruned: string[];\n /** Whether the config was modified (new defaults merged or stale pruned) */\n configChanged: boolean;\n /** Errors encountered during sync */\n errors: { path: string; error: string }[];\n /** Whether the sync was successful overall */\n success: boolean;\n}\n\n/**\n * Check if an internal bundled doc exists.\n *\n * @param location - The internal doc path (without 'internal:' prefix)\n * @returns true if the doc exists in any of the bundled doc paths\n */\nexport async function internalDocExists(location: string): Promise<boolean> {\n const basePaths = getDocsBasePath();\n\n for (const basePath of basePaths) {\n const fullPath = join(basePath, location);\n try {\n await access(fullPath);\n return true;\n } catch {\n // Try next path\n }\n }\n\n return false;\n}\n\n/**\n * Prune entries from config that point to non-existent internal sources.\n *\n * This handles the case where a bundled doc is removed in a tbd update -\n * the stale config entry is automatically cleaned up.\n *\n * @param config - The doc_cache config to prune\n * @returns Object with pruned config and list of removed entries\n */\nexport async function pruneStaleInternals(\n config: Record<string, string>,\n): Promise<{ config: Record<string, string>; pruned: string[] }> {\n const result: Record<string, string> = {};\n const pruned: string[] = [];\n\n for (const [dest, source] of Object.entries(config)) {\n if (source.startsWith(INTERNAL_SOURCE_PREFIX)) {\n const location = source.slice(INTERNAL_SOURCE_PREFIX.length);\n const exists = await internalDocExists(location);\n if (!exists) {\n pruned.push(dest);\n continue; // Don't include in result\n }\n }\n result[dest] = source;\n }\n\n return { config: result, pruned };\n}\n\n/**\n * Deep equality check for config objects.\n */\nfunction configsEqual(a: Record<string, string>, b: Record<string, string>): boolean {\n const keysA = Object.keys(a).sort();\n const keysB = Object.keys(b).sort();\n\n if (keysA.length !== keysB.length) {\n return false;\n }\n\n for (const key of keysA) {\n if (!Object.hasOwn(b, key) || a[key] !== b[key]) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Sync docs with merged defaults and auto-pruning.\n *\n * This is the single entry point for all doc sync operations that need\n * to pick up new bundled docs from tbd upgrades.\n *\n * Steps:\n * 1. Read current config\n * 2. Generate defaults from bundled docs\n * 3. Merge: defaults as base, user config overlays\n * 4. Prune entries with missing internal sources\n * 5. Sync files to .tbd/docs/\n * 6. Write config if changed\n * 7. Update last_doc_sync_at in state\n *\n * @param tbdRoot - The tbd project root directory\n * @param options - Sync options (quiet, dryRun)\n * @returns Sync result with added/updated/removed/pruned counts\n */\nexport async function syncDocsWithDefaults(\n tbdRoot: string,\n options: SyncDocsOptions = {},\n): Promise<SyncDocsResult> {\n // 1. Read current config\n const config = await readConfig(tbdRoot);\n const originalFiles = config.docs_cache?.files ?? {};\n\n // 2. Generate defaults from bundled docs\n const defaults = await generateDefaultDocCacheConfig();\n\n // 3. Merge: defaults as base, user config overlays\n const merged = mergeDocCacheConfig(originalFiles, defaults);\n\n // 4. Prune entries with missing internal sources\n const { config: prunedConfig, pruned } = await pruneStaleInternals(merged);\n\n // 5. Sync files to .tbd/docs/\n const docSync = new DocSync(tbdRoot, prunedConfig);\n const syncResult = await docSync.sync({ dryRun: options.dryRun });\n\n // 6. Check if config changed\n const configChanged = !configsEqual(prunedConfig, originalFiles);\n\n // 7. Write config if changed (and not dry run)\n if (configChanged && !options.dryRun) {\n // Preserve existing lookup_path or use default\n const lookupPath = config.docs_cache?.lookup_path ?? [\n '.tbd/docs/shortcuts/system',\n '.tbd/docs/shortcuts/standard',\n ];\n config.docs_cache = {\n lookup_path: lookupPath,\n files: prunedConfig,\n };\n await writeConfig(tbdRoot, config);\n }\n\n // 8. Update state (and not dry run)\n if (!options.dryRun) {\n await updateLocalState(tbdRoot, {\n last_doc_sync_at: new Date().toISOString(),\n });\n }\n\n return {\n added: syncResult.added,\n updated: syncResult.updated,\n removed: syncResult.removed,\n pruned,\n configChanged,\n errors: syncResult.errors,\n success: syncResult.success,\n };\n}\n","/**\n * Workspace operations for sync failure recovery, backups, and bulk editing.\n *\n * Workspaces are directories under .tbd/workspaces/ that store issue data.\n * They mirror the data-sync directory structure:\n * .tbd/workspaces/{name}/\n * issues/\n * mappings/\n * attic/\n *\n * See: plan-2026-01-30-workspace-sync-alt.md\n */\n\nimport { mkdir, readdir, rm, stat } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { writeFile } from 'atomically';\n\nimport { sortKeys, stringifyYaml } from '../utils/yaml-utils.js';\nimport { ATTIC_ENTRY_FIELD_ORDER } from '../lib/schemas.js';\n\nimport { listIssues, writeIssue, readIssue } from './storage.js';\nimport { parseIssue } from './parser.js';\nimport { mergeIssues, issuesSubstantivelyEqual, git, type ConflictEntry } from './git.js';\nimport { loadIdMapping, saveIdMapping, addIdMapping, reconcileMappings } from './id-mapping.js';\nimport {\n WORKSPACES_DIR,\n getWorkspaceDir,\n isValidWorkspaceName,\n DATA_SYNC_DIR,\n} from '../lib/paths.js';\nimport { extractUlidFromInternalId } from '../lib/ids.js';\nimport { now } from '../utils/time-utils.js';\nimport { noopLogger } from '../lib/types.js';\nimport type { AtticEntry, Issue, OperationLogger } from '../lib/types.js';\n\n/**\n * Options for saveToWorkspace.\n * One of workspace, dir, or outbox must be specified.\n */\nexport interface SaveOptions {\n /** Named workspace under .tbd/workspaces/ */\n workspace?: string;\n /** Arbitrary directory path */\n dir?: string;\n /** Shortcut for --workspace=outbox --updates-only */\n outbox?: boolean;\n /** Only save issues modified since last sync */\n updatesOnly?: boolean;\n /** Optional logger for progress reporting */\n logger?: OperationLogger;\n}\n\n/**\n * Result from saveToWorkspace operation.\n */\nexport interface SaveResult {\n /** Number of issues saved */\n saved: number;\n /** Number of conflicts (went to attic) */\n conflicts: number;\n /** Target directory where issues were saved */\n targetDir: string;\n /** Total issues in source before filtering (for informational messages) */\n totalSource: number;\n /** Whether filtering was applied (updatesOnly or outbox) */\n filtered: boolean;\n}\n\n/**\n * Options for importFromWorkspace.\n * One of workspace, dir, or outbox must be specified.\n */\nexport interface ImportOptions {\n /** Named workspace under .tbd/workspaces/ */\n workspace?: string;\n /** Arbitrary directory path */\n dir?: string;\n /** Shortcut for --workspace=outbox --clear-on-success */\n outbox?: boolean;\n /** Delete workspace after successful import */\n clearOnSuccess?: boolean;\n /** Optional logger for progress reporting */\n logger?: OperationLogger;\n}\n\n/**\n * Result from importFromWorkspace operation.\n */\nexport interface ImportResult {\n /** Number of issues imported */\n imported: number;\n /** Number of conflicts (went to attic) */\n conflicts: number;\n /** Source directory where issues were imported from */\n sourceDir: string;\n /** Whether the source was deleted after import */\n cleared: boolean;\n}\n\n/**\n * Compare local issues with remote issues and return only those that are new or modified.\n *\n * An issue is considered \"updated\" if:\n * - It doesn't exist in the remote (new issue)\n * - Its substantive content differs from the remote version (modified issue)\n *\n * Uses issuesSubstantivelyEqual to ignore metadata-only changes (version, updated_at)\n * that don't represent meaningful content changes. This prevents trivial timestamp\n * bumps from causing thousands of issues to be saved to the outbox.\n *\n * @param localIssues - Issues from the local worktree\n * @param remoteIssues - Issues from the remote tbd-sync branch\n * @returns Issues that are new or substantively modified compared to remote\n */\nexport function getUpdatedIssues(localIssues: Issue[], remoteIssues: Issue[]): Issue[] {\n // Build a map of remote issues by ID for quick lookup\n const remoteById = new Map<string, Issue>();\n for (const issue of remoteIssues) {\n remoteById.set(issue.id, issue);\n }\n\n // Filter local issues to only those that are new or substantively different\n return localIssues.filter((local) => {\n const remote = remoteById.get(local.id);\n\n // New issue - not in remote\n if (!remote) {\n return true;\n }\n\n // Modified issue - substantive content differs from remote\n // Ignores version and updated_at which change on every merge\n return !issuesSubstantivelyEqual(local, remote);\n });\n}\n\n/**\n * Ensure a directory exists.\n */\nasync function ensureDir(dir: string): Promise<void> {\n await mkdir(dir, { recursive: true });\n}\n\n/**\n * Read issues from a git ref (e.g., origin/tbd-sync).\n *\n * @param baseDir - The base directory of the git repo\n * @param remote - The remote name (e.g., 'origin')\n * @param branch - The branch name (e.g., 'tbd-sync')\n * @returns Array of issues from the remote ref\n */\nasync function readRemoteIssues(baseDir: string, remote: string, branch: string): Promise<Issue[]> {\n const ref = `${remote}/${branch}`;\n const issuesPath = `${DATA_SYNC_DIR}/issues`;\n\n // List all issue files from the remote ref\n let fileList: string;\n try {\n fileList = await git('-C', baseDir, 'ls-tree', '-r', '--name-only', ref, issuesPath);\n } catch {\n // Remote branch doesn't exist or has no issues\n return [];\n }\n\n const issueFiles = fileList\n .trim()\n .split('\\n')\n .filter((f) => f.endsWith('.md'));\n const issues: Issue[] = [];\n\n for (const filepath of issueFiles) {\n try {\n // Read file content from the remote ref\n const content = await git('-C', baseDir, 'show', `${ref}:${filepath}`);\n const issue = parseIssue(content);\n issues.push(issue);\n } catch {\n // Skip files that can't be parsed\n }\n }\n\n return issues;\n}\n\n/**\n * Convert ConflictEntry to AtticEntry format and save to workspace attic.\n */\nasync function saveConflictToAttic(\n atticDir: string,\n conflict: ConflictEntry,\n winnerSource: 'local' | 'remote',\n): Promise<void> {\n const timestamp = now();\n\n // Convert lost_value to string - handle objects, primitives, and nullish\n const lostValueStr =\n conflict.lost_value == null\n ? ''\n : typeof conflict.lost_value === 'object'\n ? JSON.stringify(conflict.lost_value)\n : JSON.stringify(conflict.lost_value);\n\n const entry: AtticEntry = {\n entity_id: conflict.issue_id,\n timestamp,\n field: conflict.field,\n lost_value: lostValueStr,\n winner_source: winnerSource,\n loser_source: winnerSource === 'local' ? 'remote' : 'local',\n context: {\n local_version: conflict.local_version,\n remote_version: conflict.remote_version,\n local_updated_at: timestamp,\n remote_updated_at: timestamp,\n },\n };\n\n // Create filename: {entity_id}_{timestamp}_{field}.yml\n const safeTimestamp = timestamp.replace(/:/g, '-');\n const filename = `${conflict.issue_id}_${safeTimestamp}_${conflict.field}.yml`;\n const filepath = join(atticDir, filename);\n\n // Sort keys using canonical field order, then serialize\n const sorted = sortKeys(entry as unknown as Record<string, unknown>, ATTIC_ENTRY_FIELD_ORDER);\n const content = stringifyYaml(sorted, { sortMapEntries: false });\n await writeFile(filepath, content);\n}\n\n/**\n * Get the target/source directory for workspace operations.\n */\nfunction resolveWorkspaceDir(\n tbdRoot: string,\n options: { workspace?: string; dir?: string; outbox?: boolean },\n): string {\n if (options.dir) {\n return options.dir;\n }\n\n const workspaceName = options.outbox ? 'outbox' : options.workspace;\n if (!workspaceName) {\n throw new Error('One of --workspace, --dir, or --outbox is required');\n }\n\n if (!isValidWorkspaceName(workspaceName)) {\n throw new Error(`Invalid workspace name: ${workspaceName}`);\n }\n\n return join(tbdRoot, getWorkspaceDir(workspaceName));\n}\n\n/**\n * Get the target directory for save operation.\n * @deprecated Use resolveWorkspaceDir instead\n */\nfunction getTargetDir(tbdRoot: string, options: SaveOptions): string {\n return resolveWorkspaceDir(tbdRoot, options);\n}\n\n/**\n * Save issues from data-sync directory to a workspace or directory.\n *\n * Uses mergeIssues() for proper conflict detection when an issue exists\n * in both source (worktree) and target (workspace). Conflicts are saved\n * to the workspace's attic.\n *\n * @param tbdRoot - The root directory of the tbd project\n * @param dataSyncDir - The data-sync directory containing source issues\n * @param options - Save options (workspace name, directory, or outbox)\n * @returns Save result with counts\n */\nexport async function saveToWorkspace(\n tbdRoot: string,\n dataSyncDir: string,\n options: SaveOptions,\n): Promise<SaveResult> {\n const log = options.logger ?? noopLogger;\n const targetDir = getTargetDir(tbdRoot, options);\n log.debug(`Target directory: ${targetDir}`);\n log.debug(`Source directory: ${dataSyncDir}`);\n\n // Create target directory structure\n const atticDir = join(targetDir, 'attic');\n\n await ensureDir(join(targetDir, 'issues'));\n await ensureDir(join(targetDir, 'mappings'));\n await ensureDir(atticDir);\n\n // List all issues in source (worktree)\n log.progress('Loading issues from worktree...');\n const allSourceIssues = await listIssues(dataSyncDir);\n const totalSource = allSourceIssues.length;\n log.info(`Loaded ${totalSource} issue(s) from worktree`);\n let sourceIssues = allSourceIssues;\n\n // Filter to only updated issues if requested\n const isUpdatesOnly = options.updatesOnly ?? options.outbox;\n if (isUpdatesOnly) {\n // Try to fetch latest remote state (may fail if offline)\n try {\n log.progress('Fetching remote for comparison...');\n await git('-C', tbdRoot, 'fetch', 'origin', 'tbd-sync');\n log.debug('Fetch succeeded');\n } catch (fetchError) {\n const fetchMsg = fetchError instanceof Error ? fetchError.message : String(fetchError);\n log.info(`Fetch failed (${fetchMsg}), using cached remote state for comparison`);\n }\n\n // Try to read remote issues (works even if fetch failed, using cached ref).\n // This prevents saving ALL issues when the network is down but we have a\n // cached copy of the remote state from a previous fetch.\n try {\n const remoteIssues = await readRemoteIssues(tbdRoot, 'origin', 'tbd-sync');\n log.info(`Loaded ${remoteIssues.length} remote issue(s) for comparison`);\n sourceIssues = getUpdatedIssues(allSourceIssues, remoteIssues);\n log.info(`Filtered to ${sourceIssues.length} updated issue(s) (of ${totalSource} total)`);\n } catch {\n // No cached remote state either (first sync, or ref was never fetched)\n log.info(`No remote state available, falling back to saving all ${totalSource} issues`);\n }\n }\n\n let saved = 0;\n let conflicts = 0;\n\n log.progress(`Saving ${sourceIssues.length} issue(s)...`);\n // Save each issue to target, merging if needed\n for (const sourceIssue of sourceIssues) {\n // Check if issue already exists in workspace\n let targetIssue = null;\n try {\n targetIssue = await readIssue(targetDir, sourceIssue.id);\n } catch {\n // Issue doesn't exist in target - will be created\n }\n\n if (targetIssue) {\n // Issue exists in both - merge using field-level LWW\n // Pass older version as \"base\" to trigger field-by-field merge instead of whole_issue conflict\n const sourceTime = new Date(sourceIssue.updated_at).getTime();\n const targetTime = new Date(targetIssue.updated_at).getTime();\n\n // Use older version as base for field-by-field merge\n let result;\n let winnerSource: 'local' | 'remote';\n\n if (sourceTime !== targetTime) {\n // Different timestamps - clear ordering, use older as base\n const older = sourceTime > targetTime ? targetIssue : sourceIssue;\n winnerSource = sourceTime > targetTime ? 'local' : 'remote';\n result = mergeIssues(older, sourceIssue, targetIssue);\n } else {\n // Equal timestamps - concurrent edits\n // Create synthetic base to force field-by-field comparison\n const syntheticBase = {\n ...sourceIssue,\n version: 0,\n updated_at: '1970-01-01T00:00:00.000Z',\n } as Issue;\n winnerSource = 'local'; // Arbitrary since timestamps are equal\n result = mergeIssues(syntheticBase, sourceIssue, targetIssue);\n }\n\n // Save merged issue\n await writeIssue(targetDir, result.merged);\n saved++;\n log.debug(`Merged issue ${sourceIssue.id} (${result.conflicts.length} field conflict(s))`);\n\n // Save any conflicts to workspace attic\n for (const conflict of result.conflicts) {\n await saveConflictToAttic(atticDir, conflict, winnerSource);\n conflicts++;\n }\n } else {\n // New issue - just save\n await writeIssue(targetDir, sourceIssue);\n saved++;\n }\n\n // Log progress periodically for large saves\n if (saved % 100 === 0 && saved > 0) {\n log.progress(`Saving issues... (${saved}/${sourceIssues.length})`);\n }\n }\n\n log.info(`Saved ${saved} issue(s), ${conflicts} conflict(s)`);\n\n // Copy ID mappings from source to target (only for saved issues)\n // Build set of saved issue ULIDs (without prefix) to filter mappings\n log.debug('Merging ID mappings...');\n const savedIssueUlids = new Set(sourceIssues.map((issue) => extractUlidFromInternalId(issue.id)));\n\n const sourceMapping = await loadIdMapping(dataSyncDir);\n const targetMapping = await loadIdMapping(targetDir);\n\n // Merge: add source mappings to target (only for saved issues, don't overwrite existing)\n for (const [shortId, ulid] of sourceMapping.shortToUlid) {\n // Only copy mapping if the ULID corresponds to a saved issue\n if (savedIssueUlids.has(ulid) && !targetMapping.shortToUlid.has(shortId)) {\n addIdMapping(targetMapping, ulid, shortId);\n }\n }\n await saveIdMapping(targetDir, targetMapping);\n\n return {\n saved,\n conflicts,\n targetDir,\n totalSource,\n filtered: isUpdatesOnly ?? false,\n };\n}\n\n/**\n * Import issues from a workspace or directory to the data-sync directory.\n *\n * Uses mergeIssues() for proper conflict detection when an issue exists\n * in both source (workspace) and target (worktree). Conflicts are saved\n * to the worktree's attic.\n *\n * @param tbdRoot - The root directory of the tbd project\n * @param dataSyncDir - The data-sync directory to import into\n * @param options - Import options (workspace name, directory, or outbox)\n * @returns Import result with counts\n */\nexport async function importFromWorkspace(\n tbdRoot: string,\n dataSyncDir: string,\n options: ImportOptions,\n): Promise<ImportResult> {\n const log = options.logger ?? noopLogger;\n const sourceDir = resolveWorkspaceDir(tbdRoot, options);\n log.debug(`Source directory: ${sourceDir}`);\n log.debug(`Target directory: ${dataSyncDir}`);\n\n // Determine if we should clear on success\n // --outbox implies --clear-on-success\n const shouldClear = options.clearOnSuccess ?? options.outbox ?? false;\n\n // Create attic directory in target (worktree)\n const atticDir = join(dataSyncDir, 'attic');\n await ensureDir(atticDir);\n\n // List all issues in source workspace\n log.progress('Loading issues from workspace...');\n const sourceIssues = await listIssues(sourceDir);\n log.info(`Loaded ${sourceIssues.length} issue(s) from workspace`);\n\n let imported = 0;\n let conflicts = 0;\n\n log.progress(`Importing ${sourceIssues.length} issue(s)...`);\n // Import each issue to data-sync, merging if needed\n for (const sourceIssue of sourceIssues) {\n // Check if issue already exists in worktree\n let targetIssue = null;\n try {\n targetIssue = await readIssue(dataSyncDir, sourceIssue.id);\n } catch {\n // Issue doesn't exist in target - will be created\n }\n\n if (targetIssue) {\n // Issue exists in both - merge using field-level LWW\n // Pass older version as \"base\" to trigger field-by-field merge instead of whole_issue conflict\n const sourceTime = new Date(sourceIssue.updated_at).getTime();\n const targetTime = new Date(targetIssue.updated_at).getTime();\n\n // Use older version as base for field-by-field merge\n let result;\n let winnerSource: 'local' | 'remote';\n\n if (sourceTime !== targetTime) {\n // Different timestamps - clear ordering, use older as base\n const older = sourceTime > targetTime ? targetIssue : sourceIssue;\n winnerSource = sourceTime > targetTime ? 'local' : 'remote';\n result = mergeIssues(older, sourceIssue, targetIssue);\n } else {\n // Equal timestamps - concurrent edits\n // Create synthetic base to force field-by-field comparison\n const syntheticBase = {\n ...sourceIssue,\n version: 0,\n updated_at: '1970-01-01T00:00:00.000Z',\n } as Issue;\n winnerSource = 'local'; // Arbitrary since timestamps are equal\n result = mergeIssues(syntheticBase, sourceIssue, targetIssue);\n }\n\n // Save merged issue\n await writeIssue(dataSyncDir, result.merged);\n imported++;\n log.debug(`Merged issue ${sourceIssue.id} (${result.conflicts.length} field conflict(s))`);\n\n // Save any conflicts to worktree attic\n for (const conflict of result.conflicts) {\n await saveConflictToAttic(atticDir, conflict, winnerSource);\n conflicts++;\n }\n } else {\n // New issue - just save\n await writeIssue(dataSyncDir, sourceIssue);\n imported++;\n }\n\n // Log progress periodically for large imports\n if (imported % 100 === 0 && imported > 0) {\n log.progress(`Importing issues... (${imported}/${sourceIssues.length})`);\n }\n }\n\n log.info(`Imported ${imported} issue(s), ${conflicts} conflict(s)`);\n\n // Merge ID mappings from source (workspace) to target (worktree) - union operation\n log.debug('Merging ID mappings...');\n const sourceMapping = await loadIdMapping(sourceDir);\n const targetMapping = await loadIdMapping(dataSyncDir);\n\n // Merge: add source mappings to target (don't overwrite existing)\n for (const [shortId, ulid] of sourceMapping.shortToUlid) {\n if (!targetMapping.shortToUlid.has(shortId)) {\n addIdMapping(targetMapping, ulid, shortId);\n }\n }\n\n // Reconcile: ensure all imported issues have mappings.\n // Imported issues may have ULIDs not present in either source or target mapping\n // (e.g., when outbox issues were created but their mappings were lost in a merge).\n // Use the source mapping as historical reference to recover original short IDs.\n const reconcileResult = reconcileMappings(\n sourceIssues.map((i) => i.id),\n targetMapping,\n sourceMapping,\n );\n if (reconcileResult.recovered.length > 0) {\n log.info(`Recovered ${reconcileResult.recovered.length} ID mapping(s) from workspace`);\n }\n if (reconcileResult.created.length > 0) {\n log.info(`Created ${reconcileResult.created.length} new ID mapping(s) for imported issues`);\n }\n\n await saveIdMapping(dataSyncDir, targetMapping);\n\n // Clear source workspace if requested\n let cleared = false;\n if (shouldClear && imported > 0) {\n const workspaceName = options.outbox ? 'outbox' : options.workspace;\n if (workspaceName) {\n await deleteWorkspace(tbdRoot, workspaceName);\n cleared = true;\n }\n }\n\n return {\n imported,\n conflicts,\n sourceDir,\n cleared,\n };\n}\n\n/**\n * Issue counts by status for a workspace.\n */\nexport interface WorkspaceIssueCounts {\n open: number;\n in_progress: number;\n closed: number;\n total: number;\n}\n\n/**\n * Information about a workspace including issue counts.\n */\nexport interface WorkspaceInfo {\n name: string;\n counts: WorkspaceIssueCounts;\n}\n\n/**\n * List all workspaces in .tbd/workspaces/.\n *\n * @param tbdRoot - The root directory of the tbd project\n * @returns Array of workspace names\n */\nexport async function listWorkspaces(tbdRoot: string): Promise<string[]> {\n const workspacesDir = join(tbdRoot, WORKSPACES_DIR);\n\n let entries: string[];\n try {\n entries = await readdir(workspacesDir);\n } catch {\n // Directory doesn't exist\n return [];\n }\n\n // Filter to directories only\n const workspaces: string[] = [];\n for (const entry of entries) {\n try {\n const entryPath = join(workspacesDir, entry);\n const entryStat = await stat(entryPath);\n if (entryStat.isDirectory()) {\n workspaces.push(entry);\n }\n } catch {\n // Skip entries we can't stat\n }\n }\n\n return workspaces;\n}\n\n/**\n * List all workspaces with issue counts by status.\n *\n * @param tbdRoot - The root directory of the tbd project\n * @returns Array of workspace info with names and counts\n */\nexport async function listWorkspacesWithCounts(tbdRoot: string): Promise<WorkspaceInfo[]> {\n const workspaceNames = await listWorkspaces(tbdRoot);\n const result: WorkspaceInfo[] = [];\n\n for (const name of workspaceNames) {\n const workspaceDir = join(tbdRoot, getWorkspaceDir(name));\n let issues: Issue[] = [];\n\n try {\n issues = await listIssues(workspaceDir);\n } catch {\n // No issues or can't read - counts will be 0\n }\n\n // Count by status\n const counts: WorkspaceIssueCounts = {\n open: 0,\n in_progress: 0,\n closed: 0,\n total: issues.length,\n };\n\n for (const issue of issues) {\n if (issue.status === 'open' || issue.status === 'blocked' || issue.status === 'deferred') {\n counts.open++;\n } else if (issue.status === 'in_progress') {\n counts.in_progress++;\n } else if (issue.status === 'closed') {\n counts.closed++;\n }\n }\n\n result.push({ name, counts });\n }\n\n return result;\n}\n\n/**\n * Delete a workspace.\n *\n * @param tbdRoot - The root directory of the tbd project\n * @param name - Workspace name\n */\nexport async function deleteWorkspace(tbdRoot: string, name: string): Promise<void> {\n const workspaceDir = join(tbdRoot, getWorkspaceDir(name));\n\n try {\n await rm(workspaceDir, { recursive: true, force: true });\n } catch {\n // Ignore errors if workspace doesn't exist\n }\n}\n\n/**\n * Check if a workspace exists.\n *\n * @param tbdRoot - The root directory of the tbd project\n * @param name - Workspace name\n * @returns true if the workspace directory exists\n */\nexport async function workspaceExists(tbdRoot: string, name: string): Promise<boolean> {\n const workspaceDir = join(tbdRoot, getWorkspaceDir(name));\n\n try {\n const s = await stat(workspaceDir);\n return s.isDirectory();\n } catch {\n return false;\n }\n}\n","/**\n * `tbd sync` - Synchronization commands.\n *\n * See: tbd-design.md §4.7 Sync Commands\n */\n\nimport { Command } from 'commander';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport {\n requireInit,\n NotInitializedError,\n SyncError,\n WorktreeMissingError,\n WorktreeCorruptedError,\n classifySyncError,\n} from '../lib/errors.js';\nimport { readConfig } from '../../file/config.js';\nimport { listIssues, readIssue, writeIssue } from '../../file/storage.js';\nimport {\n git,\n withIsolatedIndex,\n mergeIssues,\n pushWithRetry,\n checkWorktreeHealth,\n repairWorktree,\n ensureWorktreeAttached,\n type ConflictEntry,\n type PushResult,\n} from '../../file/git.js';\nimport { resolveDataSyncDir, DATA_SYNC_DIR, WORKTREE_DIR } from '../../lib/paths.js';\nimport { join } from 'node:path';\nimport {\n type SyncSummary,\n type SyncTallies,\n emptySummary,\n emptyTallies,\n hasTallies,\n formatSyncSummary,\n parseGitStatus,\n parseGitDiff,\n} from '../../lib/sync-summary.js';\nimport { syncDocsWithDefaults, type SyncDocsResult } from '../../file/doc-sync.js';\nimport { ValidationError } from '../lib/errors.js';\nimport {\n loadIdMapping,\n saveIdMapping,\n mergeIdMappings,\n parseIdMappingFromYaml,\n reconcileMappings,\n resolveIdMappingConflicts,\n} from '../../file/id-mapping.js';\nimport {\n saveToWorkspace,\n workspaceExists,\n importFromWorkspace,\n deleteWorkspace,\n} from '../../file/workspace.js';\n\ninterface SyncOptions {\n push?: boolean;\n pull?: boolean;\n status?: boolean;\n force?: boolean;\n fix?: boolean;\n issues?: boolean;\n docs?: boolean;\n autoSave?: boolean; // Commander: --no-auto-save sets this to false (default: true)\n outbox?: boolean; // Commander: --no-outbox sets this to false (default: true)\n}\n\ninterface SyncStatus {\n synced: boolean;\n localChanges: string[];\n remoteChanges: string[];\n syncBranch: string;\n remote: string;\n ahead: number;\n behind: number;\n}\n\nclass SyncHandler extends BaseCommand {\n private dataSyncDir = '';\n private tbdRoot = '';\n\n async run(options: SyncOptions): Promise<void> {\n const tbdRoot = await requireInit();\n this.tbdRoot = tbdRoot;\n\n // Validate mutually exclusive options\n // --push/--pull only apply to issues (network operations)\n if ((options.push || options.pull) && options.docs) {\n throw new ValidationError('--push/--pull only work with issue sync, not --docs');\n }\n\n // Determine what to sync:\n // - If neither --issues nor --docs specified, sync both\n // - If --push or --pull specified (without --issues/--docs), sync only issues\n const hasExclusiveIssueFlag = Boolean(options.push) || Boolean(options.pull);\n const hasSelectiveFlag = Boolean(options.issues) || Boolean(options.docs);\n\n // Sync docs: explicit --docs, or default (no selective flags and no push/pull)\n const syncDocs = Boolean(options.docs) || (!hasSelectiveFlag && !hasExclusiveIssueFlag);\n // Sync issues: explicit --issues, push/pull flags, or default (no selective flags)\n const syncIssues = Boolean(options.issues) || hasExclusiveIssueFlag || !hasSelectiveFlag;\n\n // STEP 1: Sync docs first (fast, local operations)\n // This ensures docs are updated even if issue sync fails\n if (syncDocs) {\n await this.syncDocs(options.status);\n\n // If only doing docs, return after doc sync\n if (!syncIssues) {\n return;\n }\n }\n\n // STEP 2: Sync issues (network operations)\n // Check worktree health before any issue sync operations\n // See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md\n let worktreeHealth = await checkWorktreeHealth(tbdRoot);\n if (!worktreeHealth.valid) {\n // Auto-create worktree if it's simply missing (normal for fresh clones)\n // Only require --fix for corrupted/prunable states that need repair\n if (worktreeHealth.status === 'missing') {\n // Auto-create worktree - this is the expected state on fresh clones\n await this.doRepairWorktree(tbdRoot, 'missing');\n worktreeHealth = await checkWorktreeHealth(tbdRoot);\n if (!worktreeHealth.valid) {\n throw new WorktreeCorruptedError(\n `Failed to create worktree. Status: ${worktreeHealth.status}. Run 'tbd doctor' for diagnostics.`,\n );\n }\n } else if (options.fix) {\n // Attempt repair when --fix is provided for corrupted/prunable states\n await this.doRepairWorktree(tbdRoot, worktreeHealth.status as 'prunable' | 'corrupted');\n // Re-check health after repair\n worktreeHealth = await checkWorktreeHealth(tbdRoot);\n if (!worktreeHealth.valid) {\n throw new WorktreeCorruptedError(\n `Worktree repair failed. Status: ${worktreeHealth.status}. Run 'tbd doctor' for diagnostics.`,\n );\n }\n } else {\n // No --fix flag, throw appropriate error for corrupted/prunable states\n if (worktreeHealth.status === 'prunable') {\n throw new WorktreeMissingError(\n \"Worktree directory was deleted but git still tracks it. Run 'tbd sync --fix' or 'tbd doctor --fix' to repair.\",\n );\n }\n if (worktreeHealth.status === 'corrupted') {\n throw new WorktreeCorruptedError(\n `Worktree is corrupted: ${worktreeHealth.error ?? 'unknown error'}. Run 'tbd sync --fix' or 'tbd doctor --fix' to repair.`,\n );\n }\n }\n }\n\n this.dataSyncDir = await resolveDataSyncDir(tbdRoot);\n\n // Load config to get sync branch\n let config;\n try {\n config = await readConfig(tbdRoot);\n } catch {\n throw new NotInitializedError('Not a tbd repository. Run `tbd init` first.');\n }\n\n const syncBranch = config.sync.branch;\n const remote = config.sync.remote;\n\n if (options.status) {\n await this.showIssueStatus(syncBranch, remote);\n return;\n }\n\n if (this.checkDryRun('Would sync repository', { syncBranch, remote })) {\n return;\n }\n\n if (options.pull) {\n await this.pullChanges(syncBranch, remote);\n } else if (options.push) {\n await this.pushChanges(syncBranch, remote);\n } else {\n // Full sync: pull then push\n await this.fullSync(syncBranch, remote, {\n force: options.force,\n autoSave: options.autoSave,\n outbox: options.outbox,\n });\n }\n }\n\n /**\n * Sync docs from bundled sources and config.\n * This is a fast, local operation (no network required).\n */\n private async syncDocs(statusOnly?: boolean): Promise<SyncDocsResult> {\n if (statusOnly) {\n // Show status without making changes\n const result = await syncDocsWithDefaults(this.tbdRoot, { dryRun: true });\n this.showDocStatus(result);\n return result;\n }\n\n const spinner = this.output.spinner('Syncing docs...');\n const result = await syncDocsWithDefaults(this.tbdRoot);\n spinner.stop();\n\n // Report results\n this.showDocSyncResult(result);\n return result;\n }\n\n /**\n * Show doc sync status (what would change).\n */\n private showDocStatus(result: SyncDocsResult): void {\n const colors = this.output.getColors();\n const hasChanges =\n result.added.length > 0 ||\n result.updated.length > 0 ||\n result.removed.length > 0 ||\n result.pruned.length > 0;\n\n if (!hasChanges) {\n this.output.success('Docs up to date');\n return;\n }\n\n console.log(colors.bold('Docs:'));\n if (result.added.length > 0) {\n console.log(` ${colors.success(`+${result.added.length}`)} new doc(s) available`);\n }\n if (result.updated.length > 0) {\n console.log(` ${colors.warn(`~${result.updated.length}`)} doc(s) to update`);\n }\n if (result.removed.length > 0) {\n console.log(` ${colors.error(`-${result.removed.length}`)} doc(s) to remove`);\n }\n if (result.pruned.length > 0) {\n console.log(` ${colors.dim(`${result.pruned.length}`)} stale config entry/entries`);\n }\n }\n\n /**\n * Show doc sync result after sync.\n */\n private showDocSyncResult(result: SyncDocsResult): void {\n const hasChanges =\n result.added.length > 0 ||\n result.updated.length > 0 ||\n result.removed.length > 0 ||\n result.pruned.length > 0;\n\n if (!hasChanges) {\n this.output.success('Docs up to date');\n return;\n }\n\n // Build summary string\n const parts: string[] = [];\n if (result.added.length > 0) {\n parts.push(`+${result.added.length}`);\n }\n if (result.updated.length > 0) {\n parts.push(`~${result.updated.length}`);\n }\n if (result.removed.length > 0) {\n parts.push(`-${result.removed.length}`);\n }\n\n if (parts.length > 0) {\n this.output.success(`Synced docs: ${parts.join(' ')} doc(s)`);\n }\n\n // Report pruned entries\n if (result.pruned.length > 0) {\n this.output.info(`Removed ${result.pruned.length} stale config entry/entries`);\n }\n\n // Report errors\n for (const err of result.errors) {\n this.output.warn(`Doc sync error: ${err.path}: ${err.error}`);\n }\n }\n\n /**\n * Attempt to repair an unhealthy worktree.\n * See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md\n */\n private async doRepairWorktree(\n tbdRoot: string,\n status: 'missing' | 'prunable' | 'corrupted',\n ): Promise<void> {\n const spinner = this.output.spinner(`Repairing worktree (${status})...`);\n\n try {\n // Use shared repairWorktree from git.ts\n const result = await repairWorktree(tbdRoot, status);\n\n spinner.stop();\n\n if (!result.success) {\n throw new WorktreeCorruptedError(`Failed to repair worktree: ${result.error}`);\n }\n\n if (result.backedUp) {\n this.output.info(`Corrupted worktree backed up to: ${result.backedUp}`);\n }\n this.output.success('Worktree repaired successfully');\n } catch (error) {\n spinner.stop();\n if (error instanceof WorktreeCorruptedError) throw error;\n throw new WorktreeCorruptedError(`Failed to repair worktree: ${(error as Error).message}`);\n }\n }\n\n private async showIssueStatus(syncBranch: string, remote: string): Promise<void> {\n const status = await this.getSyncStatus(syncBranch, remote);\n\n this.output.data(status, () => {\n const colors = this.output.getColors();\n\n if (status.synced) {\n this.output.success('Repository is in sync');\n return;\n }\n\n console.log(colors.bold(`Sync status: ${syncBranch} ↔ ${remote}/${syncBranch}`));\n console.log('');\n\n if (status.ahead > 0) {\n console.log(` ${colors.id(`↑ ${status.ahead}`)} commit(s) ahead (to push)`);\n }\n if (status.behind > 0) {\n console.log(` ${colors.dim(`↓ ${status.behind}`)} commit(s) behind (to pull)`);\n }\n\n if (status.localChanges.length > 0) {\n console.log('');\n console.log(colors.bold('Local changes (not yet pushed):'));\n for (const change of status.localChanges) {\n console.log(` ${change}`);\n }\n }\n\n if (status.remoteChanges.length > 0) {\n console.log('');\n console.log(colors.bold('Remote changes (not yet pulled):'));\n for (const change of status.remoteChanges) {\n console.log(` ${change}`);\n }\n }\n });\n }\n\n private async getSyncStatus(syncBranch: string, remote: string): Promise<SyncStatus> {\n const localChanges: string[] = [];\n const remoteChanges: string[] = [];\n let ahead = 0;\n let behind = 0;\n\n // Check for uncommitted changes in the worktree\n // FIX Bug 2: Previously ran git status on main branch where data-sync/ is gitignored.\n // Now check worktree status directly.\n // See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md\n try {\n const worktreePath = join(this.tbdRoot, WORKTREE_DIR);\n const status = await git('-C', worktreePath, 'status', '--porcelain');\n if (status) {\n for (const line of status.split('\\n')) {\n if (!line) continue;\n const statusCode = line.slice(0, 2).trim();\n const file = line.slice(3);\n if (statusCode === 'M') {\n localChanges.push(`modified: ${file}`);\n } else if (statusCode === 'A' || statusCode === '??') {\n localChanges.push(`new: ${file}`);\n } else if (statusCode === 'D') {\n localChanges.push(`deleted: ${file}`);\n }\n }\n }\n } catch {\n this.output.debug('Git worktree not available');\n }\n\n // Check for remote changes\n try {\n await git('fetch', remote, syncBranch);\n\n // Count commits ahead/behind\n try {\n const aheadOutput = await git(\n 'rev-list',\n '--count',\n `${remote}/${syncBranch}..${syncBranch}`,\n );\n ahead = parseInt(aheadOutput, 10) || 0;\n } catch {\n this.output.debug('Branch does not exist locally');\n }\n\n try {\n const behindOutput = await git(\n 'rev-list',\n '--count',\n `${syncBranch}..${remote}/${syncBranch}`,\n );\n behind = parseInt(behindOutput, 10) || 0;\n } catch {\n this.output.debug('Remote branch does not exist');\n }\n\n // Get commit messages for remote changes\n if (behind > 0) {\n const logOutput = await git(\n 'log',\n '--oneline',\n `${syncBranch}..${remote}/${syncBranch}`,\n '--limit=10',\n );\n for (const line of logOutput.split('\\n')) {\n if (line) {\n remoteChanges.push(line);\n }\n }\n }\n } catch {\n this.output.debug('Remote not available or sync branch does not exist');\n }\n\n return {\n synced:\n localChanges.length === 0 && remoteChanges.length === 0 && ahead === 0 && behind === 0,\n localChanges,\n remoteChanges,\n syncBranch,\n remote,\n ahead,\n behind,\n };\n }\n\n private async pullChanges(syncBranch: string, remote: string): Promise<void> {\n const spinner = this.output.spinner('Pulling from remote...');\n try {\n await git('fetch', remote, syncBranch);\n\n // Get list of changed files\n let behind = 0;\n try {\n const behindOutput = await git(\n 'rev-list',\n '--count',\n `${syncBranch}..${remote}/${syncBranch}`,\n );\n behind = parseInt(behindOutput, 10) || 0;\n } catch {\n this.output.debug('Branch does not exist');\n }\n\n spinner.stop();\n if (behind === 0) {\n this.output.success('Already up to date');\n return;\n }\n\n // Merge changes using isolated index\n await withIsolatedIndex(async () => {\n // Read the remote tree\n await git('read-tree', `${remote}/${syncBranch}`);\n\n // Update local branch to remote\n const remoteCommit = await git('rev-parse', `${remote}/${syncBranch}`);\n await git('update-ref', `refs/heads/${syncBranch}`, remoteCommit);\n });\n\n this.output.success(`Pulled ${behind} change(s) from ${remote}/${syncBranch}`);\n } catch (error) {\n spinner.stop();\n const msg = (error as Error).message;\n if (msg.includes('not found') || msg.includes('does not exist')) {\n this.output.info(`Remote branch ${remote}/${syncBranch} does not exist yet`);\n } else {\n throw new SyncError(`Failed to pull: ${msg}`);\n }\n }\n }\n\n /**\n * Commit any uncommitted changes in the worktree to the sync branch.\n * This must be called before pushing to ensure changes are captured.\n *\n * @returns Tallies of new/updated/deleted files committed\n */\n private async commitWorktreeChanges(): Promise<SyncTallies> {\n // Use tbdRoot to derive worktree path consistently\n // FIX Bug 1: Previously used process.cwd() which fails if not in repo root\n // See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md\n const worktreePath = join(this.tbdRoot, WORKTREE_DIR);\n\n try {\n // Ensure worktree is attached to sync branch (repair old tbd repos)\n await ensureWorktreeAttached(worktreePath);\n\n // Check for uncommitted changes (untracked, modified, or deleted)\n const status = await git('-C', worktreePath, 'status', '--porcelain');\n if (!status || status.trim() === '') {\n return emptyTallies(); // Nothing to commit\n }\n\n // Parse status to get tallies\n const tallies = parseGitStatus(status);\n const fileCount = tallies.new + tallies.updated + tallies.deleted;\n\n // Stage all changes\n await git('-C', worktreePath, 'add', '-A');\n\n // Commit the changes\n const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);\n await git(\n '-C',\n worktreePath,\n 'commit',\n '-m',\n `tbd sync: ${timestamp} (${fileCount} file${fileCount === 1 ? '' : 's'})`,\n );\n\n return tallies;\n } catch (error) {\n // If commit fails (e.g., nothing to commit after staging), that's ok\n const msg = (error as Error).message;\n if (msg.includes('nothing to commit')) {\n return emptyTallies();\n }\n throw error;\n }\n }\n\n private async pushChanges(syncBranch: string, remote: string): Promise<void> {\n const spinner = this.output.spinner('Pushing to remote...');\n try {\n // Commit any uncommitted changes in the worktree before pushing\n const committedTallies = await this.commitWorktreeChanges();\n const committedCount =\n committedTallies.new + committedTallies.updated + committedTallies.deleted;\n if (committedCount > 0) {\n this.output.info(`Committed ${committedCount} file(s) to sync branch`);\n }\n\n // Check how many commits we're ahead of remote\n let ahead = 0;\n try {\n await git('fetch', remote, syncBranch);\n const aheadOutput = await git(\n 'rev-list',\n '--count',\n `${remote}/${syncBranch}..${syncBranch}`,\n );\n ahead = parseInt(aheadOutput, 10) || 0;\n this.output.debug(`Ahead of remote by ${ahead} commit(s)`);\n } catch {\n // Remote branch doesn't exist - count all local commits\n try {\n const countOutput = await git('rev-list', '--count', syncBranch);\n ahead = parseInt(countOutput, 10) || 0;\n this.output.debug(`Remote branch not found, ${ahead} local commit(s) to push`);\n } catch {\n ahead = 0;\n this.output.debug('Could not count local commits');\n }\n }\n\n if (ahead === 0) {\n spinner.stop();\n this.output.success('Already up to date');\n return;\n }\n\n // Use push with retry\n const result = await this.doPushWithRetry(syncBranch, remote);\n spinner.stop();\n\n if (result.success) {\n this.output.success(`Pushed ${ahead} commit(s) to ${remote}/${syncBranch}`);\n } else if (result.conflicts && result.conflicts.length > 0) {\n this.output.warn(\n `Push completed with ${result.conflicts.length} conflict(s) (see attic for details)`,\n );\n } else {\n throw new SyncError(`Failed to push: ${result.error}`);\n }\n } catch (error) {\n spinner.stop();\n if (error instanceof SyncError) throw error;\n throw new SyncError(`Failed to push: ${(error as Error).message}`);\n }\n }\n\n private async doPushWithRetry(syncBranch: string, remote: string): Promise<PushResult> {\n return pushWithRetry(\n syncBranch,\n remote,\n async () => {\n // Merge callback - called when we need to merge remote changes\n const conflicts: ConflictEntry[] = [];\n\n // Get list of issues that need merging\n const localIssues = await listIssues(this.dataSyncDir);\n\n for (const localIssue of localIssues) {\n try {\n // Try to get the remote version (use relative path for git show)\n const remoteContent = await git(\n 'show',\n `${remote}/${syncBranch}:${DATA_SYNC_DIR}/issues/${localIssue.id}.md`,\n );\n\n if (remoteContent) {\n // Parse remote issue and merge\n const remoteIssue = await readIssue(this.dataSyncDir, localIssue.id);\n const result = mergeIssues(null, localIssue, remoteIssue);\n\n // Write merged result\n await writeIssue(this.dataSyncDir, result.merged);\n conflicts.push(...result.conflicts);\n }\n } catch {\n // Issue doesn't exist remotely - no merge needed\n this.output.debug(`Issue ${localIssue.id} not on remote, no merge needed`);\n }\n }\n\n return conflicts;\n },\n this.tbdRoot,\n );\n }\n\n /**\n * Show git log --stat output in debug mode.\n * Used to display commits that were synced.\n *\n * @param label - Label for the debug output (e.g., \"Commits sent\")\n * @param args - Arguments to pass to `git log` after `--stat --oneline`\n * Must include explicit branch/ref to avoid showing commits from the wrong branch.\n */\n private async showGitLogDebug(label: string, ...args: string[]): Promise<void> {\n try {\n const logOutput = await git('log', '--stat', '--oneline', ...args);\n if (logOutput.trim()) {\n this.output.debug(`${label}:`);\n for (const line of logOutput.split('\\n')) {\n this.output.debug(` ${line}`);\n }\n }\n } catch {\n // Ignore errors - log is just for debugging\n }\n }\n\n private async fullSync(\n syncBranch: string,\n remote: string,\n options: { force?: boolean; autoSave?: boolean; outbox?: boolean } = {},\n ): Promise<void> {\n const spinner = this.output.spinner('Syncing with remote...');\n const summary: SyncSummary = emptySummary();\n const conflicts: ConflictEntry[] = [];\n // Use tbdRoot for consistent path resolution\n const worktreePath = join(this.tbdRoot, WORKTREE_DIR);\n\n try {\n // STEP 1: Commit local changes FIRST (before pulling)\n // This ensures local work is preserved before we incorporate remote changes.\n const committedTallies = await this.commitWorktreeChanges();\n // Add committed changes to sent tallies\n summary.sent.new += committedTallies.new;\n summary.sent.updated += committedTallies.updated;\n summary.sent.deleted += committedTallies.deleted;\n if (hasTallies(committedTallies)) {\n const count = committedTallies.new + committedTallies.updated + committedTallies.deleted;\n this.output.debug(`Committed ${count} file(s) to sync branch`);\n }\n\n // STEP 2: Fetch remote\n await git('fetch', remote, syncBranch);\n\n // Get file-level changes from remote using git diff\n let behindCommits = 0;\n try {\n const behindOutput = await git(\n 'rev-list',\n '--count',\n `${syncBranch}..${remote}/${syncBranch}`,\n );\n behindCommits = parseInt(behindOutput, 10) || 0;\n this.output.debug(`Behind remote by ${behindCommits} commit(s)`);\n\n // Get file-level tallies for received changes\n if (behindCommits > 0) {\n try {\n const diffOutput = await git(\n 'diff',\n '--name-status',\n `${syncBranch}..${remote}/${syncBranch}`,\n );\n const receivedTallies = parseGitDiff(diffOutput);\n summary.received.new += receivedTallies.new;\n summary.received.updated += receivedTallies.updated;\n summary.received.deleted += receivedTallies.deleted;\n } catch {\n // If we can't get detailed diff, just track commit count\n this.output.debug('Could not get detailed diff for received changes');\n }\n }\n } catch {\n // Branch doesn't exist on remote\n this.output.debug('Remote sync branch does not exist yet');\n }\n\n // Ensure .gitattributes exists in the worktree so ids.yml uses merge=union.\n // This prevents conflicts when both sides add non-overlapping keys.\n // Written before every merge so existing repos get it on their next sync.\n {\n const { access, writeFile } = await import('node:fs/promises');\n const attrPath = join(this.dataSyncDir, 'mappings', '.gitattributes');\n try {\n await access(attrPath);\n } catch {\n await writeFile(attrPath, 'ids.yml merge=union\\n');\n await git('-C', worktreePath, 'add', attrPath);\n try {\n await git(\n '-C',\n worktreePath,\n 'commit',\n '--no-verify',\n '-m',\n 'chore: add merge=union for ids.yml',\n );\n } catch {\n // May fail if nothing to commit (already staged elsewhere)\n }\n }\n }\n\n // STEP 3: If remote has changes, merge them in\n if (behindCommits > 0) {\n // Track HEAD before merge for debug log\n let headBeforeMerge = '';\n try {\n headBeforeMerge = (await git('-C', worktreePath, 'rev-parse', 'HEAD')).trim();\n } catch {\n // Ignore - just won't show debug log\n }\n\n // Merge remote into local using worktree\n // This is a proper git merge that preserves both local and remote changes\n try {\n await git(\n '-C',\n worktreePath,\n 'merge',\n `${remote}/${syncBranch}`,\n '-m',\n 'tbd sync: merge remote changes',\n );\n this.output.debug(`Merged ${behindCommits} commit(s) from remote`);\n\n // Show received commits in debug mode\n // Use syncBranch explicitly — bare `HEAD` would resolve to the user's\n // current working branch, not the tbd-sync branch in the worktree.\n if (headBeforeMerge) {\n await this.showGitLogDebug('Commits received', `${headBeforeMerge}..${syncBranch}`);\n }\n\n // Reconcile ID mappings after clean merge.\n // A git merge may add issue files without corresponding ids.yml entries\n // (e.g., when outbox issues were committed to a feature branch).\n // Try to recover original short IDs from the remote's mapping to preserve\n // ID stability (so existing references in docs/PRs remain valid).\n const postMergeIssues = await listIssues(this.dataSyncDir);\n const postMergeMapping = await loadIdMapping(this.dataSyncDir);\n\n // Load historical mapping from remote to recover original short IDs\n let historicalMapping: Awaited<ReturnType<typeof loadIdMapping>> | undefined;\n try {\n const remoteIdsContent = await git(\n 'show',\n `${remote}/${syncBranch}:${DATA_SYNC_DIR}/mappings/ids.yml`,\n );\n if (remoteIdsContent) {\n historicalMapping = parseIdMappingFromYaml(remoteIdsContent);\n }\n } catch {\n // Remote mapping not available - will generate new IDs\n }\n\n const reconcileResult = reconcileMappings(\n postMergeIssues.map((i) => i.id),\n postMergeMapping,\n historicalMapping,\n );\n const totalReconciled = reconcileResult.created.length + reconcileResult.recovered.length;\n if (totalReconciled > 0) {\n await saveIdMapping(this.dataSyncDir, postMergeMapping);\n // Commit the updated mapping so it's included in the push\n await git('-C', worktreePath, 'add', '-A');\n try {\n await git(\n '-C',\n worktreePath,\n 'commit',\n '--no-verify',\n '-m',\n `tbd sync: reconcile ${totalReconciled} missing ID mapping(s)`,\n );\n } catch {\n // Nothing to commit if mapping file was unchanged\n }\n if (reconcileResult.recovered.length > 0) {\n this.output.debug(\n `Recovered ${reconcileResult.recovered.length} ID mapping(s) from history`,\n );\n }\n if (reconcileResult.created.length > 0) {\n this.output.debug(\n `Created ${reconcileResult.created.length} new ID mapping(s) (no history available)`,\n );\n }\n }\n } catch {\n // Merge conflict - try to resolve at file level\n this.output.info(`Merge conflict, attempting file-level resolution`);\n\n // For each conflicted issue, do field-level merge\n const localIssues = await listIssues(this.dataSyncDir);\n for (const localIssue of localIssues) {\n try {\n const remoteContent = await git(\n 'show',\n `${remote}/${syncBranch}:${DATA_SYNC_DIR}/issues/${localIssue.id}.md`,\n );\n if (remoteContent) {\n const remoteIssue = await readIssue(this.dataSyncDir, localIssue.id);\n const result = mergeIssues(null, localIssue, remoteIssue);\n await writeIssue(this.dataSyncDir, result.merged);\n conflicts.push(...result.conflicts);\n }\n } catch {\n // Issue doesn't exist remotely - keep local version\n this.output.debug(`Issue ${localIssue.id} not on remote, keeping local`);\n }\n }\n\n // Merge ids.yml (ID mappings are always additive, so we union both sides)\n // Also capture the remote mapping for recovery of original short IDs.\n // The on-disk ids.yml may contain conflict markers after a failed git merge,\n // so we resolve conflicts by extracting both sides and merging.\n let conflictRemoteMapping: Awaited<ReturnType<typeof loadIdMapping>> | undefined;\n try {\n const remoteIdsContent = await git(\n 'show',\n `${remote}/${syncBranch}:${DATA_SYNC_DIR}/mappings/ids.yml`,\n );\n if (remoteIdsContent) {\n conflictRemoteMapping = parseIdMappingFromYaml(remoteIdsContent);\n // Read the on-disk file (which may have conflict markers) and resolve\n const { readFile } = await import('node:fs/promises');\n const idsPath = join(this.dataSyncDir, 'mappings', 'ids.yml');\n const rawContent = await readFile(idsPath, 'utf-8');\n const localMapping = resolveIdMappingConflicts(rawContent);\n const mergedMapping = mergeIdMappings(localMapping, conflictRemoteMapping);\n await saveIdMapping(this.dataSyncDir, mergedMapping);\n this.output.debug(\n `Merged ID mappings: ${localMapping.shortToUlid.size} local + ${conflictRemoteMapping.shortToUlid.size} remote = ${mergedMapping.shortToUlid.size} total`,\n );\n }\n } catch (error) {\n // Remote ids.yml doesn't exist or can't be parsed - keep local\n this.output.debug(`Could not merge ids.yml: ${(error as Error).message}`);\n }\n\n // Reconcile any remaining issues without mappings after conflict resolution.\n // Use the remote mapping as historical source to recover original short IDs.\n {\n const allIssues = await listIssues(this.dataSyncDir);\n const currentMapping = await loadIdMapping(this.dataSyncDir);\n const reconcileResult = reconcileMappings(\n allIssues.map((i) => i.id),\n currentMapping,\n conflictRemoteMapping,\n );\n const totalReconciled =\n reconcileResult.created.length + reconcileResult.recovered.length;\n if (totalReconciled > 0) {\n await saveIdMapping(this.dataSyncDir, currentMapping);\n if (reconcileResult.recovered.length > 0) {\n this.output.debug(\n `Recovered ${reconcileResult.recovered.length} ID mapping(s) from remote`,\n );\n }\n if (reconcileResult.created.length > 0) {\n this.output.debug(\n `Created ${reconcileResult.created.length} new ID mapping(s) after conflict resolution`,\n );\n }\n }\n }\n\n // Stage resolved files and complete merge\n // Use --no-verify to bypass parent repo hooks (lefthook, husky, etc.)\n await git('-C', worktreePath, 'add', '-A');\n\n // SAFETY CHECK: Never commit files with unresolved merge conflict markers\n // This prevents the bug where ids.yml or other files get committed with\n // <<<<<<< HEAD markers still present\n const conflictCheck = await git(\n '-C',\n worktreePath,\n 'diff',\n '--cached',\n '-S<<<<<<< ',\n '--name-only',\n );\n if (conflictCheck.trim()) {\n const conflictedFiles = conflictCheck.trim().split('\\n');\n throw new SyncError(\n `Cannot commit: ${conflictedFiles.length} file(s) still have merge conflict markers:\\n` +\n conflictedFiles.map((f) => ` - ${f}`).join('\\n') +\n `\\n\\nThis is a bug in tbd sync. Please report it and manually resolve conflicts in:\\n` +\n ` ${worktreePath}`,\n );\n }\n\n try {\n await git(\n '-C',\n worktreePath,\n 'commit',\n '--no-verify',\n '-m',\n 'tbd sync: resolved merge conflicts',\n );\n } catch {\n // May fail if no conflicts needed resolving\n this.output.debug('No merge commit needed (conflicts already resolved)');\n }\n }\n }\n } catch (error) {\n // Remote not available - that's ok for first sync\n this.output.debug(`Fetch failed (may be first sync): ${(error as Error).message}`);\n }\n\n // Check how many commits we're ahead of remote (if any)\n let aheadCommits = 0;\n try {\n const aheadOutput = await git(\n 'rev-list',\n '--count',\n `${remote}/${syncBranch}..${syncBranch}`,\n );\n aheadCommits = parseInt(aheadOutput, 10) || 0;\n this.output.debug(`Ahead of remote by ${aheadCommits} commit(s)`);\n } catch {\n // Remote branch doesn't exist - count all local commits on sync branch\n try {\n const countOutput = await git('rev-list', '--count', syncBranch);\n aheadCommits = parseInt(countOutput, 10) || 0;\n this.output.debug(`Remote branch not found, ${aheadCommits} local commit(s) to push`);\n } catch {\n aheadCommits = 0;\n this.output.debug('Could not count local commits');\n }\n }\n\n // Push if we have commits ahead of remote\n let pushFailed = false;\n let pushError = '';\n if (aheadCommits > 0) {\n this.output.debug(`Pushing ${aheadCommits} commit(s) to remote`);\n const result = await this.doPushWithRetry(syncBranch, remote);\n if (result.conflicts) {\n conflicts.push(...result.conflicts);\n }\n if (!result.success) {\n pushFailed = true;\n pushError = result.error ?? 'Unknown push error';\n this.output.debug(`Push failed: ${pushError}`);\n } else {\n // Show pushed commits in debug mode\n // Use syncBranch explicitly — bare `-N` would resolve against the user's\n // current working branch (HEAD), not the tbd-sync branch.\n await this.showGitLogDebug('Commits sent', syncBranch, `-${aheadCommits}`);\n }\n } else {\n this.output.debug('No commits to push');\n }\n\n summary.conflicts = conflicts.length;\n spinner.stop();\n\n // Report push failure - classify error and take appropriate action\n if (pushFailed) {\n // Extract meaningful error display string\n let displayError = pushError;\n const httpMatch = /HTTP (\\d+)/.exec(pushError);\n const curlMatch = /curl \\d+ (.+?)(?:\\n|$)/.exec(pushError);\n if (httpMatch) {\n displayError = `HTTP ${httpMatch[1]}${curlMatch ? ` - ${curlMatch[1]}` : ''}`;\n } else {\n // Fall back to first meaningful line (skip \"Command failed: git push...\")\n const lines = pushError.split('\\n').filter((l) => l && !l.startsWith('Command failed'));\n displayError = lines[0] ?? pushError;\n }\n\n // Classify the error to determine recovery action\n const errorType = classifySyncError(pushError);\n\n this.output.data(\n {\n summary,\n conflicts: conflicts.length,\n pushFailed,\n pushError,\n unpushedCommits: aheadCommits,\n errorType,\n },\n () => {\n this.output.error(`Push failed: ${displayError}`);\n console.log(` ${aheadCommits} commit(s) not pushed to remote.`);\n },\n );\n\n // Handle recovery based on error type (after output.data to avoid async callback)\n // Only show options in non-JSON mode\n if (errorType === 'permanent' && options.autoSave !== false) {\n // Auto-save to outbox on permanent failure\n await this.handlePermanentFailure();\n } else if (!this.ctx.json) {\n if (errorType === 'transient') {\n // Suggest retry for transient failures\n console.log('');\n console.log(' This appears to be a temporary issue. Options:');\n console.log(' • Retry: tbd sync');\n console.log(' • Save for later: tbd save --outbox');\n } else {\n // Unknown error - suggest both options\n console.log('');\n console.log(' Options:');\n console.log(' • Retry: tbd sync');\n console.log(\" • Run 'tbd sync --status' to check status\");\n console.log(' • Save for later: tbd save --outbox');\n }\n }\n return;\n }\n\n // After successful push, import from outbox if it has data\n if (options.outbox !== false) {\n await this.maybeImportOutbox(syncBranch, remote);\n }\n\n this.output.data({ summary, conflicts: conflicts.length }, () => {\n const summaryText = formatSyncSummary(summary);\n if (!summaryText) {\n this.output.success('Already in sync');\n } else {\n this.output.success(`Synced: ${summaryText}`);\n }\n });\n }\n\n /**\n * Handle permanent push failure by auto-saving to outbox.\n * Called when push fails with a permanent error (e.g., HTTP 403).\n */\n private async handlePermanentFailure(): Promise<void> {\n // Count issues in worktree to see if there's anything to save\n const worktreeIssues = await listIssues(this.dataSyncDir);\n if (worktreeIssues.length === 0) {\n console.log('');\n console.log(' No unsynced issues to save (already in sync with remote).');\n return;\n }\n\n // Check existing outbox count before save\n let existingOutboxCount = 0;\n if (await workspaceExists(this.tbdRoot, 'outbox')) {\n const outboxPath = join(this.tbdRoot, '.tbd', 'workspaces', 'outbox');\n try {\n const existingIssues = await listIssues(outboxPath);\n existingOutboxCount = existingIssues.length;\n } catch {\n // Outbox exists but couldn't read - will be handled by saveToWorkspace\n }\n }\n\n try {\n // Auto-save to outbox (merges with existing outbox data via updatesOnly)\n const result = await saveToWorkspace(this.tbdRoot, this.dataSyncDir, { outbox: true });\n\n if (result.saved === 0) {\n // Nothing new to save - issues already in outbox from previous failure\n console.log('');\n console.log(' Issues already saved to outbox from previous sync attempt.');\n console.log('');\n console.log(' Your issues are safe. To recover later:');\n console.log(\" 1. Commit: git add .tbd/workspaces && git commit -m 'tbd: save outbox'\");\n console.log(' 2. Push your working branch: git push');\n console.log(\" 3. Run 'tbd sync' when push access is available\");\n console.log('');\n console.log(\n ' WARNING: Do NOT add .tbd/workspaces/ to .gitignore -- that would cause data loss.',\n );\n } else {\n // Show saved count and total in outbox\n const totalInOutbox = existingOutboxCount + result.saved;\n if (existingOutboxCount > 0) {\n console.log('');\n this.output.success(\n `Saved ${result.saved} issue(s) to outbox (${totalInOutbox} total in outbox)`,\n );\n } else {\n console.log('');\n this.output.success(`Saved ${result.saved} issue(s) to outbox (automatic backup)`);\n }\n console.log('');\n console.log(' Your issues are safe. To recover later:');\n console.log(\" 1. Commit: git add .tbd/workspaces && git commit -m 'tbd: save outbox'\");\n console.log(' 2. Push your working branch: git push');\n console.log(\" 3. Run 'tbd sync' when push access is available\");\n console.log(' (outbox will be imported automatically on successful sync)');\n console.log('');\n console.log(\n ' WARNING: Do NOT add .tbd/workspaces/ to .gitignore -- that would cause data loss.',\n );\n }\n } catch (saveError) {\n // Auto-save failed - report both errors\n const saveErrorMsg = saveError instanceof Error ? saveError.message : String(saveError);\n console.log('');\n this.output.error(`Auto-save to outbox also failed: ${saveErrorMsg}`);\n console.log('');\n console.log(\" Run 'tbd save --outbox' manually, or 'tbd doctor' to diagnose.\");\n }\n }\n\n /**\n * Import pending issues from outbox after a successful push.\n * Uses two-phase sync: import → commit → push → clear.\n * Only clears the outbox if all steps succeed.\n *\n * @param syncBranch - The sync branch name\n * @param remote - The remote name\n */\n private async maybeImportOutbox(syncBranch: string, remote: string): Promise<void> {\n // Check if outbox exists and has issues\n if (!(await workspaceExists(this.tbdRoot, 'outbox'))) {\n return; // No outbox - nothing to import\n }\n\n const outboxPath = join(this.tbdRoot, '.tbd', 'workspaces', 'outbox');\n let outboxIssues: Awaited<ReturnType<typeof listIssues>> = [];\n try {\n outboxIssues = await listIssues(outboxPath);\n } catch {\n return; // Can't read outbox - skip silently\n }\n\n if (outboxIssues.length === 0) {\n return; // Outbox is empty - nothing to import\n }\n\n try {\n // Step 1: Import from outbox (don't clear yet)\n const importResult = await importFromWorkspace(this.tbdRoot, this.dataSyncDir, {\n outbox: true,\n clearOnSuccess: false, // We'll clear manually after push succeeds\n });\n\n if (importResult.imported === 0) {\n return; // Nothing was actually imported\n }\n\n // Step 2: Commit the imported issues\n const committedTallies = await this.commitWorktreeChanges();\n if (committedTallies.new + committedTallies.updated + committedTallies.deleted === 0) {\n // Nothing to commit - issues were already in worktree\n // This can happen if outbox data was identical to worktree\n // Still clear the outbox since the data is already synced\n await deleteWorkspace(this.tbdRoot, 'outbox');\n return;\n }\n\n // Step 3: Push the imported issues\n const pushResult = await this.doPushWithRetry(syncBranch, remote);\n if (!pushResult.success) {\n // Secondary push failed - DON'T clear outbox\n // The issues are now in the worktree, so they'll be synced next time\n this.output.warn(\n `Could not push imported outbox issues: ${pushResult.error ?? 'unknown error'}`,\n );\n console.log(' Outbox preserved. Issues are in worktree and will sync next time.');\n return;\n }\n\n // Step 4: All succeeded - now clear the outbox\n await deleteWorkspace(this.tbdRoot, 'outbox');\n\n this.output.success(`Imported ${importResult.imported} issue(s) from outbox (also synced)`);\n } catch (err) {\n // Don't fail the whole sync - primary sync already succeeded\n const errMsg = err instanceof Error ? err.message : String(err);\n this.output.warn(`Could not sync outbox: ${errMsg}`);\n console.log(' Outbox preserved. Will retry on next sync.');\n }\n }\n}\n\nexport const syncCommand = new Command('sync')\n .description('Synchronize issues and docs (both by default)')\n .option('--issues', 'Sync only issues (not docs)')\n .option('--docs', 'Sync only docs (not issues)')\n .option('--push', 'Push local issue changes only')\n .option('--pull', 'Pull remote issue changes only')\n .option('--status', 'Show sync status')\n .option('--force', 'Force sync (overwrite conflicts)')\n .option('--fix', 'Attempt to repair unhealthy worktree before syncing')\n .option('--no-auto-save', 'Skip auto-save to outbox on permanent failure')\n .option('--no-outbox', 'Skip auto-import from outbox on success')\n .action(async (options, command) => {\n const handler = new SyncHandler(command);\n await handler.run(options);\n });\n","/**\n * Human-readable formatting utilities for tbd CLI.\n *\n * Uses sindresorhus libraries for consistent, well-tested formatting:\n * - pretty-bytes: Byte sizes (1337 -> \"1.34 kB\")\n * - pretty-ms: Durations (3600000 -> \"1h\")\n */\n\nimport prettyBytes from 'pretty-bytes';\nimport prettyMs from 'pretty-ms';\n\nimport { CHARS_PER_TOKEN } from './paths.js';\n\n// Re-export for direct use when needed\nexport { prettyBytes, prettyMs };\n\n// =============================================================================\n// Token Estimation\n// =============================================================================\n\n/**\n * Estimate token count from text content.\n * Uses CHARS_PER_TOKEN (~3.5) for markdown/code content.\n */\nexport function estimateTokens(text: string): number {\n return Math.ceil(text.length / CHARS_PER_TOKEN);\n}\n\n/**\n * Format token count: \"~1.2k tok\" or \"~450 tok\"\n */\nexport function formatTokens(tokens: number): string {\n if (tokens >= 1000) {\n return `~${(tokens / 1000).toFixed(1)}k tok`;\n }\n return `~${tokens} tok`;\n}\n\n// =============================================================================\n// Size Formatting\n// =============================================================================\n\n/**\n * Format doc size for list display: \"(1.8 kB, ~450 tok)\"\n */\nexport function formatDocSize(sizeBytes: number, approxTokens: number): string {\n return `(${prettyBytes(sizeBytes)}, ${formatTokens(approxTokens)})`;\n}\n\n// =============================================================================\n// Time Formatting\n// =============================================================================\n\n/**\n * Format relative time from Date: \"2d ago\", \"3h ago\", \"5m ago\"\n * Uses compact format for concise display.\n */\nexport function formatTimeAgo(date: Date): string {\n const ms = Date.now() - date.getTime();\n if (ms < 0) return 'just now';\n if (ms < 60000) return 'just now'; // Less than 1 minute\n return `${prettyMs(ms, { compact: true })} ago`;\n}\n\n/**\n * Format relative time from ISO timestamp string.\n * Returns null if timestamp is invalid.\n */\nexport function formatTimestampAgo(timestamp: string | undefined | null): string | null {\n if (!timestamp) return null;\n const date = new Date(timestamp);\n if (isNaN(date.getTime())) return null;\n return formatTimeAgo(date);\n}\n\n/**\n * Format duration in milliseconds: \"2h 15m\", \"3d 4h\"\n * Uses verbose format for clarity.\n */\nexport function formatDuration(ms: number): string {\n return prettyMs(ms, { verbose: true });\n}\n\n/**\n * Format duration compactly: \"2h\", \"3d\"\n * Uses compact format for tables and tight spaces.\n */\nexport function formatDurationCompact(ms: number): string {\n return prettyMs(ms, { compact: true });\n}\n","/**\n * `tbd search` - Search issues.\n *\n * See: tbd-design.md §4.8 Search Commands\n */\n\nimport { Command } from 'commander';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { applyLimit } from '../lib/limit-utils.js';\nimport { loadDataContext, type TbdDataContext } from '../lib/data-context.js';\nimport { requireInit, NotInitializedError, ValidationError } from '../lib/errors.js';\nimport { listIssues } from '../../file/storage.js';\nimport { readLocalState, updateLocalState } from '../../file/config.js';\nimport { IssueStatus } from '../../lib/schemas.js';\nimport type { Issue, IssueStatusType } from '../../lib/types.js';\nimport { formatTimestampAgo } from '../../lib/format-utils.js';\nimport { now } from '../../utils/time-utils.js';\nimport { formatDisplayId, formatDebugId } from '../../lib/ids.js';\nimport { formatIssueCompact, type IssueForDisplay } from '../lib/issue-format.js';\n\n// Staleness threshold for worktree (5 minutes)\nconst STALE_THRESHOLD_MS = 5 * 60 * 1000;\n\ninterface SearchOptions {\n status?: string;\n limit?: string;\n noRefresh?: boolean;\n field?: string;\n caseSensitive?: boolean;\n}\n\ninterface SearchResult {\n issue: Issue;\n matchField: string;\n matchText: string;\n}\n\nclass SearchHandler extends BaseCommand {\n async run(query: string, options: SearchOptions): Promise<void> {\n const tbdRoot = await requireInit();\n\n // Check worktree staleness and auto-refresh if needed\n if (!options.noRefresh) {\n const state = await readLocalState(tbdRoot);\n const lastSync = state.last_sync_at ? new Date(state.last_sync_at).getTime() : 0;\n const stale = Date.now() - lastSync > STALE_THRESHOLD_MS;\n if (stale) {\n const lastSyncAgo = formatTimestampAgo(state.last_sync_at);\n const staleInfo = lastSyncAgo ? ` (last synced ${lastSyncAgo})` : '';\n this.output.info(`Refreshing worktree${staleInfo}...`);\n await updateLocalState(tbdRoot, { last_sync_at: now() });\n }\n }\n\n // Load data context and issues\n let issues: Issue[];\n let dataCtx: TbdDataContext;\n try {\n dataCtx = await loadDataContext(tbdRoot);\n issues = await listIssues(dataCtx.dataSyncDir);\n } catch {\n throw new NotInitializedError('No issue store found. Run `tbd init` first.');\n }\n\n // Parse status filter\n let statusFilter: IssueStatusType | null = null;\n if (options.status) {\n const result = IssueStatus.safeParse(options.status);\n if (!result.success) {\n throw new ValidationError(`Invalid status: ${options.status}`);\n }\n statusFilter = result.data;\n }\n\n // Search (case-insensitive by default)\n const caseSensitive = options.caseSensitive ?? false;\n const queryForMatch = caseSensitive ? query : query.toLowerCase();\n let results: SearchResult[] = [];\n\n for (const issue of issues) {\n // Apply status filter\n if (statusFilter && issue.status !== statusFilter) continue;\n\n // Determine which fields to search\n const searchFields = options.field\n ? [options.field]\n : ['title', 'description', 'notes', 'labels'];\n\n for (const field of searchFields) {\n const match = this.searchField(issue, field, queryForMatch, caseSensitive);\n if (match) {\n results.push(match);\n break; // Only one match per issue\n }\n }\n }\n\n // Apply limit\n results = applyLimit(results, options.limit);\n\n const { mapping, prefix } = dataCtx;\n const showDebug = this.ctx.debug;\n\n // Format output\n const output = results.map((r) => ({\n id: showDebug\n ? formatDebugId(r.issue.id, mapping, prefix)\n : formatDisplayId(r.issue.id, mapping, prefix),\n priority: r.issue.priority,\n status: r.issue.status,\n kind: r.issue.kind,\n title: r.issue.title,\n matchField: r.matchField,\n match: r.matchText,\n }));\n\n this.output.data(output, () => {\n if (output.length === 0) {\n this.output.info(`No issues matching \"${query}\"`);\n return;\n }\n\n const colors = this.output.getColors();\n console.log(`Found ${output.length} result${output.length === 1 ? '' : 's'}:\\n`);\n for (const result of output) {\n console.log(formatIssueCompact(result as IssueForDisplay, colors));\n console.log(` ${colors.dim(`[${result.matchField}]`)} ${result.match}`);\n console.log('');\n }\n });\n }\n\n private searchField(\n issue: Issue,\n field: string,\n query: string,\n caseSensitive: boolean,\n ): SearchResult | null {\n switch (field) {\n case 'title': {\n const text = caseSensitive ? issue.title : issue.title.toLowerCase();\n if (text.includes(query)) {\n return { issue, matchField: 'title', matchText: issue.title };\n }\n break;\n }\n case 'description': {\n if (issue.description) {\n const text = caseSensitive ? issue.description : issue.description.toLowerCase();\n if (text.includes(query)) {\n const snippet = this.extractSnippet(issue.description, query, caseSensitive);\n return { issue, matchField: 'description', matchText: snippet };\n }\n }\n break;\n }\n case 'notes': {\n if (issue.notes) {\n const text = caseSensitive ? issue.notes : issue.notes.toLowerCase();\n if (text.includes(query)) {\n const snippet = this.extractSnippet(issue.notes, query, caseSensitive);\n return { issue, matchField: 'notes', matchText: snippet };\n }\n }\n break;\n }\n case 'labels': {\n for (const label of issue.labels) {\n const text = caseSensitive ? label : label.toLowerCase();\n if (text.includes(query)) {\n return { issue, matchField: 'labels', matchText: `label: ${label}` };\n }\n }\n break;\n }\n }\n return null;\n }\n\n private extractSnippet(text: string, query: string, caseSensitive: boolean): string {\n const searchText = caseSensitive ? text : text.toLowerCase();\n const index = searchText.indexOf(query);\n if (index === -1) return text.slice(0, 60);\n\n // Extract snippet around match\n const start = Math.max(0, index - 20);\n const end = Math.min(text.length, index + query.length + 40);\n let snippet = text.slice(start, end);\n\n if (start > 0) snippet = '...' + snippet;\n if (end < text.length) snippet = snippet + '...';\n\n return snippet.replace(/\\n/g, ' ');\n }\n}\n\nexport const searchCommand = new Command('search')\n .description('Search issues by text')\n .argument('<query>', 'Search query')\n .option('--status <status>', 'Filter by status')\n .option('--field <field>', 'Search specific field (title, description, notes, labels)')\n .option('--limit <n>', 'Limit results')\n .option('--no-refresh', 'Skip worktree refresh')\n .option('--case-sensitive', 'Case-sensitive search')\n .action(async (query, options, command) => {\n const handler = new SearchHandler(command);\n await handler.run(query, options);\n });\n","/**\n * Shared section rendering functions for CLI output.\n *\n * These functions ensure identical output formatting across commands that\n * share sections (status, doctor, stats). When doctor subsumes status,\n * it calls the same rendering functions to guarantee consistency.\n *\n * See: plan-2026-01-29-terminal-design-system.md\n */\n\nimport type { createColors } from './output.js';\nimport { ICONS, formatHeading } from './output.js';\nimport { MIN_GIT_VERSION } from '../../file/git.js';\n\n/**\n * Repository section data for rendering.\n */\nexport interface RepositorySectionData {\n version: string;\n workingDirectory: string;\n initialized: boolean;\n gitRepository: boolean;\n gitBranch: string | null;\n gitVersion: string | null;\n gitVersionSupported: boolean;\n}\n\n/**\n * Config section data for rendering.\n */\nexport interface ConfigSectionData {\n syncBranch: string | null;\n remote: string | null;\n displayPrefix: string | null;\n}\n\n/**\n * Integration check result for rendering.\n */\nexport interface IntegrationCheck {\n name: string;\n installed: boolean;\n path: string;\n}\n\n/**\n * Statistics section data for rendering.\n */\nexport interface StatisticsSectionData {\n ready: number;\n inProgress: number;\n blocked: number;\n open: number;\n total: number;\n /** Number of issues on remote branch (when local is empty but remote has data) */\n remoteTotal?: number | null;\n}\n\n/**\n * Render the REPOSITORY section.\n *\n * Used by: status, doctor\n *\n * Shows:\n * - tbd version\n * - Repository path\n * - Initialization status\n * - Git repository status and branch\n * - Git version (with support warning if needed)\n *\n * @param data - Repository data to render\n * @param colors - Color functions\n * @param options - Rendering options\n */\nexport function renderRepositorySection(\n data: RepositorySectionData,\n colors: ReturnType<typeof createColors>,\n options?: { showHeading?: boolean },\n): void {\n // Show heading if requested (doctor uses heading, status doesn't)\n if (options?.showHeading) {\n console.log(colors.bold(formatHeading('Repository')));\n }\n\n // Version line\n console.log(`${colors.bold('tbd')} v${data.version}`);\n\n // Repository path\n console.log(`Repository: ${data.workingDirectory}`);\n\n // Initialization status\n if (data.initialized) {\n console.log(` ${colors.success(ICONS.SUCCESS)} Initialized (.tbd/)`);\n } else {\n console.log(` ${colors.error(ICONS.ERROR)} Not initialized`);\n }\n\n // Git repository status\n if (data.gitRepository) {\n const branchInfo = data.gitBranch ? ` (${data.gitBranch})` : '';\n console.log(` ${colors.success(ICONS.SUCCESS)} Git repository${branchInfo}`);\n\n // Git version\n if (data.gitVersion) {\n const versionIcon = data.gitVersionSupported\n ? colors.success(ICONS.SUCCESS)\n : colors.warn(ICONS.WARN);\n const versionNote = data.gitVersionSupported\n ? ''\n : ` ${colors.dim(`(requires ${MIN_GIT_VERSION}+)`)}`;\n console.log(` ${versionIcon} Git ${data.gitVersion}${versionNote}`);\n }\n } else {\n console.log(` ${colors.error(ICONS.ERROR)} Git repository not found`);\n }\n}\n\n/**\n * Render the CONFIG section (sync branch, remote, prefix).\n *\n * Used by: status, doctor\n *\n * Shows key-value pairs with dim keys.\n *\n * @param data - Config data to render\n * @param colors - Color functions\n */\nexport function renderConfigSection(\n data: ConfigSectionData,\n colors: ReturnType<typeof createColors>,\n): void {\n if (!data.syncBranch && !data.remote && !data.displayPrefix) {\n return;\n }\n\n console.log('');\n\n if (data.syncBranch) {\n console.log(`${colors.dim('Sync branch:')} ${data.syncBranch}`);\n }\n if (data.remote) {\n console.log(`${colors.dim('Remote:')} ${data.remote}`);\n }\n if (data.displayPrefix) {\n console.log(`${colors.dim('ID prefix:')} ${data.displayPrefix}-`);\n }\n}\n\n/**\n * Render the INTEGRATIONS section.\n *\n * Used by: status, doctor\n *\n * Shows diagnostic lines for each integration check.\n *\n * @param checks - Array of integration checks\n * @param colors - Color functions\n * @returns Whether any integrations are missing (for follow-up suggestions)\n */\nexport function renderIntegrationsSection(\n checks: IntegrationCheck[],\n colors: ReturnType<typeof createColors>,\n): boolean {\n console.log('');\n console.log(colors.bold(formatHeading('Integrations')));\n\n let hasMissing = false;\n\n for (const check of checks) {\n const icon = check.installed ? colors.success(ICONS.SUCCESS) : colors.dim(ICONS.ERROR);\n const pathDim = colors.dim(`(${check.path})`);\n console.log(` ${icon} ${check.name} ${pathDim}`);\n\n if (!check.installed) {\n hasMissing = true;\n }\n }\n\n return hasMissing;\n}\n\n/**\n * Render the STATISTICS section.\n *\n * Used by: stats, doctor\n *\n * Shows aligned statistic block with labels and values.\n * When local is 0 but remote has data, shows a hint to run tbd sync.\n *\n * @param data - Statistics data to render\n * @param colors - Color functions\n * @param options - Rendering options\n */\nexport function renderStatisticsSection(\n data: StatisticsSectionData,\n colors: ReturnType<typeof createColors>,\n options?: { showHeading?: boolean },\n): void {\n if (options?.showHeading !== false) {\n console.log('');\n console.log(colors.bold(formatHeading('Statistics')));\n }\n\n // Calculate padding for alignment\n const labels = ['Ready', 'In progress', 'Blocked', 'Open', 'Total'];\n const maxLabelLen = Math.max(...labels.map((l) => l.length));\n\n const formatLine = (label: string, value: number, suffix?: string): string => {\n const padding = ' '.repeat(maxLabelLen - label.length + 1);\n const suffixStr = suffix ? ` ${colors.dim(suffix)}` : '';\n return ` ${label}:${padding}${value}${suffixStr}`;\n };\n\n console.log(formatLine('Ready', data.ready));\n console.log(formatLine('In progress', data.inProgress));\n console.log(formatLine('Blocked', data.blocked));\n console.log(formatLine('Open', data.open));\n\n // Show remote count hint when local is 0 but remote has data\n if (data.total === 0 && data.remoteTotal && data.remoteTotal > 0) {\n console.log(\n formatLine('Total', data.total, `(${data.remoteTotal} on remote - run 'tbd sync')`),\n );\n } else {\n console.log(formatLine('Total', data.total));\n }\n}\n\n/**\n * Render a warning block about beads coexistence.\n *\n * Used by: status\n *\n * @param colors - Color functions\n */\nexport function renderBeadsWarning(colors: ReturnType<typeof createColors>): void {\n console.log('');\n console.log(`${colors.warn(ICONS.WARN)} Beads directory detected alongside tbd`);\n console.log('This may cause confusion for AI agents.');\n console.log(`Run ${colors.bold('tbd setup beads --disable')} for migration options`);\n}\n\n/**\n * Render worktree status line.\n *\n * Used by: status\n *\n * @param path - Worktree path\n * @param healthy - Whether worktree is healthy\n * @param colors - Color functions\n */\nexport function renderWorktreeStatus(\n path: string,\n healthy: boolean,\n colors: ReturnType<typeof createColors>,\n): void {\n console.log('');\n if (healthy) {\n console.log(`${colors.dim('Worktree:')} ${path} (healthy)`);\n } else {\n console.log(`${colors.warn('Worktree:')} ${path} (${colors.error('unhealthy')})`);\n console.log(' Run: tbd doctor --fix');\n }\n}\n\n/**\n * Render footer with command suggestions.\n *\n * Used by: status, doctor, stats\n *\n * @param suggestions - Array of {command, description} pairs\n * @param colors - Color functions\n */\nexport function renderFooter(\n suggestions: { command: string; description: string }[],\n colors: ReturnType<typeof createColors>,\n): void {\n console.log('');\n\n if (suggestions.length === 0) {\n return;\n }\n\n const parts = suggestions.map((s) => `${colors.bold(`'${s.command}'`)} for ${s.description}`);\n\n if (parts.length === 1) {\n console.log(`Use ${parts[0]}.`);\n } else {\n console.log(`Use ${parts.join(', ')}.`);\n }\n}\n","/**\n * Centralized path constants and utilities for coding agent integrations.\n *\n * IMPORTANT: All tbd integration files (hooks, settings, skills) are installed\n * to PROJECT-LOCAL directories (.claude/, AGENTS.md) ONLY. We do NOT install to\n * global/user directories (~/.claude/).\n *\n * This file defines all path constants in one place to:\n * 1. Ensure consistency across the codebase\n * 2. Make the project-local policy explicit and auditable\n * 3. Simplify future changes to path conventions\n */\n\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\n\n// =============================================================================\n// Claude Code Integration Paths (project-local)\n// =============================================================================\n\n/**\n * Relative path to Claude Code settings file from project root.\n * This is where hooks are configured.\n */\nexport const CLAUDE_SETTINGS_REL = '.claude/settings.json';\n\n/**\n * Relative path to Claude Code directory from project root.\n */\nexport const CLAUDE_DIR_REL = '.claude';\n\n/**\n * Relative path to Claude Code scripts directory from project root.\n */\nexport const CLAUDE_SCRIPTS_DIR_REL = '.claude/scripts';\n\n/**\n * Relative path to Claude Code hooks directory from project root.\n */\nexport const CLAUDE_HOOKS_DIR_REL = '.claude/hooks';\n\n/**\n * Relative path to tbd skill file from project root.\n */\nexport const CLAUDE_SKILL_REL = '.claude/skills/tbd/SKILL.md';\n\n/**\n * Relative path to tbd session script from project root.\n */\nexport const TBD_SESSION_SCRIPT_REL = '.claude/scripts/tbd-session.sh';\n\n/**\n * Relative path to tbd closing reminder hook script from project root.\n */\nexport const TBD_CLOSING_REMINDER_REL = '.claude/hooks/tbd-closing-reminder.sh';\n\n/**\n * Relative path to gh CLI ensure script from project root.\n */\nexport const GH_CLI_SCRIPT_REL = '.claude/scripts/ensure-gh-cli.sh';\n\n// =============================================================================\n// Codex / AGENTS.md Integration Paths (project-local)\n// =============================================================================\n\n/**\n * Relative path to AGENTS.md file from project root.\n * Used by Codex, Factory.ai, Cursor (v1.6+), and other compatible tools.\n */\nexport const AGENTS_MD_REL = 'AGENTS.md';\n\n// =============================================================================\n// Global Paths (for detection only - NOT for installation)\n// =============================================================================\n\n/**\n * Global Claude Code directory in user's home.\n * Used ONLY for detecting if Claude Code is installed (for agent detection).\n * All installations are project-local.\n */\nexport const GLOBAL_CLAUDE_DIR = join(homedir(), '.claude');\n\n// =============================================================================\n// Path Resolution Utilities\n// =============================================================================\n\n/**\n * Get project-local Claude Code paths.\n *\n * @param projectRoot - The project root directory (containing .tbd/)\n * @returns Object with all Claude Code paths resolved to absolute paths\n */\nexport function getClaudePaths(projectRoot: string) {\n return {\n /** .claude/ directory */\n dir: join(projectRoot, CLAUDE_DIR_REL),\n /** .claude/settings.json */\n settings: join(projectRoot, CLAUDE_SETTINGS_REL),\n /** .claude/scripts/ directory */\n scriptsDir: join(projectRoot, CLAUDE_SCRIPTS_DIR_REL),\n /** .claude/hooks/ directory */\n hooksDir: join(projectRoot, CLAUDE_HOOKS_DIR_REL),\n /** .claude/skills/tbd/SKILL.md */\n skill: join(projectRoot, CLAUDE_SKILL_REL),\n /** .claude/scripts/tbd-session.sh */\n sessionScript: join(projectRoot, TBD_SESSION_SCRIPT_REL),\n /** .claude/hooks/tbd-closing-reminder.sh */\n closingReminder: join(projectRoot, TBD_CLOSING_REMINDER_REL),\n /** .claude/scripts/ensure-gh-cli.sh */\n ghCliScript: join(projectRoot, GH_CLI_SCRIPT_REL),\n };\n}\n\n/**\n * Get project-local Codex/AGENTS.md path.\n *\n * @param projectRoot - The project root directory\n * @returns Absolute path to AGENTS.md\n */\nexport function getAgentsMdPath(projectRoot: string): string {\n return join(projectRoot, AGENTS_MD_REL);\n}\n\n// =============================================================================\n// Display Paths (for user-facing output)\n// =============================================================================\n\n/**\n * Display path for Claude Code settings in status/doctor output.\n */\nexport const CLAUDE_SETTINGS_DISPLAY = './.claude/settings.json';\n\n/**\n * Display path for AGENTS.md in status/doctor output.\n */\nexport const AGENTS_MD_DISPLAY = './AGENTS.md';\n","/**\n * `tbd status` - Show repository status and orientation.\n *\n * This is the \"orientation\" command—like `git status`, it works regardless of\n * initialization state and helps users understand where they are.\n *\n * Unlike Beads where `bd status` is just an alias for `bd stats`, `tbd status`\n * is a distinct command that provides system orientation, not issue statistics.\n *\n * See: tbd-design.md §4.9 Status\n */\n\nimport { Command } from 'commander';\nimport { access, readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nimport { VERSION } from '../lib/version.js';\nimport { BaseCommand } from '../lib/base-command.js';\nimport { ICONS } from '../lib/output.js';\nimport {\n renderRepositorySection,\n renderConfigSection,\n renderIntegrationsSection,\n renderBeadsWarning,\n renderWorktreeStatus,\n renderFooter,\n type IntegrationCheck,\n} from '../lib/sections.js';\nimport { readConfig, findTbdRoot } from '../../file/config.js';\nimport { WORKTREE_DIR } from '../../lib/paths.js';\nimport {\n getClaudePaths,\n getAgentsMdPath,\n CLAUDE_SETTINGS_DISPLAY,\n AGENTS_MD_DISPLAY,\n} from '../../lib/integration-paths.js';\nimport {\n git,\n getCurrentBranch,\n checkWorktreeHealth,\n checkGitVersion,\n findGitRoot,\n MIN_GIT_VERSION,\n} from '../../file/git.js';\nimport { listWorkspaces } from '../../file/workspace.js';\n\ninterface StatusData {\n initialized: boolean;\n tbd_version: string;\n working_directory: string;\n\n // Git info (always available)\n git_repository: boolean;\n git_branch: string | null;\n git_version: string | null;\n git_version_supported: boolean;\n\n // Beads detection (pre-init only)\n beads_detected: boolean;\n beads_issue_count: number | null;\n\n // Post-init only\n sync_branch: string | null;\n remote: string | null;\n display_prefix: string | null;\n worktree_path: string | null;\n worktree_healthy: boolean | null;\n workspaces: string[];\n\n // Integrations\n integrations: {\n claude_code: boolean;\n claude_code_path: string;\n codex: boolean;\n codex_path: string;\n };\n}\n\nclass StatusHandler extends BaseCommand {\n async run(): Promise<void> {\n const cwd = process.cwd();\n\n // Find tbd root (may be in parent directory)\n const tbdRoot = await findTbdRoot(cwd);\n\n // Find git root for checking integrations (.claude/, .beads/ are at git root)\n const gitRoot = await findGitRoot(cwd);\n\n // Use tbdRoot if available, otherwise gitRoot, otherwise cwd\n // .tbd/, .claude/, .beads/ are all at the project root (adjacent to .git/)\n const projectRoot = tbdRoot ?? gitRoot ?? cwd;\n\n const statusData: StatusData = {\n initialized: tbdRoot !== null,\n tbd_version: VERSION,\n working_directory: cwd,\n git_repository: false,\n git_branch: null,\n git_version: null,\n git_version_supported: false,\n beads_detected: false,\n beads_issue_count: null,\n sync_branch: null,\n remote: null,\n display_prefix: null,\n worktree_path: null,\n worktree_healthy: null,\n workspaces: [],\n integrations: {\n claude_code: false,\n claude_code_path: CLAUDE_SETTINGS_DISPLAY,\n codex: false,\n codex_path: AGENTS_MD_DISPLAY,\n },\n };\n\n // Check git repository\n const gitInfo = await this.checkGitRepo();\n statusData.git_repository = gitInfo.isRepo;\n statusData.git_branch = gitInfo.branch;\n\n // Check git version (only if git is available)\n if (gitInfo.isRepo) {\n try {\n const { version, supported } = await checkGitVersion();\n statusData.git_version = `${version.major}.${version.minor}.${version.patch}`;\n statusData.git_version_supported = supported;\n } catch {\n // Git version check failed - leave as null/false\n }\n }\n\n // Check for beads (at project root, not cwd)\n const beadsInfo = await this.checkBeads(projectRoot);\n statusData.beads_detected = beadsInfo.detected;\n statusData.beads_issue_count = beadsInfo.issueCount;\n\n // Check integrations at project root (not cwd)\n statusData.integrations = await this.checkIntegrations(projectRoot);\n\n if (statusData.initialized && tbdRoot) {\n // Load config and issue info\n await this.loadPostInitInfo(tbdRoot, statusData);\n }\n\n this.output.data(statusData, () => {\n this.renderText(statusData);\n });\n }\n\n private async checkGitRepo(): Promise<{ isRepo: boolean; branch: string | null }> {\n try {\n const branch = await getCurrentBranch();\n return { isRepo: true, branch };\n } catch {\n // getCurrentBranch may fail in repos with no commits\n // Fall back to checking if we're in a git repo using git rev-parse --git-dir\n try {\n await git('rev-parse', '--git-dir');\n // We're in a git repo but can't get branch (maybe no commits)\n return { isRepo: true, branch: null };\n } catch {\n return { isRepo: false, branch: null };\n }\n }\n }\n\n private async checkBeads(\n projectRoot: string,\n ): Promise<{ detected: boolean; issueCount: number | null }> {\n const beadsDir = join(projectRoot, '.beads');\n try {\n await access(beadsDir);\n // Count issues in beads\n const issuesFile = join(beadsDir, 'issues.jsonl');\n try {\n const content = await readFile(issuesFile, 'utf-8');\n const lines = content\n .trim()\n .split('\\n')\n .filter((l) => l.trim());\n return { detected: true, issueCount: lines.length };\n } catch {\n return { detected: true, issueCount: null };\n }\n } catch {\n return { detected: false, issueCount: null };\n }\n }\n\n private async checkIntegrations(projectRoot: string): Promise<StatusData['integrations']> {\n // All integrations use project-local paths (relative to git/project root)\n const claudePaths = getClaudePaths(projectRoot);\n const agentsPath = getAgentsMdPath(projectRoot);\n\n const result: StatusData['integrations'] = {\n claude_code: false,\n claude_code_path: CLAUDE_SETTINGS_DISPLAY,\n codex: false,\n codex_path: AGENTS_MD_DISPLAY,\n };\n\n // Check Claude Code hooks in project-local settings\n try {\n await access(claudePaths.settings);\n const content = await readFile(claudePaths.settings, 'utf-8');\n const settings = JSON.parse(content) as Record<string, unknown>;\n const hooks = settings.hooks as Record<string, unknown> | undefined;\n if (hooks) {\n const sessionStart = hooks.SessionStart as { hooks?: { command?: string }[] }[];\n result.claude_code =\n sessionStart?.some((h) => h.hooks?.some((hook) => hook.command?.includes('tbd'))) ??\n false;\n }\n } catch {\n // Not installed\n }\n\n // Check Codex AGENTS.md (also used by Cursor since v1.6)\n try {\n await access(agentsPath);\n const content = await readFile(agentsPath, 'utf-8');\n result.codex = content.includes('BEGIN TBD INTEGRATION');\n } catch {\n // Not installed\n }\n\n return result;\n }\n\n private async loadPostInitInfo(cwd: string, data: StatusData): Promise<void> {\n // Load config\n try {\n const config = await readConfig(cwd);\n data.sync_branch = config.sync.branch;\n data.remote = config.sync.remote;\n data.display_prefix = config.display.id_prefix;\n } catch {\n // Config read failed\n }\n\n // Check worktree health\n const worktreePath = join(cwd, WORKTREE_DIR);\n const worktreeHealth = await checkWorktreeHealth(cwd);\n data.worktree_path = worktreePath;\n data.worktree_healthy = worktreeHealth.valid;\n\n // Check workspaces\n try {\n data.workspaces = await listWorkspaces(cwd);\n } catch {\n // Workspace check failed - leave as empty\n }\n }\n\n private renderText(data: StatusData): void {\n const colors = this.output.getColors();\n\n if (!data.initialized) {\n // Pre-init output - unique to status, not shared with doctor\n this.renderPreInitText(data, colors);\n return;\n }\n\n // Post-init output - uses shared section renderers\n // REPOSITORY section (shared with doctor)\n renderRepositorySection(\n {\n version: data.tbd_version,\n workingDirectory: data.working_directory,\n initialized: data.initialized,\n gitRepository: data.git_repository,\n gitBranch: data.git_branch,\n gitVersion: data.git_version,\n gitVersionSupported: data.git_version_supported,\n },\n colors,\n );\n\n // Beads coexistence warning\n if (data.beads_detected) {\n renderBeadsWarning(colors);\n }\n\n // CONFIG section (shared with doctor)\n renderConfigSection(\n {\n syncBranch: data.sync_branch,\n remote: data.remote,\n displayPrefix: data.display_prefix,\n },\n colors,\n );\n\n // INTEGRATIONS section (shared with doctor)\n const integrationChecks: IntegrationCheck[] = [\n {\n name: 'Claude Code hooks',\n installed: data.integrations.claude_code,\n path: data.integrations.claude_code_path,\n },\n {\n name: 'Codex AGENTS.md',\n installed: data.integrations.codex,\n path: data.integrations.codex_path,\n },\n ];\n const hasMissingIntegrations = renderIntegrationsSection(integrationChecks, colors);\n\n if (hasMissingIntegrations) {\n console.log('');\n console.log(`Run ${colors.bold('tbd setup auto')} to configure detected agents`);\n }\n\n // Worktree health\n if (data.worktree_healthy !== null && data.worktree_path) {\n renderWorktreeStatus(data.worktree_path, data.worktree_healthy, colors);\n }\n\n // Workspaces (only show if there are any)\n if (data.workspaces.length > 0) {\n console.log('');\n console.log(colors.bold('WORKSPACES'));\n for (const ws of data.workspaces) {\n console.log(` ${ws}`);\n }\n }\n\n // Footer (shared format)\n renderFooter(\n [\n { command: 'tbd stats', description: 'issue statistics' },\n { command: 'tbd doctor', description: 'health checks' },\n ],\n colors,\n );\n }\n\n /**\n * Render pre-init text (unique to status command).\n * This is not shared with doctor since doctor requires initialization.\n */\n private renderPreInitText(\n data: StatusData,\n colors: ReturnType<typeof this.output.getColors>,\n ): void {\n console.log(`${colors.warn('Not a tbd repository.')}`);\n console.log('');\n console.log('Detected:');\n\n // Git status\n if (data.git_repository) {\n const branchInfo = data.git_branch ? ` (${data.git_branch} branch)` : '';\n console.log(` ${colors.success(ICONS.SUCCESS)} Git repository${branchInfo}`);\n // Show git version\n if (data.git_version) {\n const versionStatus = data.git_version_supported\n ? colors.success(ICONS.SUCCESS)\n : colors.warn(ICONS.WARN);\n const versionNote = data.git_version_supported\n ? ''\n : ` ${colors.dim(`(requires ${MIN_GIT_VERSION}+)`)}`;\n console.log(` ${versionStatus} Git ${data.git_version}${versionNote}`);\n }\n } else {\n console.log(` ${colors.error(ICONS.ERROR)} Git repository not found`);\n }\n\n // Beads status\n if (data.beads_detected) {\n const countInfo =\n data.beads_issue_count !== null ? ` (.beads/ with ${data.beads_issue_count} issues)` : '';\n console.log(` ${colors.success(ICONS.SUCCESS)} Beads repository${countInfo}`);\n } else {\n console.log(` ${colors.dim(ICONS.ERROR)} Beads not detected`);\n }\n\n // tbd status\n console.log(` ${colors.error(ICONS.ERROR)} tbd not initialized`);\n\n console.log('');\n console.log('To get started:');\n if (data.beads_detected) {\n console.log(\n ` ${colors.bold('tbd setup --auto')} # Migrate from Beads (recommended)`,\n );\n } else {\n console.log(\n ` ${colors.bold('tbd setup --auto --prefix=<name>')} # Full setup with prefix`,\n );\n }\n console.log(` ${colors.bold('tbd init --prefix=X')} # Surgical init only`);\n }\n}\n\nexport const statusCommand = new Command('status')\n .description('Show repository status and orientation')\n .action(async (_options, command) => {\n const handler = new StatusHandler(command);\n await handler.run();\n });\n","/**\n * `tbd stats` - Show repository statistics.\n *\n * See: tbd-design.md §4.9 Stats\n */\n\nimport { Command } from 'commander';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { requireInit, NotInitializedError } from '../lib/errors.js';\nimport { listIssues } from '../../file/storage.js';\nimport type { Issue, IssueStatusType, IssueKindType } from '../../lib/types.js';\nimport { resolveDataSyncDir } from '../../lib/paths.js';\nimport { formatPriority } from '../../lib/priority.js';\nimport { renderFooter } from '../lib/sections.js';\nimport { getStatusIcon, getStatusColor } from '../../lib/status.js';\n\n/**\n * Active statuses (non-closed).\n */\nconst ACTIVE_STATUSES: IssueStatusType[] = ['open', 'in_progress', 'blocked', 'deferred'];\n\n/**\n * All statuses in display order.\n */\nconst STATUS_ORDER: IssueStatusType[] = ['open', 'in_progress', 'blocked', 'deferred', 'closed'];\n\n/**\n * All kinds in display order.\n */\nconst KIND_ORDER: IssueKindType[] = ['bug', 'feature', 'task', 'epic', 'chore'];\n\n/**\n * Priority labels for display.\n */\nconst PRIORITY_LABELS = ['Critical', 'High', 'Medium', 'Low', 'Lowest'];\n\nclass StatsHandler extends BaseCommand {\n async run(): Promise<void> {\n const tbdRoot = await requireInit();\n\n // Load all issues\n let issues: Issue[];\n try {\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n issues = await listIssues(dataSyncDir);\n } catch {\n throw new NotInitializedError('No issue store found. Run `tbd init` first.');\n }\n\n // Count by status\n const byStatus: Record<IssueStatusType, number> = {\n open: 0,\n in_progress: 0,\n blocked: 0,\n deferred: 0,\n closed: 0,\n };\n\n // Count by kind (active vs closed)\n const byKindActive: Record<IssueKindType, number> = {\n bug: 0,\n feature: 0,\n task: 0,\n epic: 0,\n chore: 0,\n };\n const byKindClosed: Record<IssueKindType, number> = {\n bug: 0,\n feature: 0,\n task: 0,\n epic: 0,\n chore: 0,\n };\n\n // Count by priority (active vs closed)\n const byPriorityActive: Record<number, number> = { 0: 0, 1: 0, 2: 0, 3: 0, 4: 0 };\n const byPriorityClosed: Record<number, number> = { 0: 0, 1: 0, 2: 0, 3: 0, 4: 0 };\n\n // Accumulate counts\n for (const issue of issues) {\n byStatus[issue.status]++;\n\n const isActive = issue.status !== 'closed';\n if (isActive) {\n byKindActive[issue.kind]++;\n if (issue.priority >= 0 && issue.priority <= 4) {\n byPriorityActive[issue.priority]!++;\n }\n } else {\n byKindClosed[issue.kind]++;\n if (issue.priority >= 0 && issue.priority <= 4) {\n byPriorityClosed[issue.priority]!++;\n }\n }\n }\n\n // Calculate totals\n const activeTotal = ACTIVE_STATUSES.reduce((sum, s) => sum + byStatus[s], 0);\n const closedTotal = byStatus.closed;\n const total = issues.length;\n\n const stats = {\n total,\n active: activeTotal,\n closed: closedTotal,\n byStatus,\n byKindActive,\n byKindClosed,\n byPriorityActive,\n byPriorityClosed,\n };\n\n this.output.data(stats, () => {\n const colors = this.output.getColors();\n\n if (stats.total === 0) {\n console.log(colors.dim('No issues found.'));\n renderFooter(\n [\n { command: 'tbd status', description: 'setup info' },\n { command: 'tbd doctor', description: 'health checks' },\n ],\n colors,\n );\n return;\n }\n\n // Column width for counts (right-aligned)\n const countWidth = 6;\n\n // === BY STATUS SECTION ===\n console.log(colors.bold('By status:'));\n\n // Find max count for determining column alignment\n const maxStatusCount = Math.max(...Object.values(stats.byStatus), activeTotal, total);\n const statusCountWidth = Math.max(countWidth, String(maxStatusCount).length + 2);\n\n // Show each status with icon and color\n for (const status of STATUS_ORDER) {\n const count = stats.byStatus[status];\n if (status === 'closed') continue; // Show closed after subtotal\n const icon = getStatusIcon(status);\n const colorFn = getStatusColor(status, colors);\n const countStr = String(count).padStart(statusCountWidth);\n console.log(` ${colorFn(icon)} ${status.padEnd(14)}${countStr}`);\n }\n\n // Subtotal separator and active total\n console.log(` ${'─'.repeat(16 + statusCountWidth)}`);\n console.log(` ${'active'.padEnd(14)}${String(activeTotal).padStart(statusCountWidth)}`);\n\n // Closed with icon\n const closedIcon = getStatusIcon('closed');\n const closedColorFn = getStatusColor('closed', colors);\n console.log(\n ` ${closedColorFn(closedIcon)} ${'closed'.padEnd(14)}${String(closedTotal).padStart(statusCountWidth)}`,\n );\n\n // Total separator and total\n console.log(` ${'═'.repeat(16 + statusCountWidth)}`);\n console.log(` ${'total'.padEnd(14)}${String(total).padStart(statusCountWidth)}`);\n\n // === BY KIND SECTION ===\n console.log('');\n const kindHeader = `${'By kind:'.padEnd(18)}${'active'.padStart(countWidth + 2)}${'closed'.padStart(countWidth + 2)}${'total'.padStart(countWidth + 2)}`;\n console.log(colors.bold(kindHeader));\n\n for (const kind of KIND_ORDER) {\n const active = stats.byKindActive[kind];\n const closed = stats.byKindClosed[kind];\n const kindTotal = active + closed;\n if (kindTotal === 0) continue;\n\n const line = ` ${kind.padEnd(16)}${String(active).padStart(countWidth + 2)}${String(closed).padStart(countWidth + 2)}${String(kindTotal).padStart(countWidth + 2)}`;\n console.log(line);\n }\n\n // === BY PRIORITY SECTION ===\n console.log('');\n const priorityHeader = `${'By priority:'.padEnd(18)}${'active'.padStart(countWidth + 2)}${'closed'.padStart(countWidth + 2)}${'total'.padStart(countWidth + 2)}`;\n console.log(colors.bold(priorityHeader));\n\n for (let i = 0; i <= 4; i++) {\n const active = stats.byPriorityActive[i] ?? 0;\n const closed = stats.byPriorityClosed[i] ?? 0;\n const priorityTotal = active + closed;\n if (priorityTotal === 0) continue;\n\n const label = `${formatPriority(i)} (${PRIORITY_LABELS[i]})`;\n const line = ` ${label.padEnd(16)}${String(active).padStart(countWidth + 2)}${String(closed).padStart(countWidth + 2)}${String(priorityTotal).padStart(countWidth + 2)}`;\n console.log(line);\n }\n\n // Footer (shared format)\n renderFooter(\n [\n { command: 'tbd status', description: 'setup info' },\n { command: 'tbd doctor', description: 'health checks' },\n ],\n colors,\n );\n });\n }\n}\n\nexport const statsCommand = new Command('stats')\n .description('Show repository statistics')\n .action(async (_options, command) => {\n const handler = new StatsHandler(command);\n await handler.run();\n });\n","/**\n * Shared diagnostic output utilities for consistent diagnostic messages\n * across doctor, setup --check, and status commands.\n *\n * See: plan-2026-01-17-cli-output-design-system.md\n */\n\nimport { ICONS } from './output.js';\n\n/**\n * Result of a diagnostic check. Used by doctor, setup --check, and status commands\n * to report configuration and health status.\n *\n * @property name - Display name of the check (e.g., \"Config file\", \"Git version\")\n * @property status - Check result: ok (pass), warn (non-blocking issue), error (failure)\n * @property message - Optional message with additional context (e.g., version number, count)\n * @property path - Optional file/directory path being checked\n * @property details - Optional list of specific items when issues found (e.g., orphaned deps)\n * @property fixable - Whether the issue can be auto-fixed (shown as [fixable] suffix)\n * @property suggestion - Optional actionable fix suggestion (e.g., \"Run: tbd setup claude\")\n */\nexport interface DiagnosticResult {\n name: string;\n status: 'ok' | 'warn' | 'error';\n message?: string;\n path?: string;\n details?: string[];\n fixable?: boolean;\n suggestion?: string;\n}\n\n/**\n * Color function type for consistent coloring across the module.\n */\ntype ColorFn = (text: string) => string;\n\n/**\n * Colors interface matching createColors() return type.\n */\ninterface Colors {\n success: ColorFn;\n error: ColorFn;\n warn: ColorFn;\n dim: ColorFn;\n}\n\n/**\n * Render a single diagnostic result to console.\n *\n * Format examples:\n * - ✓ Config file (.tbd/config.yml)\n * - ⚠ Dependencies - 2 orphaned reference(s) [fixable]\n * tbd-abc1 -> tbd-xyz9 (missing)\n * tbd-def2 -> tbd-uvw8 (missing)\n * - ✗ Issue validity - 2 invalid issue(s) (.tbd/issues)\n * tbd-aaa1: missing required field 'title'\n * Run: tbd doctor --fix\n *\n * @param result - The diagnostic result to render\n * @param colors - Color functions from createColors()\n */\nexport function renderDiagnostic(result: DiagnosticResult, colors: Colors): void {\n // Build the main line\n let line = '';\n\n // Icon based on status\n const icon =\n result.status === 'ok'\n ? colors.success(ICONS.SUCCESS)\n : result.status === 'warn'\n ? colors.warn(ICONS.WARN)\n : colors.error(ICONS.ERROR);\n\n line += `${icon} ${result.name}`;\n\n // Message after dash\n if (result.message) {\n line += ` - ${result.message}`;\n }\n\n // Path in parentheses\n if (result.path) {\n line += ` ${colors.dim(`(${result.path})`)}`;\n }\n\n // Fixable suffix (only for non-ok)\n if (result.fixable && result.status !== 'ok') {\n line += ` ${colors.dim('[fixable]')}`;\n }\n\n console.log(line);\n\n // Details (only for non-ok status)\n if (result.details && result.details.length > 0 && result.status !== 'ok') {\n for (const detail of result.details) {\n console.log(` ${colors.dim(detail)}`);\n }\n }\n\n // Suggestion (only for non-ok status)\n if (result.suggestion && result.status !== 'ok') {\n console.log(` ${colors.dim(result.suggestion)}`);\n }\n}\n\n/**\n * Render multiple diagnostic results.\n *\n * @param results - Array of diagnostic results to render\n * @param colors - Color functions from createColors()\n */\nexport function renderDiagnostics(results: DiagnosticResult[], colors: Colors): void {\n for (const result of results) {\n renderDiagnostic(result, colors);\n }\n}\n","/**\n * `tbd doctor` - Diagnose and repair repository.\n *\n * A comprehensive health check that includes status, stats, and health checks.\n *\n * See: tbd-design.md §4.9 Doctor\n */\n\nimport { Command } from 'commander';\nimport { access, readdir, readFile, unlink } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { requireInit } from '../lib/errors.js';\nimport { listIssues, type InvalidIssueFile } from '../../file/storage.js';\nimport { readConfig } from '../../file/config.js';\nimport type { Config, Issue, IssueStatusType } from '../../lib/types.js';\nimport { resolveDataSyncDir, TBD_DIR, WORKTREE_DIR, DATA_SYNC_DIR } from '../../lib/paths.js';\nimport { detectDuplicateYamlKeys } from '../../utils/yaml-utils.js';\nimport {\n getClaudePaths,\n getAgentsMdPath,\n CLAUDE_SKILL_REL,\n AGENTS_MD_REL,\n} from '../../lib/integration-paths.js';\nimport { validateIssueId, extractUlidFromInternalId } from '../../lib/ids.js';\nimport { git } from '../../file/git.js';\nimport {\n checkGitVersion,\n MIN_GIT_VERSION,\n getCurrentBranch,\n checkWorktreeHealth,\n checkLocalBranchHealth,\n checkRemoteBranchHealth,\n checkSyncConsistency,\n repairWorktree,\n migrateDataToWorktree,\n initWorktree,\n countRemoteIssues,\n} from '../../file/git.js';\nimport { type DiagnosticResult, renderDiagnostics } from '../lib/diagnostics.js';\nimport { VERSION } from '../lib/version.js';\nimport { formatHeading } from '../lib/output.js';\nimport {\n renderRepositorySection,\n renderConfigSection,\n renderStatisticsSection,\n} from '../lib/sections.js';\n\nconst CONFIG_DIR = TBD_DIR;\n\ninterface DoctorOptions {\n fix?: boolean;\n maxHistory?: string;\n}\n\nclass DoctorHandler extends BaseCommand {\n private dataSyncDir = '';\n private cwd = '';\n private config: Config | null = null;\n private issues: Issue[] = [];\n private invalidIssueFiles: InvalidIssueFile[] = [];\n\n async run(options: DoctorOptions): Promise<void> {\n const tbdRoot = await requireInit();\n\n this.cwd = tbdRoot;\n this.dataSyncDir = await resolveDataSyncDir(tbdRoot);\n\n // Load config\n try {\n this.config = await readConfig(this.cwd);\n } catch {\n // Config may be invalid - will be caught by health checks\n }\n\n // Load issues\n try {\n this.invalidIssueFiles = [];\n this.issues = await listIssues(this.dataSyncDir, {\n warnOnInvalid: false,\n onInvalidIssue: (invalidIssue) => this.invalidIssueFiles.push(invalidIssue),\n });\n } catch {\n // May fail if no issues yet\n }\n\n // Gather status info\n const statusInfo = await this.gatherStatusInfo();\n\n // Gather stats info (async to check remote when local is empty)\n const statsInfo = await this.gatherStatsInfo();\n\n // Run health checks (core system checks)\n const healthChecks: DiagnosticResult[] = [];\n\n // Check 1: Git version\n healthChecks.push(await this.checkGitVersion());\n\n // Check 2: Config directory and file\n healthChecks.push(await this.checkConfig());\n\n // Check 3: Issues directory\n healthChecks.push(await this.checkIssuesDirectory());\n\n // Check 4: Orphaned dependencies\n healthChecks.push(this.checkOrphanedDependencies(this.issues));\n\n // Check 5: Duplicate IDs\n healthChecks.push(this.checkDuplicateIds(this.issues));\n\n // Check 5b: Merge conflict markers in ids.yml\n healthChecks.push(await this.checkIdMappingConflicts(options.fix));\n\n // Check 6: Duplicate mapping keys in ids.yml\n healthChecks.push(await this.checkIdMappingDuplicates(options.fix));\n\n // Check 7: Orphaned temp files\n healthChecks.push(await this.checkTempFiles(options.fix));\n\n // Check 8: Issue validity\n healthChecks.push(this.checkIssueValidity(this.issues, this.invalidIssueFiles));\n\n // Check 9: Worktree health (with fix support)\n // Run BEFORE ID mapping check — worktree repair and data migration can\n // overwrite ids.yml, so mappings must be verified after migration.\n healthChecks.push(await this.checkWorktree(options.fix));\n\n // Check 10: Data location (issues in wrong path, with fix support)\n const dataLocationResult = await this.checkDataLocation(options.fix);\n healthChecks.push(dataLocationResult);\n\n // If data was migrated, reload issues and refresh dataSyncDir so\n // subsequent checks (especially ID mappings) see the current state.\n if (dataLocationResult.status === 'ok' && dataLocationResult.message?.includes('migrated')) {\n this.dataSyncDir = await resolveDataSyncDir(this.cwd);\n try {\n this.invalidIssueFiles = [];\n this.issues = await listIssues(this.dataSyncDir, {\n warnOnInvalid: false,\n onInvalidIssue: (invalidIssue) => this.invalidIssueFiles.push(invalidIssue),\n });\n } catch {\n // Will be caught by other health checks\n }\n }\n\n // Check 8b: Missing ID mappings (issues without short IDs)\n // Runs AFTER worktree/migration checks to ensure ids.yml is in its final location.\n const parsedMaxHistory = options.maxHistory ? parseInt(options.maxHistory, 10) : 50;\n const maxHistory =\n Number.isNaN(parsedMaxHistory) || parsedMaxHistory < 0 ? 50 : parsedMaxHistory;\n healthChecks.push(await this.checkMissingMappings(options.fix, maxHistory));\n\n // Check 11: Local sync branch health\n healthChecks.push(await this.checkLocalSyncBranch());\n\n // Check 12: Remote sync branch health\n healthChecks.push(await this.checkRemoteSyncBranch());\n\n // Check 13: Local has data but remote empty (ai-trade-arena bug detection)\n healthChecks.push(await this.checkLocalVsRemoteData());\n\n // Check 14: Multi-user/clone scenario detection\n healthChecks.push(await this.checkCloneScenarios());\n\n // Check 15: Sync consistency (worktree matches local, ahead/behind counts)\n healthChecks.push(await this.checkSyncConsistency());\n\n // Run integration checks (optional IDE/agent integrations)\n const integrationChecks: DiagnosticResult[] = [];\n\n // Integration 1: Claude Code skill file\n integrationChecks.push(await this.checkClaudeSkill());\n\n // Integration 2: Codex AGENTS.md (also used by Cursor since v1.6)\n integrationChecks.push(await this.checkCodexAgents());\n\n // Combine for overall status\n const allChecks = [...healthChecks, ...integrationChecks];\n const allOk = allChecks.every((c) => c.status === 'ok');\n const hasFixable = allChecks.some((c) => c.fixable && c.status !== 'ok');\n\n this.output.data(\n { statusInfo, statsInfo, healthChecks, integrationChecks, healthy: allOk },\n () => {\n const colors = this.output.getColors();\n\n // REPOSITORY section (shared with status command)\n renderRepositorySection(\n {\n version: VERSION,\n workingDirectory: this.cwd,\n initialized: true, // doctor requires init\n gitRepository: !!statusInfo.gitBranch,\n gitBranch: statusInfo.gitBranch,\n gitVersion: null, // Git version is shown in health checks\n gitVersionSupported: true,\n },\n colors,\n { showHeading: true },\n );\n\n // CONFIG section (shared with status command)\n if (this.config) {\n renderConfigSection(\n {\n syncBranch: this.config.sync.branch,\n remote: this.config.sync.remote,\n displayPrefix: this.config.display.id_prefix,\n },\n colors,\n );\n }\n\n // STATISTICS section (shared with stats command)\n renderStatisticsSection(statsInfo, colors);\n\n // INTEGRATIONS section\n console.log('');\n console.log(colors.bold(formatHeading('Integrations')));\n renderDiagnostics(integrationChecks, colors);\n\n // HEALTH CHECKS section (doctor-only)\n console.log('');\n console.log(colors.bold(formatHeading('Health Checks')));\n renderDiagnostics(healthChecks, colors);\n\n // Final summary\n console.log('');\n if (allOk) {\n this.output.success('Repository is healthy');\n } else if (hasFixable && !options.fix) {\n this.output.warn('Issues found. Run with --fix to repair.');\n } else {\n this.output.warn('Issues found that may require manual intervention.');\n }\n },\n );\n }\n\n private async gatherStatusInfo(): Promise<{\n gitBranch: string | null;\n worktreeHealthy: boolean;\n }> {\n let gitBranch: string | null = null;\n try {\n gitBranch = await getCurrentBranch();\n } catch {\n // Not in a git repo or no commits\n }\n\n const worktreeHealth = await checkWorktreeHealth(this.cwd);\n\n return {\n gitBranch,\n worktreeHealthy: worktreeHealth.valid,\n };\n }\n\n private async gatherStatsInfo(): Promise<{\n total: number;\n ready: number;\n inProgress: number;\n blocked: number;\n open: number;\n remoteTotal: number | null;\n }> {\n // Count by status\n const byStatus: Record<IssueStatusType, number> = {\n open: 0,\n in_progress: 0,\n blocked: 0,\n deferred: 0,\n closed: 0,\n };\n\n // Build set of blocked issue IDs\n const blockedIds = new Set<string>();\n for (const issue of this.issues) {\n for (const dep of issue.dependencies) {\n if (dep.type === 'blocks') {\n const blockedIssue = this.issues.find((i) => i.id === dep.target);\n if (blockedIssue && blockedIssue.status !== 'closed') {\n blockedIds.add(dep.target);\n }\n }\n }\n }\n\n // Count ready issues (open and not blocked)\n let readyCount = 0;\n\n for (const issue of this.issues) {\n byStatus[issue.status]++;\n if (issue.status === 'open' && !blockedIds.has(issue.id)) {\n readyCount++;\n }\n }\n\n // Check remote issue count when local is empty\n // This helps users on fresh clones understand that data exists\n let remoteTotal: number | null = null;\n if (this.issues.length === 0 && this.config) {\n const remote = this.config.sync.remote ?? 'origin';\n const syncBranch = this.config.sync.branch ?? 'tbd-sync';\n remoteTotal = await countRemoteIssues(remote, syncBranch);\n }\n\n return {\n total: this.issues.length,\n ready: readyCount,\n inProgress: byStatus.in_progress,\n blocked: blockedIds.size,\n open: byStatus.open,\n remoteTotal,\n };\n }\n\n private async checkGitVersion(): Promise<DiagnosticResult> {\n try {\n const { version, supported } = await checkGitVersion();\n const versionStr = `${version.major}.${version.minor}.${version.patch}`;\n\n if (supported) {\n return {\n name: 'Git version',\n status: 'ok',\n message: versionStr,\n };\n }\n\n return {\n name: 'Git version',\n status: 'error',\n message: `${versionStr} (requires ${MIN_GIT_VERSION}+)`,\n suggestion: 'Upgrade Git: https://git-scm.com/downloads',\n };\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n if (msg.includes('git') || msg.includes('not found') || msg.includes('ENOENT')) {\n return {\n name: 'Git version',\n status: 'error',\n message: 'Git not found',\n suggestion: 'Install Git: https://git-scm.com/downloads',\n };\n }\n return {\n name: 'Git version',\n status: 'warn',\n message: `Unable to check: ${msg}`,\n };\n }\n }\n\n private async checkConfig(): Promise<DiagnosticResult> {\n const configPath = join(CONFIG_DIR, 'config.yml');\n try {\n await access(join(this.cwd, configPath));\n await readConfig(this.cwd);\n return { name: 'Config file', status: 'ok', path: configPath };\n } catch (error) {\n const msg = (error as Error).message;\n if (msg.includes('ENOENT')) {\n return {\n name: 'Config file',\n status: 'error',\n message: 'not found',\n path: configPath,\n suggestion: 'Run: tbd init',\n };\n }\n return {\n name: 'Config file',\n status: 'error',\n message: 'Invalid config file',\n path: configPath,\n };\n }\n }\n\n private async checkIssuesDirectory(): Promise<DiagnosticResult> {\n const issuesPath = join(CONFIG_DIR, 'issues');\n try {\n await access(join(this.dataSyncDir, 'issues'));\n return { name: 'Issues directory', status: 'ok', path: issuesPath };\n } catch {\n // No issues directory is normal for a fresh/empty repo\n return {\n name: 'Issues directory',\n status: 'ok',\n message: 'empty (no issues yet)',\n path: issuesPath,\n };\n }\n }\n\n private checkOrphanedDependencies(issues: Issue[]): DiagnosticResult {\n const issueIds = new Set(issues.map((i) => i.id));\n const orphans: string[] = [];\n\n for (const issue of issues) {\n for (const dep of issue.dependencies) {\n if (!issueIds.has(dep.target)) {\n orphans.push(`${issue.id} -> ${dep.target} (missing)`);\n }\n }\n }\n\n if (orphans.length === 0) {\n return { name: 'Dependencies', status: 'ok' };\n }\n\n return {\n name: 'Dependencies',\n status: 'warn',\n message: `${orphans.length} orphaned reference(s)`,\n details: orphans,\n fixable: true,\n suggestion: 'Run: tbd doctor --fix',\n };\n }\n\n private checkDuplicateIds(issues: Issue[]): DiagnosticResult {\n const seen = new Set<string>();\n const duplicates: string[] = [];\n\n for (const issue of issues) {\n if (seen.has(issue.id)) {\n duplicates.push(issue.id);\n }\n seen.add(issue.id);\n }\n\n if (duplicates.length === 0) {\n return { name: 'Unique IDs', status: 'ok' };\n }\n\n return {\n name: 'Unique IDs',\n status: 'error',\n message: `${duplicates.length} duplicate ID(s)`,\n details: duplicates.map((id) => `${id} (duplicate)`),\n suggestion: 'Manually remove duplicate issue files',\n };\n }\n\n /**\n * Check 5b: Merge conflict markers in ids.yml.\n *\n * After a failed git merge during sync, ids.yml may retain unresolved\n * conflict markers (<<<<<<< / ======= / >>>>>>>). This blocks all tbd\n * commands since YAML parsing throws MergeConflictError.\n *\n * For ids.yml, both sides are simple key-value pairs that are append-only,\n * so the resolution is trivial: keep all entries from both sides.\n *\n * With --fix, extracts both sides, merges them, and re-saves.\n */\n private async checkIdMappingConflicts(fix?: boolean): Promise<DiagnosticResult> {\n const mappingPath = join(this.dataSyncDir, 'mappings', 'ids.yml');\n let content: string;\n\n try {\n content = await readFile(mappingPath, 'utf-8');\n } catch {\n return { name: 'ID mapping conflicts', status: 'ok' };\n }\n\n const { hasMergeConflictMarkers } = await import('../../utils/yaml-utils.js');\n if (!hasMergeConflictMarkers(content)) {\n return { name: 'ID mapping conflicts', status: 'ok' };\n }\n\n if (fix && !this.checkDryRun('Resolve merge conflicts in ids.yml')) {\n try {\n const { resolveIdMappingConflicts, saveIdMapping } =\n await import('../../file/id-mapping.js');\n const resolved = resolveIdMappingConflicts(content);\n await saveIdMapping(this.dataSyncDir, resolved);\n return {\n name: 'ID mapping conflicts',\n status: 'ok',\n message: `resolved merge conflicts (${resolved.shortToUlid.size} entries)`,\n };\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n return {\n name: 'ID mapping conflicts',\n status: 'error',\n message: `failed to resolve conflicts: ${msg}`,\n };\n }\n }\n\n return {\n name: 'ID mapping conflicts',\n status: 'error',\n message: 'ids.yml contains unresolved merge conflict markers',\n fixable: true,\n suggestion: 'Run: tbd doctor --fix to auto-resolve',\n };\n }\n\n /**\n * Check for duplicate keys in the ID mapping file (ids.yml).\n *\n * After a git merge conflict resolution that keeps entries from both sides,\n * ids.yml can end up with duplicate YAML keys. The yaml parser throws\n * \"Map keys must be unique\" which breaks tbd commands.\n *\n * With --fix, re-saves the file to eliminate duplicates.\n */\n private async checkIdMappingDuplicates(fix?: boolean): Promise<DiagnosticResult> {\n const mappingPath = join(this.dataSyncDir, 'mappings', 'ids.yml');\n let content: string;\n\n try {\n content = await readFile(mappingPath, 'utf-8');\n } catch {\n // File doesn't exist — normal for repos with no issues yet\n return { name: 'ID mapping keys', status: 'ok' };\n }\n\n const duplicates = detectDuplicateYamlKeys(content);\n\n if (duplicates.length === 0) {\n return { name: 'ID mapping keys', status: 'ok' };\n }\n\n if (fix && !this.checkDryRun('Fix duplicate ID mapping keys')) {\n // Load and re-save to deduplicate (Map + saveIdMapping naturally dedupes)\n try {\n const { loadIdMapping, saveIdMapping } = await import('../../file/id-mapping.js');\n const mapping = await loadIdMapping(this.dataSyncDir);\n await saveIdMapping(this.dataSyncDir, mapping);\n return {\n name: 'ID mapping keys',\n status: 'ok',\n message: `fixed ${duplicates.length} duplicate key(s)`,\n };\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n return {\n name: 'ID mapping keys',\n status: 'error',\n message: `failed to fix duplicates: ${msg}`,\n };\n }\n }\n\n return {\n name: 'ID mapping keys',\n status: 'warn',\n message: `${duplicates.length} duplicate key(s) in ids.yml`,\n details: duplicates.map((k) => `\"${k}\" appears multiple times`),\n fixable: true,\n suggestion: 'Run: tbd doctor --fix to deduplicate',\n };\n }\n\n private async checkTempFiles(fix?: boolean): Promise<DiagnosticResult> {\n const issuesPath = join(CONFIG_DIR, 'issues');\n const issuesDir = join(this.dataSyncDir, 'issues');\n let tempFiles: string[] = [];\n\n try {\n const files = await readdir(issuesDir);\n tempFiles = files.filter((f) => f.endsWith('.tmp'));\n } catch {\n // Directory doesn't exist - no temp files\n return { name: 'Temp files', status: 'ok', path: issuesPath };\n }\n\n if (tempFiles.length === 0) {\n return { name: 'Temp files', status: 'ok', path: issuesPath };\n }\n\n if (fix && !this.checkDryRun('Clean temp files')) {\n // Clean up temp files\n for (const file of tempFiles) {\n try {\n await unlink(join(issuesDir, file));\n } catch {\n // Ignore errors\n }\n }\n return {\n name: 'Temp files',\n status: 'ok',\n message: `Cleaned ${tempFiles.length} temp file(s)`,\n path: issuesPath,\n };\n }\n\n return {\n name: 'Temp files',\n status: 'warn',\n message: `${tempFiles.length} orphaned temp file(s)`,\n path: issuesPath,\n details: tempFiles,\n fixable: true,\n suggestion: 'Run: tbd doctor --fix',\n };\n }\n\n private checkIssueValidity(\n issues: Issue[],\n invalidIssueFiles: InvalidIssueFile[],\n ): DiagnosticResult {\n const invalid: { id: string; reason: string }[] = [];\n\n for (const invalidIssueFile of invalidIssueFiles) {\n invalid.push({ id: invalidIssueFile.file, reason: invalidIssueFile.reason });\n }\n\n for (const issue of issues) {\n const issueId = issue.id ?? 'unknown';\n // Check required fields\n if (!issue.id) {\n invalid.push({ id: issueId, reason: 'missing required field: id' });\n continue;\n }\n if (!issue.title) {\n invalid.push({ id: issueId, reason: 'missing required field: title' });\n continue;\n }\n if (!issue.status) {\n invalid.push({ id: issueId, reason: 'missing required field: status' });\n continue;\n }\n if (!issue.kind) {\n invalid.push({ id: issueId, reason: 'missing required field: kind' });\n continue;\n }\n // Check ID format\n if (!validateIssueId(issue.id)) {\n invalid.push({ id: issueId, reason: 'invalid ID format' });\n continue;\n }\n // Check priority range\n if (issue.priority < 0 || issue.priority > 4) {\n invalid.push({ id: issueId, reason: `invalid priority ${issue.priority} (must be 0-4)` });\n }\n }\n\n if (invalid.length === 0) {\n return { name: 'Issue validity', status: 'ok' };\n }\n\n return {\n name: 'Issue validity',\n status: 'error',\n message: `${invalid.length} invalid issue file(s)`,\n path: join(CONFIG_DIR, 'issues'),\n details: invalid.map((i) => `${i.id}: ${i.reason}`),\n suggestion: 'Manually fix or delete invalid issue files',\n };\n }\n\n /**\n * Check for issues that have no short ID mapping in ids.yml.\n *\n * This can happen when a git merge brings in issue files (e.g., from\n * a feature branch with outbox issues) without the corresponding\n * ids.yml entries. Without a mapping, any command that tries to\n * display the issue ID will crash.\n *\n * With --fix, creates missing mappings automatically.\n */\n private async checkMissingMappings(fix?: boolean, maxHistory = 50): Promise<DiagnosticResult> {\n if (this.issues.length === 0) {\n return { name: 'ID mapping coverage', status: 'ok' };\n }\n\n const { loadIdMapping, saveIdMapping, reconcileMappings } =\n await import('../../file/id-mapping.js');\n const mapping = await loadIdMapping(this.dataSyncDir);\n\n // Find issues missing from the mapping\n const missingIds: string[] = [];\n for (const issue of this.issues) {\n const ulid = extractUlidFromInternalId(issue.id);\n if (!mapping.ulidToShort.has(ulid)) {\n missingIds.push(issue.id);\n }\n }\n\n if (missingIds.length === 0) {\n return { name: 'ID mapping coverage', status: 'ok' };\n }\n\n if (fix && !this.checkDryRun('Create missing ID mappings')) {\n // Try to recover original short IDs from git history before generating new ones.\n // Search recent commits on the tbd-sync branch that touched ids.yml, not\n // just the latest. This handles the case where a bug (e.g., migration\n // overwrite) destroyed entries in a recent commit — the entries still exist\n // in earlier commits. Since mappings are append-only, merging all versions\n // is safe. Capped via --max-history (default 50, 0 = full history).\n const { parseIdMappingFromYaml, mergeIdMappings } = await import('../../file/id-mapping.js');\n let historicalMapping: Awaited<ReturnType<typeof loadIdMapping>> | undefined;\n try {\n const config = await import('../../file/config.js').then((m) => m.readConfig(this.cwd));\n const syncBranch = config.sync.branch;\n // Get recent commits that touched ids.yml (most recent first, capped)\n const logArgs = ['log', '--format=%H'];\n if (maxHistory > 0) {\n logArgs.push(`-${maxHistory}`);\n }\n logArgs.push(syncBranch, '--', `${DATA_SYNC_DIR}/mappings/ids.yml`);\n const commitLog = await git(...logArgs);\n const commitHashes = commitLog.trim().split('\\n').filter(Boolean);\n for (const commitHash of commitHashes) {\n try {\n const idsContent = await git('show', `${commitHash}:${DATA_SYNC_DIR}/mappings/ids.yml`);\n if (idsContent) {\n const versionMapping = parseIdMappingFromYaml(idsContent);\n if (!historicalMapping) {\n historicalMapping = versionMapping;\n } else {\n historicalMapping = mergeIdMappings(historicalMapping, versionMapping);\n }\n }\n } catch {\n // Individual commit may be unreachable — skip\n }\n }\n } catch {\n // Git history not available - will generate new IDs\n }\n\n const historicalCount = historicalMapping?.shortToUlid.size ?? 0;\n const result = reconcileMappings(missingIds, mapping, historicalMapping);\n await saveIdMapping(this.dataSyncDir, mapping);\n\n const parts: string[] = [];\n if (result.recovered.length > 0) {\n parts.push(`recovered ${result.recovered.length} from git history`);\n }\n if (result.created.length > 0) {\n parts.push(`created ${result.created.length} new`);\n }\n const details: string[] = [\n `Scanned ${maxHistory > 0 ? `up to ${maxHistory}` : 'all'} git commits for ids.yml history`,\n `Found ${historicalCount} historical mapping(s) to use for recovery`,\n `${missingIds.length} issue(s) were missing short ID mappings`,\n ];\n if (result.recovered.length > 0) {\n details.push(`Recovered ${result.recovered.length} original short ID(s) from git history`);\n }\n if (result.created.length > 0) {\n details.push(\n `Generated ${result.created.length} new short ID(s) (originals not found in history)`,\n );\n }\n return {\n name: 'ID mapping coverage',\n status: 'ok',\n message: parts.join(', '),\n details,\n };\n }\n\n return {\n name: 'ID mapping coverage',\n status: 'error',\n message: `${missingIds.length} issue(s) without short ID mapping`,\n details: missingIds.map((id) => `${id} (no short ID)`),\n fixable: true,\n suggestion: 'Run: tbd doctor --fix to create missing mappings',\n };\n }\n\n private async checkClaudeSkill(): Promise<DiagnosticResult> {\n const claudePaths = getClaudePaths(this.cwd);\n try {\n await access(claudePaths.skill);\n return { name: 'Claude Code skill', status: 'ok', path: CLAUDE_SKILL_REL };\n } catch {\n return {\n name: 'Claude Code skill',\n status: 'warn',\n message: 'not installed',\n path: CLAUDE_SKILL_REL,\n suggestion: 'Run: tbd setup --auto',\n };\n }\n }\n\n private async checkCodexAgents(): Promise<DiagnosticResult> {\n const agentsPath = getAgentsMdPath(this.cwd);\n try {\n await access(agentsPath);\n const content = await readFile(agentsPath, 'utf-8');\n if (content.includes('BEGIN TBD INTEGRATION')) {\n return { name: 'Codex AGENTS.md', status: 'ok', path: AGENTS_MD_REL };\n }\n return {\n name: 'Codex AGENTS.md',\n status: 'warn',\n message: 'exists but missing tbd integration',\n path: AGENTS_MD_REL,\n suggestion: 'Run: tbd setup --auto',\n };\n } catch {\n return {\n name: 'Codex AGENTS.md',\n status: 'warn',\n message: 'not installed',\n path: AGENTS_MD_REL,\n suggestion: 'Run: tbd setup --auto',\n };\n }\n }\n\n /**\n * Check worktree health with enhanced status detection.\n * See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md §4\n */\n private async checkWorktree(fix?: boolean): Promise<DiagnosticResult> {\n const worktreePath = WORKTREE_DIR;\n const worktreeHealth = await checkWorktreeHealth(this.cwd);\n\n switch (worktreeHealth.status) {\n case 'valid':\n return { name: 'Worktree', status: 'ok', path: worktreePath };\n\n case 'missing':\n // Worktree not existing is OK - it's created on demand\n return { name: 'Worktree', status: 'ok', message: 'not created yet', path: worktreePath };\n\n case 'prunable':\n case 'corrupted': {\n // Attempt repair if --fix is provided and not in dry-run mode\n if (fix && !this.checkDryRun('Repair worktree')) {\n const result = await repairWorktree(this.cwd, worktreeHealth.status);\n\n if (result.success) {\n const message = result.backedUp\n ? `repaired (backed up to ${result.backedUp})`\n : 'repaired successfully';\n return { name: 'Worktree', status: 'ok', message, path: worktreePath };\n }\n\n return {\n name: 'Worktree',\n status: 'error',\n message: `repair failed: ${result.error}`,\n path: worktreePath,\n };\n }\n\n // No --fix flag, report the issue\n if (worktreeHealth.status === 'prunable') {\n return {\n name: 'Worktree',\n status: 'error',\n message: 'prunable (directory deleted)',\n path: worktreePath,\n details: [\n 'The worktree directory was deleted but git still tracks it.',\n 'This can cause data to be written to the wrong location.',\n ],\n fixable: true,\n suggestion: 'Run: tbd doctor --fix to recreate worktree',\n };\n }\n\n return {\n name: 'Worktree',\n status: 'error',\n message: worktreeHealth.error ?? 'corrupted',\n path: worktreePath,\n details: ['The worktree exists but is not a valid git worktree.'],\n fixable: true,\n suggestion: 'Run: tbd doctor --fix to repair',\n };\n }\n\n default:\n return {\n name: 'Worktree',\n status: 'warn',\n message: worktreeHealth.error ?? 'unknown status',\n path: worktreePath,\n fixable: true,\n suggestion: 'Run: tbd doctor --fix',\n };\n }\n }\n\n /**\n * Check for issues in wrong location.\n * See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md §5\n *\n * Issues should be in .tbd/data-sync-worktree/.tbd/data-sync/issues/\n * If they're in .tbd/data-sync/issues/ on main branch, the worktree was missing\n * and data was written to the fallback path - this is a bug requiring migration.\n */\n private async checkDataLocation(fix?: boolean): Promise<DiagnosticResult> {\n const wrongPath = join(this.cwd, DATA_SYNC_DIR);\n const wrongIssuesPath = join(wrongPath, 'issues');\n\n // Try to list issues in the wrong location\n let wrongPathIssues: Issue[] = [];\n try {\n wrongPathIssues = await listIssues(wrongPath);\n } catch {\n // No issues in wrong path - this is expected\n }\n\n if (wrongPathIssues.length === 0) {\n return { name: 'Data location', status: 'ok' };\n }\n\n // Issues found in wrong location - attempt migration if --fix and not dry-run\n if (fix && !this.checkDryRun('Migrate data to worktree')) {\n // First ensure worktree exists - create it if missing\n let worktreeHealth = await checkWorktreeHealth(this.cwd);\n if (worktreeHealth.status === 'missing') {\n // Worktree doesn't exist yet - create it for migration\n const initResult = await initWorktree(this.cwd);\n if (!initResult.success) {\n return {\n name: 'Data location',\n status: 'error',\n message: `${wrongPathIssues.length} issue(s) in wrong location, failed to create worktree: ${initResult.error}`,\n path: wrongIssuesPath,\n };\n }\n worktreeHealth = await checkWorktreeHealth(this.cwd);\n }\n\n if (worktreeHealth.status !== 'valid') {\n return {\n name: 'Data location',\n status: 'error',\n message: `${wrongPathIssues.length} issue(s) in wrong location, worktree not ready`,\n path: wrongIssuesPath,\n details: [\n 'Cannot migrate: worktree must be repaired first.',\n 'The worktree repair should have run before this check.',\n ],\n };\n }\n\n // Migrate data to worktree (remove source after backup + copy)\n const result = await migrateDataToWorktree(this.cwd, true);\n\n if (result.success) {\n const details: string[] = [];\n if (result.backupPath) {\n details.push(`Backed up to ${result.backupPath}`);\n }\n details.push(\n `Migrated ${result.migratedCount} file(s) from .tbd/data-sync/ to worktree`,\n 'Source files removed after successful migration',\n );\n const message = result.backupPath\n ? `migrated ${result.migratedCount} file(s), backed up to ${result.backupPath}`\n : `migrated ${result.migratedCount} file(s)`;\n return {\n name: 'Data location',\n status: 'ok',\n message,\n path: wrongIssuesPath,\n details,\n };\n }\n\n return {\n name: 'Data location',\n status: 'error',\n message: `migration failed: ${result.error}`,\n path: wrongIssuesPath,\n };\n }\n\n // No --fix flag, report the issue\n return {\n name: 'Data location',\n status: 'error',\n message: `${wrongPathIssues.length} issue(s) in wrong location`,\n path: wrongIssuesPath,\n details: [\n `Found ${wrongPathIssues.length} issues in .tbd/data-sync/ (wrong)`,\n 'Issues should be in .tbd/data-sync-worktree/.tbd/data-sync/',\n 'This indicates the worktree was missing when issues were created',\n ],\n fixable: true,\n suggestion: 'Run: tbd doctor --fix to migrate issues to worktree',\n };\n }\n\n /**\n * Check local sync branch health.\n * See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md §4b\n */\n private async checkLocalSyncBranch(): Promise<DiagnosticResult> {\n const syncBranch = this.config?.sync.branch ?? 'tbd-sync';\n const localHealth = await checkLocalBranchHealth(syncBranch);\n\n if (localHealth.exists && !localHealth.orphaned) {\n return { name: 'Local sync branch', status: 'ok', message: syncBranch };\n }\n\n if (!localHealth.exists) {\n // Local branch doesn't exist - check if remote exists\n const remote = this.config?.sync.remote ?? 'origin';\n const remoteHealth = await checkRemoteBranchHealth(remote, syncBranch);\n\n if (remoteHealth.exists) {\n // Remote exists but local doesn't - can be created from remote\n return {\n name: 'Local sync branch',\n status: 'warn',\n message: `${syncBranch} not found (remote exists)`,\n suggestion: 'Run: tbd sync to create from remote',\n };\n }\n\n // Neither local nor remote - new repo, this is OK\n return {\n name: 'Local sync branch',\n status: 'ok',\n message: 'not created yet',\n };\n }\n\n // Branch exists but is orphaned (no commits)\n return {\n name: 'Local sync branch',\n status: 'warn',\n message: `${syncBranch} exists but has no commits`,\n suggestion: 'Run: tbd sync to push data',\n };\n }\n\n /**\n * Check remote sync branch health.\n * See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md §4b\n */\n private async checkRemoteSyncBranch(): Promise<DiagnosticResult> {\n const syncBranch = this.config?.sync.branch ?? 'tbd-sync';\n const remote = this.config?.sync.remote ?? 'origin';\n const remoteHealth = await checkRemoteBranchHealth(remote, syncBranch);\n\n if (remoteHealth.exists) {\n if (remoteHealth.diverged) {\n return {\n name: 'Remote sync branch',\n status: 'warn',\n message: `${remote}/${syncBranch} has diverged`,\n suggestion: 'Run: tbd sync to reconcile changes',\n };\n }\n return { name: 'Remote sync branch', status: 'ok', message: `${remote}/${syncBranch}` };\n }\n\n // Remote branch doesn't exist\n const localHealth = await checkLocalBranchHealth(syncBranch);\n if (localHealth.exists) {\n // Local exists but remote doesn't - needs push\n return {\n name: 'Remote sync branch',\n status: 'warn',\n message: `${remote}/${syncBranch} not found`,\n suggestion: 'Run: tbd sync to push local branch',\n };\n }\n\n // Neither exists - new repo, this is OK\n return {\n name: 'Remote sync branch',\n status: 'ok',\n message: 'not created yet',\n };\n }\n\n /**\n * Check for local data that hasn't been synced to remote.\n * This detects the ai-trade-arena bug scenario.\n * See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md §4\n */\n private async checkLocalVsRemoteData(): Promise<DiagnosticResult> {\n // Only check if worktree exists and has issues\n const worktreeHealth = await checkWorktreeHealth(this.cwd);\n if (worktreeHealth.status !== 'valid') {\n // Worktree not valid - can't compare\n return { name: 'Sync status', status: 'ok', message: 'worktree not active' };\n }\n\n // Count local issues in worktree\n const localIssueCount = this.issues.length;\n if (localIssueCount === 0) {\n return { name: 'Sync status', status: 'ok' };\n }\n\n // Check if remote branch exists and has commits\n const syncBranch = this.config?.sync.branch ?? 'tbd-sync';\n const remote = this.config?.sync.remote ?? 'origin';\n const remoteHealth = await checkRemoteBranchHealth(remote, syncBranch);\n\n if (!remoteHealth.exists) {\n // Remote doesn't exist - issues haven't been pushed\n return {\n name: 'Sync status',\n status: 'warn',\n message: `${localIssueCount} local issues, remote branch not found`,\n suggestion: 'Run: tbd sync to push issues to remote',\n };\n }\n\n // Note: Full remote issue count comparison would require fetching the remote\n // For now, we flag if local has issues but remote branch exists but is empty\n // This is detected by comparing commit counts or checking issue files\n // A simpler check: if worktree has uncommitted changes, we know they aren't synced\n\n return { name: 'Sync status', status: 'ok' };\n }\n\n /**\n * Check for multi-user/clone scenarios that indicate lost data.\n * See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md §6\n */\n private async checkCloneScenarios(): Promise<DiagnosticResult> {\n // Only relevant if we have no issues\n const localIssueCount = this.issues.length;\n if (localIssueCount > 0) {\n return { name: 'Clone status', status: 'ok' };\n }\n\n // Check 1: Beads migration evidence exists but tbd has no issues\n const beadsDisabledPath = join(this.cwd, '.beads-disabled');\n let beadsMigrationExists = false;\n try {\n await access(beadsDisabledPath);\n beadsMigrationExists = true;\n } catch {\n // No beads migration - that's fine\n }\n\n if (beadsMigrationExists) {\n // Check if beads had issues\n const beadsJsonl = join(beadsDisabledPath, '.beads', 'issues.jsonl');\n let beadsIssueCount = 0;\n try {\n const content = await readFile(beadsJsonl, 'utf-8');\n beadsIssueCount = content.trim().split('\\n').filter(Boolean).length;\n } catch {\n // Can't read beads file - ignore\n }\n\n if (beadsIssueCount > 0) {\n return {\n name: 'Clone status',\n status: 'error',\n message: `Beads migration has ${beadsIssueCount} issues, tbd has none`,\n details: [\n 'This repo was migrated from beads but issues were never synced.',\n 'Another user may have the issues locally but they were not pushed.',\n ],\n suggestion: 'Contact the repo owner to run: tbd sync',\n };\n }\n }\n\n // Check 2: Config has id_prefix but no issues (suggests prior usage)\n if (!beadsMigrationExists && this.config?.display?.id_prefix) {\n return {\n name: 'Clone status',\n status: 'warn',\n message: `Config has prefix '${this.config.display.id_prefix}' but no issues`,\n details: [\n 'This suggests issues may have been created but not synced,',\n 'or were lost due to sync issues on another machine.',\n ],\n suggestion: 'If you expect issues to exist, contact the repo owner',\n };\n }\n\n // Check 3: Active beads directory exists (not migrated yet)\n const beadsPath = join(this.cwd, '.beads');\n let beadsActiveExists = false;\n try {\n await access(beadsPath);\n beadsActiveExists = true;\n } catch {\n // No active beads - that's fine\n }\n\n if (beadsActiveExists) {\n return {\n name: 'Clone status',\n status: 'ok',\n message: 'beads directory exists (migration available)',\n };\n }\n\n return { name: 'Clone status', status: 'ok' };\n }\n\n /**\n * Check sync consistency - worktree matches local, ahead/behind counts.\n * See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md §4\n */\n private async checkSyncConsistency(): Promise<DiagnosticResult> {\n const syncBranch = this.config?.sync.branch ?? 'tbd-sync';\n const remote = this.config?.sync.remote ?? 'origin';\n\n // Only check if worktree is valid\n const worktreeHealth = await checkWorktreeHealth(this.cwd);\n if (worktreeHealth.status !== 'valid') {\n return { name: 'Sync consistency', status: 'ok', message: 'worktree not active' };\n }\n\n try {\n const consistency = await checkSyncConsistency(this.cwd, syncBranch, remote);\n\n // Check if worktree matches local\n if (!consistency.worktreeMatchesLocal) {\n return {\n name: 'Sync consistency',\n status: 'error',\n message: 'worktree HEAD does not match local branch',\n details: [\n `Worktree HEAD: ${consistency.worktreeHead.slice(0, 7)}`,\n `Local ${syncBranch}: ${consistency.localHead.slice(0, 7)}`,\n ],\n fixable: true,\n suggestion: 'Run: tbd doctor --fix to synchronize',\n };\n }\n\n // Check ahead/behind status\n if (consistency.localAhead > 0 && consistency.localBehind > 0) {\n return {\n name: 'Sync consistency',\n status: 'warn',\n message: `diverged (${consistency.localAhead} ahead, ${consistency.localBehind} behind)`,\n suggestion: 'Run: tbd sync to reconcile',\n };\n }\n\n if (consistency.localAhead > 0) {\n return {\n name: 'Sync consistency',\n status: 'ok',\n message: `${consistency.localAhead} local commit(s) not yet pushed — run \\`tbd sync\\` to push`,\n };\n }\n\n if (consistency.localBehind > 0) {\n return {\n name: 'Sync consistency',\n status: 'ok',\n message: `${consistency.localBehind} remote commit(s) not yet pulled — run \\`tbd sync\\` to pull`,\n };\n }\n\n return { name: 'Sync consistency', status: 'ok' };\n } catch (error) {\n // Sync consistency check failed - may be normal if branches don't exist yet\n const msg = error instanceof Error ? error.message : String(error);\n if (msg.includes('not found') || msg.includes('no commits')) {\n return { name: 'Sync consistency', status: 'ok', message: 'branches not yet established' };\n }\n return {\n name: 'Sync consistency',\n status: 'warn',\n message: `Unable to check: ${msg}`,\n };\n }\n }\n}\n\nexport const doctorCommand = new Command('doctor')\n .description('Diagnose and repair repository')\n .option('--fix', 'Attempt to fix issues')\n .option(\n '--max-history <n>',\n 'Max git commits to scan for ID mapping recovery (0 = full history)',\n '50',\n )\n .action(async (options, command) => {\n const handler = new DoctorHandler(command);\n await handler.run(options);\n });\n","/**\n * `tbd config` - Configuration management.\n *\n * See: tbd-design.md §4.9 Config\n */\n\nimport { Command } from 'commander';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { requireInit, NotInitializedError, ValidationError } from '../lib/errors.js';\nimport { readConfig, writeConfig } from '../../file/config.js';\nimport type { Config } from '../../lib/types.js';\n\n// Show config\nclass ConfigShowHandler extends BaseCommand {\n async run(): Promise<void> {\n const tbdRoot = await requireInit();\n\n let config: Config;\n try {\n config = await readConfig(tbdRoot);\n } catch {\n throw new NotInitializedError('No configuration found. Run `tbd init` first.');\n }\n\n this.output.data(config, () => {\n // Output as YAML format\n const colors = this.output.getColors();\n console.log(`${colors.dim('tbd_version:')} ${config.tbd_version}`);\n console.log(`${colors.dim('sync:')}`);\n console.log(` ${colors.dim('branch:')} ${config.sync.branch}`);\n console.log(` ${colors.dim('remote:')} ${config.sync.remote}`);\n console.log(`${colors.dim('display:')}`);\n console.log(` ${colors.dim('id_prefix:')} ${config.display.id_prefix}`);\n console.log(`${colors.dim('settings:')}`);\n console.log(` ${colors.dim('auto_sync:')} ${config.settings.auto_sync}`);\n });\n }\n}\n\n// Set config value\nclass ConfigSetHandler extends BaseCommand {\n async run(key: string, value: string): Promise<void> {\n const tbdRoot = await requireInit();\n\n let config: Config;\n try {\n config = await readConfig(tbdRoot);\n } catch {\n throw new NotInitializedError('No configuration found. Run `tbd init` first.');\n }\n\n if (this.checkDryRun('Would set config', { key, value })) {\n return;\n }\n\n // Parse the key path and set value\n const keys = key.split('.');\n const parsedValue = this.parseValue(value);\n\n try {\n this.setNestedValue(config, keys, parsedValue);\n } catch {\n throw new ValidationError(`Invalid key: ${key}`);\n }\n\n await this.execute(async () => {\n await writeConfig(tbdRoot, config);\n }, 'Failed to write config');\n\n this.output.success(`Set ${key} = ${value}`);\n }\n\n private parseValue(value: string): unknown {\n // Parse boolean\n if (value === 'true') return true;\n if (value === 'false') return false;\n // Parse number\n const num = Number(value);\n if (!isNaN(num)) return num;\n // Return as string\n return value;\n }\n\n private setNestedValue(obj: Record<string, unknown>, keys: string[], value: unknown): void {\n let current = obj;\n for (let i = 0; i < keys.length - 1; i++) {\n const key = keys[i]!;\n if (typeof current[key] !== 'object' || current[key] === null) {\n throw new Error(`Invalid path: ${keys.slice(0, i + 1).join('.')}`);\n }\n current = current[key] as Record<string, unknown>;\n }\n const lastKey = keys[keys.length - 1]!;\n if (!(lastKey in current)) {\n throw new Error(`Unknown key: ${keys.join('.')}`);\n }\n current[lastKey] = value;\n }\n}\n\n// Get config value\nclass ConfigGetHandler extends BaseCommand {\n async run(key: string): Promise<void> {\n const tbdRoot = await requireInit();\n\n let config: Config;\n try {\n config = await readConfig(tbdRoot);\n } catch {\n throw new NotInitializedError('No configuration found. Run `tbd init` first.');\n }\n\n const keys = key.split('.');\n let value: unknown = config;\n\n for (const k of keys) {\n if (typeof value !== 'object' || value === null || !(k in value)) {\n throw new ValidationError(`Unknown key: ${key}`);\n }\n value = (value as Record<string, unknown>)[k];\n }\n\n this.output.data({ key, value }, () => {\n console.log(String(value));\n });\n }\n}\n\nconst showConfigCommand = new Command('show')\n .description('Show all configuration')\n .action(async (_options, command) => {\n const handler = new ConfigShowHandler(command);\n await handler.run();\n });\n\nconst setConfigCommand = new Command('set')\n .description('Set a configuration value')\n .argument('<key>', 'Configuration key (e.g., sync.branch)')\n .argument('<value>', 'Value to set')\n .action(async (key, value, _options, command) => {\n const handler = new ConfigSetHandler(command);\n await handler.run(key, value);\n });\n\nconst getConfigCommand = new Command('get')\n .description('Get a configuration value')\n .argument('<key>', 'Configuration key')\n .action(async (key, _options, command) => {\n const handler = new ConfigGetHandler(command);\n await handler.run(key);\n });\n\nexport const configCommand = new Command('config')\n .description('Manage configuration')\n .addCommand(showConfigCommand)\n .addCommand(setConfigCommand)\n .addCommand(getConfigCommand);\n","/**\n * `tbd attic` - Attic (conflict archive) commands.\n *\n * See: tbd-design.md §4.11 Attic Commands\n */\n\nimport { Command } from 'commander';\nimport { readdir, readFile, mkdir } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nimport { parseYamlWithConflictDetection, sortKeys, stringifyYaml } from '../../utils/yaml-utils.js';\n\nimport { writeFile } from 'atomically';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { requireInit, NotFoundError, ValidationError } from '../lib/errors.js';\nimport { readIssue, writeIssue } from '../../file/storage.js';\nimport { normalizeIssueId, formatDisplayId, formatDebugId } from '../../lib/ids.js';\nimport { resolveDataSyncDir, resolveAtticDir } from '../../lib/paths.js';\nimport { formatTimestampAgo } from '../../lib/format-utils.js';\nimport { now } from '../../utils/time-utils.js';\nimport { loadIdMapping } from '../../file/id-mapping.js';\nimport { readConfig } from '../../file/config.js';\nimport type { AtticEntry } from '../../lib/types.js';\nimport { AtticEntrySchema, ATTIC_ENTRY_FIELD_ORDER } from '../../lib/schemas.js';\n\n/**\n * Get attic entry filename from components.\n */\nfunction getAtticFilename(entityId: string, timestamp: string, field: string): string {\n // Convert timestamp colons to hyphens for filesystem safety\n const safeTimestamp = timestamp.replace(/:/g, '-');\n return `${entityId}_${safeTimestamp}_${field}.yml`;\n}\n\n/**\n * Parse attic entry filename to components.\n */\nfunction parseAtticFilename(\n filename: string,\n): { entityId: string; timestamp: string; field: string } | null {\n // Format: is-abc123_2025-01-07T10-30-00Z_description.yml\n const match = /^(is-[a-f0-9]+)_(.+)_([^_]+)\\.yml$/.exec(filename);\n if (!match) return null;\n const [, entityId, timestamp, field] = match;\n // Convert hyphens back to colons in timestamp\n const isoTimestamp = timestamp!.replace(/T(\\d{2})-(\\d{2})-(\\d{2})/, 'T$1:$2:$3');\n return { entityId: entityId!, timestamp: isoTimestamp, field: field! };\n}\n\n/**\n * List all attic entries.\n */\nasync function listAtticEntries(tbdRoot: string, filterById?: string): Promise<AtticEntry[]> {\n const atticPath = await resolveAtticDir(tbdRoot);\n let files: string[];\n\n try {\n files = await readdir(atticPath);\n } catch {\n // Attic directory doesn't exist - return empty\n return [];\n }\n\n const entries: AtticEntry[] = [];\n\n for (const file of files) {\n if (!file.endsWith('.yml')) continue;\n\n const parsed = parseAtticFilename(file);\n if (!parsed) continue;\n\n // Filter by ID if specified\n if (filterById && parsed.entityId !== filterById) continue;\n\n try {\n const filePath = join(atticPath, file);\n const content = await readFile(filePath, 'utf-8');\n const rawData = parseYamlWithConflictDetection<unknown>(content, filePath);\n const entry = AtticEntrySchema.parse(rawData);\n entries.push(entry);\n } catch {\n // Skip invalid files (including those with merge conflicts or schema errors)\n }\n }\n\n // Sort by timestamp descending (most recent first)\n entries.sort((a, b) => b.timestamp.localeCompare(a.timestamp));\n\n return entries;\n}\n\n/**\n * Save an attic entry.\n */\nexport async function saveAtticEntry(tbdRoot: string, entry: AtticEntry): Promise<void> {\n const atticPath = await resolveAtticDir(tbdRoot);\n await mkdir(atticPath, { recursive: true });\n\n const filename = getAtticFilename(entry.entity_id, entry.timestamp, entry.field);\n const filepath = join(atticPath, filename);\n // Sort keys using canonical field order, then serialize\n const sorted = sortKeys(entry as unknown as Record<string, unknown>, ATTIC_ENTRY_FIELD_ORDER);\n const content = stringifyYaml(sorted, { sortMapEntries: false });\n\n await writeFile(filepath, content);\n}\n\n// List attic entries\nclass AtticListHandler extends BaseCommand {\n async run(id?: string): Promise<void> {\n const tbdRoot = await requireInit();\n\n const filterId = id ? normalizeIssueId(id) : undefined;\n const entries = await listAtticEntries(tbdRoot, filterId);\n\n // Load ID mapping and config for display\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n const mapping = await loadIdMapping(dataSyncDir);\n const config = await readConfig(tbdRoot);\n const prefix = config.display.id_prefix;\n const showDebug = this.ctx.debug;\n\n const output = entries.map((e) => ({\n id: showDebug\n ? formatDebugId(e.entity_id, mapping, prefix)\n : formatDisplayId(e.entity_id, mapping, prefix),\n timestamp: e.timestamp,\n field: e.field,\n winner: e.winner_source,\n }));\n\n this.output.data(output, () => {\n const colors = this.output.getColors();\n if (output.length === 0) {\n console.log('No attic entries');\n return;\n }\n console.log(\n `${colors.dim('ID'.padEnd(12))}${colors.dim('WHEN'.padEnd(14))}${colors.dim('FIELD'.padEnd(14))}${colors.dim('WINNER')}`,\n );\n for (const entry of output) {\n const when = formatTimestampAgo(entry.timestamp) ?? entry.timestamp;\n console.log(\n `${colors.id(entry.id.padEnd(12))}${when.padEnd(14)}${entry.field.padEnd(14)}${entry.winner}`,\n );\n }\n });\n }\n}\n\n// Show attic entry\nclass AtticShowHandler extends BaseCommand {\n async run(id: string, timestamp: string): Promise<void> {\n const tbdRoot = await requireInit();\n\n const normalizedId = normalizeIssueId(id);\n const entries = await listAtticEntries(tbdRoot, normalizedId);\n\n // Find entry matching timestamp (approximate match for different formats)\n const entry = entries.find(\n (e) => e.timestamp === timestamp || e.timestamp.replace(/:/g, '-') === timestamp,\n );\n\n if (!entry) {\n throw new NotFoundError('Attic entry', `${id} at ${timestamp}`);\n }\n\n // Load ID mapping and config for display\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n const mapping = await loadIdMapping(dataSyncDir);\n const config = await readConfig(tbdRoot);\n const prefix = config.display.id_prefix;\n const showDebug = this.ctx.debug;\n const displayId = showDebug\n ? formatDebugId(entry.entity_id, mapping, prefix)\n : formatDisplayId(entry.entity_id, mapping, prefix);\n\n this.output.data(entry, () => {\n const colors = this.output.getColors();\n console.log(`${colors.bold('Entity:')} ${displayId}`);\n console.log(`${colors.bold('Timestamp:')} ${entry.timestamp}`);\n console.log(`${colors.bold('Field:')} ${entry.field}`);\n console.log(`${colors.bold('Winner:')} ${entry.winner_source}`);\n console.log(`${colors.bold('Loser:')} ${entry.loser_source}`);\n console.log('');\n console.log(`${colors.bold('Lost value:')}`);\n console.log(entry.lost_value);\n console.log('');\n console.log(`${colors.bold('Context:')}`);\n console.log(` Local version: ${entry.context.local_version}`);\n console.log(` Remote version: ${entry.context.remote_version}`);\n const localAgo = formatTimestampAgo(entry.context.local_updated_at);\n const remoteAgo = formatTimestampAgo(entry.context.remote_updated_at);\n console.log(` Local updated: ${localAgo ?? entry.context.local_updated_at}`);\n console.log(` Remote updated: ${remoteAgo ?? entry.context.remote_updated_at}`);\n });\n }\n}\n\n// Restore from attic\nclass AtticRestoreHandler extends BaseCommand {\n async run(id: string, timestamp: string): Promise<void> {\n const tbdRoot = await requireInit();\n\n const normalizedId = normalizeIssueId(id);\n const entries = await listAtticEntries(tbdRoot, normalizedId);\n\n // Find entry matching timestamp\n const entry = entries.find(\n (e) => e.timestamp === timestamp || e.timestamp.replace(/:/g, '-') === timestamp,\n );\n\n if (!entry) {\n throw new NotFoundError('Attic entry', `${id} at ${timestamp}`);\n }\n\n if (this.checkDryRun('Would restore from attic', { id: normalizedId, field: entry.field })) {\n return;\n }\n\n // Load the current issue\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n let issue;\n try {\n issue = await readIssue(dataSyncDir, normalizedId);\n } catch {\n throw new NotFoundError('Issue', id);\n }\n\n // Restore the field value\n const field = entry.field as keyof typeof issue;\n if (field === 'description' || field === 'notes' || field === 'title') {\n (issue as Record<string, unknown>)[field] = entry.lost_value;\n } else {\n throw new ValidationError(`Cannot restore field: ${entry.field}`);\n }\n\n issue.version += 1;\n issue.updated_at = now();\n\n await this.execute(async () => {\n await writeIssue(dataSyncDir, issue);\n }, 'Failed to restore from attic');\n\n // Load ID mapping and config for display\n const mapping = await loadIdMapping(dataSyncDir);\n const config = await readConfig(tbdRoot);\n const prefix = config.display.id_prefix;\n const showDebug = this.ctx.debug;\n const displayId = showDebug\n ? formatDebugId(normalizedId, mapping, prefix)\n : formatDisplayId(normalizedId, mapping, prefix);\n\n this.output.success(`Restored ${entry.field} for ${displayId} from attic entry ${timestamp}`);\n }\n}\n\ninterface AtticListOptions {\n since?: string;\n limit?: string;\n}\n\nconst listAtticCommand = new Command('list')\n .description('List attic entries')\n .argument('[id]', 'Filter by issue ID')\n .option('--since <date>', 'Entries since date')\n .option('--limit <n>', 'Limit results')\n .action(async (id, options: AtticListOptions, command) => {\n const handler = new AtticListHandler(command);\n await handler.run(id);\n });\n\nconst showAtticCommand = new Command('show')\n .description('Show attic entry details')\n .argument('<id>', 'Issue ID')\n .argument('<timestamp>', 'Entry timestamp')\n .action(async (id, timestamp, _options, command) => {\n const handler = new AtticShowHandler(command);\n await handler.run(id, timestamp);\n });\n\nconst restoreAtticCommand = new Command('restore')\n .description('Restore lost value from attic')\n .argument('<id>', 'Issue ID')\n .argument('<timestamp>', 'Entry timestamp')\n .action(async (id, timestamp, _options, command) => {\n const handler = new AtticRestoreHandler(command);\n await handler.run(id, timestamp);\n });\n\nexport const atticCommand = new Command('attic')\n .description('Manage conflict archive (attic)')\n .addCommand(listAtticCommand)\n .addCommand(showAtticCommand)\n .addCommand(restoreAtticCommand);\n","/**\n * `tbd import` - Import from Beads or other sources.\n *\n * See: tbd-design.md §5.1 Import Strategy\n */\n\nimport { Command } from 'commander';\nimport { readFile, access } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { requireInit, ValidationError, NotFoundError } from '../lib/errors.js';\nimport { writeIssue, listIssues } from '../../file/storage.js';\nimport {\n generateInternalId,\n extractShortId,\n extractUlidFromInternalId,\n makeInternalId,\n extractPrefix,\n} from '../../lib/ids.js';\nimport {\n loadIdMapping,\n saveIdMapping,\n addIdMapping,\n hasShortId,\n generateUniqueShortId,\n} from '../../file/id-mapping.js';\nimport { IssueStatus, IssueKind } from '../../lib/schemas.js';\nimport type { Issue, IssueStatusType, IssueKindType, DependencyType } from '../../lib/types.js';\nimport { resolveDataSyncDir } from '../../lib/paths.js';\nimport { now, normalizeTimestamp } from '../../utils/time-utils.js';\nimport { readConfig, writeConfig } from '../../file/config.js';\nimport {\n importFromWorkspace,\n type ImportOptions as WorkspaceImportOptions,\n} from '../../file/workspace.js';\n\ninterface ImportOptions {\n beadsDir?: string;\n merge?: boolean;\n verbose?: boolean;\n validate?: boolean;\n // Workspace import options\n workspace?: string;\n dir?: string;\n outbox?: boolean;\n clearOnSuccess?: boolean;\n}\n\ninterface ValidationIssue {\n beadsId: string;\n tbdId?: string;\n issue: string;\n severity: 'error' | 'warning';\n}\n\n/**\n * Beads issue structure (from JSONL export).\n */\ninterface BeadsIssue {\n id: string;\n title: string;\n description?: string;\n notes?: string;\n type?: string;\n issue_type?: string;\n status: string;\n priority?: number | string;\n assignee?: string;\n labels?: string[];\n dependencies?: { type: string; target: string }[];\n created_at: string;\n updated_at: string;\n closed_at?: string;\n close_reason?: string;\n due?: string;\n defer?: string;\n parent?: string;\n}\n\n/**\n * BeadsTotbd mapping: maps beads external ID to tbd internal ID.\n * This is a local structure used during import processing.\n */\ntype BeadsTotbdMapping = Record<string, string>;\n\n/**\n * Map Beads status to tbd status.\n */\nfunction mapStatus(beadsStatus: string): IssueStatusType {\n const statusMap: Record<string, IssueStatusType> = {\n open: 'open',\n in_progress: 'in_progress',\n blocked: 'blocked',\n deferred: 'deferred',\n done: 'closed', // Beads uses 'done' for completed items\n closed: 'closed',\n tombstone: 'closed',\n };\n const result = IssueStatus.safeParse(statusMap[beadsStatus] ?? beadsStatus);\n return result.success ? result.data : 'open';\n}\n\n/**\n * Map Beads issue type to tbd kind.\n */\nfunction mapKind(beadsType?: string): IssueKindType {\n const kindMap: Record<string, IssueKindType> = {\n bug: 'bug',\n feature: 'feature',\n task: 'task',\n epic: 'epic',\n chore: 'chore',\n };\n if (!beadsType) return 'task';\n const result = IssueKind.safeParse(kindMap[beadsType] ?? beadsType);\n return result.success ? result.data : 'task';\n}\n\n/**\n * Map Beads priority to tbd priority (0-4 integer).\n * Handles numeric values, \"P0\"-\"P4\" strings, and fallback to default P2.\n */\nfunction mapPriority(priority: unknown): number {\n if (\n typeof priority === 'number' &&\n Number.isInteger(priority) &&\n priority >= 0 &&\n priority <= 4\n ) {\n return priority;\n }\n if (typeof priority === 'string') {\n const match = /^[Pp]?(\\d)$/.exec(priority.trim());\n if (match) {\n const num = parseInt(match[1]!, 10);\n if (num >= 0 && num <= 4) return num;\n }\n }\n return 2; // Default P2\n}\n\n/**\n * Convert Beads issue to tbd issue.\n */\nfunction convertIssue(beads: BeadsIssue, tbdId: string, depMapping: BeadsTotbdMapping): Issue {\n // Convert dependencies, translating IDs\n const dependencies: DependencyType[] = [];\n if (beads.dependencies) {\n for (const dep of beads.dependencies) {\n if (dep.type === 'blocks' || dep.type === 'blocked_by') {\n const targetId = depMapping[dep.target];\n if (targetId) {\n // \"blocked_by\" in Beads means the target blocks this issue\n // In tbd, we only have \"blocks\", so we need to handle this carefully\n // For now, we store \"blocks\" dependencies directly\n if (dep.type === 'blocks') {\n dependencies.push({ type: 'blocks', target: targetId });\n }\n // Note: blocked_by would need to be added to the target issue's dependencies\n }\n }\n }\n }\n\n return {\n type: 'is',\n id: tbdId,\n version: 1,\n kind: mapKind(beads.type ?? beads.issue_type),\n title: beads.title,\n description: beads.description,\n notes: beads.notes,\n status: mapStatus(beads.status),\n priority: mapPriority(beads.priority),\n assignee: beads.assignee,\n labels: beads.labels ?? [],\n dependencies,\n created_at: normalizeTimestamp(beads.created_at) ?? now(),\n updated_at: normalizeTimestamp(beads.updated_at) ?? now(),\n closed_at: normalizeTimestamp(beads.closed_at),\n close_reason: beads.close_reason ?? null,\n due_date: normalizeTimestamp(beads.due),\n deferred_until: normalizeTimestamp(beads.defer),\n parent_id: beads.parent ? depMapping[beads.parent] : null,\n extensions: {\n beads: {\n original_id: beads.id,\n imported_at: now(),\n },\n },\n };\n}\n\nclass ImportHandler extends BaseCommand {\n private dataSyncDir = '';\n private tbdRoot = '';\n\n async run(file: string | undefined, options: ImportOptions): Promise<void> {\n // Check if this is a workspace import\n const isWorkspaceImport =\n options.workspace != null || options.dir != null || options.outbox === true;\n\n if (isWorkspaceImport) {\n await this.importFromWorkspaceCmd(options);\n return;\n }\n\n // Validate input first\n if (!file && !options.validate) {\n throw new ValidationError(\n 'Provide a JSONL file path to import.\\n\\n' +\n 'For Beads migration, use: tbd setup --from-beads\\n' +\n 'For workspace import, use: tbd import --workspace=<name> or --outbox',\n );\n }\n\n // Handle validation mode - requires init\n if (options.validate) {\n this.tbdRoot = await requireInit();\n this.dataSyncDir = await resolveDataSyncDir(this.tbdRoot);\n await this.validateImport(options);\n return;\n }\n\n // File import requires initialization\n if (file) {\n this.tbdRoot = await requireInit();\n this.dataSyncDir = await resolveDataSyncDir(this.tbdRoot);\n await this.importFromFile(file, options);\n }\n }\n\n /**\n * Import issues from a workspace.\n */\n private async importFromWorkspaceCmd(options: ImportOptions): Promise<void> {\n this.tbdRoot = await requireInit();\n this.dataSyncDir = await resolveDataSyncDir(this.tbdRoot);\n\n const wsOptions: WorkspaceImportOptions = {\n workspace: options.workspace,\n dir: options.dir,\n outbox: options.outbox,\n clearOnSuccess: options.clearOnSuccess,\n };\n\n if (this.checkDryRun('Would import from workspace', wsOptions)) {\n return;\n }\n\n const spinner = this.output.spinner('Importing from workspace...');\n wsOptions.logger = this.output.logger(spinner);\n\n const result = await this.execute(async () => {\n return await importFromWorkspace(this.tbdRoot, this.dataSyncDir, wsOptions);\n }, 'Failed to import from workspace');\n\n spinner.stop();\n\n if (!result) {\n return;\n }\n\n // Format output\n const sourceName = options.outbox ? 'outbox' : (options.workspace ?? options.dir ?? 'unknown');\n\n this.output.data(\n {\n imported: result.imported,\n conflicts: result.conflicts,\n source: sourceName,\n cleared: result.cleared,\n },\n () => {\n if (result.imported === 0) {\n this.output.info('No issues to import');\n } else {\n this.output.success(`Imported ${result.imported} issue(s) from ${sourceName}`);\n if (result.conflicts > 0) {\n this.output.warn(`${result.conflicts} conflict(s) moved to attic`);\n }\n if (result.cleared) {\n this.output.info(`Workspace \"${sourceName}\" cleared`);\n }\n // Suggest next step\n this.output.info('Run `tbd sync` to commit and push imported issues');\n }\n },\n );\n }\n\n /**\n * Validate import by comparing Beads source with imported tbd issues.\n * Reports any discrepancies or missing issues.\n */\n private async validateImport(options: ImportOptions): Promise<void> {\n const beadsDir = options.beadsDir ?? '.beads';\n const jsonlPath = join(beadsDir, 'issues.jsonl');\n\n try {\n await access(jsonlPath);\n } catch {\n throw new NotFoundError('Beads database', `${beadsDir} (use --beads-dir to specify)`);\n }\n\n console.log('Validating import...\\n');\n\n // Load Beads issues\n const content = await readFile(jsonlPath, 'utf-8');\n const lines = content\n .trim()\n .split('\\n')\n .filter((l) => l);\n const beadsIssues: BeadsIssue[] = [];\n\n for (const line of lines) {\n try {\n const issue = JSON.parse(line) as BeadsIssue;\n if (issue.id && issue.title) {\n beadsIssues.push(issue);\n }\n } catch {\n // Skip invalid lines\n }\n }\n\n // Load tbd issues and short ID mapping\n const tbdIssues = await this.loadExistingIssues();\n const shortIdMapping = await loadIdMapping(this.dataSyncDir);\n\n // Build mapping from beads ID to tbd internal ID using preserved short IDs\n // e.g., \"tbd-100\" -> extract \"100\" -> lookup in shortIdMapping -> \"is-{ulid}\"\n const beadsTotbd: BeadsTotbdMapping = {};\n const reverseMapping: Record<string, string> = {};\n\n for (const beads of beadsIssues) {\n const shortId = extractShortId(beads.id);\n const ulid = shortIdMapping.shortToUlid.get(shortId);\n if (ulid) {\n const internalId = makeInternalId(ulid);\n beadsTotbd[beads.id] = internalId;\n reverseMapping[internalId] = beads.id;\n }\n }\n\n // Build lookup by tbd ID\n const tbdById = new Map<string, Issue>();\n for (const issue of tbdIssues) {\n tbdById.set(issue.id, issue);\n }\n\n // Validate each Beads issue\n const issues: ValidationIssue[] = [];\n let validCount = 0;\n\n for (const beads of beadsIssues) {\n const tbdId = beadsTotbd[beads.id];\n\n if (!tbdId) {\n issues.push({\n beadsId: beads.id,\n issue: 'Not imported - no ID mapping exists',\n severity: 'error',\n });\n continue;\n }\n\n const tbdIssue = tbdById.get(tbdId);\n if (!tbdIssue) {\n issues.push({\n beadsId: beads.id,\n tbdId,\n issue: 'ID mapping exists but issue file not found',\n severity: 'error',\n });\n continue;\n }\n\n // Validate fields\n const fieldIssues: string[] = [];\n\n if (tbdIssue.title !== beads.title) {\n fieldIssues.push(`title mismatch: \"${tbdIssue.title}\" vs \"${beads.title}\"`);\n }\n\n const expectedStatus = mapStatus(beads.status);\n if (tbdIssue.status !== expectedStatus) {\n fieldIssues.push(`status mismatch: \"${tbdIssue.status}\" vs expected \"${expectedStatus}\"`);\n }\n\n const expectedKind = mapKind(beads.type ?? beads.issue_type);\n if (tbdIssue.kind !== expectedKind) {\n fieldIssues.push(`kind mismatch: \"${tbdIssue.kind}\" vs expected \"${expectedKind}\"`);\n }\n\n if (mapPriority(beads.priority) !== tbdIssue.priority) {\n fieldIssues.push(\n `priority mismatch: ${tbdIssue.priority} vs ${mapPriority(beads.priority)}`,\n );\n }\n\n // Check labels\n const beadsLabels = new Set(beads.labels ?? []);\n const tbdLabels = new Set(tbdIssue.labels ?? []);\n const missingLabels = [...beadsLabels].filter((l) => !tbdLabels.has(l));\n if (missingLabels.length > 0) {\n fieldIssues.push(`missing labels: ${missingLabels.join(', ')}`);\n }\n\n if (fieldIssues.length > 0) {\n issues.push({\n beadsId: beads.id,\n tbdId,\n issue: fieldIssues.join('; '),\n severity: 'warning',\n });\n } else {\n validCount++;\n }\n }\n\n // Check for orphaned tbd issues (not in Beads)\n const beadsIds = new Set(beadsIssues.map((b) => b.id));\n for (const tbdIssue of tbdIssues) {\n const beadsId = reverseMapping[tbdIssue.id];\n if (beadsId && !beadsIds.has(beadsId)) {\n issues.push({\n beadsId,\n tbdId: tbdIssue.id,\n issue: 'tbd issue has mapping but Beads issue no longer exists',\n severity: 'warning',\n });\n }\n }\n\n // Report results\n const errors = issues.filter((i) => i.severity === 'error');\n const warnings = issues.filter((i) => i.severity === 'warning');\n\n console.log('Validation Results');\n console.log('─'.repeat(60));\n console.log(`Total Beads issues: ${beadsIssues.length}`);\n console.log(`Total tbd issues: ${tbdIssues.length}`);\n console.log(`Valid imports: ${validCount}`);\n console.log(`Errors: ${errors.length}`);\n console.log(`Warnings: ${warnings.length}`);\n console.log('─'.repeat(60));\n\n if (errors.length > 0) {\n console.log('\\nErrors:');\n for (const err of errors) {\n console.log(` ✗ ${err.beadsId}: ${err.issue}`);\n }\n }\n\n if (warnings.length > 0 && options.verbose) {\n console.log('\\nWarnings:');\n for (const warn of warnings) {\n console.log(` ⚠ ${warn.beadsId}: ${warn.issue}`);\n }\n }\n\n console.log();\n if (errors.length === 0 && warnings.length === 0) {\n this.output.success('All imports validated successfully!');\n } else if (errors.length === 0) {\n this.output.warn(`Validation complete with ${warnings.length} warnings`);\n if (!options.verbose) {\n console.log(' Use --verbose to see warning details');\n }\n } else {\n this.output.error(`Validation failed with ${errors.length} errors`);\n }\n\n // Output JSON for programmatic use\n this.output.data({\n valid: validCount,\n errors: errors.length,\n warnings: warnings.length,\n total: beadsIssues.length,\n issues: options.verbose ? issues : undefined,\n });\n }\n\n private async importFromFile(filePath: string, options: ImportOptions): Promise<void> {\n // Check file exists\n try {\n await access(filePath);\n } catch {\n throw new NotFoundError('File', filePath);\n }\n\n if (this.checkDryRun('Would import issues', { file: filePath })) {\n // For dry run, still parse and show what would happen\n const content = await readFile(filePath, 'utf-8');\n const lines = content\n .trim()\n .split('\\n')\n .filter((l) => l);\n this.output.info(`Would import ${lines.length} issues from ${filePath}`);\n return;\n }\n\n // Load file content\n const content = await readFile(filePath, 'utf-8');\n const lines = content\n .trim()\n .split('\\n')\n .filter((l) => l);\n\n // Parse JSONL\n const beadsIssues: BeadsIssue[] = [];\n for (const line of lines) {\n try {\n const issue = JSON.parse(line) as BeadsIssue;\n if (issue.id && issue.title) {\n beadsIssues.push(issue);\n }\n } catch {\n if (options.verbose) {\n this.output.warn(`Skipping invalid JSON line`);\n }\n }\n }\n\n if (beadsIssues.length === 0) {\n this.output.info('No valid issues found in file');\n return;\n }\n\n // Auto-detect prefix from imported issues and update config if needed\n const detectedPrefix = this.detectPrefixFromIssues(beadsIssues);\n await this.updateConfigPrefixIfNeeded(detectedPrefix);\n\n // Load existing issues and short ID mapping\n const existingIssues = await this.loadExistingIssues();\n const shortIdMapping = await loadIdMapping(this.dataSyncDir);\n\n // Build lookup maps\n const existingByBeadsId = new Map<string, Issue>();\n const existingByShortId = new Map<string, Issue>();\n\n // Build reverse lookup from extensions and from short ID mapping\n for (const issue of existingIssues) {\n const beadsExt = issue.extensions?.beads as { original_id?: string } | undefined;\n if (beadsExt?.original_id) {\n existingByBeadsId.set(beadsExt.original_id, issue);\n }\n // Also track by short ID\n const ulid = extractUlidFromInternalId(issue.id);\n const shortId = shortIdMapping.ulidToShort.get(ulid);\n if (shortId) {\n existingByShortId.set(shortId, issue);\n }\n }\n\n // Build beads-to-tbd mapping, preserving original short IDs\n // e.g., \"tbd-100\" preserves \"100\" as the short ID\n const beadsTotbd: BeadsTotbdMapping = {};\n\n // First pass: assign IDs to all issues (needed for dependency translation)\n for (const beads of beadsIssues) {\n // Extract the short ID from beads ID (e.g., \"tbd-100\" -> \"100\")\n const shortId = extractShortId(beads.id);\n\n // Check if we already have this issue by beads ID (from previous import)\n const existingByBeads = existingByBeadsId.get(beads.id);\n if (existingByBeads) {\n beadsTotbd[beads.id] = existingByBeads.id;\n continue;\n }\n\n // Check if we already have a mapping for this short ID\n const existingByShort = existingByShortId.get(shortId);\n if (existingByShort) {\n beadsTotbd[beads.id] = existingByShort.id;\n continue;\n }\n\n // Check if the short ID is already in the mapping (collision check)\n if (hasShortId(shortIdMapping, shortId)) {\n // Short ID already exists but for a different issue - generate a new one\n if (options.verbose) {\n this.output.warn(\n `Short ID \"${shortId}\" already exists, generating new ID for ${beads.id}`,\n );\n }\n const internalId = generateInternalId();\n beadsTotbd[beads.id] = internalId;\n // Generate a random short ID since the original is taken\n const ulid = extractUlidFromInternalId(internalId);\n const newShortId = generateUniqueShortId(shortIdMapping);\n addIdMapping(shortIdMapping, ulid, newShortId);\n } else {\n // Create new mapping, preserving the original short ID\n const internalId = generateInternalId();\n beadsTotbd[beads.id] = internalId;\n const ulid = extractUlidFromInternalId(internalId);\n addIdMapping(shortIdMapping, ulid, shortId);\n }\n }\n\n // Second pass: convert and save issues\n let imported = 0;\n let skipped = 0;\n let merged = 0;\n\n for (const beads of beadsIssues) {\n const tbdId = beadsTotbd[beads.id]!;\n const existing = existingByBeadsId.get(beads.id);\n\n if (existing && !options.merge) {\n // Check if Beads is newer\n if (new Date(beads.updated_at) <= new Date(existing.updated_at)) {\n skipped++;\n continue;\n }\n }\n\n const issue = convertIssue(beads, tbdId, beadsTotbd);\n\n if (existing) {\n // Merge: keep higher version, update fields\n issue.version = existing.version + 1;\n merged++;\n } else {\n imported++;\n }\n\n try {\n await writeIssue(this.dataSyncDir, issue);\n } catch (error) {\n if (options.verbose) {\n this.output.warn(`Failed to write issue ${beads.id}: ${(error as Error).message}`);\n }\n }\n }\n\n // Save updated short ID mapping (no separate beads.yml needed - IDs are preserved)\n await saveIdMapping(this.dataSyncDir, shortIdMapping);\n\n const result = { imported, skipped, merged, total: beadsIssues.length };\n\n this.output.data(result, () => {\n this.output.success(`Import complete from ${filePath}`);\n console.log(` New issues: ${imported}`);\n console.log(` Merged: ${merged}`);\n console.log(` Skipped: ${skipped}`);\n });\n }\n\n private async loadExistingIssues(): Promise<Issue[]> {\n try {\n return await listIssues(this.dataSyncDir);\n } catch {\n return [];\n }\n }\n\n /**\n * Detect the prefix used by beads issues from a file path.\n * Reads the first few issues and extracts the common prefix pattern.\n * Falls back to 'tbd' if no consistent prefix is found.\n */\n private async detectBeadsPrefix(jsonlPath: string): Promise<string> {\n try {\n const content = await readFile(jsonlPath, 'utf-8');\n const lines = content\n .trim()\n .split('\\n')\n .filter((l) => l)\n .slice(0, 10); // Sample first 10 issues\n\n const issues: BeadsIssue[] = [];\n for (const line of lines) {\n try {\n const issue = JSON.parse(line) as BeadsIssue;\n if (issue.id) {\n issues.push(issue);\n }\n } catch {\n // Skip invalid lines\n }\n }\n\n return this.detectPrefixFromIssues(issues);\n } catch {\n return 'tbd'; // Default fallback\n }\n }\n\n /**\n * Detect the prefix used by a list of beads issues.\n * Extracts the common prefix pattern from issue IDs.\n * Falls back to 'tbd' if no consistent prefix is found.\n */\n private detectPrefixFromIssues(issues: BeadsIssue[]): string {\n const prefixes = new Map<string, number>();\n\n for (const issue of issues.slice(0, 10)) {\n // Sample first 10\n if (issue.id) {\n const prefix = extractPrefix(issue.id);\n if (prefix) {\n prefixes.set(prefix, (prefixes.get(prefix) ?? 0) + 1);\n }\n }\n }\n\n // Find the most common prefix\n let maxCount = 0;\n let mostCommonPrefix = 'tbd';\n for (const [prefix, count] of prefixes) {\n if (count > maxCount) {\n maxCount = count;\n mostCommonPrefix = prefix;\n }\n }\n\n return mostCommonPrefix;\n }\n\n /**\n * Update config prefix if it differs from the detected prefix.\n * Returns true if prefix was updated.\n */\n private async updateConfigPrefixIfNeeded(detectedPrefix: string): Promise<boolean> {\n try {\n const config = await readConfig(this.tbdRoot);\n if (config.display.id_prefix !== detectedPrefix) {\n const oldPrefix = config.display.id_prefix;\n config.display.id_prefix = detectedPrefix;\n await writeConfig(this.tbdRoot, config);\n this.output.info(`Updated ID prefix: ${oldPrefix} → ${detectedPrefix}`);\n return true;\n }\n return false;\n } catch {\n // Config doesn't exist or can't be read - skip update\n return false;\n }\n }\n}\n\nexport const importCommand = new Command('import')\n .description(\n 'Import issues from JSONL file or workspace.\\n' +\n 'For Beads migration, use: tbd setup --from-beads\\n' +\n 'For workspace import, use: tbd import --workspace=<name> or --outbox',\n )\n .argument('[file]', 'JSONL file to import')\n .option('--beads-dir <path>', 'Beads data directory (for --validate)')\n .option('--merge', 'Merge with existing issues instead of skipping duplicates')\n .option('--verbose', 'Show detailed import progress')\n .option('--validate', 'Validate existing import against Beads source')\n // Workspace import options\n .option('--workspace <name>', 'Import from named workspace under .tbd/workspaces/')\n .option('--dir <path>', 'Import from arbitrary directory')\n .option('--outbox', 'Shortcut for --workspace=outbox --clear-on-success')\n .option('--clear-on-success', 'Delete workspace after successful import')\n .action(async (file, options, command) => {\n const handler = new ImportHandler(command);\n await handler.run(file, options);\n });\n","/**\n * `tbd docs` - Display CLI documentation.\n *\n * Shows the bundled documentation for tbd CLI.\n * Documentation can be filtered by section.\n *\n * Note: Doc cache sync functionality has moved to `tbd sync --docs`.\n * See: docs/project/specs/active/plan-2026-01-29-unified-sync-command.md\n */\n\nimport { Command } from 'commander';\nimport { readFile } from 'node:fs/promises';\nimport { fileURLToPath } from 'node:url';\nimport { dirname, join } from 'node:path';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { shouldUseInteractiveOutput } from '../lib/context.js';\nimport { CLIError, NotFoundError } from '../lib/errors.js';\nimport { renderMarkdown, paginateOutput } from '../lib/output.js';\nimport type { DocSection } from '../../lib/types.js';\nimport GithubSlugger from 'github-slugger';\n\n/**\n * Get the path to the bundled docs file.\n * The docs file is copied to dist/docs/ during build.\n */\nfunction getDocsPath(): string {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n // When bundled, runs from dist/bin.mjs or dist/cli.mjs\n // Docs are at dist/docs/tbd-docs.md (same level as the bundle)\n return join(__dirname, 'docs', 'tbd-docs.md');\n}\n\ninterface DocsOptions {\n section?: string;\n list?: boolean;\n all?: boolean;\n}\n\nclass DocsHandler extends BaseCommand {\n async run(topic: string | undefined, options: DocsOptions): Promise<void> {\n let content: string;\n try {\n content = await readFile(getDocsPath(), 'utf-8');\n } catch {\n // Fallback: try to read from source location during development\n try {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n // During development: src/cli/commands -> packages/tbd/docs\n const devPath = join(__dirname, '..', '..', '..', 'docs', 'tbd-docs.md');\n content = await readFile(devPath, 'utf-8');\n } catch {\n throw new CLIError('Documentation file not found. Please rebuild the CLI.');\n }\n }\n\n const sections = this.extractSections(content);\n\n // Show comprehensive documentation listing\n if (options.all) {\n await this.showComprehensiveListing();\n return;\n }\n\n // List available sections\n if (options.list) {\n this.output.data(sections, () => {\n const colors = this.output.getColors();\n console.log(colors.bold('Available documentation sections:'));\n console.log('');\n // Calculate max slug length for alignment\n const maxSlugLen = Math.max(...sections.map((s) => s.slug.length));\n for (const section of sections) {\n const paddedSlug = section.slug.padEnd(maxSlugLen);\n console.log(` ${colors.id(paddedSlug)} ${section.title}`);\n }\n console.log('');\n console.log(`Use ${colors.dim('tbd docs <topic>')} to view a specific section.`);\n });\n return;\n }\n\n // Determine which section to show (positional topic takes precedence)\n const sectionQuery = topic ?? options.section;\n\n // Filter by section if specified\n if (sectionQuery) {\n const sectionContent = this.extractSection(content, sections, sectionQuery);\n if (!sectionContent) {\n throw new NotFoundError(\n 'Section',\n `\"${sectionQuery}\" (use --list to see available sections)`,\n );\n }\n content = sectionContent;\n }\n\n // Output the documentation with Markdown colorization and pagination for interactive\n if (shouldUseInteractiveOutput(this.ctx)) {\n const rendered = renderMarkdown(content, this.ctx.color);\n await paginateOutput(rendered, true);\n } else {\n console.log(content);\n }\n }\n\n /**\n * Extract section metadata from the documentation.\n * Sections are top-level headers (## ).\n * Returns title and slugified ID for each section.\n */\n private extractSections(content: string): DocSection[] {\n const sections: DocSection[] = [];\n const lines = content.split('\\n');\n const slugger = new GithubSlugger();\n\n for (const line of lines) {\n if (line.startsWith('## ')) {\n const title = line.slice(3).trim();\n const slug = slugger.slug(title);\n sections.push({ title, slug });\n }\n }\n\n return sections;\n }\n\n /**\n * Extract a specific section from the documentation.\n * Matches by slug or partial title match.\n * Returns content from the section header to the next section header.\n */\n private extractSection(content: string, sections: DocSection[], query: string): string | null {\n const lowerQuery = query.toLowerCase();\n\n // Find matching section - first try exact slug match, then partial title match\n const matchedSection =\n sections.find((s) => s.slug === lowerQuery) ??\n sections.find((s) => s.title.toLowerCase().includes(lowerQuery));\n\n if (!matchedSection) {\n return null;\n }\n\n const lines = content.split('\\n');\n let inSection = false;\n const sectionLines: string[] = [];\n\n for (const line of lines) {\n if (line.startsWith('## ')) {\n if (inSection) {\n // End of our section\n break;\n }\n const currentTitle = line.slice(3).trim();\n if (currentTitle === matchedSection.title) {\n inSection = true;\n sectionLines.push(line);\n }\n } else if (inSection) {\n sectionLines.push(line);\n }\n }\n\n if (sectionLines.length === 0) {\n return null;\n }\n\n // Trim trailing empty lines\n while (sectionLines.length > 0) {\n const lastLine = sectionLines[sectionLines.length - 1];\n if (lastLine?.trim() === '') {\n sectionLines.pop();\n } else {\n break;\n }\n }\n\n return sectionLines.join('\\n');\n }\n\n /**\n * Show a comprehensive listing of all documentation resources organized by purpose.\n */\n private async showComprehensiveListing(): Promise<void> {\n const colors = this.output.getColors();\n\n console.log(colors.bold('=== tbd Documentation Resources ==='));\n console.log('');\n\n // Getting Started\n console.log(colors.bold('Getting Started:'));\n console.log(' tbd Full orientation and project status');\n console.log(' tbd prime Workflow context and guidance');\n console.log(' tbd prime --brief Quick reference (~35 lines)');\n console.log(' tbd --help CLI command reference');\n console.log('');\n\n // Workflows (Shortcuts)\n console.log(colors.bold('Workflows (Shortcuts):'));\n console.log(' tbd shortcut --list List all available shortcuts');\n console.log(' tbd shortcut new-plan-spec Plan a new feature');\n console.log(' tbd shortcut code-review-and-commit Commit code properly');\n console.log(' tbd shortcut create-or-update-pr-simple Create a pull request');\n console.log('');\n\n // Guidelines\n console.log(colors.bold('Guidelines (Coding Standards):'));\n console.log(' tbd guidelines --list List all available guidelines');\n console.log(' tbd guidelines typescript-rules TypeScript best practices');\n console.log(' tbd guidelines general-tdd-guidelines Test-driven development');\n console.log(' tbd guidelines golden-testing-guidelines Snapshot/golden testing');\n console.log('');\n\n // Templates\n console.log(colors.bold('Templates:'));\n console.log(' tbd template --list List all available templates');\n console.log(' tbd template plan-spec Feature planning template');\n console.log(' tbd template architecture-doc Architecture document template');\n console.log('');\n\n // Design & Reference\n console.log(colors.bold('Design & Reference:'));\n console.log(' tbd docs --list List documentation sections');\n console.log(' tbd design tbd design document');\n console.log(' tbd closing Session closing protocol');\n console.log('');\n\n // Quick Tips\n console.log(colors.bold('Quick Tips:'));\n console.log(' - Run tbd ready to see what issues are available to work on');\n console.log(' - Run tbd shortcut <name> to get step-by-step instructions');\n console.log(' - Run tbd guidelines <name> to get coding standards');\n console.log(' - Always run tbd sync at the end of a session');\n }\n}\n\nexport const docsCommand = new Command('docs')\n .description('Display CLI documentation (use tbd sync --docs for doc cache sync)')\n .argument('[topic]', 'Topic to display (e.g., \"commands\", \"id-system\")')\n .option('--section <name>', 'Show specific section (e.g., \"commands\", \"workflows\")')\n .option('--list', 'List available sections')\n .option('--all', 'Show comprehensive listing of all documentation resources')\n .action(async (topic: string | undefined, options: DocsOptions, command: Command) => {\n const handler = new DocsHandler(command);\n await handler.run(topic, options);\n });\n","/**\n * `tbd closing` - Display the session closing protocol reminder.\n *\n * Shows the close protocol checklist for completing work.\n * Used by the Claude Code PostToolUse hook after git push.\n */\n\nimport { Command } from 'commander';\nimport { readFile } from 'node:fs/promises';\nimport { fileURLToPath } from 'node:url';\nimport { dirname, join } from 'node:path';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { CLIError } from '../lib/errors.js';\nimport { renderMarkdown } from '../lib/output.js';\n\n/**\n * Get the path to the bundled closing file.\n * The file is copied to dist/docs/ during build.\n */\nfunction getCloseProtocolPath(): string {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n return join(__dirname, 'docs', 'tbd-closing.md');\n}\n\nclass CloseProtocolHandler extends BaseCommand {\n async run(): Promise<void> {\n let content: string;\n try {\n content = await readFile(getCloseProtocolPath(), 'utf-8');\n } catch {\n // Fallback: try to read from source location during development\n try {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n const devPath = join(__dirname, '..', '..', 'docs', 'tbd-closing.md');\n content = await readFile(devPath, 'utf-8');\n } catch {\n // Last fallback: repo-level docs\n try {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n const repoPath = join(__dirname, '..', '..', '..', 'docs', 'tbd-closing.md');\n content = await readFile(repoPath, 'utf-8');\n } catch {\n throw new CLIError('Close protocol file not found. Please rebuild the CLI.');\n }\n }\n }\n\n console.log(renderMarkdown(content, this.ctx.color));\n }\n}\n\nexport const closeProtocolCommand = new Command('closing')\n .description('Display the session closing protocol reminder')\n .action(async (_options: unknown, command: Command) => {\n const handler = new CloseProtocolHandler(command);\n await handler.run();\n });\n","/**\n * `tbd design` - Display design documentation.\n *\n * Shows the bundled design documentation for tbd,\n * including architecture, design decisions, and Beads comparison.\n */\n\nimport { Command } from 'commander';\nimport { readFile } from 'node:fs/promises';\nimport { fileURLToPath } from 'node:url';\nimport { dirname, join } from 'node:path';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { CLIError, NotFoundError } from '../lib/errors.js';\nimport { renderMarkdown } from '../lib/output.js';\nimport type { DocSection } from '../../lib/types.js';\nimport GithubSlugger from 'github-slugger';\n\n/**\n * Get the path to the bundled design doc file.\n * The design doc is copied to dist/docs/ during build.\n */\nfunction getDesignPath(): string {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n // When bundled, runs from dist/bin.mjs or dist/cli.mjs\n // Docs are at dist/docs/tbd-design.md (same level as the bundle)\n return join(__dirname, 'docs', 'tbd-design.md');\n}\n\ninterface DesignOptions {\n section?: string;\n list?: boolean;\n}\n\nclass DesignHandler extends BaseCommand {\n async run(topic: string | undefined, options: DesignOptions): Promise<void> {\n let content: string;\n try {\n content = await readFile(getDesignPath(), 'utf-8');\n } catch {\n // Fallback: try to read from source location during development\n try {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n // During development: src/cli/commands -> packages/tbd/docs\n const devPath = join(__dirname, '..', '..', '..', 'docs', 'tbd-design.md');\n content = await readFile(devPath, 'utf-8');\n } catch {\n throw new CLIError('Design documentation file not found. Please rebuild the CLI.');\n }\n }\n\n const sections = this.extractSections(content);\n\n // List available sections\n if (options.list) {\n this.output.data(sections, () => {\n const colors = this.output.getColors();\n console.log(colors.bold('Available design documentation sections:'));\n console.log('');\n // Calculate max slug length for alignment\n const maxSlugLen = Math.max(...sections.map((s) => s.slug.length));\n for (const section of sections) {\n const paddedSlug = section.slug.padEnd(maxSlugLen);\n console.log(` ${colors.id(paddedSlug)} ${section.title}`);\n }\n console.log('');\n console.log(`Use ${colors.dim('tbd design <topic>')} to view a specific section.`);\n });\n return;\n }\n\n // Determine which section to show (positional topic takes precedence)\n const sectionQuery = topic ?? options.section;\n\n // Filter by section if specified\n if (sectionQuery) {\n const sectionContent = this.extractSection(content, sections, sectionQuery);\n if (!sectionContent) {\n throw new NotFoundError(\n 'Section',\n `\"${sectionQuery}\" (use --list to see available sections)`,\n );\n }\n content = sectionContent;\n }\n\n // Output the documentation with Markdown colorization\n console.log(renderMarkdown(content, this.ctx.color));\n }\n\n /**\n * Extract section metadata from the documentation.\n * Sections are top-level headers (## ).\n * Returns title and slugified ID for each section.\n */\n private extractSections(content: string): DocSection[] {\n const sections: DocSection[] = [];\n const lines = content.split('\\n');\n const slugger = new GithubSlugger();\n\n for (const line of lines) {\n if (line.startsWith('## ')) {\n const title = line.slice(3).trim();\n const slug = slugger.slug(title);\n sections.push({ title, slug });\n }\n }\n\n return sections;\n }\n\n /**\n * Extract a specific section from the documentation.\n * Matches by slug or partial title match.\n * Returns content from the section header to the next section header.\n */\n private extractSection(content: string, sections: DocSection[], query: string): string | null {\n const lowerQuery = query.toLowerCase();\n\n // Find matching section - first try exact slug match, then partial title match\n const matchedSection =\n sections.find((s) => s.slug === lowerQuery) ??\n sections.find((s) => s.title.toLowerCase().includes(lowerQuery));\n\n if (!matchedSection) {\n return null;\n }\n\n const lines = content.split('\\n');\n let inSection = false;\n const sectionLines: string[] = [];\n\n for (const line of lines) {\n if (line.startsWith('## ')) {\n if (inSection) {\n // End of our section\n break;\n }\n const currentTitle = line.slice(3).trim();\n if (currentTitle === matchedSection.title) {\n inSection = true;\n sectionLines.push(line);\n }\n } else if (inSection) {\n sectionLines.push(line);\n }\n }\n\n if (sectionLines.length === 0) {\n return null;\n }\n\n // Trim trailing empty lines\n while (sectionLines.length > 0) {\n const lastLine = sectionLines[sectionLines.length - 1];\n if (lastLine?.trim() === '') {\n sectionLines.pop();\n } else {\n break;\n }\n }\n\n return sectionLines.join('\\n');\n }\n}\n\nexport const designCommand = new Command('design')\n .description('Display design documentation and Beads comparison')\n .argument('[topic]', 'Topic to display (e.g., \"architecture\", \"tbd-vs-beads\")')\n .option('--section <name>', 'Show specific section')\n .option('--list', 'List available sections')\n .action(async (topic: string | undefined, options: DesignOptions, command: Command) => {\n const handler = new DesignHandler(command);\n await handler.run(topic, options);\n });\n","/**\n * `tbd readme` - Display the README.\n *\n * Shows the bundled README (same as the GitHub landing page).\n */\n\nimport { Command } from 'commander';\nimport { readFile } from 'node:fs/promises';\nimport { fileURLToPath } from 'node:url';\nimport { dirname, join } from 'node:path';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { shouldUseInteractiveOutput } from '../lib/context.js';\nimport { CLIError } from '../lib/errors.js';\nimport { renderMarkdown, paginateOutput } from '../lib/output.js';\n\n/**\n * Get the path to the bundled README file.\n * The README is copied to dist/docs/ during build.\n */\nfunction getReadmePath(): string {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n // When bundled, runs from dist/bin.mjs or dist/cli.mjs\n // README is at dist/docs/README.md (same level as the bundle)\n return join(__dirname, 'docs', 'README.md');\n}\n\nclass ReadmeHandler extends BaseCommand {\n async run(): Promise<void> {\n let content: string;\n try {\n content = await readFile(getReadmePath(), 'utf-8');\n } catch {\n // Fallback: try to read from source location during development\n try {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n // During development without bundle: src/cli/commands -> repo root\n const devPath = join(__dirname, '..', '..', '..', '..', '..', 'README.md');\n content = await readFile(devPath, 'utf-8');\n } catch {\n // Last fallback: try package-level README\n try {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n // From packages/tbd/src/cli/commands -> packages/tbd/README.md\n const pkgPath = join(__dirname, '..', '..', '..', 'README.md');\n content = await readFile(pkgPath, 'utf-8');\n } catch {\n throw new CLIError('README file not found. Please rebuild the CLI.');\n }\n }\n }\n\n // Output the README with Markdown colorization and pagination for interactive\n if (shouldUseInteractiveOutput(this.ctx)) {\n const rendered = renderMarkdown(content, this.ctx.color);\n await paginateOutput(rendered, true);\n } else {\n console.log(content);\n }\n }\n}\n\nexport const readmeCommand = new Command('readme')\n .description('Display the README (same as GitHub landing page)')\n .action(async (_options: object, command: Command) => {\n const handler = new ReadmeHandler(command);\n await handler.run();\n });\n","/**\n * `tbd uninstall` - Remove tbd from a repository.\n *\n * Removes the .tbd directory, worktree, and optionally the sync branch.\n */\n\nimport { Command } from 'commander';\nimport { rm, access, readdir, stat } from 'node:fs/promises';\nimport { execSync } from 'node:child_process';\nimport { join, relative } from 'node:path';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { NotInitializedError, CLIError } from '../lib/errors.js';\nimport { findTbdRoot, readConfig } from '../../file/config.js';\nimport { SYNC_BRANCH } from '../../lib/paths.js';\n\ninterface UninstallOptions {\n confirm?: boolean;\n keepBranch?: boolean;\n removeRemote?: boolean;\n}\n\nclass UninstallHandler extends BaseCommand {\n async run(options: UninstallOptions): Promise<void> {\n const colors = this.output.getColors();\n\n // Resolve tbd root (walks up from cwd)\n const tbdRoot = await findTbdRoot(process.cwd());\n if (!tbdRoot) {\n throw new NotInitializedError('No .tbd directory found. Nothing to uninstall.');\n }\n\n // Read config to get branch info\n let config;\n try {\n config = await readConfig(tbdRoot);\n } catch {\n config = null;\n }\n\n const syncBranch = config?.sync.branch ?? SYNC_BRANCH;\n const remote = config?.sync.remote ?? 'origin';\n const tbdDir = join(tbdRoot, '.tbd');\n const worktreePath = join(tbdDir, 'data-sync-worktree');\n\n // Display paths relative to cwd for readability\n const displayPath = (p: string) => relative(process.cwd(), p) || p;\n\n // Check what exists\n const items: string[] = [];\n\n // Check worktree\n let worktreeExists = false;\n try {\n await access(worktreePath);\n worktreeExists = true;\n const worktreeStats = await this.getDirectoryStats(worktreePath);\n items.push(` - Worktree: ${displayPath(worktreePath)} (${worktreeStats.files} files)`);\n } catch {\n // Worktree doesn't exist\n }\n\n // Check local sync branch\n let localBranchExists = false;\n try {\n execSync(`git rev-parse --verify ${syncBranch}`, {\n encoding: 'utf-8',\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n localBranchExists = true;\n if (!options.keepBranch) {\n items.push(` - Local branch: ${syncBranch}`);\n }\n } catch {\n // Branch doesn't exist\n }\n\n // Check remote sync branch\n let remoteBranchExists = false;\n if (options.removeRemote) {\n try {\n execSync(`git rev-parse --verify ${remote}/${syncBranch}`, {\n encoding: 'utf-8',\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n remoteBranchExists = true;\n items.push(` - Remote branch: ${remote}/${syncBranch}`);\n } catch {\n // Remote branch doesn't exist\n }\n }\n\n // Count .tbd contents\n const tbdStats = await this.getDirectoryStats(tbdDir);\n items.push(` - Directory: ${displayPath(tbdDir)}/ (${tbdStats.files} files)`);\n\n // Show what will be removed\n console.log(colors.bold('The following will be removed:'));\n console.log('');\n for (const item of items) {\n console.log(colors.warn(item));\n }\n console.log('');\n\n if (!options.confirm) {\n console.log(`This action is ${colors.bold('irreversible')}.`);\n console.log('');\n console.log(`To confirm, run: ${colors.dim('tbd uninstall --confirm')}`);\n if (!options.keepBranch && localBranchExists) {\n console.log(\n `To keep the sync branch: ${colors.dim('tbd uninstall --confirm --keep-branch')}`,\n );\n }\n if (!options.removeRemote) {\n console.log(\n `To also remove from remote: ${colors.dim('tbd uninstall --confirm --remove-remote')}`,\n );\n }\n return;\n }\n\n // Check dry-run\n if (this.checkDryRun('Would remove tbd from repository', { items })) {\n return;\n }\n\n // Perform uninstall\n this.output.info('Uninstalling tbd...');\n\n // 1. Remove worktree first (git worktree remove)\n if (worktreeExists) {\n try {\n // First try to remove the worktree through git\n execSync(`git worktree remove --force \"${worktreePath}\"`, {\n encoding: 'utf-8',\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n console.log(` ${colors.success('✓')} Removed git worktree`);\n } catch {\n // If git worktree remove fails, force delete the directory\n try {\n await rm(worktreePath, { recursive: true, force: true });\n console.log(` ${colors.success('✓')} Removed worktree directory`);\n } catch {\n console.log(` ${colors.warn('⚠')} Could not remove worktree directory`);\n }\n }\n }\n\n // 2. Remove local sync branch\n if (localBranchExists && !options.keepBranch) {\n try {\n execSync(`git branch -D ${syncBranch}`, {\n encoding: 'utf-8',\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n console.log(` ${colors.success('✓')} Removed local branch: ${syncBranch}`);\n } catch {\n console.log(` ${colors.warn('⚠')} Could not remove local branch: ${syncBranch}`);\n }\n }\n\n // 3. Remove remote sync branch\n if (remoteBranchExists && options.removeRemote) {\n try {\n execSync(`git push ${remote} --delete ${syncBranch}`, {\n encoding: 'utf-8',\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n console.log(` ${colors.success('✓')} Removed remote branch: ${remote}/${syncBranch}`);\n } catch {\n console.log(\n ` ${colors.warn('⚠')} Could not remove remote branch: ${remote}/${syncBranch}`,\n );\n }\n }\n\n // 4. Clean up orphaned worktree references\n try {\n execSync('git worktree prune', {\n encoding: 'utf-8',\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n } catch {\n // Ignore errors\n }\n\n // 5. Remove .tbd directory\n try {\n await rm(tbdDir, { recursive: true, force: true });\n console.log(` ${colors.success('✓')} Removed .tbd directory`);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new CLIError(`Failed to remove .tbd directory: ${message}`);\n }\n\n console.log('');\n this.output.success('tbd has been uninstalled from this repository.');\n\n if (options.keepBranch && localBranchExists) {\n console.log('');\n console.log(colors.dim(`Note: The ${syncBranch} branch was preserved. Delete it with:`));\n console.log(colors.dim(` git branch -D ${syncBranch}`));\n }\n\n if (!options.removeRemote && remoteBranchExists) {\n console.log('');\n console.log(\n colors.dim(\n `Note: The remote ${remote}/${syncBranch} branch was preserved. Delete it with:`,\n ),\n );\n console.log(colors.dim(` git push ${remote} --delete ${syncBranch}`));\n }\n }\n\n /**\n * Get stats about a directory (file count, size).\n */\n private async getDirectoryStats(dirPath: string): Promise<{ files: number; size: number }> {\n let files = 0;\n let size = 0;\n\n const walk = async (dir: string): Promise<void> => {\n try {\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n await walk(fullPath);\n } else {\n files++;\n try {\n const stats = await stat(fullPath);\n size += stats.size;\n } catch {\n // Ignore stat errors\n }\n }\n }\n } catch {\n // Ignore errors\n }\n };\n\n await walk(dirPath);\n return { files, size };\n }\n}\n\nexport const uninstallCommand = new Command('uninstall')\n .description('Remove tbd from this repository')\n .option('--confirm', 'Confirm removal (required to proceed)')\n .option('--keep-branch', 'Keep the local sync branch')\n .option('--remove-remote', 'Also remove the remote sync branch')\n .action(async (options, command) => {\n const handler = new UninstallHandler(command);\n await handler.run(options);\n });\n","/**\n * DocCache - Path-ordered markdown document cache with lookup.\n *\n * Provides document lookups for the `tbd shortcut` command, supporting\n * both exact matching by filename and fuzzy matching against metadata.\n *\n * Also provides auto-sync functionality when docs are stale (per spec).\n *\n * See: docs/project/specs/active/plan-2026-01-22-doc-cache-abstraction.md\n * See: docs/project/specs/active/plan-2026-01-26-configurable-doc-cache-sync.md\n */\n\nimport { readdir, readFile } from 'node:fs/promises';\nimport { join, basename } from 'node:path';\nimport matter from 'gray-matter';\n\nimport { readConfig, readLocalState, findTbdRoot } from './config.js';\nimport { isDocsStale, syncDocsWithDefaults } from './doc-sync.js';\nimport { estimateTokens } from '../lib/format-utils.js';\n\n// =============================================================================\n// Scoring Constants\n// =============================================================================\n\n/** Score for exact filename match (with or without .md extension) */\nexport const SCORE_EXACT_MATCH = 1.0;\n\n/** Score when query is a prefix of the filename */\nexport const SCORE_PREFIX_MATCH = 0.9;\n\n/** Score when filename contains all query words */\nexport const SCORE_CONTAINS_ALL = 0.8;\n\n/** Base score for partial word matches (multiplied by matched/total ratio) */\nexport const SCORE_PARTIAL_BASE = 0.7;\n\n/** Minimum score threshold to return a fuzzy match result */\nexport const SCORE_MIN_THRESHOLD = 0.5;\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * Frontmatter fields used for shortcut documents.\n * These are the expected fields in YAML frontmatter for searchability.\n */\nexport interface DocFrontmatter {\n /** Display title for the shortcut */\n title?: string;\n /** Brief description for fuzzy matching and listing */\n description?: string;\n /** Category for filtering (e.g., planning, review, git) */\n category?: string;\n /** Optional categorization tags */\n tags?: string[];\n}\n\n/**\n * A cached document loaded from the doc path.\n */\nexport interface CachedDoc {\n /** Full filesystem path to the document */\n path: string;\n /** Filename without extension (used for lookups) */\n name: string;\n /** Parsed YAML frontmatter, if present */\n frontmatter?: DocFrontmatter;\n /** Full file content (including frontmatter for output) */\n content: string;\n /** Which directory in the path this doc came from */\n sourceDir: string;\n /** File size in bytes */\n sizeBytes: number;\n /** Estimated token count (based on ~3.5 chars/token) */\n approxTokens: number;\n}\n\n/**\n * A document match with relevance score.\n */\nexport interface DocMatch {\n /** The matched document */\n doc: CachedDoc;\n /** Match score: 1.0 = exact, lower = fuzzier */\n score: number;\n}\n\n// =============================================================================\n// DocCache Class\n// =============================================================================\n\n/**\n * Options for loading the doc cache.\n */\nexport interface DocCacheLoadOptions {\n /** If true, suppress auto-sync output (default: false) */\n quiet?: boolean;\n}\n\n/**\n * Path-ordered markdown document cache.\n *\n * Loads all .md files from configured paths in order, with earlier paths\n * taking precedence (like shell $PATH). Supports exact lookup by filename\n * and fuzzy search across filename + frontmatter metadata.\n */\nexport class DocCache {\n /** Active docs (first occurrence of each name) */\n private docs: CachedDoc[] = [];\n\n /** All docs including shadowed ones */\n private allDocs: CachedDoc[] = [];\n\n /** Track names we've seen for shadow detection */\n private seenNames = new Set<string>();\n\n /** Whether the cache has been loaded */\n private loaded = false;\n\n /**\n * Create a new DocCache.\n *\n * @param paths - Ordered array of directory paths to search (relative to baseDir)\n * @param baseDir - Base directory for resolving relative paths (default: cwd)\n */\n constructor(\n private readonly paths: string[],\n private readonly baseDir: string = process.cwd(),\n ) {}\n\n /**\n * Load all documents from configured paths.\n *\n * Reads all .md files from each path in order. Documents with the same\n * name in later paths are shadowed (tracked but not returned by default).\n *\n * If auto-sync is enabled and docs are stale, triggers a sync first.\n *\n * @param options - Load options (quiet: suppress auto-sync output)\n */\n async load(options?: DocCacheLoadOptions): Promise<void> {\n if (this.loaded) return;\n\n // Check for auto-sync before loading\n await this.checkAutoSync(options?.quiet ?? false);\n\n for (const relativePath of this.paths) {\n const dirPath = join(this.baseDir, relativePath);\n await this.loadDirectory(dirPath, relativePath);\n }\n\n this.loaded = true;\n }\n\n /**\n * Check if docs are stale and auto-sync if needed.\n * Respects the quiet option - only silent when explicitly requested.\n *\n * Uses syncDocsWithDefaults() to ensure auto-sync also picks up new bundled\n * docs from tbd upgrades, not just existing config entries.\n *\n * @param quiet - If true, suppress sync output\n */\n private async checkAutoSync(quiet: boolean): Promise<void> {\n try {\n // Find tbd root\n const tbdRoot = await findTbdRoot(this.baseDir);\n if (!tbdRoot) return;\n\n // Read config and state\n const config = await readConfig(tbdRoot);\n const state = await readLocalState(tbdRoot);\n\n // Check if auto-sync is enabled and docs are stale\n const autoSyncHours = config.settings?.doc_auto_sync_hours ?? 24;\n if (!isDocsStale(state.last_doc_sync_at, autoSyncHours)) {\n return;\n }\n\n // Use syncDocsWithDefaults to merge bundled defaults with user config\n // This ensures new bundled docs from tbd upgrades are picked up by auto-sync\n await syncDocsWithDefaults(tbdRoot, { quiet });\n } catch {\n // Auto-sync errors are silent - don't interrupt the user\n }\n }\n\n /**\n * Load documents from a single directory.\n */\n private async loadDirectory(dirPath: string, sourceDir: string): Promise<void> {\n let entries: string[];\n\n try {\n entries = await readdir(dirPath);\n } catch {\n // Directory doesn't exist or isn't readable - skip silently\n // This is expected when paths haven't been initialized yet\n return;\n }\n\n for (const entry of entries) {\n if (!entry.endsWith('.md')) continue;\n\n const filePath = join(dirPath, entry);\n const name = basename(entry, '.md');\n\n try {\n const content = await readFile(filePath, 'utf-8');\n const frontmatter = this.parseFrontmatterData(content);\n const sizeBytes = Buffer.byteLength(content, 'utf-8');\n const approxTokens = estimateTokens(content);\n\n const doc: CachedDoc = {\n path: filePath,\n name,\n frontmatter,\n content,\n sourceDir,\n sizeBytes,\n approxTokens,\n };\n\n // Track all docs\n this.allDocs.push(doc);\n\n // Only add to active docs if not shadowed\n if (!this.seenNames.has(name)) {\n this.docs.push(doc);\n this.seenNames.add(name);\n }\n } catch (error) {\n // Failed to read or parse file - skip with warning context\n console.warn(`Failed to load shortcut ${filePath}: ${(error as Error).message}`);\n }\n }\n }\n\n /**\n * Parse YAML frontmatter from content and return typed data.\n * Uses gray-matter for consistent frontmatter parsing.\n */\n private parseFrontmatterData(content: string): DocFrontmatter | undefined {\n if (!matter.test(content)) {\n return undefined;\n }\n\n try {\n const parsed = matter(content).data as Record<string, unknown>;\n return {\n title: typeof parsed.title === 'string' ? parsed.title : undefined,\n description: typeof parsed.description === 'string' ? parsed.description : undefined,\n category: typeof parsed.category === 'string' ? parsed.category : undefined,\n tags: Array.isArray(parsed.tags)\n ? parsed.tags.filter((t) => typeof t === 'string')\n : undefined,\n };\n } catch {\n // Invalid YAML in frontmatter - return undefined\n return undefined;\n }\n }\n\n /**\n * Get a document by exact name match.\n *\n * @param name - Filename to match (with or without .md extension)\n * @returns Match with score SCORE_EXACT_MATCH, or null if not found\n */\n get(name: string): DocMatch | null {\n // Strip .md extension if present\n const lookupName = name.endsWith('.md') ? name.slice(0, -3) : name;\n\n const doc = this.docs.find((d) => d.name === lookupName);\n if (!doc) return null;\n\n return { doc, score: SCORE_EXACT_MATCH };\n }\n\n /**\n * Search for documents matching a query.\n *\n * Performs fuzzy matching against filename, title, and description.\n * Returns matches sorted by score descending.\n *\n * @param query - Search query string\n * @param limit - Maximum number of results (default: 10)\n * @returns Array of matches sorted by score descending\n */\n search(query: string, limit = 10): DocMatch[] {\n const matches: DocMatch[] = [];\n\n for (const doc of this.docs) {\n const score = this.calculateScore(doc, query);\n if (score >= SCORE_MIN_THRESHOLD) {\n matches.push({ doc, score });\n }\n }\n\n // Sort by score descending, then by name for stability\n matches.sort((a, b) => {\n if (b.score !== a.score) return b.score - a.score;\n return a.doc.name.localeCompare(b.doc.name);\n });\n\n return matches.slice(0, limit);\n }\n\n /**\n * Calculate relevance score for a document against a query.\n */\n private calculateScore(doc: CachedDoc, query: string): number {\n const queryLower = query.toLowerCase().trim();\n\n // Empty query matches nothing\n if (queryLower.length === 0) {\n return 0;\n }\n\n const nameLower = doc.name.toLowerCase();\n const titleLower = doc.frontmatter?.title?.toLowerCase() ?? '';\n const descLower = doc.frontmatter?.description?.toLowerCase() ?? '';\n\n // Exact match on name\n if (nameLower === queryLower) {\n return SCORE_EXACT_MATCH;\n }\n\n // Prefix match on name\n if (nameLower.startsWith(queryLower)) {\n return SCORE_PREFIX_MATCH;\n }\n\n // Split query into words for multi-word matching\n const queryWords = queryLower.split(/\\s+/).filter((w) => w.length > 0);\n const searchableText = `${nameLower} ${titleLower} ${descLower}`;\n\n // Check if all query words are contained\n const allWordsMatch = queryWords.every((word) => searchableText.includes(word));\n if (allWordsMatch && queryWords.length > 0) {\n return SCORE_CONTAINS_ALL;\n }\n\n // Partial match - count how many words match\n const matchedWords = queryWords.filter((word) => searchableText.includes(word));\n if (matchedWords.length > 0) {\n const ratio = matchedWords.length / queryWords.length;\n return SCORE_PARTIAL_BASE * ratio;\n }\n\n return 0;\n }\n\n /**\n * List all documents.\n *\n * @param includeAll - If true, include shadowed documents\n * @returns Array of cached documents\n */\n list(includeAll = false): CachedDoc[] {\n return includeAll ? this.allDocs : this.docs;\n }\n\n /**\n * Check if a document is shadowed by an earlier path.\n *\n * @param doc - Document to check\n * @returns True if this doc is shadowed (not the first with this name)\n */\n isShadowed(doc: CachedDoc): boolean {\n const firstDoc = this.docs.find((d) => d.name === doc.name);\n return firstDoc !== doc;\n }\n\n /**\n * Check if the cache has been loaded.\n */\n isLoaded(): boolean {\n return this.loaded;\n }\n}\n\n// =============================================================================\n// Shortcut Directory Generation\n// =============================================================================\n\n/**\n * Marker comments for shortcut directory section in skill files.\n * Used for incremental updates without overwriting user content.\n */\nconst SHORTCUT_DIRECTORY_BEGIN = '<!-- BEGIN SHORTCUT DIRECTORY -->';\nconst SHORTCUT_DIRECTORY_END = '<!-- END SHORTCUT DIRECTORY -->';\n\n/**\n * Build table rows from docs (shared helper for shortcuts and guidelines).\n */\nfunction buildTableRows(docs: CachedDoc[], skipNames: string[] = []): string[] {\n const sortedDocs = [...docs].sort((a, b) => a.name.localeCompare(b.name));\n const rows: string[] = [];\n\n for (const doc of sortedDocs) {\n if (skipNames.includes(doc.name)) {\n continue;\n }\n\n const name = doc.name;\n const description = doc.frontmatter?.description ?? '';\n const escapedDescription = description.replace(/\\|/g, '\\\\|');\n\n rows.push(`| ${name} | ${escapedDescription} |`);\n }\n\n return rows;\n}\n\n/**\n * Generate a formatted markdown directory of shortcuts and guidelines.\n *\n * The output includes:\n * 1. Marker comments for incremental updates\n * 2. Available Shortcuts section with name and description\n * 3. Available Guidelines section with name and description (if provided)\n *\n * @param shortcuts - Array of shortcut CachedDoc objects\n * @param guidelines - Optional array of guideline CachedDoc objects\n * @returns Formatted markdown string with shortcuts and guidelines directory\n *\n * @example\n * const directory = generateShortcutDirectory(shortcutDocs, guidelineDocs);\n * // Returns:\n * // <!-- BEGIN SHORTCUT DIRECTORY -->\n * // ## Available Shortcuts\n * // | Name | Description |\n * // | --- | --- |\n * // | code-review-and-commit | Run pre-commit checks, review changes, and commit code |\n * // ...\n * // ## Available Guidelines\n * // | Name | Description |\n * // | --- | --- |\n * // | typescript-rules | TypeScript coding rules and best practices |\n * // ...\n * // <!-- END SHORTCUT DIRECTORY -->\n */\nexport function generateShortcutDirectory(\n shortcuts: CachedDoc[],\n guidelines: CachedDoc[] = [],\n): string {\n const lines: string[] = [SHORTCUT_DIRECTORY_BEGIN];\n\n // Shortcuts section\n const shortcutRows = buildTableRows(shortcuts, [\n 'skill-baseline',\n 'skill-brief',\n 'skill-minimal',\n 'shortcut-explanation',\n ]);\n\n lines.push('## Available Shortcuts');\n lines.push('');\n lines.push('Run `tbd shortcut <name>` to use any of these shortcuts:');\n lines.push('');\n\n if (shortcutRows.length === 0) {\n lines.push('No shortcuts available. Create shortcuts in `.tbd/docs/shortcuts/standard/`.');\n } else {\n lines.push('| Name | Description |');\n lines.push('| --- | --- |');\n lines.push(...shortcutRows);\n }\n\n // Guidelines section (if provided)\n if (guidelines.length > 0) {\n const guidelineRows = buildTableRows(guidelines);\n\n if (guidelineRows.length > 0) {\n lines.push('');\n lines.push('## Available Guidelines');\n lines.push('');\n lines.push('Run `tbd guidelines <name>` to apply any of these guidelines:');\n lines.push('');\n lines.push('| Name | Description |');\n lines.push('| --- | --- |');\n lines.push(...guidelineRows);\n }\n }\n\n lines.push('');\n lines.push(SHORTCUT_DIRECTORY_END);\n\n return lines.join('\\n');\n}\n","/**\n * `tbd prime` - Output dashboard and workflow context for AI agents.\n *\n * Designed to be called by hooks at session start and before context compaction\n * to ensure agents remember the tbd workflow.\n *\n * See: tbd-design.md §6.4.3 The tbd prime Command\n */\n\nimport { Command } from 'commander';\nimport { readFile, access } from 'node:fs/promises';\nimport { join, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { shouldUseInteractiveOutput } from '../lib/context.js';\nimport { renderMarkdown, paginateOutput } from '../lib/output.js';\nimport { findTbdRoot, readConfig, hasSeenWelcome, markWelcomeSeen } from '../../file/config.js';\nimport { stripFrontmatter } from '../../utils/markdown-utils.js';\nimport { VERSION } from '../lib/version.js';\nimport { listIssues } from '../../file/storage.js';\nimport {\n resolveDataSyncDir,\n DEFAULT_SHORTCUT_PATHS,\n DEFAULT_GUIDELINES_PATHS,\n} from '../../lib/paths.js';\nimport { getClaudePaths } from '../../lib/integration-paths.js';\nimport type { Issue } from '../../lib/types.js';\nimport { DocCache, generateShortcutDirectory } from '../../file/doc-cache.js';\n\ninterface PrimeOptions {\n export?: boolean;\n brief?: boolean;\n}\n\n/**\n * Get the path to the bundled SKILL.md file.\n */\nfunction getSkillPath(): string {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n // When bundled, runs from dist/bin.mjs or dist/cli.mjs\n // Docs are at dist/docs/SKILL.md (same level as the bundle)\n return join(__dirname, 'docs', 'SKILL.md');\n}\n\n/**\n * Load the skill content from the bundled SKILL.md file with fallbacks.\n * This is exported for use by setup.ts for skill installation.\n */\nexport async function loadSkillContent(): Promise<string> {\n // Try bundled location first (dist/docs/SKILL.md)\n try {\n return await readFile(getSkillPath(), 'utf-8');\n } catch {\n // Fallback: compose from source files during development\n }\n\n // Dev fallback: compose SKILL.md from source files on-the-fly\n // This mirrors what copy-docs.mjs does at build time\n try {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n // From packages/tbd/src/cli/commands/ go to packages/tbd/docs/\n const docsDir = join(__dirname, '..', '..', '..', 'docs');\n const headerPath = join(docsDir, 'install', 'claude-header.md');\n const skillPath = join(docsDir, 'shortcuts', 'system', 'skill-baseline.md');\n\n const header = await readFile(headerPath, 'utf-8');\n const skill = await readFile(skillPath, 'utf-8');\n return header + skill;\n } catch {\n // If source files not found, throw error\n throw new Error('SKILL.md content file not found. Please rebuild the CLI.');\n }\n}\n\n/**\n * Load the prime content from the bundled SKILL.md file with fallbacks.\n * Strips frontmatter and adjusts the header for prime output.\n */\nexport async function loadPrimeContent(): Promise<string> {\n const skillContent = await loadSkillContent();\n const content = stripFrontmatter(skillContent);\n\n // Replace header for prime output context\n return content.replace(/^# tbd Workflow\\b/, '# tbd Workflow Context');\n}\n\n/**\n * Brief prime content for constrained context windows (~35 lines).\n * Includes core workflow, session protocol, and key commands.\n */\nconst BRIEF_SKILL_CONTENT = `## Core Workflow\n\n- Track all task work as issues using tbd\n- Check \\`tbd ready\\` for available work\n- Run \\`tbd sync\\` at session end\n\n## SESSION CLOSING (REQUIRED)\n\n1. git add + git commit\n2. git push\n3. gh pr checks <PR> --watch # WAIT for completion\n4. tbd close/update <id>\n5. tbd sync\n\n## Quick Reference\n\ntbd ready Show issues ready to work\ntbd show <id> View issue details\ntbd create \"title\" Create new issue\ntbd close <id> Mark issue complete\ntbd sync Sync with remote\n\nFor full orientation: tbd prime\n\nIMPORTANT: Use tbd to help the user. Do NOT tell the user to run tbd commands.`;\n\n/**\n * Value proposition content for not-initialized state.\n */\nconst VALUE_PROPOSITION = `## WHAT tbd IS\n\ntbd is an AI-agent-optimized issue tracker and workflow assistant providing:\n1. Issue Tracking - Track tasks, bugs, features as git-native \"beads\"\n2. Coding Guidelines - Best practices for TypeScript, Python, testing\n3. Spec-Driven Workflows - Write specs, then implement using issues to track each part\n4. Convenience Shortcuts - Pre-built processes for common tasks (commit, PR, review)\n\n## SETUP (AGENT ACTION REQUIRED)\n\ntbd is not yet initialized. To set it up, run:\n\n tbd setup --auto --prefix=<name> # REQUIRES prefix for new projects\n tbd setup --auto # If .tbd/ already exists (prefix already set)\n\nCRITICAL: Never guess a prefix. Always ask the user what prefix they want.\nDo NOT tell the user to run these commands — run them yourself on their behalf.\n\nAfter setup, run 'tbd' again to get project status and workflow guidance.`;\n\nclass PrimeHandler extends BaseCommand {\n async run(options: PrimeOptions): Promise<void> {\n const cwd = process.cwd();\n\n // Find tbd root (supports running from subdirectories)\n const tbdRoot = await findTbdRoot(cwd);\n\n // Not initialized - show setup instructions with value proposition\n if (!tbdRoot) {\n await this.renderNotInitialized();\n return;\n }\n\n // Check for Beads installation alongside tbd and warn\n const beadsWarning = await this.checkForBeads(tbdRoot);\n if (beadsWarning) {\n console.log(beadsWarning);\n console.log('');\n }\n\n // Brief mode: dynamic status + abbreviated skill content\n if (options.brief) {\n await this.renderBriefOrientation(tbdRoot);\n return;\n }\n\n // Check for custom override file\n const customPrimePath = join(tbdRoot, '.tbd', 'PRIME.md');\n\n // If --export, always show default content\n if (!options.export) {\n try {\n await access(customPrimePath);\n const customContent = await readFile(customPrimePath, 'utf-8');\n console.log(customContent);\n return;\n } catch {\n // No custom file, use default full orientation\n }\n }\n\n // Default: full orientation (dynamic status + full skill content)\n await this.renderFullOrientation(tbdRoot);\n }\n\n /**\n * Render dynamic status section (installation + project status).\n */\n private async renderDynamicStatus(tbdRoot: string): Promise<void> {\n const colors = this.output.getColors();\n\n console.log(`${colors.bold('tbd')} v${VERSION}`);\n console.log('');\n\n // === INSTALLATION ===\n console.log(colors.bold('=== INSTALLATION ==='));\n console.log(`${colors.success('✓')} tbd installed (v${VERSION})`);\n console.log(`${colors.success('✓')} Initialized in this repo`);\n\n // Check if hooks are installed\n const hooksInstalled = await this.checkHooksInstalled(tbdRoot);\n if (hooksInstalled) {\n console.log(`${colors.success('✓')} Hooks installed`);\n } else {\n console.log(`${colors.dim('✗')} Hooks not installed (run: tbd setup --auto)`);\n }\n console.log('');\n\n // === PROJECT STATUS ===\n console.log(colors.bold('=== PROJECT STATUS ==='));\n try {\n const config = await readConfig(tbdRoot);\n console.log(`Repository: ${config.display.id_prefix || 'unknown'}`);\n } catch {\n console.log('Repository: unknown');\n }\n\n // Get issue stats\n const stats = await this.getIssueStats(tbdRoot);\n if (stats) {\n const statusInfo = `${stats.open} open (${stats.inProgress} in_progress)`;\n const blockedInfo = stats.blocked > 0 ? ` | ${stats.blocked} blocked` : '';\n console.log(`Issues: ${statusInfo}${blockedInfo}`);\n } else {\n console.log('Issues: (none)');\n }\n console.log('');\n }\n\n /**\n * Render full orientation: dynamic status + full skill content.\n * If the user hasn't seen the welcome message, add a welcome banner.\n */\n private async renderFullOrientation(tbdRoot: string): Promise<void> {\n // Dynamic status\n await this.renderDynamicStatus(tbdRoot);\n\n // Check if this is the user's first time\n const isNewUser = !(await hasSeenWelcome(tbdRoot));\n if (isNewUser) {\n await this.renderWelcomeBanner(tbdRoot);\n }\n\n // Full skill content\n const primeContent = await loadPrimeContent();\n\n // Shortcut directory\n const shortcutDir = await this.getShortcutDirectory(tbdRoot);\n\n // Build the full markdown content\n let markdownContent = primeContent;\n if (shortcutDir) {\n markdownContent += '\\n\\n' + shortcutDir;\n }\n markdownContent +=\n '\\n\\nIMPORTANT: Use tbd to help the user — do NOT tell the user to run tbd commands.\\n' +\n 'When the user asks for help or orientation, run `tbd shortcut welcome-user`.';\n\n // Output with interactive formatting (colors, pagination) when appropriate\n if (shouldUseInteractiveOutput(this.ctx)) {\n const rendered = renderMarkdown(markdownContent, this.ctx.color);\n await paginateOutput(rendered, true);\n } else {\n console.log(markdownContent);\n }\n }\n\n /**\n * Render brief orientation: dynamic status + abbreviated skill content.\n */\n private async renderBriefOrientation(tbdRoot: string): Promise<void> {\n // Dynamic status\n await this.renderDynamicStatus(tbdRoot);\n\n // Abbreviated skill content with interactive formatting\n if (shouldUseInteractiveOutput(this.ctx)) {\n console.log(renderMarkdown(BRIEF_SKILL_CONTENT, this.ctx.color));\n } else {\n console.log(BRIEF_SKILL_CONTENT);\n }\n }\n\n /**\n * Render output for not initialized state with value proposition.\n */\n private async renderNotInitialized(): Promise<void> {\n const colors = this.output.getColors();\n\n console.log(`${colors.bold('tbd')} v${VERSION}`);\n console.log('');\n console.log(colors.bold('=== NOT INITIALIZED ==='));\n console.log(`${colors.warn('✗')} tbd not initialized in this repository`);\n console.log('');\n\n // Value proposition with interactive formatting\n if (shouldUseInteractiveOutput(this.ctx)) {\n console.log(renderMarkdown(VALUE_PROPOSITION, this.ctx.color));\n } else {\n console.log(VALUE_PROPOSITION);\n }\n }\n\n /**\n * Render a welcome banner for first-time users.\n * Instructs the agent to show the welcome message.\n */\n private async renderWelcomeBanner(tbdRoot: string): Promise<void> {\n const colors = this.output.getColors();\n\n console.log(colors.bold('=== WELCOME (FIRST TIME) ==='));\n console.log('');\n console.log('AGENT INSTRUCTION:');\n console.log('1. Welcome the user! Run `tbd shortcut welcome-user` and follow its');\n console.log(' instructions to give the user a warm orientation.');\n console.log('2. Show the user examples of what THEY can say (e.g. \"there\\'s a bug\",');\n console.log(' \"let\\'s plan a feature\") — do NOT show them tbd CLI commands.');\n console.log(\"3. You use tbd on the user's behalf. The user talks to you naturally;\");\n console.log(' you translate their requests into tbd actions.');\n console.log('');\n\n // Mark welcome as seen\n try {\n await markWelcomeSeen(tbdRoot);\n } catch {\n // Non-critical\n }\n }\n\n /**\n * Check if Claude Code hooks are installed in project-local .claude/settings.json.\n */\n private async checkHooksInstalled(projectRoot: string): Promise<boolean> {\n const { settings } = getClaudePaths(projectRoot);\n try {\n const content = await readFile(settings, 'utf-8');\n return content.includes('tbd');\n } catch {\n return false;\n }\n }\n\n /**\n * Get issue statistics.\n */\n private async getIssueStats(tbdRoot: string): Promise<{\n open: number;\n inProgress: number;\n blocked: number;\n } | null> {\n try {\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n const issues: Issue[] = await listIssues(dataSyncDir);\n\n let open = 0;\n let inProgress = 0;\n const blockedIds = new Set<string>();\n\n // Find blocked issues\n // \"blocks\" dependency: issue A with {type: 'blocks', target: B} means A blocks B\n // B is blocked only if A (the blocker) is not closed\n for (const issue of issues) {\n for (const dep of issue.dependencies) {\n if (dep.type === 'blocks') {\n // Only count target as blocked if the blocker (this issue) is not closed\n if (issue.status !== 'closed') {\n blockedIds.add(dep.target);\n }\n }\n }\n }\n\n // Count by status\n for (const issue of issues) {\n if (issue.status === 'open') {\n open++;\n } else if (issue.status === 'in_progress') {\n inProgress++;\n }\n }\n\n return { open, inProgress, blocked: blockedIds.size };\n } catch {\n return null;\n }\n }\n\n /**\n * Check if Beads is installed alongside tbd and return a warning message.\n * This helps users who are migrating from Beads to tbd.\n */\n private async checkForBeads(cwd: string): Promise<string | null> {\n const beadsDir = join(cwd, '.beads');\n try {\n await access(beadsDir);\n // .beads/ exists - warn the agent\n return `⚠️ WARNING: A .beads/ directory was detected alongside .tbd/\n When asked to use beads, use \\`tbd\\` commands, NOT \\`bd\\` commands.\n To complete migration: tbd setup beads --disable --confirm`;\n } catch {\n // No .beads/ directory, no warning needed\n return null;\n }\n }\n\n /**\n * Generate the shortcut and guidelines directory on-the-fly.\n */\n private async getShortcutDirectory(tbdRoot: string): Promise<string | null> {\n // Load shortcuts\n const shortcutCache = new DocCache(DEFAULT_SHORTCUT_PATHS, tbdRoot);\n await shortcutCache.load({ quiet: this.ctx.quiet });\n const shortcuts = shortcutCache.list();\n\n // Load guidelines\n const guidelinesCache = new DocCache(DEFAULT_GUIDELINES_PATHS, tbdRoot);\n await guidelinesCache.load({ quiet: this.ctx.quiet });\n const guidelines = guidelinesCache.list();\n\n // If no docs loaded, skip directory\n if (shortcuts.length === 0 && guidelines.length === 0) {\n return null;\n }\n\n return generateShortcutDirectory(shortcuts, guidelines);\n }\n}\n\nexport const primeCommand = new Command('prime')\n .description('Show full orientation with workflow context')\n .option('--export', 'Output default content (ignores PRIME.md override)')\n .option('--brief', 'Output abbreviated orientation (~35 lines) for constrained contexts')\n .action(async (options: PrimeOptions, command) => {\n const handler = new PrimeHandler(command);\n await handler.run(options);\n });\n","/**\n * `tbd skill` - Output AI agent skill file content.\n *\n * See: tbd-design.md §Prime-First Design\n */\n\nimport { Command } from 'commander';\nimport { readFile } from 'node:fs/promises';\nimport { join, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { shouldUseInteractiveOutput } from '../lib/context.js';\nimport { renderMarkdownWithFrontmatter, paginateOutput } from '../lib/output.js';\nimport { findTbdRoot } from '../../file/config.js';\nimport { DocCache, generateShortcutDirectory } from '../../file/doc-cache.js';\nimport { DEFAULT_SHORTCUT_PATHS, DEFAULT_GUIDELINES_PATHS } from '../../lib/paths.js';\n\ninterface SkillOptions {\n brief?: boolean;\n}\n\n/**\n * Get the path to a bundled doc file.\n */\nfunction getDocPath(filename: string): string {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n // When bundled, runs from dist/bin.mjs or dist/cli.mjs\n // Docs are at dist/docs/ (same level as the bundle)\n return join(__dirname, 'docs', filename);\n}\n\n/**\n * Load a doc file content.\n */\nasync function loadDocContent(filename: string): Promise<string> {\n // Try bundled location first\n try {\n return await readFile(getDocPath(filename), 'utf-8');\n } catch {\n // Fallback for development\n }\n\n // Fallback: try to read from source location during development\n try {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n // During development: src/cli/commands -> packages/tbd/docs\n const devPath = join(__dirname, '..', '..', '..', 'docs', filename);\n return await readFile(devPath, 'utf-8');\n } catch {\n throw new Error(`${filename} not found. Please rebuild the CLI.`);\n }\n}\n\nclass SkillHandler extends BaseCommand {\n async run(options: SkillOptions): Promise<void> {\n await this.execute(async () => {\n let content: string;\n if (options.brief) {\n // Brief mode: just output skill-brief.md\n content = await loadDocContent('skill-brief.md');\n } else {\n // Full mode: compose header + skill-baseline.md + shortcut directory\n content = await this.composeFullSkill();\n }\n\n // Output with interactive formatting (colors, pagination) when appropriate\n if (shouldUseInteractiveOutput(this.ctx)) {\n const rendered = renderMarkdownWithFrontmatter(content, this.ctx.color);\n await paginateOutput(rendered, true);\n } else {\n console.log(content);\n }\n }, 'Failed to output skill content');\n }\n\n /**\n * Compose the full skill output by combining:\n * 1. Claude header (YAML frontmatter)\n * 2. Base skill content (skill-baseline.md from shortcuts/system)\n * 3. Shortcut directory (from cache or generated on-the-fly)\n */\n private async composeFullSkill(): Promise<string> {\n // Load header (YAML frontmatter for Claude)\n const header = await loadDocContent('install/claude-header.md');\n\n // Load base skill content\n const baseSkill = await loadDocContent('shortcuts/system/skill-baseline.md');\n\n // Get shortcut directory\n const directory = await this.getShortcutDirectory();\n\n // Compose: header + base skill + (optional) shortcut directory\n let result = header + baseSkill;\n if (directory) {\n result = result.trimEnd() + '\\n\\n' + directory;\n }\n\n return result;\n }\n\n /**\n * Generate the shortcut directory on-the-fly.\n */\n private async getShortcutDirectory(): Promise<string | null> {\n // Try to find tbd root (may not be initialized)\n const tbdRoot = await findTbdRoot(process.cwd());\n if (!tbdRoot) {\n return null;\n }\n\n // Load shortcuts\n const shortcutCache = new DocCache(DEFAULT_SHORTCUT_PATHS, tbdRoot);\n await shortcutCache.load({ quiet: this.ctx.quiet });\n const shortcuts = shortcutCache.list();\n\n // Load guidelines\n const guidelinesCache = new DocCache(DEFAULT_GUIDELINES_PATHS, tbdRoot);\n await guidelinesCache.load({ quiet: this.ctx.quiet });\n const guidelines = guidelinesCache.list();\n\n // If no docs loaded, skip directory\n if (shortcuts.length === 0 && guidelines.length === 0) {\n return null;\n }\n\n return generateShortcutDirectory(shortcuts, guidelines);\n }\n}\n\nexport const skillCommand = new Command('skill')\n .description('Output AI agent skill file content')\n .option('--brief', 'Output condensed workflow rules only')\n .action(async (options: SkillOptions, command) => {\n const handler = new SkillHandler(command);\n await handler.run(options);\n });\n","/**\n * Agent instruction prompts for document commands.\n *\n * These prompts are displayed when tbd outputs a document to help\n * the agent understand how to use the content.\n */\n\n/**\n * Header shown when outputting a shortcut document.\n */\nexport const SHORTCUT_AGENT_HEADER =\n 'Agent instructions: You have activated a shortcut with task instructions. If a user has asked you to do a task that requires this work, follow the instructions below carefully.';\n\n/**\n * Header shown when outputting a guidelines document.\n */\nexport const GUIDELINES_AGENT_HEADER =\n 'Agent instructions: You have activated a guidelines document. If a user has asked you to apply these rules, read them carefully and apply them. Use beads to track each step.';\n","/**\n * Add external documentation to the tbd doc cache.\n *\n * Uses the shared github-fetch utility for URL conversion and fetching.\n * Handles doc-specific concerns: content validation, file writing, and\n * atomic config updates.\n */\n\nimport { join, dirname } from 'node:path';\nimport { mkdir } from 'node:fs/promises';\nimport { writeFile } from 'atomically';\n\nimport { readConfig, writeConfig } from './config.js';\nimport { githubBlobToRawUrl, fetchWithGhFallback } from './github-fetch.js';\nimport { TBD_DOCS_DIR } from '../lib/paths.js';\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * The type of document being added.\n */\nexport type DocType = 'guideline' | 'shortcut' | 'template';\n\n/**\n * Options for adding a document.\n */\nexport interface AddDocOptions {\n /** URL to fetch the document from */\n url: string;\n /** Name for the document (without .md extension) */\n name: string;\n /** Type of document */\n docType: DocType;\n}\n\n/**\n * Result of adding a document.\n */\nexport interface AddDocResult {\n /** The destination path relative to .tbd/docs/ */\n destPath: string;\n /** The raw URL used to fetch the content */\n rawUrl: string;\n /** Whether gh CLI was used as fallback */\n usedGhCli: boolean;\n}\n\n// =============================================================================\n// Content Validation\n// =============================================================================\n\n/**\n * Validate that fetched content looks like a reasonable markdown document.\n *\n * @throws If content fails sanity checks\n */\nexport function validateDocContent(content: string, name: string): void {\n if (!content || content.trim().length === 0) {\n throw new Error(`Fetched content for \"${name}\" is empty`);\n }\n\n if (content.length < 10) {\n throw new Error(`Fetched content for \"${name}\" is too short (${content.length} chars)`);\n }\n\n // Check for HTML error pages\n if (content.trimStart().startsWith('<!DOCTYPE') || content.trimStart().startsWith('<html')) {\n throw new Error(\n `Fetched content for \"${name}\" appears to be an HTML page, not a markdown document`,\n );\n }\n\n // Check for binary content (high ratio of non-printable characters)\n const nonPrintable = content.split('').filter((c) => {\n const code = c.charCodeAt(0);\n return code < 32 && code !== 9 && code !== 10 && code !== 13;\n }).length;\n if (nonPrintable / content.length > 0.1) {\n throw new Error(`Fetched content for \"${name}\" appears to be binary, not a text document`);\n }\n}\n\n// =============================================================================\n// Doc Type to Path Mapping\n// =============================================================================\n\n/**\n * Get the destination subdirectory for a doc type.\n */\nexport function getDocTypeSubdir(docType: DocType): string {\n switch (docType) {\n case 'guideline':\n return 'guidelines';\n case 'shortcut':\n return 'shortcuts/custom';\n case 'template':\n return 'templates';\n }\n}\n\n// =============================================================================\n// Main Add Function\n// =============================================================================\n\n/**\n * Add an external document to the tbd doc cache.\n *\n * This function:\n * 1. Converts GitHub blob URLs to raw URLs\n * 2. Fetches the content (with gh CLI fallback on 403)\n * 3. Validates the content looks like markdown\n * 4. Writes the file to .tbd/docs/{type}/{name}.md\n * 5. Atomically updates the config to include the new source\n *\n * @throws If fetching, validation, or config update fails\n */\nexport async function addDoc(tbdRoot: string, options: AddDocOptions): Promise<AddDocResult> {\n const { url, name, docType } = options;\n\n // Normalize name (strip .md if provided)\n const cleanName = name.endsWith('.md') ? name.slice(0, -3) : name;\n const filename = `${cleanName}.md`;\n const subdir = getDocTypeSubdir(docType);\n const destPath = `${subdir}/${filename}`;\n const rawUrl = githubBlobToRawUrl(url);\n\n // Fetch content\n const { content, usedGhCli } = await fetchWithGhFallback(url);\n\n // Validate content\n validateDocContent(content, cleanName);\n\n // Write file to .tbd/docs/{subdir}/{name}.md\n const fullPath = join(tbdRoot, TBD_DOCS_DIR, destPath);\n await mkdir(dirname(fullPath), { recursive: true });\n await writeFile(fullPath, content);\n\n // Atomically update config\n const config = await readConfig(tbdRoot);\n config.docs_cache ??= { files: {}, lookup_path: [] };\n config.docs_cache.files ??= {};\n config.docs_cache.files[destPath] = rawUrl;\n\n // Ensure the lookup_path includes the subdir\n const lookupDir = `.tbd/docs/${subdir}`;\n if (!config.docs_cache.lookup_path.includes(lookupDir)) {\n config.docs_cache.lookup_path.push(lookupDir);\n }\n\n await writeConfig(tbdRoot, config);\n\n return { destPath, rawUrl, usedGhCli };\n}\n","/**\n * `tbd shortcut` - Find and output documentation shortcuts.\n *\n * Shortcuts are reusable instruction templates for common tasks.\n * Give a name or description and tbd will find the matching shortcut.\n *\n * See: docs/project/specs/active/plan-2026-01-22-doc-cache-abstraction.md\n */\n\nimport { Command } from 'commander';\nimport pc from 'picocolors';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { SHORTCUT_AGENT_HEADER } from '../lib/doc-prompts.js';\nimport { requireInit, CLIError } from '../lib/errors.js';\nimport { DocCache, SCORE_PREFIX_MATCH } from '../../file/doc-cache.js';\nimport { addDoc } from '../../file/doc-add.js';\nimport { readConfig } from '../../file/config.js';\nimport { DEFAULT_SHORTCUT_PATHS } from '../../lib/paths.js';\nimport { truncate } from '../../lib/truncate.js';\nimport { formatDocSize } from '../../lib/format-utils.js';\nimport { getTerminalWidth } from '../lib/output.js';\n\ninterface ShortcutOptions {\n list?: boolean;\n all?: boolean;\n refresh?: boolean;\n quiet?: boolean;\n category?: string;\n add?: string;\n name?: string;\n}\n\n/**\n * Shortcut categories for filtering.\n * Categories are read from frontmatter.\n */\ntype ShortcutCategory =\n | 'planning'\n | 'documentation'\n | 'review'\n | 'git'\n | 'cleanup'\n | 'session'\n | 'meta';\n\nclass ShortcutHandler extends BaseCommand {\n async run(query: string | undefined, options: ShortcutOptions): Promise<void> {\n await this.execute(async () => {\n // Add mode\n if (options.add) {\n if (!options.name) {\n throw new CLIError('--name is required when using --add');\n }\n const tbdRoot = await requireInit();\n console.log(`Adding shortcut: ${options.name}`);\n console.log(` URL: ${options.add}`);\n const result = await addDoc(tbdRoot, {\n url: options.add,\n name: options.name,\n docType: 'shortcut',\n });\n if (result.usedGhCli) {\n console.log(pc.dim(' (fetched via gh CLI due to direct access restriction)'));\n }\n console.log(pc.green(` Added to ${result.destPath}`));\n console.log(pc.green(` Config updated with source: ${result.rawUrl}`));\n console.log('');\n console.log('Run `tbd shortcut --list` to verify.');\n return;\n }\n\n // Get tbd root (supports running from subdirectories)\n const tbdRoot = await requireInit();\n\n // Read config to get lookup paths (fall back to defaults)\n const config = await readConfig(tbdRoot);\n const lookupPaths = config.docs_cache?.lookup_path ?? DEFAULT_SHORTCUT_PATHS;\n\n // Create and load the doc cache with proper base directory\n const cache = new DocCache(lookupPaths, tbdRoot);\n await cache.load({ quiet: this.ctx.quiet });\n\n // Refresh mode: regenerate cache and update skill files\n if (options.refresh) {\n await this.handleRefresh(cache, tbdRoot, options.quiet);\n return;\n }\n\n // List mode\n if (options.list || options.category) {\n await this.handleList(cache, options.all, options.category);\n return;\n }\n\n // No query: show explanation + help\n if (!query) {\n await this.handleNoQuery(cache);\n return;\n }\n\n // Query provided: try exact match first, then fuzzy\n await this.handleQuery(cache, query);\n }, 'Failed to find shortcut');\n }\n\n /**\n * Handle --refresh mode: no-op since shortcuts are now generated on-the-fly.\n * Kept for backward compatibility.\n */\n private async handleRefresh(cache: DocCache, _tbdRoot: string, quiet?: boolean): Promise<void> {\n const docs = cache.list();\n\n // Count shortcuts (excluding system docs)\n const shortcutCount = docs.filter(\n (d) =>\n d.name !== 'skill-baseline' &&\n d.name !== 'skill-brief' &&\n d.name !== 'skill-minimal' &&\n d.name !== 'shortcut-explanation',\n ).length;\n\n if (!quiet) {\n if (this.ctx.json) {\n this.output.data({\n refreshed: true,\n shortcutCount,\n message: 'Shortcuts are now generated on-the-fly (no cache)',\n });\n } else {\n console.log(`${shortcutCount} shortcut(s) available (generated on-the-fly)`);\n }\n }\n }\n\n /**\n * Handle --list mode: show all available shortcuts.\n */\n private async handleList(\n cache: DocCache,\n includeAll?: boolean,\n category?: string,\n ): Promise<void> {\n let docs = cache.list(includeAll);\n\n // Filter by category if specified (read from frontmatter)\n if (category) {\n docs = docs.filter((d) => {\n const docCategory = d.frontmatter?.category as ShortcutCategory | undefined;\n return docCategory === category;\n });\n }\n\n if (this.ctx.json) {\n this.output.data(\n docs.map((d) => ({\n name: d.name,\n title: d.frontmatter?.title,\n description: d.frontmatter?.description,\n category: d.frontmatter?.category,\n path: d.path,\n sourceDir: d.sourceDir,\n sizeBytes: d.sizeBytes,\n approxTokens: d.approxTokens,\n shadowed: cache.isShadowed(d),\n })),\n );\n return;\n }\n\n if (docs.length === 0) {\n console.log('No shortcuts found.');\n console.log('Run `tbd setup --auto` to install built-in shortcuts.');\n return;\n }\n\n const maxWidth = getTerminalWidth();\n\n for (const doc of docs) {\n const shadowed = cache.isShadowed(doc);\n const name = doc.name;\n const title = doc.frontmatter?.title;\n const description = doc.frontmatter?.description ?? this.extractFallbackText(doc.content);\n\n if (shadowed) {\n // Muted style for shadowed entries\n const line = `${name} (${doc.sourceDir}) [shadowed]`;\n console.log(pc.dim(truncate(line, maxWidth)));\n } else {\n // Line 1: name (bold) + size/token info (dimmed)\n const sizeInfo = formatDocSize(doc.sizeBytes, doc.approxTokens);\n console.log(`${pc.bold(name)} ${pc.dim(sizeInfo)}`);\n\n // Line 2+: Indented \"Title: Description\"\n // Only truncate fallback body text; never truncate actual title/description\n const hasFrontmatter = title ?? doc.frontmatter?.description;\n const content =\n title && description ? `${title}: ${description}` : (title ?? description ?? '');\n if (content) {\n this.printWrappedDescription(content, maxWidth, !hasFrontmatter);\n }\n }\n }\n }\n\n /**\n * Extract fallback text from content when no frontmatter description exists.\n * Strips frontmatter and markdown syntax, takes first text, condenses whitespace.\n */\n private extractFallbackText(content: string): string | undefined {\n // Strip YAML frontmatter if present\n let text = content;\n if (text.startsWith('---')) {\n const endIndex = text.indexOf('---', 3);\n if (endIndex !== -1) {\n text = text.slice(endIndex + 3);\n }\n }\n\n // Strip markdown headers (# Title -> Title)\n text = text.replace(/^#+\\s*/gm, '');\n // Strip bold/italic markers\n text = text.replace(/\\*\\*|__|\\*|_/g, '');\n // Strip code blocks\n text = text.replace(/```[\\s\\S]*?```/g, '');\n // Strip inline code\n text = text.replace(/`[^`]+`/g, '');\n // Strip blockquotes\n text = text.replace(/^>\\s*/gm, '');\n\n // Condense all whitespace to single spaces and trim\n text = text.replace(/\\s+/g, ' ').trim();\n\n // Return first chunk of text (up to ~200 chars for reasonable fallback)\n if (text.length === 0) return undefined;\n return text.slice(0, 200);\n }\n\n /**\n * Print description indented, wrapped across lines.\n * @param text - Text to print\n * @param maxWidth - Terminal width\n * @param shouldTruncate - If true, truncate to two lines; if false, wrap all lines\n */\n private printWrappedDescription(text: string, maxWidth: number, shouldTruncate: boolean): void {\n const indent = ' ';\n const availableWidth = maxWidth - indent.length;\n\n if (text.length <= availableWidth) {\n // Fits on one line\n console.log(`${indent}${text}`);\n return;\n }\n\n if (shouldTruncate) {\n // Truncate to two lines max (for fallback body text)\n const firstLine = this.wrapAtWord(text, availableWidth);\n const remainder = text.slice(firstLine.length).trimStart();\n console.log(`${indent}${firstLine}`);\n if (remainder) {\n console.log(`${indent}${truncate(remainder, availableWidth)}`);\n }\n } else {\n // Wrap all lines without truncation (for title/description)\n let remaining = text;\n while (remaining.length > 0) {\n if (remaining.length <= availableWidth) {\n console.log(`${indent}${remaining}`);\n break;\n }\n const line = this.wrapAtWord(remaining, availableWidth);\n console.log(`${indent}${line}`);\n remaining = remaining.slice(line.length).trimStart();\n }\n }\n }\n\n /**\n * Wrap text at word boundary to fit within maxWidth.\n */\n private wrapAtWord(text: string, maxWidth: number): string {\n if (text.length <= maxWidth) return text;\n const lastSpace = text.lastIndexOf(' ', maxWidth);\n if (lastSpace > 0) {\n return text.slice(0, lastSpace);\n }\n return text.slice(0, maxWidth);\n }\n\n /**\n * Handle no query: show explanation + help.\n */\n private async handleNoQuery(cache: DocCache): Promise<void> {\n // Try to find the shortcut-explanation.md\n const explanation = cache.get('shortcut-explanation');\n if (explanation) {\n console.log(explanation.doc.content);\n } else {\n // Fallback explanation\n console.log('tbd shortcut - Find and output documentation shortcuts');\n console.log('');\n console.log('Usage:');\n console.log(' tbd shortcut <name> Find shortcut by exact name');\n console.log(' tbd shortcut <description> Find shortcut by fuzzy match');\n console.log(' tbd shortcut --list List all available shortcuts');\n console.log(' tbd shortcut --list --all Include shadowed shortcuts');\n console.log('');\n console.log('No shortcuts found. Run `tbd setup --auto` to install built-in shortcuts.');\n }\n }\n\n /**\n * Handle query: exact match first, then fuzzy.\n */\n private async handleQuery(cache: DocCache, query: string): Promise<void> {\n // Try exact match first\n const exactMatch = cache.get(query);\n if (exactMatch) {\n if (this.ctx.json) {\n this.output.data({\n name: exactMatch.doc.name,\n title: exactMatch.doc.frontmatter?.title,\n score: exactMatch.score,\n content: exactMatch.doc.content,\n });\n } else {\n console.log(SHORTCUT_AGENT_HEADER + '\\n');\n console.log(exactMatch.doc.content);\n }\n return;\n }\n\n // Fuzzy match\n const matches = cache.search(query, 5);\n if (matches.length === 0) {\n console.log(`No shortcut found matching: ${query}`);\n console.log('Run `tbd shortcut --list` to see available shortcuts.');\n return;\n }\n\n const best = matches[0]!;\n // Use PREFIX_MATCH (0.9) as threshold for high confidence\n // Below this, show suggestions instead of auto-selecting\n if (best.score < SCORE_PREFIX_MATCH) {\n // Low confidence - show suggestions instead\n console.log(`No exact match for \"${query}\". Did you mean:`);\n for (const m of matches) {\n const name = m.doc.frontmatter?.title ?? m.doc.name;\n console.log(` ${name} ${pc.dim(`(score: ${m.score.toFixed(2)})`)}`);\n }\n return;\n }\n\n // Good fuzzy match - output it\n if (this.ctx.json) {\n this.output.data({\n name: best.doc.name,\n title: best.doc.frontmatter?.title,\n score: best.score,\n content: best.doc.content,\n });\n } else {\n console.log(SHORTCUT_AGENT_HEADER + '\\n');\n console.log(best.doc.content);\n }\n }\n}\n\nexport const shortcutCommand = new Command('shortcut')\n .description('Find and output documentation shortcuts')\n .argument('[query]', 'Shortcut name or description to search for')\n .option('--list', 'List all available shortcuts')\n .option('--all', 'Include shadowed shortcuts (use with --list)')\n .option(\n '--category <category>',\n 'Filter by category: planning, documentation, review, git, cleanup, session, meta',\n )\n .option('--refresh', 'Refresh the cached shortcut directory')\n .option('--quiet', 'Suppress output (use with --refresh)')\n .option('--add <url>', 'Add a shortcut from a URL')\n .option('--name <name>', 'Name for the added shortcut (required with --add)')\n .action(async (query: string | undefined, options: ShortcutOptions, command) => {\n const handler = new ShortcutHandler(command);\n await handler.run(query, options);\n });\n","/**\n * Shared base class for document listing/lookup commands.\n *\n * Used by shortcuts, guidelines, and templates commands to provide\n * consistent behavior for --list, fuzzy search, and exact match.\n */\n\nimport type { Command } from 'commander';\nimport pc from 'picocolors';\n\nimport { BaseCommand } from './base-command.js';\nimport { shouldUseInteractiveOutput } from './context.js';\nimport { GUIDELINES_AGENT_HEADER } from './doc-prompts.js';\nimport { requireInit } from './errors.js';\nimport { DocCache, SCORE_PREFIX_MATCH } from '../../file/doc-cache.js';\nimport { addDoc, type DocType } from '../../file/doc-add.js';\nimport { truncate } from '../../lib/truncate.js';\nimport { formatDocSize } from '../../lib/format-utils.js';\nimport { getTerminalWidth, renderMarkdownWithFrontmatter, paginateOutput } from './output.js';\n\n/**\n * Configuration for a doc command handler.\n */\nexport interface DocCommandConfig {\n /** Display name for the doc type (e.g., \"shortcut\", \"guideline\", \"template\") */\n typeName: string;\n /** Plural display name (e.g., \"shortcuts\", \"guidelines\", \"templates\") */\n typeNamePlural: string;\n /** Paths to search for documents (relative to tbd root) */\n paths: string[];\n /** Names to exclude from listings (e.g., system docs) */\n excludeFromList?: string[];\n /** Content to show when no query is provided (optional) */\n noQueryDocName?: string;\n /** The doc type for --add operations */\n docType: DocType;\n}\n\n/**\n * Common options for doc commands.\n */\nexport interface DocCommandOptions {\n list?: boolean;\n all?: boolean;\n refresh?: boolean;\n quiet?: boolean;\n add?: string;\n name?: string;\n}\n\n/**\n * Base handler for document commands (shortcuts, guidelines, templates).\n *\n * Provides shared functionality for:\n * - Listing documents with --list\n * - Exact name lookup\n * - Fuzzy search\n * - Wrapped description output\n */\nexport abstract class DocCommandHandler extends BaseCommand {\n protected cache: DocCache | null = null;\n protected tbdRoot = '';\n\n constructor(\n command: Command,\n protected readonly config: DocCommandConfig,\n ) {\n super(command);\n }\n\n /**\n * Initialize the doc cache. Must be called before other operations.\n */\n protected async initCache(): Promise<void> {\n this.tbdRoot = await requireInit();\n this.cache = new DocCache(this.config.paths, this.tbdRoot);\n await this.cache.load({ quiet: this.ctx.quiet });\n }\n\n /**\n * Handle --list mode: show all available documents.\n */\n protected async handleList(includeAll?: boolean): Promise<void> {\n if (!this.cache) throw new Error('Cache not initialized');\n\n const docs = this.cache.list(includeAll);\n\n if (this.ctx.json) {\n this.output.data(\n docs.map((d) => ({\n name: d.name,\n title: d.frontmatter?.title,\n description: d.frontmatter?.description,\n path: d.path,\n sourceDir: d.sourceDir,\n sizeBytes: d.sizeBytes,\n approxTokens: d.approxTokens,\n shadowed: this.cache!.isShadowed(d),\n })),\n );\n return;\n }\n\n if (docs.length === 0) {\n console.log(`No ${this.config.typeNamePlural} found.`);\n console.log(`Run \\`tbd setup --auto\\` to install built-in ${this.config.typeNamePlural}.`);\n return;\n }\n\n const maxWidth = getTerminalWidth();\n\n for (const doc of docs) {\n const shadowed = this.cache.isShadowed(doc);\n const name = doc.name;\n const title = doc.frontmatter?.title;\n const description = doc.frontmatter?.description ?? this.extractFallbackText(doc.content);\n\n if (shadowed) {\n // Muted style for shadowed entries\n const line = `${name} (${doc.sourceDir}) [shadowed]`;\n console.log(pc.dim(truncate(line, maxWidth)));\n } else {\n // Line 1: name (bold) + size/token info (dimmed)\n const sizeInfo = formatDocSize(doc.sizeBytes, doc.approxTokens);\n console.log(`${pc.bold(name)} ${pc.dim(sizeInfo)}`);\n\n // Line 2+: Indented \"Title: Description\"\n const hasFrontmatter = title ?? doc.frontmatter?.description;\n const content =\n title && description ? `${title}: ${description}` : (title ?? description ?? '');\n if (content) {\n this.printWrappedDescription(content, maxWidth, !hasFrontmatter);\n }\n }\n }\n }\n\n /**\n * Handle no query: show explanation + help.\n */\n protected async handleNoQuery(): Promise<void> {\n if (!this.cache) throw new Error('Cache not initialized');\n\n // Try to find the explanation doc if configured\n if (this.config.noQueryDocName) {\n const explanation = this.cache.get(this.config.noQueryDocName);\n if (explanation) {\n console.log(explanation.doc.content);\n return;\n }\n }\n\n // Fallback explanation\n const { typeName, typeNamePlural } = this.config;\n console.log(`tbd ${typeNamePlural} - Find and output ${typeNamePlural}`);\n console.log('');\n console.log('Usage:');\n console.log(` tbd ${typeNamePlural} <name> Find ${typeName} by exact name`);\n console.log(` tbd ${typeNamePlural} <description> Find ${typeName} by fuzzy match`);\n console.log(` tbd ${typeNamePlural} --list List all available ${typeNamePlural}`);\n console.log(` tbd ${typeNamePlural} --list --all Include shadowed ${typeNamePlural}`);\n console.log('');\n console.log(\n `No ${typeNamePlural} found. Run \\`tbd setup --auto\\` to install built-in ${typeNamePlural}.`,\n );\n }\n\n /**\n * Get the agent instruction header for the doc type.\n * Returns undefined if no header should be shown.\n */\n protected getAgentHeader(): string | undefined {\n if (this.config.typeName === 'guideline') {\n return GUIDELINES_AGENT_HEADER;\n }\n // Templates and other types don't need a header\n return undefined;\n }\n\n /**\n * Handle query: exact match first, then fuzzy.\n */\n protected async handleQuery(query: string): Promise<void> {\n if (!this.cache) throw new Error('Cache not initialized');\n\n // Try exact match first\n const exactMatch = this.cache.get(query);\n if (exactMatch) {\n if (this.ctx.json) {\n this.output.data({\n name: exactMatch.doc.name,\n title: exactMatch.doc.frontmatter?.title,\n score: exactMatch.score,\n content: exactMatch.doc.content,\n });\n } else {\n await this.outputDocContent(exactMatch.doc.content);\n }\n return;\n }\n\n // Fuzzy match\n const matches = this.cache.search(query, 5);\n if (matches.length === 0) {\n console.log(`No ${this.config.typeName} found matching: ${query}`);\n console.log(\n `Run \\`tbd ${this.config.typeNamePlural} --list\\` to see available ${this.config.typeNamePlural}.`,\n );\n return;\n }\n\n const best = matches[0]!;\n // Use PREFIX_MATCH (0.9) as threshold for high confidence\n if (best.score < SCORE_PREFIX_MATCH) {\n // Low confidence - show suggestions instead\n console.log(`No exact match for \"${query}\". Did you mean:`);\n for (const m of matches) {\n const name = m.doc.frontmatter?.title ?? m.doc.name;\n console.log(` ${name} ${pc.dim(`(score: ${m.score.toFixed(2)})`)}`);\n }\n return;\n }\n\n // Good fuzzy match - output it\n if (this.ctx.json) {\n this.output.data({\n name: best.doc.name,\n title: best.doc.frontmatter?.title,\n score: best.score,\n content: best.doc.content,\n });\n } else {\n await this.outputDocContent(best.doc.content);\n }\n }\n\n /**\n * Output document content with interactive formatting (colors, pagination) when appropriate.\n * For non-interactive output (pipes, agents), outputs plain text.\n *\n * Handles YAML frontmatter properly by rendering it separately with YAML syntax highlighting.\n */\n protected async outputDocContent(content: string): Promise<void> {\n const header = this.getAgentHeader();\n\n // Use interactive formatting (colors, pagination) only for TTY\n if (shouldUseInteractiveOutput(this.ctx)) {\n // Render content with proper frontmatter handling (YAML gets syntax highlighting)\n let output = renderMarkdownWithFrontmatter(content, this.ctx.color);\n\n // Prepend header if present (after rendering so it doesn't interfere with frontmatter)\n if (header) {\n output = header + '\\n\\n' + output;\n }\n\n await paginateOutput(output, true);\n } else {\n // Plain text output - prepend header to raw content\n let output = content;\n if (header) {\n output = header + '\\n\\n' + output;\n }\n console.log(output);\n }\n }\n\n /**\n * Handle --add mode: add an external document by URL.\n */\n protected async handleAdd(url: string, name: string): Promise<void> {\n if (!this.tbdRoot) {\n this.tbdRoot = await requireInit();\n }\n\n const { typeName, docType } = this.config;\n\n console.log(`Adding ${typeName}: ${name}`);\n console.log(` URL: ${url}`);\n\n const result = await addDoc(this.tbdRoot, { url, name, docType });\n\n if (result.usedGhCli) {\n console.log(pc.dim(' (fetched via gh CLI due to direct access restriction)'));\n }\n\n console.log(pc.green(` Added to ${result.destPath}`));\n console.log(pc.green(` Config updated with source: ${result.rawUrl}`));\n console.log('');\n console.log(`Run \\`tbd ${this.config.typeNamePlural} --list\\` to verify.`);\n }\n\n /**\n * Extract fallback text from content when no frontmatter description exists.\n */\n protected extractFallbackText(content: string): string | undefined {\n // Strip YAML frontmatter if present\n let text = content;\n if (text.startsWith('---')) {\n const endIndex = text.indexOf('---', 3);\n if (endIndex !== -1) {\n text = text.slice(endIndex + 3);\n }\n }\n\n // Strip markdown headers (# Title -> Title)\n text = text.replace(/^#+\\s*/gm, '');\n // Strip bold/italic markers\n text = text.replace(/\\*\\*|__|\\*|_/g, '');\n // Strip code blocks\n text = text.replace(/```[\\s\\S]*?```/g, '');\n // Strip inline code\n text = text.replace(/`[^`]+`/g, '');\n // Strip blockquotes\n text = text.replace(/^>\\s*/gm, '');\n\n // Condense all whitespace to single spaces and trim\n text = text.replace(/\\s+/g, ' ').trim();\n\n // Return first chunk of text (up to ~200 chars for reasonable fallback)\n if (text.length === 0) return undefined;\n return text.slice(0, 200);\n }\n\n /**\n * Print description indented, wrapped across lines.\n */\n protected printWrappedDescription(text: string, maxWidth: number, shouldTruncate: boolean): void {\n const indent = ' ';\n const availableWidth = maxWidth - indent.length;\n\n if (text.length <= availableWidth) {\n console.log(`${indent}${text}`);\n return;\n }\n\n if (shouldTruncate) {\n // Truncate to two lines max (for fallback body text)\n const firstLine = this.wrapAtWord(text, availableWidth);\n const remainder = text.slice(firstLine.length).trimStart();\n console.log(`${indent}${firstLine}`);\n if (remainder) {\n console.log(`${indent}${truncate(remainder, availableWidth)}`);\n }\n } else {\n // Wrap all lines without truncation (for title/description)\n let remaining = text;\n while (remaining.length > 0) {\n if (remaining.length <= availableWidth) {\n console.log(`${indent}${remaining}`);\n break;\n }\n const line = this.wrapAtWord(remaining, availableWidth);\n console.log(`${indent}${line}`);\n remaining = remaining.slice(line.length).trimStart();\n }\n }\n }\n\n /**\n * Wrap text at word boundary to fit within maxWidth.\n */\n protected wrapAtWord(text: string, maxWidth: number): string {\n if (text.length <= maxWidth) return text;\n const lastSpace = text.lastIndexOf(' ', maxWidth);\n if (lastSpace > 0) {\n return text.slice(0, lastSpace);\n }\n return text.slice(0, maxWidth);\n }\n}\n","/**\n * `tbd guidelines` - Find and output coding guidelines.\n *\n * Guidelines are reusable coding rules and best practices documents.\n * Give a name or description and tbd will find the matching guideline.\n */\n\nimport { Command } from 'commander';\nimport pc from 'picocolors';\n\nimport { DocCommandHandler, type DocCommandOptions } from '../lib/doc-command-handler.js';\nimport { CLIError } from '../lib/errors.js';\nimport { DEFAULT_GUIDELINES_PATHS } from '../../lib/paths.js';\nimport { truncate } from '../../lib/truncate.js';\nimport { formatDocSize } from '../../lib/format-utils.js';\nimport { getTerminalWidth } from '../lib/output.js';\n\n/**\n * Guideline categories for filtering.\n */\ntype GuidelineCategory = 'typescript' | 'python' | 'testing' | 'general';\n\n/**\n * Infer category from guideline name.\n */\nfunction inferGuidelineCategory(name: string): GuidelineCategory | undefined {\n // TypeScript guidelines\n if (name.startsWith('typescript-')) {\n return 'typescript';\n }\n\n // Python guidelines\n if (name.startsWith('python-')) {\n return 'python';\n }\n\n // Testing guidelines\n if (name.includes('tdd') || name.includes('testing') || name.includes('golden')) {\n return 'testing';\n }\n\n // General guidelines (everything else starting with general- or other general rules)\n if (\n name.startsWith('general-') ||\n name.includes('rules') ||\n name.includes('patterns') ||\n name.startsWith('backward-') ||\n name.startsWith('convex-') ||\n name.startsWith('release-') ||\n name.startsWith('writing-')\n ) {\n return 'general';\n }\n\n return undefined;\n}\n\ninterface GuidelinesOptions extends DocCommandOptions {\n category?: string;\n add?: string;\n name?: string;\n}\n\nclass GuidelinesHandler extends DocCommandHandler {\n constructor(command: Command) {\n super(command, {\n typeName: 'guideline',\n typeNamePlural: 'guidelines',\n paths: DEFAULT_GUIDELINES_PATHS,\n docType: 'guideline',\n });\n }\n\n async run(query: string | undefined, options: GuidelinesOptions): Promise<void> {\n await this.execute(async () => {\n // Add mode\n if (options.add) {\n if (!options.name) {\n throw new CLIError('--name is required when using --add');\n }\n await this.handleAdd(options.add, options.name);\n return;\n }\n\n await this.initCache();\n\n // List mode (also triggered by --category)\n if (options.list || options.category) {\n await this.handleListWithCategory(options.all, options.category);\n return;\n }\n\n // No query: show help\n if (!query) {\n await this.handleNoQuery();\n return;\n }\n\n // Query provided: try exact match first, then fuzzy\n await this.handleQuery(query);\n }, 'Failed to find guideline');\n }\n\n /**\n * Handle --list mode with optional category filtering.\n */\n private async handleListWithCategory(includeAll?: boolean, category?: string): Promise<void> {\n if (!this.cache) throw new Error('Cache not initialized');\n\n let docs = this.cache.list(includeAll);\n\n // Filter by category if specified\n if (category) {\n docs = docs.filter((d) => {\n const docCategory = inferGuidelineCategory(d.name);\n return docCategory === category;\n });\n }\n\n if (this.ctx.json) {\n this.output.data(\n docs.map((d) => ({\n name: d.name,\n title: d.frontmatter?.title,\n description: d.frontmatter?.description,\n category: inferGuidelineCategory(d.name),\n path: d.path,\n sourceDir: d.sourceDir,\n sizeBytes: d.sizeBytes,\n approxTokens: d.approxTokens,\n shadowed: this.cache!.isShadowed(d),\n })),\n );\n return;\n }\n\n if (docs.length === 0) {\n if (category) {\n console.log(`No guidelines found in category: ${category}`);\n console.log('Valid categories: typescript, python, testing, general');\n } else {\n console.log('No guidelines found.');\n console.log('Run `tbd setup --auto` to install built-in guidelines.');\n }\n return;\n }\n\n const maxWidth = getTerminalWidth();\n\n for (const doc of docs) {\n const shadowed = this.cache.isShadowed(doc);\n const name = doc.name;\n const title = doc.frontmatter?.title;\n const description = doc.frontmatter?.description ?? this.extractFallbackText(doc.content);\n\n if (shadowed) {\n const line = `${name} (${doc.sourceDir}) [shadowed]`;\n console.log(pc.dim(truncate(line, maxWidth)));\n } else {\n const sizeInfo = formatDocSize(doc.sizeBytes, doc.approxTokens);\n console.log(`${pc.bold(name)} ${pc.dim(sizeInfo)}`);\n const hasFrontmatter = title ?? doc.frontmatter?.description;\n const content =\n title && description ? `${title}: ${description}` : (title ?? description ?? '');\n if (content) {\n this.printWrappedDescription(content, maxWidth, !hasFrontmatter);\n }\n }\n }\n }\n}\n\nexport const guidelinesCommand = new Command('guidelines')\n .description('Find and output coding guidelines')\n .argument('[query]', 'Guideline name or description to search for')\n .option('--list', 'List all available guidelines')\n .option('--all', 'Include shadowed guidelines (use with --list)')\n .option('--category <category>', 'Filter by category: typescript, python, testing, general')\n .option('--add <url>', 'Add a guideline from a URL')\n .option('--name <name>', 'Name for the added guideline (required with --add)')\n .action(async (query: string | undefined, options: GuidelinesOptions, command) => {\n const handler = new GuidelinesHandler(command);\n await handler.run(query, options);\n });\n","/**\n * `tbd template` - Find and output document templates.\n *\n * Templates are reusable document templates for specs, research briefs, etc.\n * Give a name or description and tbd will find the matching template.\n */\n\nimport { Command } from 'commander';\n\nimport { DocCommandHandler, type DocCommandOptions } from '../lib/doc-command-handler.js';\nimport { CLIError } from '../lib/errors.js';\nimport { DEFAULT_TEMPLATE_PATHS } from '../../lib/paths.js';\n\nclass TemplateHandler extends DocCommandHandler {\n constructor(command: Command) {\n super(command, {\n typeName: 'template',\n typeNamePlural: 'templates',\n paths: DEFAULT_TEMPLATE_PATHS,\n docType: 'template',\n });\n }\n\n async run(query: string | undefined, options: DocCommandOptions): Promise<void> {\n await this.execute(async () => {\n // Add mode\n if (options.add) {\n if (!options.name) {\n throw new CLIError('--name is required when using --add');\n }\n await this.handleAdd(options.add, options.name);\n return;\n }\n\n await this.initCache();\n\n // List mode\n if (options.list) {\n await this.handleList(options.all);\n return;\n }\n\n // No query: show help\n if (!query) {\n await this.handleNoQuery();\n return;\n }\n\n // Query provided: try exact match first, then fuzzy\n await this.handleQuery(query);\n }, 'Failed to find template');\n }\n}\n\nexport const templateCommand = new Command('template')\n .description('Find and output document templates')\n .argument('[query]', 'Template name or description to search for')\n .option('--list', 'List all available templates')\n .option('--all', 'Include shadowed templates (use with --list)')\n .option('--add <url>', 'Add a template from a URL')\n .option('--name <name>', 'Name for the added template (required with --add)')\n .action(async (query: string | undefined, options: DocCommandOptions, command) => {\n const handler = new TemplateHandler(command);\n await handler.run(query, options);\n });\n","/**\n * `tbd setup` - Configure tbd integration with editors and tools.\n *\n * Requires a git repository. All setup artifacts (.tbd/, .claude/) are placed\n * at the git root, adjacent to .git/. Installation is always project-local —\n * there is no global/user-level install.\n *\n * Options:\n * - `tbd setup --auto` - Non-interactive setup (for agents/scripts)\n * - `tbd setup --interactive` - Interactive setup with prompts (for humans)\n * - `tbd setup --from-beads` - Migrate from Beads to tbd\n *\n * See: tbd-design.md §6.4.2 Claude Code Integration\n */\n\nimport { Command } from 'commander';\nimport { readFile, mkdir, access, rm, rename, chmod, readdir } from 'node:fs/promises';\nimport { spawnSync } from 'node:child_process';\nimport { join, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { writeFile } from 'atomically';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { CLIError } from '../lib/errors.js';\nimport { loadSkillContent } from './prime.js';\nimport { stripFrontmatter, insertAfterFrontmatter } from '../../utils/markdown-utils.js';\nimport { pathExists } from '../../utils/file-utils.js';\nimport { ensureGitignorePatterns } from '../../utils/gitignore-utils.js';\nimport { type DiagnosticResult, renderDiagnostics } from '../lib/diagnostics.js';\nimport { isValidPrefix, isRecommendedPrefix, getBeadsPrefix } from '../lib/prefix-detection.js';\nimport {\n initConfig,\n isInitialized,\n readConfig,\n readConfigWithMigration,\n findTbdRoot,\n writeConfig,\n markWelcomeSeen,\n} from '../../file/config.js';\nimport { syncDocsWithDefaults } from '../../file/doc-sync.js';\nimport { VERSION } from '../lib/version.js';\nimport {\n TBD_DIR,\n TBD_DOCS_DIR,\n WORKTREE_DIR_NAME,\n DATA_SYNC_DIR_NAME,\n DEFAULT_SHORTCUT_PATHS,\n DEFAULT_GUIDELINES_PATHS,\n TBD_SHORTCUTS_SYSTEM,\n TBD_SHORTCUTS_STANDARD,\n TBD_GUIDELINES_DIR,\n TBD_TEMPLATES_DIR,\n} from '../../lib/paths.js';\nimport { getClaudePaths, getAgentsMdPath, GLOBAL_CLAUDE_DIR } from '../../lib/integration-paths.js';\nimport { initWorktree, isInGitRepo, findGitRoot, checkWorktreeHealth } from '../../file/git.js';\nimport { DocCache, generateShortcutDirectory } from '../../file/doc-cache.js';\n\n/**\n * Get the shortcut and guidelines directory content for appending to installed skill files.\n * Always generates on-the-fly from installed shortcuts and guidelines.\n *\n * @param quiet - If true, suppress auto-sync output (default: false)\n */\nasync function getShortcutDirectory(quiet = false): Promise<string | null> {\n const cwd = process.cwd();\n\n // Try to find tbd root (may not be initialized)\n const tbdRoot = await findTbdRoot(cwd);\n if (!tbdRoot) {\n return null;\n }\n\n // Load shortcuts\n const shortcutCache = new DocCache(DEFAULT_SHORTCUT_PATHS, tbdRoot);\n await shortcutCache.load({ quiet });\n const shortcuts = shortcutCache.list();\n\n // Load guidelines\n const guidelinesCache = new DocCache(DEFAULT_GUIDELINES_PATHS, tbdRoot);\n await guidelinesCache.load({ quiet });\n const guidelines = guidelinesCache.list();\n\n // If no docs loaded, skip directory\n if (shortcuts.length === 0 && guidelines.length === 0) {\n return null;\n }\n\n return generateShortcutDirectory(shortcuts, guidelines);\n}\n\n/**\n * Get the tbd section content for AGENTS.md (Codex integration).\n * Loads from SKILL.md, strips frontmatter, and wraps in TBD INTEGRATION markers.\n *\n * @param quiet - If true, suppress auto-sync output (default: false)\n */\nasync function getCodexTbdSection(quiet = false): Promise<string> {\n const skillContent = await loadSkillContent();\n let content = stripFrontmatter(skillContent);\n const directory = await getShortcutDirectory(quiet);\n if (directory) {\n content = content.trimEnd() + '\\n\\n' + directory + '\\n';\n }\n return `<!-- BEGIN TBD INTEGRATION -->\\n${content}<!-- END TBD INTEGRATION -->\\n`;\n}\n\ninterface SetupClaudeOptions {\n check?: boolean;\n remove?: boolean;\n}\n\ninterface SetupCodexOptions {\n check?: boolean;\n remove?: boolean;\n}\n\n/**\n * Script to ensure tbd CLI is installed and run tbd prime.\n * Installed to project .claude/scripts/tbd-session.sh.\n * Runs on SessionStart and PreCompact to ensure tbd is available and provide orientation.\n *\n * Usage:\n * tbd-session.sh # Ensure tbd + run tbd prime\n * tbd-session.sh --brief # Ensure tbd + run tbd prime --brief (for PreCompact)\n */\nconst TBD_SESSION_SCRIPT = `#!/bin/bash\n# Ensure tbd CLI is installed and run tbd prime for Claude Code sessions\n# Installed by: tbd setup --auto\n# This script runs on SessionStart and PreCompact\n\n# Get npm global bin directory (if npm is available)\nNPM_GLOBAL_BIN=\"\"\nif command -v npm &> /dev/null; then\n NPM_PREFIX=$(npm config get prefix 2>/dev/null)\n if [ -n \"$NPM_PREFIX\" ] && [ -d \"$NPM_PREFIX/bin\" ]; then\n NPM_GLOBAL_BIN=\"$NPM_PREFIX/bin\"\n fi\nfi\n\n# Add common binary locations to PATH (persists for entire script)\n# Include npm global bin if found\nexport PATH=\"$NPM_GLOBAL_BIN:$HOME/.local/bin:$HOME/bin:/usr/local/bin:$PATH\"\n\n# Function to ensure tbd is available\nensure_tbd() {\n # Check if tbd is already installed\n if command -v tbd &> /dev/null; then\n return 0\n fi\n\n echo \"[tbd] CLI not found, installing...\"\n\n # Try npm first (most common for Node.js tools)\n if command -v npm &> /dev/null; then\n echo \"[tbd] Installing via npm...\"\n npm install -g get-tbd 2>/dev/null || {\n # If global install fails (permissions), try local install\n echo \"[tbd] Global npm install failed, trying user install...\"\n mkdir -p ~/.local/bin\n npm install --prefix ~/.local get-tbd\n # Create symlink if needed\n if [ -f ~/.local/node_modules/.bin/tbd ]; then\n ln -sf ~/.local/node_modules/.bin/tbd ~/.local/bin/tbd\n fi\n }\n elif command -v pnpm &> /dev/null; then\n echo \"[tbd] Installing via pnpm...\"\n pnpm add -g get-tbd\n elif command -v yarn &> /dev/null; then\n echo \"[tbd] Installing via yarn...\"\n yarn global add get-tbd\n else\n echo \"[tbd] ERROR: No package manager found (npm, pnpm, or yarn required)\"\n echo \"[tbd] Please install Node.js and npm, then run: npm install -g get-tbd\"\n return 1\n fi\n\n # Verify installation\n if command -v tbd &> /dev/null; then\n echo \"[tbd] Successfully installed to $(which tbd)\"\n return 0\n else\n echo \"[tbd] WARNING: tbd installed but not found in PATH\"\n echo \"[tbd] Checking common locations...\"\n # Try to find and add to path (include npm global bin)\n for dir in \"$NPM_GLOBAL_BIN\" ~/.local/bin ~/.local/node_modules/.bin /usr/local/bin; do\n if [ -n \"$dir\" ] && [ -x \"$dir/tbd\" ]; then\n export PATH=\"$dir:$PATH\"\n echo \"[tbd] Found at $dir/tbd\"\n return 0\n fi\n done\n echo \"[tbd] Could not locate tbd after installation\"\n return 1\n fi\n}\n\n# Main\nensure_tbd || exit 1\n\n# Run tbd prime with any passed arguments (e.g., --brief for PreCompact)\ntbd prime \"$@\"\n`;\n\n/**\n * Claude Code session hooks configuration.\n * Always uses project-relative paths so hooks work in any environment\n * (local dev, Claude Code Cloud, etc.).\n */\nconst CLAUDE_SESSION_HOOKS = {\n hooks: {\n SessionStart: [\n {\n matcher: '',\n hooks: [{ type: 'command', command: 'bash .claude/scripts/tbd-session.sh' }],\n },\n ],\n PreCompact: [\n {\n matcher: '',\n hooks: [{ type: 'command', command: 'bash .claude/scripts/tbd-session.sh --brief' }],\n },\n ],\n },\n};\n\n/**\n * Claude Code project-local hooks configuration (installed to .claude/settings.json)\n * PostToolUse hook reminds about tbd sync after git push\n */\nconst CLAUDE_PROJECT_HOOKS = {\n hooks: {\n PostToolUse: [\n {\n matcher: 'Bash',\n hooks: [\n {\n type: 'command',\n command: '\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/tbd-closing-reminder.sh',\n },\n ],\n },\n ],\n },\n};\n\n/**\n * Script to remind about close protocol after git push\n */\nconst TBD_CLOSE_PROTOCOL_SCRIPT = `#!/bin/bash\n# Remind about close protocol after git push\n# Installed by: tbd setup claude\n\ninput=$(cat)\ncommand=$(echo \"$input\" | jq -r '.tool_input.command // empty')\n\n# Check if this is a git push command and .tbd exists\nif [[ \"$command\" == git\\\\ push* ]] || [[ \"$command\" == *\"&& git push\"* ]] || [[ \"$command\" == *\"; git push\"* ]]; then\n if [ -d \".tbd\" ]; then\n tbd closing\n fi\nfi\n\nexit 0\n`;\n\n/**\n * SessionStart hook entry for the gh CLI ensure script.\n * Installed to project-local .claude/settings.json when use_gh_cli is true.\n */\nconst GH_CLI_HOOK_ENTRY = {\n matcher: '',\n hooks: [{ type: 'command', command: 'bash .claude/scripts/ensure-gh-cli.sh', timeout: 120 }],\n};\n\n/**\n * Command string used to identify the gh CLI hook entry in settings.json.\n */\nconst GH_CLI_HOOK_COMMAND_PATTERN = 'ensure-gh-cli';\n\n/**\n * Load a bundled script from dist/docs/install/ (or dev fallback).\n * Used to read real .sh files that are copied into the npm package at build time.\n */\nasync function loadBundledScript(name: string): Promise<string> {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n // Flat bundle: dist/docs/install/<name> (tsdown produces flat dist/)\n const flatBundlePath = join(__dirname, 'docs', 'install', name);\n // Nested bundle: dist/cli/commands/../../docs/install/<name>\n const nestedBundlePath = join(__dirname, '..', 'docs', 'install', name);\n // Dev fallback: packages/tbd/docs/install/<name>\n const devPath = join(__dirname, '..', '..', '..', 'docs', 'install', name);\n for (const p of [flatBundlePath, nestedBundlePath, devPath]) {\n try {\n return await readFile(p, 'utf-8');\n } catch {\n continue;\n }\n }\n throw new Error(`Bundled script not found: ${name}`);\n}\n\n/**\n * AGENTS.md integration markers for Codex/Factory.ai\n * Content is now generated dynamically from SKILL.md via getCodexTbdSection()\n */\nconst CODEX_BEGIN_MARKER = '<!-- BEGIN TBD INTEGRATION -->';\nconst CODEX_END_MARKER = '<!-- END TBD INTEGRATION -->';\n\n/**\n * Generate a new AGENTS.md file with tbd integration.\n *\n * @param quiet - If true, suppress auto-sync output (default: false)\n */\nasync function getCodexNewAgentsFile(quiet = false): Promise<string> {\n const tbdSection = await getCodexTbdSection(quiet);\n return `# Project Instructions for AI Agents\n\nThis file provides instructions and context for AI coding agents working on this project.\n\n${tbdSection}\n## Build & Test\n\n_Add your build and test commands here_\n\n\\`\\`\\`bash\n# Example:\n# npm install\n# npm test\n\\`\\`\\`\n\n## Architecture Overview\n\n_Add a brief overview of your project architecture_\n\n## Conventions & Patterns\n\n_Add your project-specific conventions here_\n`;\n}\n\n/**\n * Legacy script patterns to clean up from .claude/scripts/\n * These were used in older versions of tbd before hooks moved to `tbd prime`\n */\nconst LEGACY_TBD_SCRIPTS = ['setup-tbd.sh', 'ensure-tbd-cli.sh', 'ensure-tbd.sh', 'tbd-setup.sh'];\n\n/**\n * Patterns to identify legacy tbd hooks that should be removed.\n * These patterns match old-style commands that are no longer used.\n */\nconst LEGACY_TBD_HOOK_PATTERNS = [\n /\\.claude\\/scripts\\/.*tbd/i, // Any tbd-related script in .claude/scripts/\n /tbd\\s+setup\\s+claude/i, // Old command: tbd setup claude\n /setup-tbd\\.sh/i, // Old script name\n /ensure-tbd/i, // Old script names\n];\n\nclass SetupClaudeHandler extends BaseCommand {\n private projectDir: string | undefined;\n\n setProjectDir(dir: string): void {\n this.projectDir = dir;\n }\n\n async run(options: SetupClaudeOptions): Promise<void> {\n const cwd = this.projectDir ?? process.cwd();\n const claudePaths = getClaudePaths(cwd);\n\n if (options.check) {\n await this.checkClaudeSetup(claudePaths.skill);\n return;\n }\n\n if (options.remove) {\n await this.removeClaudeSetup(claudePaths.skill);\n return;\n }\n\n await this.installClaudeSetup(claudePaths.skill);\n }\n\n private async checkClaudeSetup(skillPath: string): Promise<void> {\n const cwd = this.projectDir ?? process.cwd();\n let sessionScriptInstalled = false;\n let sessionStartHook = false;\n let preCompactHook = false;\n let postToolUseHook = false;\n let hookScriptInstalled = false;\n let skillInstalled = false;\n\n // All hooks and scripts are project-local in .claude/\n const projectSettingsPath = join(cwd, '.claude', 'settings.json');\n const sessionScript = join(cwd, '.claude', 'scripts', 'tbd-session.sh');\n const hookScriptPath = join(cwd, '.claude', 'hooks', 'tbd-closing-reminder.sh');\n\n // Check for tbd-session.sh script\n try {\n await access(sessionScript);\n sessionScriptInstalled = true;\n } catch {\n // Script doesn't exist\n }\n\n // Check hooks in project settings\n try {\n await access(projectSettingsPath);\n const content = await readFile(projectSettingsPath, 'utf-8');\n const settings = JSON.parse(content) as Record<string, unknown>;\n const hooks = settings.hooks as Record<string, unknown> | undefined;\n\n if (hooks) {\n const sessionStart = hooks.SessionStart as { hooks?: { command?: string }[] }[];\n const preCompact = hooks.PreCompact as { hooks?: { command?: string }[] }[];\n const postToolUse = hooks.PostToolUse as { hooks?: { command?: string }[] }[];\n\n sessionStartHook =\n sessionStart?.some((h) =>\n h.hooks?.some(\n (hook) =>\n (hook.command?.includes('tbd prime') ?? false) ||\n (hook.command?.includes('tbd-session.sh') ?? false),\n ),\n ) ?? false;\n\n preCompactHook =\n preCompact?.some((h) =>\n h.hooks?.some(\n (hook) =>\n (hook.command?.includes('tbd prime') ?? false) ||\n (hook.command?.includes('tbd-session.sh') ?? false),\n ),\n ) ?? false;\n\n postToolUseHook =\n postToolUse?.some((h) =>\n h.hooks?.some((hook) => hook.command?.includes('tbd-closing-reminder')),\n ) ?? false;\n }\n } catch {\n // Project settings file doesn't exist\n }\n\n try {\n await access(hookScriptPath);\n hookScriptInstalled = true;\n } catch {\n // Hook script doesn't exist\n }\n\n const sessionHooksInstalled = sessionStartHook && preCompactHook && sessionScriptInstalled;\n const projectHooksInstalled = postToolUseHook && hookScriptInstalled;\n\n // Check skill file in project\n try {\n await access(skillPath);\n skillInstalled = true;\n } catch {\n // Skill file doesn't exist\n }\n\n const fullyInstalled = sessionHooksInstalled && projectHooksInstalled && skillInstalled;\n\n // Build diagnostic results for text output\n const diagnostics: DiagnosticResult[] = [];\n const settingsRelPath = '.claude/settings.json';\n\n // Session hooks diagnostic\n if (sessionHooksInstalled) {\n diagnostics.push({\n name: 'Session hooks',\n status: 'ok',\n message: 'SessionStart, PreCompact',\n path: settingsRelPath,\n });\n } else if (sessionStartHook || preCompactHook) {\n diagnostics.push({\n name: 'Session hooks',\n status: 'warn',\n message: 'partially configured',\n path: settingsRelPath,\n suggestion: 'Run: tbd setup --auto',\n });\n } else {\n diagnostics.push({\n name: 'Session hooks',\n status: 'warn',\n message: 'not configured',\n path: settingsRelPath,\n suggestion: 'Run: tbd setup --auto',\n });\n }\n\n // Project hooks diagnostic\n if (projectHooksInstalled) {\n diagnostics.push({\n name: 'Project hooks',\n status: 'ok',\n message: 'PostToolUse sync reminder',\n path: settingsRelPath,\n });\n } else if (postToolUseHook || hookScriptInstalled) {\n diagnostics.push({\n name: 'Project hooks',\n status: 'warn',\n message: 'partially configured',\n path: settingsRelPath,\n suggestion: 'Run: tbd setup --auto',\n });\n } else {\n diagnostics.push({\n name: 'Project hooks',\n status: 'warn',\n message: 'not configured',\n path: settingsRelPath,\n suggestion: 'Run: tbd setup --auto',\n });\n }\n\n // Skill file diagnostic\n const skillRelPath = '.claude/skills/tbd/SKILL.md';\n if (skillInstalled) {\n diagnostics.push({\n name: 'Skill file',\n status: 'ok',\n path: skillRelPath,\n });\n } else {\n diagnostics.push({\n name: 'Skill file',\n status: 'warn',\n message: 'not found',\n path: skillRelPath,\n suggestion: 'Run: tbd setup --auto',\n });\n }\n\n this.output.data(\n {\n installed: fullyInstalled,\n sessionHooks: {\n installed: sessionHooksInstalled,\n sessionStart: sessionStartHook,\n preCompact: preCompactHook,\n script: sessionScriptInstalled,\n path: projectSettingsPath,\n },\n projectHooks: {\n installed: projectHooksInstalled,\n postToolUse: postToolUseHook,\n hookScript: hookScriptInstalled,\n path: projectSettingsPath,\n },\n skill: { installed: skillInstalled, path: skillPath },\n },\n () => {\n const colors = this.output.getColors();\n renderDiagnostics(diagnostics, colors);\n },\n );\n }\n\n private async removeClaudeSetup(skillPath: string): Promise<void> {\n const cwd = this.projectDir ?? process.cwd();\n const claudePaths = getClaudePaths(cwd);\n let removedHooks = false;\n let removedScripts = false;\n let removedSkill = false;\n\n // Remove hooks from project .claude/settings.json\n try {\n await access(claudePaths.settings);\n const content = await readFile(claudePaths.settings, 'utf-8');\n const settings = JSON.parse(content) as Record<string, unknown>;\n\n if (settings.hooks) {\n const hooks = settings.hooks as Record<string, unknown>;\n\n // Remove all tbd hooks (SessionStart, PreCompact, PostToolUse)\n const filterTbdHooks = (arr: { hooks?: { command?: string }[] }[] | undefined) => {\n if (!arr) return undefined;\n return arr.filter(\n (h) =>\n !h.hooks?.some(\n (hook) =>\n (hook.command?.includes('tbd-closing-reminder') ?? false) ||\n (hook.command?.includes('tbd-session.sh') ?? false) ||\n (hook.command?.includes('tbd prime') ?? false),\n ),\n );\n };\n\n for (const hookType of ['PostToolUse', 'SessionStart', 'PreCompact'] as const) {\n const filtered = filterTbdHooks(hooks[hookType] as { hooks?: { command?: string }[] }[]);\n if (filtered?.length === 0) delete hooks[hookType];\n else if (filtered) hooks[hookType] = filtered;\n }\n\n if (Object.keys(hooks).length === 0) {\n delete settings.hooks;\n }\n\n await writeFile(claudePaths.settings, JSON.stringify(settings, null, 2) + '\\n');\n removedHooks = true;\n }\n } catch {\n // Project settings file doesn't exist\n }\n\n // Remove hook script\n try {\n await rm(claudePaths.closingReminder);\n removedHooks = true;\n } catch {\n // Hook script doesn't exist\n }\n\n // Remove tbd scripts from project\n try {\n await rm(claudePaths.sessionScript);\n removedScripts = true;\n } catch {\n // Script doesn't exist\n }\n\n // Remove skill file from project\n try {\n await rm(skillPath);\n removedSkill = true;\n } catch {\n // Skill file doesn't exist\n }\n\n // Report what was removed\n if (removedHooks || removedScripts) {\n this.output.success('Removed hooks and scripts');\n } else {\n this.output.info('No hooks to remove');\n }\n\n if (removedSkill) {\n this.output.success('Removed skill file');\n } else {\n this.output.info('No skill file to remove');\n }\n }\n\n private async installClaudeSetup(skillPath: string): Promise<void> {\n const cwd = this.projectDir ?? process.cwd();\n const claudePaths = getClaudePaths(cwd);\n\n if (\n this.checkDryRun('Would install Claude Code hooks and skill file', {\n settingsPath: claudePaths.settings,\n skillPath,\n })\n ) {\n return;\n }\n\n try {\n // Always install to project .claude/ directory (create if needed).\n // This avoids confusion between global vs project-level settings and\n // ensures hooks work in any environment (local dev, Claude Code Cloud, etc.).\n await mkdir(claudePaths.dir, { recursive: true });\n\n // Read existing project settings\n let settings: Record<string, unknown> = {};\n try {\n await access(claudePaths.settings);\n const content = await readFile(claudePaths.settings, 'utf-8');\n settings = JSON.parse(content) as Record<string, unknown>;\n } catch {\n // File doesn't exist, start fresh\n }\n\n // Merge session hooks (SessionStart, PreCompact) - append without overwriting\n const existingHooks = (settings.hooks as Record<string, unknown[]>) || {};\n const newHooks = CLAUDE_SESSION_HOOKS.hooks as Record<string, unknown[]>;\n const mergedHooks: Record<string, unknown[]> = { ...existingHooks };\n\n for (const [hookType, hookEntries] of Object.entries(newHooks)) {\n if (mergedHooks[hookType]) {\n // Filter out any existing tbd-session.sh hooks before adding new ones\n const filtered = (mergedHooks[hookType] as { hooks?: { command?: string }[] }[]).filter(\n (entry) => !entry.hooks?.some((h) => h.command?.includes('tbd-session.sh')),\n );\n mergedHooks[hookType] = [...filtered, ...hookEntries];\n } else {\n mergedHooks[hookType] = hookEntries;\n }\n }\n\n // Merge PostToolUse hooks\n const projectHooks = CLAUDE_PROJECT_HOOKS.hooks as Record<string, unknown[]>;\n for (const [hookType, hookEntries] of Object.entries(projectHooks)) {\n mergedHooks[hookType] ??= hookEntries;\n }\n\n settings.hooks = mergedHooks;\n\n // Manage gh CLI SessionStart hook based on use_gh_cli config setting\n const useGhCli = await this.getUseGhCliSetting();\n const finalHooks = settings.hooks as Record<string, unknown>;\n let sessionStartEntries = (finalHooks.SessionStart as Record<string, unknown>[]) || [];\n\n if (useGhCli) {\n // Add gh CLI hook if not already present\n const hasGhCliHook = sessionStartEntries.some((h: Record<string, unknown>) =>\n (h.hooks as { command?: string }[])?.some((hook) =>\n hook.command?.includes(GH_CLI_HOOK_COMMAND_PATTERN),\n ),\n );\n if (!hasGhCliHook) {\n sessionStartEntries = [...sessionStartEntries, GH_CLI_HOOK_ENTRY];\n }\n\n // Install the script file\n await mkdir(claudePaths.scriptsDir, { recursive: true });\n const ghScriptContent = await loadBundledScript('ensure-gh-cli.sh');\n await writeFile(claudePaths.ghCliScript, ghScriptContent);\n await chmod(claudePaths.ghCliScript, 0o755);\n this.output.success('Installed gh CLI setup script');\n } else {\n // Remove gh CLI hook entries\n sessionStartEntries = sessionStartEntries.filter(\n (h: Record<string, unknown>) =>\n !(h.hooks as { command?: string }[])?.some((hook) =>\n hook.command?.includes(GH_CLI_HOOK_COMMAND_PATTERN),\n ),\n );\n\n // Remove the script file\n try {\n await rm(claudePaths.ghCliScript);\n this.output.success('Removed gh CLI setup script');\n } catch {\n // Script doesn't exist, ignore\n }\n }\n\n if (sessionStartEntries.length > 0) {\n finalHooks.SessionStart = sessionStartEntries;\n } else {\n delete finalHooks.SessionStart;\n }\n\n // Write all hooks to project settings in a single write\n await writeFile(claudePaths.settings, JSON.stringify(settings, null, 2) + '\\n');\n this.output.success('Installed hooks to .claude/settings.json');\n\n // Install tbd-session.sh script\n await mkdir(claudePaths.scriptsDir, { recursive: true });\n await writeFile(claudePaths.sessionScript, TBD_SESSION_SCRIPT);\n await chmod(claudePaths.sessionScript, 0o755);\n\n // Clean up legacy scripts in project\n const legacyScripts = ['ensure-tbd-cli.sh', 'setup-tbd.sh', 'ensure-tbd.sh'];\n for (const script of legacyScripts) {\n try {\n await rm(join(claudePaths.scriptsDir, script));\n } catch {\n // Script doesn't exist, ignore\n }\n }\n\n this.output.success('Installed tbd session script to .claude/scripts/');\n\n // Add .claude/.gitignore to ignore backup files\n // NOTE: Pattern re-addition is intentional - see comment in initializeTbd\n const claudeGitignorePath = join(claudePaths.dir, '.gitignore');\n const claudeGitignoreResult = await ensureGitignorePatterns(claudeGitignorePath, [\n '# Backup files',\n '*.bak',\n ]);\n if (claudeGitignoreResult.created) {\n this.output.success('Created .claude/.gitignore');\n } else if (claudeGitignoreResult.added.length > 0) {\n this.output.success('Updated .claude/.gitignore');\n }\n // else: file is up-to-date, no message needed\n\n // Install hook script\n await mkdir(claudePaths.hooksDir, { recursive: true });\n await writeFile(claudePaths.closingReminder, TBD_CLOSE_PROTOCOL_SCRIPT);\n await chmod(claudePaths.closingReminder, 0o755);\n this.output.success('Installed sync reminder hook script');\n\n // Install skill file in project (with shortcut directory appended)\n await mkdir(dirname(skillPath), { recursive: true });\n let skillContent = await loadSkillContent();\n const directory = await getShortcutDirectory(this.ctx.quiet);\n if (directory) {\n skillContent = skillContent.trimEnd() + '\\n\\n' + directory;\n }\n // Insert DO NOT EDIT marker after frontmatter (formatted to match flowmark output)\n const markerComment =\n \"<!-- DO NOT EDIT: Generated by tbd setup.\\nRun 'tbd setup' to update.\\n-->\";\n skillContent = insertAfterFrontmatter(skillContent, markerComment);\n // Ensure file ends with newline\n skillContent = skillContent.trimEnd() + '\\n';\n await writeFile(skillPath, skillContent);\n this.output.success('Installed skill file');\n this.output.info(` ${skillPath}`);\n\n this.output.info('');\n this.output.info('What was installed:');\n this.output.info(' - Session hooks: SessionStart and PreCompact run `tbd prime`');\n this.output.info(' - Session script: .claude/scripts/tbd-session.sh');\n this.output.info(' - Project hooks: PostToolUse reminds about `tbd sync` after git push');\n this.output.info(' - Project skill: .claude/skills/tbd/SKILL.md');\n } catch (error) {\n throw new CLIError(`Failed to install: ${(error as Error).message}`);\n }\n }\n\n /**\n * Read the use_gh_cli setting from config. Defaults to true if not set or if\n * tbd is not yet initialized (so fresh setup installs gh CLI by default).\n */\n private async getUseGhCliSetting(): Promise<boolean> {\n try {\n const tbdRoot = await findTbdRoot(process.cwd());\n if (!tbdRoot) return true;\n const config = await readConfig(tbdRoot);\n return config.settings.use_gh_cli ?? true;\n } catch {\n return true;\n }\n }\n}\n\nclass SetupCodexHandler extends BaseCommand {\n private projectDir: string | undefined;\n\n setProjectDir(dir: string): void {\n this.projectDir = dir;\n }\n\n async run(options: SetupCodexOptions): Promise<void> {\n const cwd = this.projectDir ?? process.cwd();\n const agentsPath = join(cwd, 'AGENTS.md');\n\n if (options.check) {\n await this.checkCodexSetup(agentsPath);\n return;\n }\n\n if (options.remove) {\n await this.removeCodexSection(agentsPath);\n return;\n }\n\n await this.installCodexSection(agentsPath);\n }\n\n private async checkCodexSetup(agentsPath: string): Promise<void> {\n const agentsRelPath = './AGENTS.md';\n try {\n await access(agentsPath);\n const content = await readFile(agentsPath, 'utf-8');\n\n if (content.includes(CODEX_BEGIN_MARKER)) {\n const diagnostic: DiagnosticResult = {\n name: 'AGENTS.md',\n status: 'ok',\n message: 'tbd section found',\n path: agentsRelPath,\n };\n this.output.data({ installed: true, path: agentsPath, hastbdSection: true }, () => {\n const colors = this.output.getColors();\n renderDiagnostics([diagnostic], colors);\n });\n } else {\n const diagnostic: DiagnosticResult = {\n name: 'AGENTS.md',\n status: 'warn',\n message: 'exists but no tbd section',\n path: agentsRelPath,\n suggestion: 'Run: tbd setup --auto',\n };\n this.output.data({ installed: false, path: agentsPath, hastbdSection: false }, () => {\n const colors = this.output.getColors();\n renderDiagnostics([diagnostic], colors);\n });\n }\n } catch {\n const diagnostic: DiagnosticResult = {\n name: 'AGENTS.md',\n status: 'warn',\n message: 'not found',\n path: agentsRelPath,\n suggestion: 'Run: tbd setup --auto',\n };\n this.output.data({ installed: false, expectedPath: agentsPath }, () => {\n const colors = this.output.getColors();\n renderDiagnostics([diagnostic], colors);\n });\n }\n }\n\n private async removeCodexSection(agentsPath: string): Promise<void> {\n try {\n await access(agentsPath);\n const content = await readFile(agentsPath, 'utf-8');\n\n if (!content.includes(CODEX_BEGIN_MARKER)) {\n this.output.info('No tbd section found in AGENTS.md');\n return;\n }\n\n const newContent = this.removetbdSection(content);\n const trimmed = newContent.trim();\n\n if (trimmed === '' || trimmed === '# Project Instructions for AI Agents') {\n // File is empty or only has the default header, remove it\n await rm(agentsPath);\n this.output.success('Removed AGENTS.md (file was empty after removing tbd section)');\n } else {\n await writeFile(agentsPath, newContent);\n this.output.success('Removed tbd section from AGENTS.md');\n }\n } catch {\n this.output.info('AGENTS.md not found');\n }\n }\n\n private async installCodexSection(agentsPath: string): Promise<void> {\n if (this.checkDryRun('Would create/update AGENTS.md', { path: agentsPath })) {\n return;\n }\n\n try {\n let existingContent = '';\n try {\n await access(agentsPath);\n existingContent = await readFile(agentsPath, 'utf-8');\n } catch {\n // File doesn't exist\n }\n\n let newContent: string;\n\n const tbdSection = await getCodexTbdSection(this.ctx.quiet);\n\n if (existingContent) {\n if (existingContent.includes(CODEX_BEGIN_MARKER)) {\n // Update existing section\n newContent = this.updatetbdSection(existingContent, tbdSection);\n await writeFile(agentsPath, newContent);\n this.output.success('Updated existing tbd section in AGENTS.md');\n } else {\n // Append section to existing file\n newContent = existingContent + '\\n\\n' + tbdSection;\n await writeFile(agentsPath, newContent);\n this.output.success('Added tbd section to existing AGENTS.md');\n }\n } else {\n // Create new file\n const newAgentsFile = await getCodexNewAgentsFile(this.ctx.quiet);\n await writeFile(agentsPath, newAgentsFile);\n this.output.success('Created new AGENTS.md with tbd integration');\n }\n\n this.output.info(` File: ${agentsPath}`);\n this.output.info('');\n this.output.info('Codex and other AGENTS.md-compatible tools will automatically');\n this.output.info('read this file on session start.');\n } catch (error) {\n throw new CLIError(`Failed to update AGENTS.md: ${(error as Error).message}`);\n }\n }\n\n private updatetbdSection(content: string, tbdSection: string): string {\n const startIdx = content.indexOf(CODEX_BEGIN_MARKER);\n const endIdx = content.indexOf(CODEX_END_MARKER);\n\n if (startIdx === -1 || endIdx === -1 || startIdx > endIdx) {\n // Markers not found or invalid, append instead\n return content + '\\n\\n' + tbdSection;\n }\n\n // Find the end of the end marker line\n let endOfEndMarker = endIdx + CODEX_END_MARKER.length;\n const nextNewline = content.indexOf('\\n', endOfEndMarker);\n if (nextNewline !== -1) {\n endOfEndMarker = nextNewline + 1;\n }\n\n return content.slice(0, startIdx) + tbdSection + content.slice(endOfEndMarker);\n }\n\n private removetbdSection(content: string): string {\n const startIdx = content.indexOf(CODEX_BEGIN_MARKER);\n const endIdx = content.indexOf(CODEX_END_MARKER);\n\n if (startIdx === -1 || endIdx === -1 || startIdx > endIdx) {\n return content;\n }\n\n // Find the end of the end marker line\n let endOfEndMarker = endIdx + CODEX_END_MARKER.length;\n const nextNewline = content.indexOf('\\n', endOfEndMarker);\n if (nextNewline !== -1) {\n endOfEndMarker = nextNewline + 1;\n }\n\n // Also remove leading blank lines before the section\n let trimStart = startIdx;\n while (trimStart > 0 && (content[trimStart - 1] === '\\n' || content[trimStart - 1] === '\\r')) {\n trimStart--;\n }\n\n return content.slice(0, trimStart) + content.slice(endOfEndMarker);\n }\n}\n\n// ============================================================================\n// Setup Default Handler (for --auto and --interactive modes)\n// ============================================================================\n\ninterface SetupDefaultOptions {\n auto?: boolean;\n interactive?: boolean;\n fromBeads?: boolean;\n prefix?: string;\n force?: boolean;\n ghCli?: boolean; // Commander sets to false when --no-gh-cli is passed\n}\n\n/**\n * Default handler for `tbd setup` with --auto or --interactive flags.\n *\n * This implements the unified onboarding flow:\n * - `tbd setup --auto`: Non-interactive setup with smart defaults (for agents)\n * - `tbd setup --interactive`: Interactive setup with prompts (for humans)\n *\n * Decision tree:\n * 1. Not in git repo → Error (git init first)\n * 2. Resolve to git root → All paths relative to .git/ parent\n * 3. Has .tbd/ → Already initialized, check/update integrations\n * 4. Has .beads/ → Beads migration flow\n * 5. Fresh repo → Initialize + configure integrations\n */\nclass SetupDefaultHandler extends BaseCommand {\n private cmd: Command;\n\n constructor(command: Command) {\n super(command);\n this.cmd = command;\n }\n\n async run(options: SetupDefaultOptions): Promise<void> {\n const colors = this.output.getColors();\n const cwd = process.cwd();\n\n // Determine mode\n const isAutoMode = options.auto === true;\n // Note: options.interactive will be used when we add interactive prompts\n\n // Header\n console.log(colors.bold('tbd: Git-native issue tracking for AI agents and humans'));\n console.log('');\n\n // Check if in git repo and resolve to git root\n const inGitRepo = await isInGitRepo(cwd);\n if (!inGitRepo) {\n throw new CLIError('Not a git repository. Run `git init` first.');\n }\n\n // Resolve to git root so .tbd/ and .claude/ are always adjacent to .git/\n const gitRoot = await findGitRoot(cwd);\n if (!gitRoot) {\n throw new CLIError('Could not determine git repository root.');\n }\n\n // Use git root as the working directory for all setup operations\n const projectDir = gitRoot;\n\n // Check current state\n const hasTbd = await isInitialized(projectDir);\n const hasBeads = await pathExists(join(projectDir, '.beads'));\n\n // Validate --from-beads flag requires .beads/ directory\n if (options.fromBeads && !hasBeads) {\n throw new CLIError(\n 'The --from-beads flag requires a .beads/ directory to migrate from.\\n' +\n 'For fresh setup, use: tbd setup --auto --prefix=<name>',\n );\n }\n\n console.log('Checking repository...');\n console.log(` ${colors.success('✓')} Git repository detected`);\n\n if (hasTbd) {\n // Already initialized flow - check for migrations\n const { config, migrated, changes } = await readConfigWithMigration(projectDir);\n console.log(` ${colors.success('✓')} tbd initialized (prefix: ${config.display.id_prefix})`);\n\n // Apply --no-gh-cli flag to config if specified\n let needsConfigWrite = migrated;\n if (options.ghCli === false && config.settings.use_gh_cli !== false) {\n config.settings.use_gh_cli = false;\n needsConfigWrite = true;\n }\n\n // Persist config if migrated or --no-gh-cli was applied\n if (needsConfigWrite) {\n await writeConfig(projectDir, config);\n if (migrated) {\n console.log(` ${colors.success('✓')} Config migrated to latest format`);\n for (const change of changes) {\n console.log(` ${colors.dim(change)}`);\n }\n }\n if (options.ghCli === false) {\n console.log(` ${colors.success('✓')} Disabled gh CLI auto-setup`);\n }\n }\n\n console.log('');\n await this.handleAlreadyInitialized(projectDir, isAutoMode);\n } else if (hasBeads || options.fromBeads) {\n // Beads migration flow\n console.log(` ${colors.dim('✗')} tbd not initialized`);\n console.log(` ${colors.warn('!')} Beads detected (.beads/ directory found)`);\n console.log('');\n await this.handleBeadsMigration(projectDir, isAutoMode, options);\n } else {\n // Fresh setup flow\n console.log(` ${colors.dim('✗')} tbd not initialized`);\n console.log('');\n await this.handleFreshSetup(projectDir, isAutoMode, options);\n }\n }\n\n private async handleAlreadyInitialized(projectDir: string, _isAutoMode: boolean): Promise<void> {\n const colors = this.output.getColors();\n\n // Ensure .tbd/.gitignore is up-to-date (may have new patterns from newer versions)\n const tbdGitignoreResult = await ensureGitignorePatterns(\n join(projectDir, TBD_DIR, '.gitignore'),\n [\n '# Synced documentation cache (regenerated by tbd sync --docs)',\n 'docs/',\n '',\n '# Hidden worktree for tbd-sync branch',\n `${WORKTREE_DIR_NAME}/`,\n '',\n '# Data sync directory (only exists in worktree)',\n `${DATA_SYNC_DIR_NAME}/`,\n '',\n '# Local state',\n 'state.yml',\n '',\n '# Migration backups (local only, not synced)',\n 'backups/',\n '',\n '# Temporary files',\n '*.tmp',\n '*.temp',\n '',\n '# workspaces/ stores state (including outbox) committed to the working branch',\n '!workspaces/',\n ],\n );\n if (tbdGitignoreResult.created) {\n console.log(` ${colors.success('✓')} Created .tbd/.gitignore`);\n } else if (tbdGitignoreResult.added.length > 0) {\n console.log(` ${colors.success('✓')} Updated .tbd/.gitignore with new patterns`);\n }\n\n // Ensure .tbd/.gitattributes has merge protection for outbox ids.yml\n // Placed inside .tbd/ so all tbd settings are self-contained in one directory.\n // Git supports .gitattributes in subdirectories — patterns are relative to that directory.\n const gitattributesResult = await ensureGitignorePatterns(\n join(projectDir, TBD_DIR, '.gitattributes'),\n [\n '# Protect ID mappings from merge deletion (always keep all rows)',\n '**/mappings/ids.yml merge=union',\n ],\n );\n if (gitattributesResult.created) {\n console.log(` ${colors.success('✓')} Created .tbd/.gitattributes (merge protection)`);\n } else if (gitattributesResult.added.length > 0) {\n console.log(` ${colors.success('✓')} Updated .tbd/.gitattributes (merge protection)`);\n }\n\n console.log('Checking integrations...');\n\n // Use SetupAutoHandler to configure integrations\n const autoHandler = new SetupAutoHandler(this.cmd);\n await autoHandler.run(projectDir);\n\n console.log('');\n console.log(colors.success('All set!'));\n }\n\n private async handleBeadsMigration(\n cwd: string,\n isAutoMode: boolean,\n options: SetupDefaultOptions,\n ): Promise<void> {\n const colors = this.output.getColors();\n\n if (isAutoMode) {\n console.log(` ${colors.warn('!')} Beads detected - auto-migrating`);\n console.log('');\n }\n\n // Get prefix from beads config or use provided --prefix\n const beadsPrefix = await getBeadsPrefix(cwd);\n const prefix = options.prefix ?? beadsPrefix;\n\n if (!prefix) {\n throw new CLIError(\n 'Could not read prefix from beads config.\\n' +\n 'Please specify a prefix (2-8 letters recommended):\\n' +\n ' tbd setup --auto --prefix=tbd',\n );\n }\n\n // Hard validation: always enforced\n if (!isValidPrefix(prefix)) {\n throw new CLIError(\n 'Invalid prefix format.\\n' +\n 'Prefix must be 1-20 lowercase characters:\\n' +\n ' - Must start with a letter (a-z)\\n' +\n ' - Must end with alphanumeric (a-z, 0-9)\\n' +\n ' - Middle characters can include dots (.) and underscores (_)\\n' +\n ' - No dashes allowed (breaks ID syntax)\\n\\n' +\n 'Please specify a valid prefix:\\n' +\n ' tbd setup --from-beads --prefix=tbd',\n );\n }\n\n // Soft validation: only for user-provided prefixes (not from beads config)\n // If prefix came from beads config, we accept it to ease migration\n const prefixFromBeads = beadsPrefix && prefix === beadsPrefix;\n if (!prefixFromBeads && !isRecommendedPrefix(prefix) && !options.force) {\n throw new CLIError(\n `Prefix \"${prefix}\" is not recommended.\\n` +\n 'Recommended prefixes are 2-8 alphabetic characters (e.g., \"tbd\", \"myp\", \"proj\").\\n\\n' +\n 'If you really want to use this prefix, add --force to override.\\n\\n' +\n 'Example:\\n' +\n ` tbd setup --from-beads --prefix=${prefix} --force`,\n );\n }\n\n // Initialize tbd first\n await this.initializeTbd(cwd, prefix);\n\n // Apply --no-gh-cli flag to newly created config\n if (options.ghCli === false) {\n const config = await readConfig(cwd);\n config.settings.use_gh_cli = false;\n await writeConfig(cwd, config);\n console.log(` ${colors.success('✓')} Disabled gh CLI auto-setup`);\n }\n\n // Import beads issues from the JSONL file\n console.log('Importing from Beads...');\n const beadsDir = join(cwd, '.beads');\n const jsonlPath = join(beadsDir, 'issues.jsonl');\n\n try {\n await access(jsonlPath);\n // Import directly from the JSONL file (tbd is already initialized)\n const result = spawnSync('tbd', ['import', jsonlPath, '--verbose'], {\n cwd,\n stdio: 'inherit',\n });\n if (result.status !== 0) {\n console.log(colors.warn('Warning: Some issues may not have imported correctly'));\n }\n } catch {\n console.log(colors.dim(' No issues.jsonl found - skipping import'));\n }\n\n // Disable beads\n await this.disableBeads(cwd);\n\n console.log('');\n console.log('Configuring integrations...');\n\n // Configure integrations\n const autoHandler = new SetupAutoHandler(this.cmd);\n await autoHandler.run(cwd);\n\n console.log('');\n console.log(colors.success('Setup complete!'));\n\n this.showWhatsNext(colors);\n\n // Show dashboard after setup\n spawnSync('tbd', ['prime'], { stdio: 'inherit' });\n\n // Mark welcome as seen since the user got the full onboarding experience\n try {\n await markWelcomeSeen(cwd);\n } catch {\n // Non-critical: don't fail setup if state write fails\n }\n }\n\n private async handleFreshSetup(\n cwd: string,\n isAutoMode: boolean,\n options: SetupDefaultOptions,\n ): Promise<void> {\n const colors = this.output.getColors();\n\n // Require --prefix for fresh setup (no auto-detection)\n const prefix = options.prefix;\n\n if (!prefix) {\n throw new CLIError(\n '--prefix is required for tbd setup --auto\\n\\n' +\n 'The --prefix flag specifies your project name for issue IDs.\\n' +\n 'Use a short 2-4 letter prefix so issue IDs stand out clearly.\\n\\n' +\n 'Example:\\n' +\n ' tbd setup --auto --prefix=tbd # Issues: tbd-a1b2\\n' +\n ' tbd setup --auto --prefix=myp # Issues: myp-c3d4\\n\\n' +\n 'Note: If migrating from beads, the prefix is automatically read from your beads config.',\n );\n }\n\n // Hard validation: always enforced\n if (!isValidPrefix(prefix)) {\n throw new CLIError(\n 'Invalid prefix format.\\n' +\n 'Prefix must be 1-20 lowercase characters:\\n' +\n ' - Must start with a letter (a-z)\\n' +\n ' - Must end with alphanumeric (a-z, 0-9)\\n' +\n ' - Middle characters can include dots (.) and underscores (_)\\n' +\n ' - No dashes allowed (breaks ID syntax)\\n\\n' +\n 'Example:\\n' +\n ' tbd setup --auto --prefix=tbd',\n );\n }\n\n // Soft validation: recommended format (2-8 alphabetic)\n if (!isRecommendedPrefix(prefix) && !options.force) {\n throw new CLIError(\n `Prefix \"${prefix}\" is not recommended.\\n` +\n 'Recommended prefixes are 2-8 alphabetic characters (e.g., \"tbd\", \"myp\", \"proj\").\\n\\n' +\n 'If you really want to use this prefix, add --force to override.\\n\\n' +\n 'Example:\\n' +\n ` tbd setup --auto --prefix=${prefix} --force`,\n );\n }\n\n console.log(`Initializing with prefix \"${prefix}\"...`);\n\n await this.initializeTbd(cwd, prefix);\n\n // Apply --no-gh-cli flag to newly created config\n if (options.ghCli === false) {\n const config = await readConfig(cwd);\n config.settings.use_gh_cli = false;\n await writeConfig(cwd, config);\n console.log(` ${colors.success('✓')} Disabled gh CLI auto-setup`);\n }\n\n console.log('');\n console.log('Configuring integrations...');\n\n // Configure integrations\n const autoHandler = new SetupAutoHandler(this.cmd);\n await autoHandler.run(cwd);\n\n console.log('');\n console.log(colors.success('Setup complete!'));\n\n this.showWhatsNext(colors);\n\n // Show dashboard after setup\n spawnSync('tbd', ['prime'], { stdio: 'inherit' });\n\n // Mark welcome as seen since the user got the full onboarding experience\n try {\n await markWelcomeSeen(cwd);\n } catch {\n // Non-critical: don't fail setup if state write fails\n }\n }\n\n /**\n * Show \"What's Next\" guidance after setup completion.\n * Framed as what users can SAY to get help, not as CLI commands to run.\n */\n private showWhatsNext(colors: ReturnType<typeof this.output.getColors>): void {\n console.log('');\n console.log(colors.bold(\"WHAT'S NEXT\"));\n console.log('');\n console.log(' Try saying things like:');\n console.log(' \"There\\'s a bug where ...\" → Creates and tracks a bug');\n console.log(' \"Let\\'s plan a new feature\" → Walks through a planning spec');\n console.log(' \"Let\\'s work on current issues\" → Shows ready issues to tackle');\n console.log(' \"Commit this code\" → Reviews and commits properly');\n console.log(' \"Review for best practices\" → Code review with guidelines');\n console.log('');\n }\n\n private async initializeTbd(cwd: string, prefix: string): Promise<void> {\n const colors = this.output.getColors();\n\n // 1. Create .tbd/ directory with config.yml\n await initConfig(cwd, VERSION, prefix);\n console.log(` ${colors.success('✓')} Created .tbd/config.yml`);\n\n // 2. Create/update .tbd/.gitignore (idempotent)\n // NOTE: Pattern re-addition is intentional - these are tool-managed files\n // that are regenerated from the npm package on every setup. If a user removes\n // a pattern, we re-add it because tracking these directories in git would\n // cause noise on every tbd upgrade.\n const tbdGitignoreResult = await ensureGitignorePatterns(join(cwd, TBD_DIR, '.gitignore'), [\n '# Synced documentation cache (regenerated by tbd sync --docs)',\n 'docs/',\n '',\n '# Hidden worktree for tbd-sync branch',\n `${WORKTREE_DIR_NAME}/`,\n '',\n '# Data sync directory (only exists in worktree)',\n `${DATA_SYNC_DIR_NAME}/`,\n '',\n '# Local state',\n 'state.yml',\n '',\n '# Migration backups (local only, not synced)',\n 'backups/',\n '',\n '# Temporary files',\n '*.tmp',\n '*.temp',\n '',\n '# workspaces/ stores state (including outbox) committed to the working branch',\n '!workspaces/',\n ]);\n if (tbdGitignoreResult.created) {\n console.log(` ${colors.success('✓')} Created .tbd/.gitignore`);\n } else if (tbdGitignoreResult.added.length > 0) {\n console.log(` ${colors.success('✓')} Updated .tbd/.gitignore`);\n }\n // else: file is up-to-date, no message needed\n\n // 2b. Create/update .tbd/.gitattributes with merge strategies for tbd files.\n // Placed inside .tbd/ so all tbd settings are self-contained in one directory.\n // Git supports .gitattributes in subdirectories — patterns are relative to that directory.\n // The outbox ids.yml must use \"merge=union\" so git never drops rows during merge.\n // Without this, AI agents resolving merge conflicts can delete the mapping file\n // (main has no outbox, so the merge considers \"no file\" as the correct version),\n // causing all tbd commands to crash with \"No short ID mapping found\".\n // See: https://github.com/jlevy/tbd/issues/99\n const gitattributesResult = await ensureGitignorePatterns(\n join(cwd, TBD_DIR, '.gitattributes'),\n [\n '# Protect ID mappings from merge deletion (always keep all rows)',\n '**/mappings/ids.yml merge=union',\n ],\n );\n if (gitattributesResult.created) {\n console.log(` ${colors.success('✓')} Created .tbd/.gitattributes (merge protection)`);\n } else if (gitattributesResult.added.length > 0) {\n console.log(` ${colors.success('✓')} Updated .tbd/.gitattributes (merge protection)`);\n }\n\n // 3. Initialize worktree for sync branch\n try {\n await initWorktree(cwd);\n\n // Verify worktree health after creation (prevents silent failures)\n const health = await checkWorktreeHealth(cwd);\n if (health.valid) {\n console.log(` ${colors.success('✓')} Initialized sync branch`);\n } else {\n console.log(\n ` ${colors.warn('!')} Sync branch created but verification failed (status: ${health.status})`,\n );\n console.log(` Run 'tbd doctor' to diagnose`);\n }\n } catch {\n // Non-fatal - sync will work, just not optimally\n console.log(` ${colors.dim('○')} Sync branch will be created on first sync`);\n }\n }\n\n private async disableBeads(cwd: string): Promise<void> {\n const colors = this.output.getColors();\n\n // Move .beads to .beads-disabled\n const beadsDir = join(cwd, '.beads');\n const disabledDir = join(cwd, '.beads-disabled');\n\n try {\n await rename(beadsDir, disabledDir);\n console.log(` ${colors.success('✓')} Disabled beads (moved to .beads-disabled/)`);\n } catch {\n console.log(` ${colors.dim('○')} Could not move .beads directory`);\n }\n }\n}\n\n// ============================================================================\n// Auto Setup Command\n// ============================================================================\n\ninterface AutoSetupResult {\n name: string;\n detected: boolean;\n installed: boolean;\n alreadyInstalled: boolean;\n error?: string;\n}\n\nclass SetupAutoHandler extends BaseCommand {\n private cmd: Command;\n\n constructor(command: Command) {\n super(command);\n this.cmd = command;\n }\n\n /**\n * Clean up legacy scripts from project .claude/scripts/ directory.\n * This runs during any setup, regardless of whether Claude Code is detected,\n * since we want to clean up old project-level scripts that are no longer needed.\n */\n private async cleanupLegacyProjectScripts(cwd: string): Promise<string[]> {\n const scriptsDir = join(cwd, '.claude', 'scripts');\n const scriptsRemoved: string[] = [];\n\n try {\n await access(scriptsDir);\n const entries = await readdir(scriptsDir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.isFile()) {\n const filename = entry.name;\n // Check against known legacy script names\n if (LEGACY_TBD_SCRIPTS.includes(filename)) {\n try {\n await rm(join(scriptsDir, filename));\n scriptsRemoved.push(filename);\n } catch {\n // Ignore removal errors\n }\n }\n }\n }\n } catch {\n // Scripts directory doesn't exist, nothing to clean\n }\n\n return scriptsRemoved;\n }\n\n /**\n * Filter out hook entries that match legacy tbd patterns from project settings.\n */\n private filterLegacyHooks(\n hookList: { hooks?: { command?: string }[] }[],\n ): { hooks?: { command?: string }[] }[] {\n return hookList.filter((entry) => {\n // Check if any hook command matches legacy patterns\n const hasLegacyCommand = entry.hooks?.some((hook) => {\n if (!hook.command) return false;\n return LEGACY_TBD_HOOK_PATTERNS.some((pattern) => pattern.test(hook.command!));\n });\n // Keep entries that DON'T have legacy commands\n return !hasLegacyCommand;\n });\n }\n\n /**\n * Clean up legacy hooks from project .claude/settings.json.\n * This runs during any setup, regardless of whether Claude Code is detected.\n */\n private async cleanupLegacyProjectHooks(cwd: string): Promise<number> {\n const projectSettingsPath = join(cwd, '.claude', 'settings.json');\n let hooksRemoved = 0;\n\n try {\n await access(projectSettingsPath);\n const content = await readFile(projectSettingsPath, 'utf-8');\n const settings = JSON.parse(content) as Record<string, unknown>;\n\n if (settings.hooks) {\n const hooks = settings.hooks as Record<string, unknown>;\n let modified = false;\n\n for (const hookType of ['SessionStart', 'PreCompact', 'PostToolUse']) {\n if (hooks[hookType]) {\n const hookList = hooks[hookType] as { hooks?: { command?: string }[] }[];\n const filtered = this.filterLegacyHooks(hookList);\n if (filtered.length !== hookList.length) {\n hooksRemoved += hookList.length - filtered.length;\n hooks[hookType] = filtered.length > 0 ? filtered : undefined;\n if (!hooks[hookType]) delete hooks[hookType];\n modified = true;\n }\n }\n }\n\n if (modified) {\n if (Object.keys(hooks).length === 0) {\n delete settings.hooks;\n }\n await writeFile(projectSettingsPath, JSON.stringify(settings, null, 2) + '\\n');\n }\n }\n } catch {\n // Project settings file doesn't exist, nothing to clean\n }\n\n return hooksRemoved;\n }\n\n async run(projectDir?: string): Promise<void> {\n const colors = this.output.getColors();\n const cwd = projectDir ?? process.cwd();\n const results: AutoSetupResult[] = [];\n\n // Clean up legacy project-level scripts and hooks FIRST,\n // regardless of whether any coding agent is detected.\n // This ensures old tbd scripts are removed even if user switches tools.\n const scriptsRemoved = await this.cleanupLegacyProjectScripts(cwd);\n const hooksRemoved = await this.cleanupLegacyProjectHooks(cwd);\n if (scriptsRemoved.length > 0 || hooksRemoved > 0) {\n const parts = [];\n if (scriptsRemoved.length > 0) parts.push(`${scriptsRemoved.length} script(s)`);\n if (hooksRemoved > 0) parts.push(`${hooksRemoved} hook(s)`);\n console.log(colors.dim(`Cleaned up legacy ${parts.join(' and ')}`));\n }\n\n // Sync docs using DocSync\n await this.syncDocs(cwd);\n\n // Detect and set up Claude Code\n const claudeResult = await this.setupClaudeIfDetected(cwd);\n results.push(claudeResult);\n\n // Detect and set up Codex/AGENTS.md (also used by Cursor since v1.6)\n const codexResult = await this.setupCodexIfDetected(cwd);\n results.push(codexResult);\n\n // Report results\n const installed = results.filter((r) => r.installed && !r.alreadyInstalled);\n const alreadyInstalled = results.filter((r) => r.alreadyInstalled);\n const skipped = results.filter((r) => !r.detected);\n\n if (installed.length > 0) {\n console.log(colors.bold('Configured integrations:'));\n for (const r of installed) {\n console.log(` ${colors.success('✓')} ${r.name}`);\n }\n }\n\n if (alreadyInstalled.length > 0) {\n console.log(colors.dim('Already configured:'));\n for (const r of alreadyInstalled) {\n console.log(` ${colors.dim('✓')} ${r.name}`);\n }\n }\n\n if (skipped.length > 0 && (installed.length > 0 || alreadyInstalled.length > 0)) {\n console.log(colors.dim('Not detected (skipped):'));\n for (const r of skipped) {\n console.log(` ${colors.dim('-')} ${r.name}`);\n }\n }\n\n if (installed.length === 0 && alreadyInstalled.length === 0) {\n console.log(colors.dim('No coding agents detected.'));\n console.log('');\n console.log(\n 'Install a coding agent (Claude Code, Codex, or any AGENTS.md-compatible tool) and re-run:',\n );\n console.log(' tbd setup --auto');\n }\n }\n\n /**\n * Sync docs using syncDocsWithDefaults.\n * Uses the shared function that merges bundled defaults, prunes stale entries,\n * syncs files, and updates config.\n */\n private async syncDocs(cwd: string): Promise<void> {\n const colors = this.output.getColors();\n\n // Ensure docs directories exist\n await mkdir(join(cwd, TBD_SHORTCUTS_SYSTEM), { recursive: true });\n await mkdir(join(cwd, TBD_SHORTCUTS_STANDARD), { recursive: true });\n await mkdir(join(cwd, TBD_GUIDELINES_DIR), { recursive: true });\n await mkdir(join(cwd, TBD_TEMPLATES_DIR), { recursive: true });\n\n // Use shared sync function that handles:\n // - Merging bundled defaults with user config\n // - Pruning stale internal entries\n // - Syncing files to .tbd/docs/\n // - Writing config if changed\n // - Updating last_doc_sync_at in state\n const result = await syncDocsWithDefaults(cwd);\n\n // Report sync results\n if (result.configChanged) {\n console.log(colors.dim('Updated docs_cache config'));\n }\n\n const total = result.added.length + result.updated.length;\n if (total > 0) {\n console.log(colors.dim(`Synced ${total} doc(s) to ${TBD_DOCS_DIR}/`));\n }\n if (result.removed.length > 0) {\n console.log(colors.dim(`Removed ${result.removed.length} outdated doc(s)`));\n }\n if (result.pruned.length > 0) {\n console.log(colors.dim(`Pruned ${result.pruned.length} stale config entry/entries`));\n }\n if (result.errors.length > 0) {\n for (const { path, error } of result.errors) {\n console.log(colors.warn(`Warning: ${path}: ${error}`));\n }\n }\n }\n\n private async setupClaudeIfDetected(cwd: string): Promise<AutoSetupResult> {\n const result: AutoSetupResult = {\n name: 'Claude Code',\n detected: false,\n installed: false,\n alreadyInstalled: false,\n };\n\n // Detect Claude Code: check for ~/.claude/ directory or CLAUDE_* env vars\n // Note: We check global dir for DETECTION only, not for installation\n const hasClaudeDir = await pathExists(GLOBAL_CLAUDE_DIR);\n const hasClaudeEnv = Object.keys(process.env).some((k) => k.startsWith('CLAUDE_'));\n\n if (!hasClaudeDir && !hasClaudeEnv) {\n return result;\n }\n\n result.detected = true;\n\n // Check if already installed (project-local settings - all installs are project-local)\n const claudePaths = getClaudePaths(cwd);\n\n try {\n if (await pathExists(claudePaths.settings)) {\n const content = await readFile(claudePaths.settings, 'utf-8');\n const settings = JSON.parse(content) as Record<string, unknown>;\n const hooks = settings.hooks as Record<string, unknown> | undefined;\n if (hooks) {\n const sessionStart = hooks.SessionStart as { hooks?: { command?: string }[] }[];\n const hasTbdHook = sessionStart?.some((h) =>\n h.hooks?.some(\n (hook) =>\n (hook.command?.includes('tbd prime') ?? false) ||\n (hook.command?.includes('tbd-session.sh') ?? false),\n ),\n );\n if (hasTbdHook && (await pathExists(claudePaths.skill))) {\n result.alreadyInstalled = true;\n // Note: We still run the handler to update the skill file content\n // even if hooks are already installed. This ensures users get the\n // latest skill file when running `tbd setup --auto`.\n }\n }\n }\n\n // Install/update Claude Code setup (always runs to update skill file)\n const handler = new SetupClaudeHandler(this.cmd);\n handler.setProjectDir(cwd);\n await handler.run({});\n result.installed = true;\n } catch (error) {\n result.error = (error as Error).message;\n }\n\n return result;\n }\n\n private async setupCodexIfDetected(cwd: string): Promise<AutoSetupResult> {\n const result: AutoSetupResult = {\n name: 'Codex/AGENTS.md',\n detected: false,\n installed: false,\n alreadyInstalled: false,\n };\n\n // Detect Codex: check for existing AGENTS.md or CODEX_* env vars\n const agentsPath = getAgentsMdPath(cwd);\n const hasAgentsMd = await pathExists(agentsPath);\n const hasCodexEnv = Object.keys(process.env).some((k) => k.startsWith('CODEX_'));\n\n if (!hasAgentsMd && !hasCodexEnv) {\n return result;\n }\n\n result.detected = true;\n\n // Check if already has tbd section\n if (hasAgentsMd) {\n const content = await readFile(agentsPath, 'utf-8');\n if (content.includes('BEGIN TBD INTEGRATION')) {\n result.alreadyInstalled = true;\n // Note: We still run the handler to update the AGENTS.md content\n // even if tbd section exists. This ensures users get the latest\n // content when running `tbd setup --auto`.\n }\n }\n\n try {\n // Install/update Codex AGENTS.md (always runs to update content)\n const handler = new SetupCodexHandler(this.cmd);\n handler.setProjectDir(cwd);\n await handler.run({});\n result.installed = true;\n } catch (error) {\n result.error = (error as Error).message;\n }\n\n return result;\n }\n}\n\n// Main setup command\nexport const setupCommand = new Command('setup')\n .description('Configure tbd integration with editors and tools')\n .option('--auto', 'Non-interactive mode with smart defaults (for agents/scripts)')\n .option('--interactive', 'Interactive mode with prompts (for humans)')\n .option('--from-beads', 'Migrate from Beads to tbd')\n .option('--prefix <name>', 'Project prefix for issue IDs (required for fresh setup)')\n .option('--force', 'Allow non-recommended prefix format (not 2-8 alphabetic)')\n .option('--no-gh-cli', 'Disable automatic GitHub CLI installation hook')\n .action(async (options: SetupDefaultOptions, command) => {\n // If --auto or --interactive flag is set, run the default handler\n if (options.auto || options.interactive) {\n const handler = new SetupDefaultHandler(command);\n await handler.run(options);\n return;\n }\n\n // If --from-beads is set without --auto/--interactive, treat as --auto\n if (options.fromBeads) {\n const handler = new SetupDefaultHandler(command);\n await handler.run({ ...options, auto: true });\n return;\n }\n\n // No flags provided - show help\n console.log('Usage: tbd setup [options]');\n console.log('');\n console.log('Initialize tbd and configure agent integrations.');\n console.log('Must be run inside a git repository. Installs .tbd/ and .claude/');\n console.log('at the git root (adjacent to .git/).');\n console.log('');\n console.log('Modes (one required):');\n console.log(\n ' --auto Non-interactive mode with smart defaults (for agents/scripts)',\n );\n console.log(' --interactive Interactive mode with prompts (for humans)');\n console.log(' --from-beads Migrate from Beads to tbd (implies --auto)');\n console.log('');\n console.log('Options:');\n console.log(' --prefix <name> Project prefix for issue IDs (2-8 alphabetic recommended)');\n console.log(' --force Allow non-recommended prefix format');\n console.log(' --no-gh-cli Disable automatic GitHub CLI installation hook');\n console.log('');\n console.log('Examples:');\n console.log(' tbd setup --auto --prefix=tbd # Full automatic setup with prefix');\n console.log(' tbd setup --from-beads # Migrate from Beads (uses beads prefix)');\n console.log(' tbd setup --interactive # Interactive setup with prompts');\n console.log('');\n console.log('For surgical initialization without integrations, see: tbd init --help');\n });\n","/**\n * `tbd save` - Save issues to a workspace or directory.\n *\n * Saves issues from the data-sync worktree to a named workspace or directory.\n * Used for sync failure recovery, backups, and bulk editing workflows.\n *\n * See: plan-2026-01-30-workspace-sync-alt.md\n */\n\nimport { Command } from 'commander';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { requireInit, ValidationError } from '../lib/errors.js';\nimport { resolveDataSyncDir } from '../../lib/paths.js';\nimport { saveToWorkspace, type SaveOptions } from '../../file/workspace.js';\n\ninterface SaveCommandOptions {\n workspace?: string;\n dir?: string;\n outbox?: boolean;\n updatesOnly?: boolean;\n}\n\nclass SaveHandler extends BaseCommand {\n async run(options: SaveCommandOptions): Promise<void> {\n const tbdRoot = await requireInit();\n const dataSyncDir = await resolveDataSyncDir(tbdRoot);\n\n // Validate that at least one target is specified\n if (!options.workspace && !options.dir && !options.outbox) {\n throw new ValidationError('One of --workspace, --dir, or --outbox is required');\n }\n\n // Build save options\n const saveOptions: SaveOptions = {\n workspace: options.workspace,\n dir: options.dir,\n outbox: options.outbox,\n updatesOnly: options.updatesOnly,\n };\n\n if (this.checkDryRun('Would save issues to workspace', saveOptions)) {\n return;\n }\n\n const spinner = this.output.spinner('Saving issues...');\n saveOptions.logger = this.output.logger(spinner);\n\n const result = await this.execute(async () => {\n return await saveToWorkspace(tbdRoot, dataSyncDir, saveOptions);\n }, 'Failed to save issues');\n\n spinner.stop();\n\n if (!result) {\n return;\n }\n\n // Format output\n const targetName = options.outbox ? 'outbox' : (options.workspace ?? options.dir ?? 'unknown');\n\n this.output.data(\n {\n saved: result.saved,\n conflicts: result.conflicts,\n target: targetName,\n totalSource: result.totalSource,\n filtered: result.filtered,\n },\n () => {\n if (result.saved === 0) {\n if (result.filtered) {\n this.output.info(`No issues to save (0 of ${result.totalSource} issues have updates)`);\n } else {\n this.output.info('No issues to save');\n }\n } else {\n if (result.filtered) {\n this.output.success(\n `Saved ${result.saved} issue(s) to ${targetName} (${result.saved} of ${result.totalSource} filtered)`,\n );\n } else {\n this.output.success(`Saved ${result.saved} issue(s) to ${targetName}`);\n }\n if (result.conflicts > 0) {\n this.output.warn(`${result.conflicts} conflict(s) moved to attic`);\n }\n }\n },\n );\n\n // Remind user to commit if saving to workspace\n if (options.workspace || options.outbox) {\n const colors = this.output.getColors();\n console.log(\n colors.dim(\n `\\nRemember to commit: git add .tbd/workspaces && git commit -m \"tbd: save workspace\"`,\n ),\n );\n }\n }\n}\n\nexport const saveCommand = new Command('save')\n .description('Save issues to a workspace or directory')\n .option('--workspace <name>', 'Save to named workspace under .tbd/workspaces/')\n .option('--dir <path>', 'Save to arbitrary directory')\n .option('--outbox', 'Shortcut for --workspace=outbox --updates-only')\n .option('--updates-only', 'Only save issues modified since last sync')\n .action(async (options, command) => {\n const handler = new SaveHandler(command);\n await handler.run(options);\n });\n","/**\n * `tbd workspace` - Workspace management commands.\n *\n * Workspaces are named directories for sync failure recovery, backups,\n * and bulk editing. Issues can be saved to a workspace and imported back.\n *\n * See: plan-2026-01-30-workspace-sync-alt.md\n */\n\nimport { Command } from 'commander';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { requireInit, NotFoundError, ValidationError } from '../lib/errors.js';\nimport {\n listWorkspacesWithCounts,\n deleteWorkspace,\n workspaceExists,\n} from '../../file/workspace.js';\nimport { isValidWorkspaceName } from '../../lib/paths.js';\n\n/**\n * List all workspaces with issue counts.\n */\nclass WorkspaceListHandler extends BaseCommand {\n async run(): Promise<void> {\n const tbdRoot = await requireInit();\n\n const workspaces = await listWorkspacesWithCounts(tbdRoot);\n\n this.output.data(workspaces, () => {\n const colors = this.output.getColors();\n if (workspaces.length === 0) {\n console.log('No workspaces');\n return;\n }\n\n // Calculate column widths\n const maxNameLen = Math.max(9, ...workspaces.map((ws) => ws.name.length)); // 9 = \"WORKSPACE\".length\n const countWidth = 6;\n\n // Header\n const header = `${colors.dim('WORKSPACE'.padEnd(maxNameLen))} ${colors.dim('open'.padStart(countWidth))} ${colors.dim('in_progress'.padStart(11))} ${colors.dim('closed'.padStart(countWidth))} ${colors.dim('total'.padStart(countWidth))}`;\n console.log(header);\n\n // Rows\n for (const ws of workspaces) {\n const { name, counts } = ws;\n const row = `${name.padEnd(maxNameLen)} ${String(counts.open).padStart(countWidth)} ${String(counts.in_progress).padStart(11)} ${String(counts.closed).padStart(countWidth)} ${String(counts.total).padStart(countWidth)}`;\n console.log(row);\n }\n });\n }\n}\n\n/**\n * Delete a workspace.\n */\nclass WorkspaceDeleteHandler extends BaseCommand {\n async run(name: string, options: { force?: boolean }): Promise<void> {\n const tbdRoot = await requireInit();\n\n // Validate workspace name\n if (!isValidWorkspaceName(name)) {\n throw new ValidationError(\n `Invalid workspace name: \"${name}\". Use lowercase alphanumeric characters, hyphens, and underscores.`,\n );\n }\n\n // Check if workspace exists\n const exists = await workspaceExists(tbdRoot, name);\n if (!exists && !options.force) {\n throw new NotFoundError('Workspace', name);\n }\n\n if (this.checkDryRun('Would delete workspace', { name })) {\n return;\n }\n\n await this.execute(async () => {\n await deleteWorkspace(tbdRoot, name);\n }, 'Failed to delete workspace');\n\n this.output.success(`Deleted workspace \"${name}\"`);\n }\n}\n\nconst listWorkspaceCommand = new Command('list')\n .description('List all workspaces')\n .action(async (_options, command) => {\n const handler = new WorkspaceListHandler(command);\n await handler.run();\n });\n\nconst deleteWorkspaceCommand = new Command('delete')\n .description('Delete a workspace')\n .argument('<name>', 'Workspace name to delete')\n .option('--force', 'Delete without error if workspace does not exist')\n .action(async (name, options, command) => {\n const handler = new WorkspaceDeleteHandler(command);\n await handler.run(name, options);\n });\n\nexport const workspaceCommand = new Command('workspace')\n .description('Manage workspaces for sync recovery and backups')\n .addCommand(listWorkspaceCommand)\n .addCommand(deleteWorkspaceCommand);\n","/**\n * CLI program setup using Commander.js\n *\n * See: research-modern-typescript-cli-patterns.md\n */\n\nimport { Command } from 'commander';\n\nimport { VERSION } from './lib/version.js';\nimport {\n configureColoredHelp,\n createColoredHelpConfig,\n createHelpEpilog,\n getColorOptionFromArgv,\n} from './lib/output.js';\nimport { initCommand } from './commands/init.js';\nimport { createCommand } from './commands/create.js';\nimport { listCommand } from './commands/list.js';\nimport { showCommand } from './commands/show.js';\nimport { updateCommand } from './commands/update.js';\nimport { closeCommand } from './commands/close.js';\nimport { reopenCommand } from './commands/reopen.js';\nimport { readyCommand } from './commands/ready.js';\nimport { blockedCommand } from './commands/blocked.js';\nimport { staleCommand } from './commands/stale.js';\nimport { labelCommand } from './commands/label.js';\nimport { depCommand } from './commands/dep.js';\nimport { syncCommand } from './commands/sync.js';\nimport { searchCommand } from './commands/search.js';\nimport { statusCommand } from './commands/status.js';\nimport { statsCommand } from './commands/stats.js';\nimport { doctorCommand } from './commands/doctor.js';\nimport { configCommand } from './commands/config.js';\nimport { atticCommand } from './commands/attic.js';\nimport { importCommand } from './commands/import.js';\nimport { docsCommand } from './commands/docs.js';\nimport { closeProtocolCommand } from './commands/closing.js';\nimport { designCommand } from './commands/design.js';\nimport { readmeCommand } from './commands/readme.js';\nimport { uninstallCommand } from './commands/uninstall.js';\nimport { primeCommand } from './commands/prime.js';\nimport { skillCommand } from './commands/skill.js';\nimport { shortcutCommand } from './commands/shortcut.js';\nimport { guidelinesCommand } from './commands/guidelines.js';\nimport { templateCommand } from './commands/template.js';\nimport { setupCommand } from './commands/setup.js';\nimport { saveCommand } from './commands/save.js';\nimport { workspaceCommand } from './commands/workspace.js';\nimport { CLIError } from './lib/errors.js';\n\n/**\n * Create and configure the CLI program.\n */\nfunction createProgram(): Command {\n const program = new Command()\n .name('tbd')\n .description('Git-native issue tracking for AI agents and humans')\n .version(VERSION, '--version', 'Show version number')\n .helpOption('--help', 'Display help for command')\n .showHelpAfterError('(add --help for additional information)');\n\n // Configure colored help output (respects --color option)\n configureColoredHelp(program);\n\n // Global options\n program\n .option('--dry-run', 'Show what would be done without making changes')\n .option('--verbose', 'Enable verbose output')\n .option('--quiet', 'Suppress non-essential output')\n .option('--json', 'Output as JSON')\n .option('--color <when>', 'Colorize output: auto, always, never', 'auto')\n .option('--no-sync', 'Skip automatic sync after write operations')\n .option('--debug', 'Show internal IDs alongside public IDs for debugging');\n\n // Add commands in logical groups\n // Note: commandsGroup() sets the heading for all following addCommand() calls\n\n program.commandsGroup('Documentation:');\n program.addCommand(readmeCommand);\n program.addCommand(primeCommand);\n program.addCommand(skillCommand);\n program.addCommand(shortcutCommand);\n program.addCommand(guidelinesCommand);\n program.addCommand(templateCommand);\n program.addCommand(closeProtocolCommand);\n program.addCommand(docsCommand);\n program.addCommand(designCommand);\n\n program.commandsGroup('Setup & Configuration:');\n program.addCommand(initCommand);\n program.addCommand(configCommand);\n program.addCommand(setupCommand);\n\n program.commandsGroup('Working With Issues:');\n\n program.addCommand(createCommand);\n program.addCommand(showCommand);\n program.addCommand(updateCommand);\n program.addCommand(closeCommand);\n program.addCommand(reopenCommand);\n program.addCommand(searchCommand);\n\n program.commandsGroup('Views and Filtering:');\n program.addCommand(readyCommand);\n program.addCommand(listCommand);\n program.addCommand(blockedCommand);\n program.addCommand(staleCommand);\n\n program.commandsGroup('Labels and Dependencies:');\n program.addCommand(depCommand);\n program.addCommand(labelCommand);\n\n program.commandsGroup('Sync and Status:');\n program.addCommand(syncCommand);\n program.addCommand(saveCommand);\n program.addCommand(statusCommand);\n program.addCommand(statsCommand);\n\n program.commandsGroup('Maintenance:');\n program.addCommand(doctorCommand);\n program.addCommand(atticCommand);\n program.addCommand(workspaceCommand);\n program.addCommand(importCommand);\n program.addCommand(uninstallCommand);\n\n // Apply colored help to all commands recursively\n // Note: addCommand() does NOT inherit parent's configureHelp settings,\n // unlike command() which does inherit. So we must apply manually.\n applyColoredHelpToAllCommands(program);\n\n return program;\n}\n\n/**\n * Apply colored help configuration and epilog to all commands recursively.\n * This is needed because Commander.js's addCommand() does not inherit\n * configureHelp settings from the parent command.\n */\nfunction applyColoredHelpToAllCommands(program: Command): void {\n const colorOption = getColorOptionFromArgv();\n const helpConfig = createColoredHelpConfig(colorOption);\n const epilog = createHelpEpilog(colorOption);\n\n // Add epilog to main program only - it shows for all help including subcommands\n program.addHelpText('afterAll', `\\n${epilog}`);\n\n const applyRecursively = (cmd: Command) => {\n cmd.configureHelp(helpConfig);\n for (const sub of cmd.commands) {\n applyRecursively(sub);\n }\n };\n\n for (const cmd of program.commands) {\n applyRecursively(cmd);\n }\n}\n\n/**\n * Check if --json flag is present in argv.\n */\nfunction isJsonMode(): boolean {\n return process.argv.includes('--json');\n}\n\n/**\n * Check if --debug flag is present in argv.\n */\nfunction isDebugMode(): boolean {\n return process.argv.includes('--debug');\n}\n\n/**\n * Output error in the appropriate format (JSON or text).\n * In debug mode, shows full error details, stack trace, and cause chain.\n */\nfunction outputError(message: string, error?: Error): void {\n const debugMode = isDebugMode();\n\n if (isJsonMode()) {\n const errorObj: {\n error: string;\n type?: string;\n details?: string;\n stack?: string;\n cause?: string;\n } = {\n error: message,\n };\n if (error instanceof CLIError) {\n errorObj.type = error.name;\n }\n if (error && error.message !== message) {\n errorObj.details = error.message;\n }\n if (debugMode && error?.stack) {\n errorObj.stack = error.stack;\n }\n if (error?.cause instanceof Error) {\n errorObj.cause = error.cause.message;\n }\n console.error(JSON.stringify(errorObj));\n } else {\n console.error(`Error: ${message}`);\n if (debugMode && error?.stack) {\n console.error('');\n console.error('Stack trace:');\n console.error(error.stack);\n // Walk the cause chain to show underlying errors\n let cause = error.cause;\n while (cause instanceof Error) {\n console.error('');\n console.error(`Caused by: ${cause.message}`);\n if (cause.stack) {\n console.error(cause.stack);\n }\n cause = cause.cause;\n }\n }\n }\n}\n\n/**\n * Check if running with no command (just options or nothing).\n * Returns true if: `tbd`, `tbd --help`, `tbd --version`, `tbd --color never`\n * Returns false if there's a command: `tbd list`, `tbd show foo`\n */\nfunction hasNoCommand(): boolean {\n // process.argv is: [node, script, ...args]\n const rawArgs = process.argv.slice(2);\n\n // Global options that take a value (space-separated form)\n const optionsWithValues = new Set(['--color']);\n\n const nonOptionArgs: string[] = [];\n let skipNext = false;\n\n for (const arg of rawArgs) {\n if (skipNext) {\n // This arg is a value for the previous option, skip it\n skipNext = false;\n continue;\n }\n\n if (arg.startsWith('-')) {\n // Check if this option takes a value (and doesn't use = syntax)\n const optionName = arg.includes('=') ? arg.split('=')[0] : arg;\n if (optionsWithValues.has(optionName!) && !arg.includes('=')) {\n skipNext = true;\n }\n continue;\n }\n\n // This is a non-option argument (potential command)\n nonOptionArgs.push(arg);\n }\n\n return nonOptionArgs.length === 0;\n}\n\n/**\n * Run the CLI. This is the main entry point.\n */\nexport async function runCli(): Promise<void> {\n const program = createProgram();\n\n // If no command specified (and not help/version), run prime by default\n // But only if no --help or --version flags\n const isHelpOrVersion =\n process.argv.includes('--help') ||\n process.argv.includes('-h') ||\n process.argv.includes('--version') ||\n process.argv.includes('-V');\n\n // Show help by default when no command given (changed from prime)\n // The help epilog guides agents to run `tbd prime` for full context\n if (hasNoCommand() && !isHelpOrVersion) {\n // Show help instead of defaulting to prime\n process.argv.splice(2, 0, '--help');\n }\n\n try {\n await program.parseAsync(process.argv);\n } catch (error) {\n if (error instanceof CLIError) {\n outputError(error.message, error);\n process.exit(error.exitCode);\n }\n // Unexpected error\n const message = error instanceof Error ? error.message : String(error);\n outputError(message, error instanceof Error ? error : undefined);\n process.exit(1);\n }\n}\n\n// Handle SIGINT (Ctrl+C)\nprocess.on('SIGINT', () => {\n console.error('\\nInterrupted');\n process.exit(130); // 128 + SIGINT(2)\n});\n"],"mappings":"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"}