get-tbd 0.1.13 → 0.1.14

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.
Files changed (51) hide show
  1. package/README.md +37 -24
  2. package/dist/bin.mjs +410 -170
  3. package/dist/bin.mjs.map +1 -1
  4. package/dist/cli.mjs +202 -94
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/docs/README.md +37 -24
  7. package/dist/docs/SKILL.md +61 -18
  8. package/dist/docs/guidelines/cli-agent-skill-patterns.md +77 -4
  9. package/dist/docs/guidelines/error-handling-rules.md +66 -0
  10. package/dist/docs/guidelines/release-notes-guidelines.md +140 -0
  11. package/dist/docs/guidelines/typescript-yaml-handling-rules.md +195 -0
  12. package/dist/docs/install/claude-header.md +13 -6
  13. package/dist/docs/shortcuts/standard/agent-handoff.md +1 -0
  14. package/dist/docs/shortcuts/standard/checkout-third-party-repo.md +50 -0
  15. package/dist/docs/shortcuts/standard/{cleanup-all.md → code-cleanup-all.md} +3 -2
  16. package/dist/docs/shortcuts/standard/{cleanup-update-docstrings.md → code-cleanup-docstrings.md} +1 -0
  17. package/dist/docs/shortcuts/standard/{cleanup-remove-trivial-tests.md → code-cleanup-tests.md} +1 -0
  18. package/dist/docs/shortcuts/standard/{commit-code.md → code-review-and-commit.md} +1 -0
  19. package/dist/docs/shortcuts/standard/create-or-update-pr-simple.md +1 -0
  20. package/dist/docs/shortcuts/standard/create-or-update-pr-with-validation-plan.md +1 -0
  21. package/dist/docs/shortcuts/standard/implement-beads.md +1 -0
  22. package/dist/docs/shortcuts/standard/merge-upstream.md +1 -0
  23. package/dist/docs/shortcuts/standard/new-architecture-doc.md +1 -0
  24. package/dist/docs/shortcuts/standard/new-guideline.md +8 -0
  25. package/dist/docs/shortcuts/standard/new-plan-spec.md +1 -0
  26. package/dist/docs/shortcuts/standard/new-research-brief.md +1 -0
  27. package/dist/docs/shortcuts/standard/new-shortcut.md +27 -1
  28. package/dist/docs/shortcuts/standard/new-validation-plan.md +1 -0
  29. package/dist/docs/shortcuts/standard/plan-implementation-with-beads.md +1 -0
  30. package/dist/docs/shortcuts/standard/precommit-process.md +1 -0
  31. package/dist/docs/shortcuts/standard/review-code-python.md +1 -0
  32. package/dist/docs/shortcuts/standard/review-code-typescript.md +1 -0
  33. package/dist/docs/shortcuts/standard/review-code.md +1 -0
  34. package/dist/docs/shortcuts/standard/review-github-pr.md +1 -0
  35. package/dist/docs/shortcuts/standard/revise-all-architecture-docs.md +1 -0
  36. package/dist/docs/shortcuts/standard/revise-architecture-doc.md +1 -0
  37. package/dist/docs/shortcuts/standard/setup-github-cli.md +1 -0
  38. package/dist/docs/shortcuts/standard/sync-failure-recovery.md +6 -53
  39. package/dist/docs/shortcuts/standard/update-specs-status.md +1 -0
  40. package/dist/docs/shortcuts/standard/welcome-user.md +2 -1
  41. package/dist/docs/shortcuts/system/skill-brief.md +1 -1
  42. package/dist/docs/shortcuts/system/skill.md +48 -12
  43. package/dist/docs/skill-brief.md +1 -1
  44. package/dist/docs/tbd-design.md +13 -1
  45. package/dist/index.d.mts +20 -6
  46. package/dist/index.mjs +2 -2
  47. package/dist/{src-BfhjLZXE.mjs → src-DdSZ1dgK.mjs} +154 -22
  48. package/dist/src-DdSZ1dgK.mjs.map +1 -0
  49. package/dist/tbd +410 -170
  50. package/package.json +1 -1
  51. package/dist/src-BfhjLZXE.mjs.map +0 -1
package/dist/cli.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.mjs","names":["BUILD_VERSION","WorktreeMissingError","parseYaml","stringifyYaml","parsePath","execFileAsync","parseYaml","stringifyYaml","normalizePath","addCommand","removeCommand","parseYaml","stringifyYaml","stringifyYaml","parseYaml","parseYaml"],"sources":["../src/cli/lib/version.ts","../src/cli/lib/context.ts","../src/cli/lib/output.ts","../src/lib/paths.ts","../src/lib/tbd-format.ts","../src/file/config.ts","../src/cli/lib/errors.ts","../src/cli/lib/base-command.ts","../src/utils/file-utils.ts","../src/utils/gitignore-utils.ts","../src/utils/time-utils.ts","../src/file/git.ts","../src/cli/commands/init.ts","../src/lib/ids.ts","../src/file/storage.ts","../src/lib/sort.ts","../src/file/id-mapping.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/comparison-chain.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/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/file/workspace.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/lib/prefix-detection.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 nonInteractive: boolean;\n yes: boolean;\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 const isCI = Boolean(process.env.CI);\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 nonInteractive: opts.nonInteractive ?? (!process.stdin.isTTY || isCI),\n yes: opts.yes ?? false,\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 running in interactive mode.\n */\nexport function isInteractive(ctx: CommandContext): boolean {\n return !ctx.nonInteractive && process.stdin.isTTY === true && !process.env.CI;\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';\n\nimport type { CommandContext, ColorOption } from './context.js';\nimport { shouldColorize } from './context.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 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('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 ` For interactive setup: ${colors.dim('tbd setup --interactive')}`,\n ` For manual control: ${colors.dim('tbd init --help')}`,\n '',\n colors.bold('Orientation:'),\n ` For workflow guidance, run: ${colors.green('tbd prime')}`,\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 * 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 * 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 * Centralized path constants for tbd.\n *\n * Directory structure (per spec):\n *\n * On main/dev branches:\n * .tbd/\n * config.yml - Project configuration (tracked)\n * state.yml - Local state (gitignored)\n * .gitignore - Ignores docs/, data-sync-worktree/, data-sync/\n * docs/ - Installed documentation (gitignored, regenerated on setup)\n * data-sync-worktree/ - Hidden worktree checkout of tbd-sync branch\n * .tbd/\n * data-sync/\n * issues/\n * mappings/\n * attic/\n * meta.yml\n *\n * On tbd-sync branch:\n * .tbd/\n * data-sync/\n * issues/\n * mappings/\n * attic/\n * meta.yml\n */\n\nimport { join } from 'node:path';\n\n/** The tbd configuration directory on main branch */\nexport const TBD_DIR = '.tbd';\n\n/** The config file path */\nexport const CONFIG_FILE = join(TBD_DIR, 'config.yml');\n\n/** The local state file (gitignored) */\nexport const STATE_FILE = join(TBD_DIR, 'state.yml');\n\n/** The worktree directory name */\nexport const WORKTREE_DIR_NAME = 'data-sync-worktree';\n\n/** The worktree path (gitignored) */\nexport const WORKTREE_DIR = join(TBD_DIR, WORKTREE_DIR_NAME);\n\n/** The data directory name on the sync branch */\nexport const DATA_SYNC_DIR_NAME = 'data-sync';\n\n/**\n * The base directory for synced data.\n *\n * NOTE: This is currently pointing directly to .tbd/data-sync/ which is WRONG\n * per the spec. The correct path should be via the worktree:\n * .tbd/data-sync-worktree/.tbd/data-sync/\n *\n * TODO(tbd-208): Update this to use the worktree path once worktree\n * management is implemented.\n */\nexport const DATA_SYNC_DIR = join(TBD_DIR, DATA_SYNC_DIR_NAME);\n\n/**\n * The correct path for synced data via worktree (per spec).\n * Use this once worktree management is implemented.\n */\nexport const DATA_SYNC_DIR_VIA_WORKTREE = join(WORKTREE_DIR, TBD_DIR, DATA_SYNC_DIR_NAME);\n\n/** Issues directory */\nexport const ISSUES_DIR = join(DATA_SYNC_DIR, 'issues');\n\n/** Mappings directory */\nexport const MAPPINGS_DIR = join(DATA_SYNC_DIR, 'mappings');\n\n/** Attic directory for conflict resolution */\nexport const ATTIC_DIR = join(DATA_SYNC_DIR, 'attic');\n\n/** Meta file for schema version */\nexport const META_FILE = join(DATA_SYNC_DIR, 'meta.yml');\n\n/** The sync branch name */\nexport const SYNC_BRANCH = 'tbd-sync';\n\n// =============================================================================\n// Workspace Paths (for sync failure recovery, backups, bulk editing)\n// =============================================================================\n\n/** The workspaces directory name within .tbd/ */\nexport const WORKSPACES_DIR_NAME = 'workspaces';\n\n/** Full path to workspaces directory: .tbd/workspaces/ */\nexport const WORKSPACES_DIR = join(TBD_DIR, WORKSPACES_DIR_NAME);\n\n/**\n * Get the path to a named workspace directory.\n *\n * Workspaces are stored at: .tbd/workspaces/{name}/\n *\n * @param workspaceName - The name of the workspace (e.g., 'outbox', 'my-feature')\n * @returns Path to the workspace directory\n */\nexport function getWorkspaceDir(workspaceName: string): string {\n return join(WORKSPACES_DIR, workspaceName);\n}\n\n/**\n * Get the path to a workspace's issues directory.\n *\n * @param workspaceName - The name of the workspace\n * @returns Path to the workspace's issues directory\n */\nexport function getWorkspaceIssuesDir(workspaceName: string): string {\n return join(getWorkspaceDir(workspaceName), 'issues');\n}\n\n/**\n * Get the path to a workspace's mappings directory.\n *\n * @param workspaceName - The name of the workspace\n * @returns Path to the workspace's mappings directory\n */\nexport function getWorkspaceMappingsDir(workspaceName: string): string {\n return join(getWorkspaceDir(workspaceName), 'mappings');\n}\n\n/**\n * Get the path to a workspace's attic directory.\n *\n * The attic stores conflict backups during workspace save operations.\n *\n * @param workspaceName - The name of the workspace\n * @returns Path to the workspace's attic directory\n */\nexport function getWorkspaceAtticDir(workspaceName: string): string {\n return join(getWorkspaceDir(workspaceName), 'attic');\n}\n\n/**\n * Validate a workspace name.\n *\n * Valid workspace names:\n * - Lowercase alphanumeric characters\n * - Hyphens and underscores allowed\n * - Must not be empty\n * - Must not contain path separators or dots at start\n *\n * @param name - The workspace name to validate\n * @returns true if the name is valid\n */\nexport function isValidWorkspaceName(name: string): boolean {\n if (!name || name.length === 0) {\n return false;\n }\n\n // Must not start with dot (hidden files)\n if (name.startsWith('.')) {\n return false;\n }\n\n // Only allow lowercase alphanumeric, hyphens, and underscores\n // No spaces, path separators, or special characters\n const validPattern = /^[a-z0-9][a-z0-9_-]*$/;\n return validPattern.test(name);\n}\n\n// =============================================================================\n// Documentation/Shortcuts Paths\n// =============================================================================\n\n/** Docs directory name within .tbd/ */\nexport const DOCS_DIR = 'docs';\n\n/** Shortcuts directory name within docs/ */\nexport const SHORTCUTS_DIR = 'shortcuts';\n\n/** System shortcuts directory name (core docs like skill.md) */\nexport const SYSTEM_DIR = 'system';\n\n/** Standard shortcuts directory name (workflow shortcuts) */\nexport const STANDARD_DIR = 'standard';\n\n/** Guidelines directory name (coding rules and best practices) */\nexport const GUIDELINES_DIR = 'guidelines';\n\n/** Templates directory name (document templates) */\nexport const TEMPLATES_DIR = 'templates';\n\n/** Full path to docs directory: .tbd/docs/ */\nexport const TBD_DOCS_DIR = join(TBD_DIR, DOCS_DIR);\n\n/** Full path to shortcuts directory: .tbd/docs/shortcuts/ */\nexport const TBD_SHORTCUTS_DIR = join(TBD_DOCS_DIR, SHORTCUTS_DIR);\n\n/** Full path to system shortcuts: .tbd/docs/shortcuts/system/ */\nexport const TBD_SHORTCUTS_SYSTEM = join(TBD_SHORTCUTS_DIR, SYSTEM_DIR);\n\n/** Full path to standard shortcuts: .tbd/docs/shortcuts/standard/ */\nexport const TBD_SHORTCUTS_STANDARD = join(TBD_SHORTCUTS_DIR, STANDARD_DIR);\n\n/** Full path to guidelines: .tbd/docs/guidelines/ (top-level, not under shortcuts) */\nexport const TBD_GUIDELINES_DIR = join(TBD_DOCS_DIR, GUIDELINES_DIR);\n\n/** Full path to templates: .tbd/docs/templates/ (top-level, not under shortcuts) */\nexport const TBD_TEMPLATES_DIR = join(TBD_DOCS_DIR, TEMPLATES_DIR);\n\n/** @deprecated Use TBD_GUIDELINES_DIR instead */\nexport const TBD_SHORTCUTS_GUIDELINES = TBD_GUIDELINES_DIR;\n\n/** @deprecated Use TBD_TEMPLATES_DIR instead */\nexport const TBD_SHORTCUTS_TEMPLATES = TBD_TEMPLATES_DIR;\n\n/** Built-in docs source paths (relative to package docs/) */\nexport const BUILTIN_SHORTCUTS_SYSTEM = join(SHORTCUTS_DIR, SYSTEM_DIR);\nexport const BUILTIN_SHORTCUTS_STANDARD = join(SHORTCUTS_DIR, STANDARD_DIR);\n\n/** Built-in guidelines source path (relative to package docs/) */\nexport const BUILTIN_GUIDELINES_DIR = GUIDELINES_DIR;\n\n/** Built-in templates source path (relative to package docs/) */\nexport const BUILTIN_TEMPLATES_DIR = TEMPLATES_DIR;\n\n/** Install directory name (header files for tool-specific installation) */\nexport const INSTALL_DIR = 'install';\n\n/** Built-in install source path (relative to package docs/) */\nexport const BUILTIN_INSTALL_DIR = INSTALL_DIR;\n\n/**\n * Default shortcut lookup paths (searched in order, relative to tbd root).\n * Earlier paths take precedence over later paths.\n * Note: Guidelines and templates are now separate top-level directories.\n */\nexport const DEFAULT_SHORTCUT_PATHS = [\n TBD_SHORTCUTS_SYSTEM, // .tbd/docs/shortcuts/system/\n TBD_SHORTCUTS_STANDARD, // .tbd/docs/shortcuts/standard/\n];\n\n/**\n * Default guidelines lookup paths (relative to tbd root).\n */\nexport const DEFAULT_GUIDELINES_PATHS = [\n TBD_GUIDELINES_DIR, // .tbd/docs/guidelines/\n];\n\n/**\n * Default template lookup paths (relative to tbd root).\n */\nexport const DEFAULT_TEMPLATE_PATHS = [\n TBD_TEMPLATES_DIR, // .tbd/docs/templates/\n];\n\n/**\n * Get the full path to an issue file.\n */\nexport function getIssuePath(issueId: string): string {\n return join(ISSUES_DIR, `${issueId}.md`);\n}\n\n/**\n * Get the full path to a mapping file.\n */\nexport function getMappingPath(name: string): string {\n return join(MAPPINGS_DIR, `${name}.yml`);\n}\n\n/**\n * Get the full path to an attic entry.\n */\nexport function getAtticPath(issueId: string, filename: string): string {\n return join(ATTIC_DIR, 'conflicts', issueId, filename);\n}\n\n// =============================================================================\n// Dynamic Path Resolution\n// =============================================================================\n\nimport { access } from 'node:fs/promises';\n\n/**\n * Options for resolveDataSyncDir.\n */\nexport interface ResolveDataSyncDirOptions {\n /**\n * Allow fallback to direct path when worktree is missing.\n * Set to true for test environments or diagnostic tools.\n * Default: true. When false and worktree is missing, throws WorktreeMissingError.\n */\n allowFallback?: boolean;\n}\n\n/**\n * Error thrown when worktree is missing and fallback is not allowed.\n * Defined inline to avoid circular dependency with errors.ts.\n */\nexport class WorktreeMissingError extends Error {\n constructor(\n message = \"Worktree not found at .tbd/data-sync-worktree/. Run 'tbd doctor --fix' to repair.\",\n ) {\n super(message);\n this.name = 'WorktreeMissingError';\n }\n}\n\n/**\n * Cache for resolved data sync directory.\n * Reset when baseDir changes.\n */\nlet _resolvedDataSyncDir: string | null = null;\nlet _resolvedBaseDir: string | null = null;\nlet _resolvedAllowFallback: boolean | null = null;\n\n/**\n * Resolve the actual data sync directory path.\n *\n * This function detects whether we're running with a git worktree\n * (production) or in a test environment without worktree.\n *\n * Order of preference:\n * 1. Worktree path if worktree exists: .tbd/data-sync-worktree/.tbd/data-sync/\n * 2. Direct path as fallback (only if allowFallback: true)\n *\n * @param baseDir - The base directory of the repository (default: process.cwd())\n * @param options - Options for path resolution\n * @returns Resolved data sync directory path\n * @throws WorktreeMissingError if worktree missing and allowFallback is false\n *\n * See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md\n */\nexport async function resolveDataSyncDir(\n baseDir: string = process.cwd(),\n options?: ResolveDataSyncDirOptions,\n): Promise<string> {\n const allowFallback = options?.allowFallback ?? true;\n\n // Return cached result if baseDir and options haven't changed\n if (\n _resolvedDataSyncDir &&\n _resolvedBaseDir === baseDir &&\n _resolvedAllowFallback === allowFallback\n ) {\n return _resolvedDataSyncDir;\n }\n\n const worktreePath = join(baseDir, DATA_SYNC_DIR_VIA_WORKTREE);\n const directPath = join(baseDir, DATA_SYNC_DIR);\n\n // Check if worktree path exists\n try {\n await access(worktreePath);\n _resolvedDataSyncDir = worktreePath;\n _resolvedBaseDir = baseDir;\n _resolvedAllowFallback = allowFallback;\n return worktreePath;\n } catch {\n // Worktree doesn't exist\n if (!allowFallback) {\n throw new WorktreeMissingError();\n }\n\n // Fallback to direct path (test mode or diagnostic tools)\n // Note: In production, sync.ts checks worktree health before calling this\n // Debug warning to help detect unintended fallback usage\n if (process.env.DEBUG || process.env.TBD_DEBUG) {\n console.warn(\n '[tbd:paths] resolveDataSyncDir: worktree not found, falling back to direct path',\n );\n }\n _resolvedDataSyncDir = directPath;\n _resolvedBaseDir = baseDir;\n _resolvedAllowFallback = allowFallback;\n return directPath;\n }\n}\n\n/**\n * Resolve issues directory path.\n */\nexport async function resolveIssuesDir(\n baseDir: string = process.cwd(),\n options?: ResolveDataSyncDirOptions,\n): Promise<string> {\n const dataSyncDir = await resolveDataSyncDir(baseDir, options);\n return join(dataSyncDir, 'issues');\n}\n\n/**\n * Resolve mappings directory path.\n */\nexport async function resolveMappingsDir(\n baseDir: string = process.cwd(),\n options?: ResolveDataSyncDirOptions,\n): Promise<string> {\n const dataSyncDir = await resolveDataSyncDir(baseDir, options);\n return join(dataSyncDir, 'mappings');\n}\n\n/**\n * Resolve attic directory path.\n */\nexport async function resolveAtticDir(\n baseDir: string = process.cwd(),\n options?: ResolveDataSyncDirOptions,\n): Promise<string> {\n const dataSyncDir = await resolveDataSyncDir(baseDir, options);\n return join(dataSyncDir, 'attic');\n}\n\n/**\n * Clear the resolved path cache.\n * Call this when the repository state changes (e.g., after init).\n */\nexport function clearPathCache(): void {\n _resolvedDataSyncDir = null;\n _resolvedBaseDir = null;\n _resolvedAllowFallback = null;\n}\n\n// =============================================================================\n// Doc Path Resolution\n// =============================================================================\n\nimport { isAbsolute } from 'node:path';\nimport { homedir } from 'node:os';\n\n/**\n * Resolve a doc path for consistent handling across the codebase.\n *\n * Path resolution rules:\n * - Absolute paths (starting with /): used as-is\n * - Home directory paths (starting with ~/): expanded to user home directory\n * - Relative paths: resolved from tbd root (baseDir)\n *\n * @param docPath - The path to resolve\n * @param baseDir - The tbd root directory (parent of .tbd/), defaults to cwd\n * @returns Resolved absolute path\n *\n * @example\n * // Absolute path - returned as-is\n * resolveDocPath('/usr/local/docs/file.md') // => '/usr/local/docs/file.md'\n *\n * // Home path - expanded\n * resolveDocPath('~/docs/file.md') // => '/Users/username/docs/file.md'\n *\n * // Relative path - resolved from baseDir\n * resolveDocPath('docs/file.md', '/project') // => '/project/docs/file.md'\n */\nexport function resolveDocPath(docPath: string, baseDir: string = process.cwd()): string {\n // Handle home directory expansion\n if (docPath.startsWith('~/')) {\n return join(homedir(), docPath.slice(2));\n }\n\n // Absolute paths used as-is\n if (isAbsolute(docPath)) {\n return docPath;\n }\n\n // Relative paths resolved from baseDir (tbd root)\n return join(baseDir, docPath);\n}\n\n// =============================================================================\n// Token Estimation Settings\n// =============================================================================\n\n/**\n * Characters per token ratio for estimating token counts.\n *\n * Based on research of OpenAI (tiktoken) and Claude tokenizers:\n * - Pure English prose: ~4-5 chars/token\n * - Code and symbols: ~3 chars/token\n * - Mixed markdown/code docs: ~3.5 chars/token\n *\n * We use 3.5 as our docs are markdown with code examples.\n * This provides ~15-20% accuracy, sufficient for cost estimation.\n */\nexport const CHARS_PER_TOKEN = 3.5;\n","/**\n * tbd Directory Format Versioning\n * ================================\n *\n * This file is the SINGLE SOURCE OF TRUTH for .tbd/ directory format versions.\n *\n * WHEN TO BUMP THE FORMAT VERSION:\n * - Bump when changes REQUIRE migration (deleting files, changing formats, moving files)\n * - **Bump when changing config schema** (adding, removing, or modifying fields)\n * - Do NOT bump for additive changes that don't affect config.yml (new directories, etc.)\n *\n * HOW TO ADD A NEW FORMAT VERSION:\n * 1. Add entry to FORMAT_HISTORY with detailed description\n * 2. Implement migrate_fXX_to_fYY() function\n * 3. Add case to migrateToLatest()\n * 4. Update CURRENT_FORMAT\n * 5. Add tests for the migration path\n *\n * FORWARD COMPATIBILITY POLICY:\n * ConfigSchema uses Zod's strip() mode, which discards unknown fields. To prevent\n * data loss when users mix tbd versions:\n *\n * 1. When changing config schema, bump the format version (e.g., f03 → f04)\n * 2. config.ts checks format compatibility via isCompatibleFormat()\n * 3. Older tbd versions will error with \"format 'fXX' is from a newer tbd version\"\n * 4. The error tells users to upgrade: npm install -g get-tbd@latest\n *\n * This ensures older versions fail fast rather than silently corrupting config.\n * See ConfigSchema in schemas.ts and checkFormatCompatibility() in config.ts.\n */\n\n// =============================================================================\n// Format Constants\n// =============================================================================\n\n/**\n * Current format version.\n * Bump this ONLY for breaking changes that require migration.\n */\nexport const CURRENT_FORMAT = 'f03';\n\n/**\n * Initial format version for configs that don't have tbd_format field.\n */\nexport const INITIAL_FORMAT = 'f01';\n\n// =============================================================================\n// Format History\n// =============================================================================\n\n/**\n * Complete history of format versions with their changes.\n * This serves as documentation and enables version detection.\n */\nexport const FORMAT_HISTORY = {\n f01: {\n introduced: '0.1.0',\n description: 'Initial format',\n structure: {\n 'config.yml': 'Project configuration',\n 'state.yml': 'Local state (gitignored)',\n 'docs/': 'Documentation cache (gitignored)',\n 'issues/': 'Issue YAML files',\n },\n },\n f02: {\n introduced: '0.1.5',\n description: 'Adds configurable doc_cache',\n changes: [\n 'Added doc_cache: key to config.yml for configurable doc sources',\n 'Added settings.doc_auto_sync_hours for automatic doc refresh',\n 'Added last_doc_sync_at to state.yml for tracking sync time',\n ],\n migration: 'Populates default doc_cache config from bundled docs',\n },\n f03: {\n introduced: '0.1.6',\n description: 'Consolidates docs_cache config structure',\n changes: [\n 'Consolidated doc_cache: and docs: into single docs_cache: key',\n 'Moved doc_cache: -> docs_cache.files:',\n 'Moved docs.paths: -> docs_cache.lookup_path:',\n 'Removed separate docs: key',\n ],\n migration: 'Migrates old config keys to new docs_cache structure',\n },\n} as const;\n\nexport type FormatVersion = keyof typeof FORMAT_HISTORY;\n\n// =============================================================================\n// Migration Types\n// =============================================================================\n\n/**\n * Raw config data before parsing/validation.\n * Used during migration when we need to work with potentially old formats.\n */\nexport interface RawConfig {\n tbd_format?: string;\n tbd_version?: string;\n sync?: {\n branch?: string;\n remote?: string;\n };\n display?: {\n id_prefix?: string;\n };\n settings?: {\n auto_sync?: boolean;\n doc_auto_sync_hours?: number;\n };\n // Old format (f02 and earlier)\n docs?: {\n paths?: string[];\n };\n doc_cache?: Record<string, string>;\n // New format (f03+)\n docs_cache?: {\n files?: Record<string, string>;\n lookup_path?: string[];\n };\n}\n\n/**\n * Result of a migration operation.\n */\nexport interface MigrationResult {\n /** The migrated config */\n config: RawConfig;\n /** Format version before migration */\n fromFormat: FormatVersion;\n /** Format version after migration */\n toFormat: FormatVersion;\n /** Whether any changes were made */\n changed: boolean;\n /** Description of changes made */\n changes: string[];\n}\n\n// =============================================================================\n// Migration Functions\n// =============================================================================\n\n/**\n * Migrate from f01 to f02.\n * - Adds tbd_format field\n * - Adds doc_auto_sync_hours setting (default: 24)\n * - doc_cache will be populated separately during setup (requires file system access)\n */\nfunction migrate_f01_to_f02(config: RawConfig): MigrationResult {\n const changes: string[] = [];\n const migrated = { ...config };\n\n // Add format version\n migrated.tbd_format = 'f02';\n changes.push('Added tbd_format: f02');\n\n // Ensure settings exists and add doc_auto_sync_hours\n migrated.settings ??= {};\n if (migrated.settings.doc_auto_sync_hours === undefined) {\n migrated.settings.doc_auto_sync_hours = 24;\n changes.push('Added settings.doc_auto_sync_hours: 24');\n }\n\n // Note: doc_cache is intentionally NOT added here.\n // It will be populated during setup when we have access to the file system\n // and can enumerate the bundled docs.\n\n return {\n config: migrated,\n fromFormat: 'f01',\n toFormat: 'f02',\n changed: changes.length > 0,\n changes,\n };\n}\n\n/**\n * Migrate from f02 to f03.\n * - Consolidates doc_cache: and docs: into docs_cache:\n * - Moves doc_cache: -> docs_cache.files:\n * - Moves docs.paths: -> docs_cache.lookup_path:\n * - Removes separate docs: and doc_cache: keys\n */\nfunction migrate_f02_to_f03(config: RawConfig): MigrationResult {\n const changes: string[] = [];\n const migrated = { ...config };\n\n // Update format version\n migrated.tbd_format = 'f03';\n changes.push('Updated tbd_format: f03');\n\n // Initialize docs_cache if it doesn't exist\n migrated.docs_cache ??= {};\n\n // Migrate doc_cache -> docs_cache.files\n if (migrated.doc_cache && Object.keys(migrated.doc_cache).length > 0) {\n migrated.docs_cache.files = { ...migrated.doc_cache };\n changes.push('Moved doc_cache: -> docs_cache.files:');\n delete migrated.doc_cache;\n }\n\n // Migrate docs.paths -> docs_cache.lookup_path\n if (migrated.docs?.paths && migrated.docs.paths.length > 0) {\n migrated.docs_cache.lookup_path = [...migrated.docs.paths];\n changes.push('Moved docs.paths: -> docs_cache.lookup_path:');\n }\n\n // Remove old docs: key\n if (migrated.docs) {\n delete migrated.docs;\n changes.push('Removed docs: key');\n }\n\n return {\n config: migrated,\n fromFormat: 'f02',\n toFormat: 'f03',\n changed: changes.length > 0,\n changes,\n };\n}\n\n// =============================================================================\n// Public API\n// =============================================================================\n\n/**\n * Detect the format version of a config.\n * Returns INITIAL_FORMAT ('f01') if no tbd_format field is present.\n */\nexport function detectFormat(config: RawConfig): FormatVersion {\n const format = config.tbd_format;\n if (!format) {\n return INITIAL_FORMAT;\n }\n if (format in FORMAT_HISTORY) {\n return format as FormatVersion;\n }\n // Unknown format - treat as latest (will fail validation if incompatible)\n return CURRENT_FORMAT;\n}\n\n/**\n * Check if a config needs migration.\n */\nexport function needsMigration(config: RawConfig): boolean {\n const currentFormat = detectFormat(config);\n return currentFormat !== CURRENT_FORMAT;\n}\n\n/**\n * Migrate a config to the latest format version.\n *\n * This function applies all necessary migrations in sequence.\n * It does NOT populate doc_cache - that requires file system access\n * and should be done separately during setup.\n *\n * @param config - The raw config to migrate\n * @returns Migration result with the migrated config and change log\n */\nexport function migrateToLatest(config: RawConfig): MigrationResult {\n const fromFormat = detectFormat(config);\n\n if (fromFormat === CURRENT_FORMAT) {\n return {\n config,\n fromFormat,\n toFormat: CURRENT_FORMAT,\n changed: false,\n changes: [],\n };\n }\n\n let current = config;\n let currentFormat: FormatVersion = fromFormat;\n const allChanges: string[] = [];\n\n // Apply migrations in sequence\n if (currentFormat === 'f01') {\n const result = migrate_f01_to_f02(current);\n current = result.config;\n currentFormat = 'f02' as FormatVersion;\n allChanges.push(...result.changes);\n }\n\n if (currentFormat === 'f02') {\n const result = migrate_f02_to_f03(current);\n current = result.config;\n currentFormat = 'f03' as FormatVersion;\n allChanges.push(...result.changes);\n }\n\n // Add more migrations here as new format versions are added\n\n return {\n config: current,\n fromFormat,\n toFormat: currentFormat,\n changed: allChanges.length > 0,\n changes: allChanges,\n };\n}\n\n/**\n * Check if a format version is compatible with the current tbd version.\n * Future format versions are considered incompatible (would need tbd upgrade).\n */\nexport function isCompatibleFormat(format: string): boolean {\n const formatVersions = Object.keys(FORMAT_HISTORY);\n const currentIndex = formatVersions.indexOf(CURRENT_FORMAT);\n const checkIndex = formatVersions.indexOf(format);\n\n if (checkIndex === -1) {\n // Unknown format - might be from a newer tbd version\n return false;\n }\n\n // Compatible if same or older format (we can migrate up)\n return checkIndex <= currentIndex;\n}\n\n/**\n * Get a human-readable description of what migrations will be applied.\n */\nexport function describeMigration(fromFormat: FormatVersion): string[] {\n const descriptions: string[] = [];\n let current = fromFormat;\n\n if (current === 'f01') {\n descriptions.push('f01 → f02: Add doc_cache configuration support');\n current = 'f02';\n }\n\n if (current === 'f02') {\n descriptions.push('f02 → f03: Consolidate doc_cache and docs into docs_cache');\n current = 'f03';\n }\n\n // Add more migration descriptions here\n\n return descriptions;\n}\n","/**\n * Config file operations.\n *\n * Config is stored at .tbd/config.yml and contains project-level settings.\n *\n * ⚠️ FORMAT VERSIONING: See tbd-format.ts for version history and migration rules.\n *\n * See: tbd-design.md §2.2.2 Config File\n */\n\nimport { readFile, mkdir, access } from 'node:fs/promises';\nimport { join, dirname, parse as parsePath } from 'node:path';\nimport { writeFile } from 'atomically';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\n\nimport type { Config, LocalState } from '../lib/types.js';\nimport { ConfigSchema, LocalStateSchema } from '../lib/schemas.js';\nimport { CONFIG_FILE, STATE_FILE, SYNC_BRANCH } from '../lib/paths.js';\nimport {\n CURRENT_FORMAT,\n needsMigration,\n migrateToLatest,\n isCompatibleFormat,\n type RawConfig,\n} from '../lib/tbd-format.js';\n\n/**\n * Error thrown when the config format version is from a newer tbd version.\n * This prevents older tbd versions from silently stripping new config fields.\n */\nexport class IncompatibleFormatError extends Error {\n constructor(\n public readonly foundFormat: string,\n public readonly supportedFormat: string,\n ) {\n super(\n `Config format '${foundFormat}' is from a newer tbd version.\\n` +\n `This tbd version supports up to format '${supportedFormat}'.\\n` +\n `Please upgrade tbd: npm install -g get-tbd@latest`,\n );\n this.name = 'IncompatibleFormatError';\n }\n}\n\n/**\n * Check if config format is compatible, throw if not.\n * This prevents older tbd versions from silently stripping fields added by newer versions.\n */\nfunction checkFormatCompatibility(data: RawConfig): void {\n const format = data.tbd_format;\n if (format && !isCompatibleFormat(format)) {\n throw new IncompatibleFormatError(format, CURRENT_FORMAT);\n }\n}\n\n/**\n * Create default config for a new project.\n * @param prefix - Required: the project prefix for display IDs (e.g., \"proj\", \"myapp\")\n */\nfunction createDefaultConfig(version: string, prefix: string): Config {\n return ConfigSchema.parse({\n tbd_format: CURRENT_FORMAT,\n tbd_version: version,\n sync: {\n branch: SYNC_BRANCH,\n remote: 'origin',\n },\n display: {\n id_prefix: prefix,\n },\n settings: {\n auto_sync: false,\n doc_auto_sync_hours: 24,\n },\n });\n}\n\n/**\n * Initialize a new config file with default settings.\n * Creates .tbd directory if it doesn't exist.\n * @param prefix - Required: the project prefix for display IDs (e.g., \"proj\", \"myapp\")\n */\nexport async function initConfig(\n baseDir: string,\n version: string,\n prefix: string,\n): Promise<Config> {\n const tbdDir = join(baseDir, '.tbd');\n await mkdir(tbdDir, { recursive: true });\n\n const config = createDefaultConfig(version, prefix);\n await writeConfig(baseDir, config);\n\n return config;\n}\n\n/**\n * Read config from file with automatic migration if needed.\n *\n * ⚠️ FORMAT VERSIONING: See tbd-format.ts for version history and migration rules.\n *\n * @throws {IncompatibleFormatError} If config is from a newer tbd version.\n * @throws If config file doesn't exist or is invalid.\n */\nexport async function readConfig(baseDir: string): Promise<Config> {\n const configPath = join(baseDir, CONFIG_FILE);\n const content = await readFile(configPath, 'utf-8');\n const data = parseYaml(content) as RawConfig;\n\n // Check for incompatible (future) format versions first\n checkFormatCompatibility(data);\n\n // Check if migration is needed (for older formats)\n if (needsMigration(data)) {\n const result = migrateToLatest(data);\n // Note: We don't automatically write the migrated config here.\n // Migration writes should be explicit via writeConfig() after setup.\n return ConfigSchema.parse(result.config);\n }\n\n return ConfigSchema.parse(data);\n}\n\n/**\n * Read config from file, returning migration info if a migration was applied.\n * Use this when you need to know if the config was migrated.\n *\n * @throws {IncompatibleFormatError} If config is from a newer tbd version.\n */\nexport async function readConfigWithMigration(\n baseDir: string,\n): Promise<{ config: Config; migrated: boolean; changes: string[] }> {\n const configPath = join(baseDir, CONFIG_FILE);\n const content = await readFile(configPath, 'utf-8');\n const data = parseYaml(content) as RawConfig;\n\n // Check for incompatible (future) format versions first\n checkFormatCompatibility(data);\n\n if (needsMigration(data)) {\n const result = migrateToLatest(data);\n return {\n config: ConfigSchema.parse(result.config),\n migrated: result.changed,\n changes: result.changes,\n };\n }\n\n return {\n config: ConfigSchema.parse(data),\n migrated: false,\n changes: [],\n };\n}\n\n/**\n * Write config to file with explanatory comments.\n */\nexport async function writeConfig(baseDir: string, config: Config): Promise<void> {\n const configPath = join(baseDir, CONFIG_FILE);\n\n const yaml = stringifyYaml(config, {\n sortMapEntries: true,\n lineWidth: 0,\n });\n\n // Add explanatory comments for docs_cache section\n let content = yaml;\n if (config.docs_cache && Object.keys(config.docs_cache).length > 0) {\n const docsCacheComment = `# Documentation cache configuration.\n# files: Maps destination paths (relative to .tbd/docs/) to source locations.\n# Sources can be:\n# - internal: prefix for bundled docs (e.g., \"internal:shortcuts/standard/commit-code.md\")\n# - Full URL for external docs (e.g., \"https://raw.githubusercontent.com/org/repo/main/file.md\")\n# lookup_path: Search paths for doc lookup (like shell $PATH). Earlier paths take precedence.\n#\n# To sync docs: tbd sync --docs\n# To check status: tbd sync --status\n#\n# Auto-sync: Docs are automatically synced when stale (default: every 24 hours).\n# Configure with settings.doc_auto_sync_hours (0 = disabled).\n`;\n content = content.replace('docs_cache:', docsCacheComment + 'docs_cache:');\n }\n\n await writeFile(configPath, content);\n}\n\n/**\n * Check if tbd is initialized in the given directory (immediate check only).\n * Returns true if .tbd/ directory exists directly in baseDir.\n */\nasync function hasTbdDir(dir: string): Promise<boolean> {\n const tbdDir = join(dir, '.tbd');\n try {\n await access(tbdDir);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Find the tbd repository root by walking up the directory tree.\n * Similar to how git finds .git/ directories.\n *\n * @param startDir - Directory to start searching from\n * @returns The tbd root directory path, or null if not found\n */\nexport async function findTbdRoot(startDir: string): Promise<string | null> {\n let currentDir = startDir;\n const { root } = parsePath(startDir);\n\n while (currentDir !== root) {\n if (await hasTbdDir(currentDir)) {\n return currentDir;\n }\n currentDir = dirname(currentDir);\n }\n\n // Check root directory as well\n if (await hasTbdDir(root)) {\n return root;\n }\n\n return null;\n}\n\n/**\n * Check if tbd is initialized in the given directory or any parent directory.\n * Walks up the directory tree looking for .tbd/.\n */\nexport async function isInitialized(baseDir: string): Promise<boolean> {\n const root = await findTbdRoot(baseDir);\n return root !== null;\n}\n\n// =============================================================================\n// Local State Operations\n// =============================================================================\n\n/**\n * Read local state from .tbd/state.yml\n * Returns empty state if file doesn't exist.\n */\nexport async function readLocalState(baseDir: string): Promise<LocalState> {\n const statePath = join(baseDir, STATE_FILE);\n try {\n const content = await readFile(statePath, 'utf-8');\n const data: unknown = parseYaml(content);\n return LocalStateSchema.parse(data ?? {});\n } catch {\n // File doesn't exist or is invalid - return empty state\n return {};\n }\n}\n\n/**\n * Write local state to .tbd/state.yml\n */\nexport async function writeLocalState(baseDir: string, state: LocalState): Promise<void> {\n const statePath = join(baseDir, STATE_FILE);\n\n // Ensure .tbd directory exists\n await mkdir(join(baseDir, '.tbd'), { recursive: true });\n\n const yaml = stringifyYaml(state, {\n sortMapEntries: true,\n lineWidth: 0,\n });\n\n await writeFile(statePath, yaml);\n}\n\n/**\n * Update specific fields in local state (merge with existing).\n */\nexport async function updateLocalState(\n baseDir: string,\n updates: Partial<LocalState>,\n): Promise<LocalState> {\n const current = await readLocalState(baseDir);\n const updated = { ...current, ...updates };\n await writeLocalState(baseDir, updated);\n return updated;\n}\n\n// =============================================================================\n// Welcome State Operations\n// =============================================================================\n\n/**\n * Check if the user has seen the welcome message.\n */\nexport async function hasSeenWelcome(baseDir: string): Promise<boolean> {\n const state = await readLocalState(baseDir);\n return state.welcome_seen === true;\n}\n\n/**\n * Mark the welcome message as seen.\n */\nexport async function markWelcomeSeen(baseDir: string): Promise<void> {\n await updateLocalState(baseDir, { welcome_seen: true });\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 * 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 */\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 this.output.error(errorMessage, error instanceof Error ? error : undefined);\n throw new CLIError(errorMessage);\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 * 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 * 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, one was created independently - LWW based on created_at\n if (!base) {\n const localTime = new Date(local.created_at).getTime();\n const remoteTime = new Date(remote.created_at).getTime();\n\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 // 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 // Compare 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 // Always increment version after merge\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 */\nexport async function pushWithRetry(\n syncBranch: string,\n remote: string,\n onMergeNeeded: () => Promise<ConflictEntry[]>,\n): Promise<PushResult> {\n for (let attempt = 1; attempt <= MAX_PUSH_RETRIES; attempt++) {\n try {\n // Try to push\n await git('push', remote, syncBranch);\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('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 // 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 * 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 for (const file of mappingFiles) {\n await cp(join(wrongMappingsPath, file), join(correctMappingsPath, file));\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 { 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 MIN_GIT_VERSION,\n} from '../../file/git.js';\n\ninterface InitOptions {\n prefix?: string;\n syncBranch?: string;\n remote?: string;\n}\n\nclass InitHandler extends BaseCommand {\n async run(options: InitOptions): Promise<void> {\n const cwd = process.cwd();\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-4 letter prefix for your project (e.g., tbd, myp).\\n\\n' +\n 'For full setup with integrations: tbd setup --auto --prefix=<name>',\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 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 (e.g., \"proj\", \"myapp\")')\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 * ID generation and validation utilities.\n *\n * The system uses dual IDs for usability:\n * - Internal ID: is-{ulid} - ULID-based (26 lowercase chars), stored in files\n * - External ID: {prefix}-{short} - 4-5 base36 chars for CLI display/input\n *\n * For Beads compatibility, bd- prefix is accepted on input for external IDs.\n *\n * See: tbd-design.md §2.5 ID Generation\n */\n\nimport { ulid } from 'ulid';\nimport { randomBytes } from 'node:crypto';\n\n// =============================================================================\n// Branded Types for Type-Safe ID Handling\n// =============================================================================\n\n/**\n * Branded type for internal issue IDs (is-{ulid} format).\n *\n * Internal IDs are stored in files and used as the canonical identifier.\n * Format: is-{26 lowercase alphanumeric chars}\n * Example: is-01hx5zzkbkactav9wevgemmvrz\n *\n * Use this type when:\n * - Reading/writing issue files\n * - Storing parent_id, dependencies, child_order_hints\n * - Passing IDs between internal functions\n */\ndeclare const InternalIssueIdBrand: unique symbol;\nexport type InternalIssueId = string & { [InternalIssueIdBrand]: never };\n\n/**\n * Branded type for display issue IDs ({prefix}-{short} format).\n *\n * Display IDs are shown to users and accepted as CLI input.\n * Format: {prefix}-{short} where short is typically 4 base36 chars\n * Example: tbd-a7k2, bd-100\n *\n * Use this type when:\n * - Formatting output for users\n * - Accepting user input (before resolution)\n * - Building tree views for display\n */\ndeclare const DisplayIssueIdBrand: unique symbol;\nexport type DisplayIssueId = string & { [DisplayIssueIdBrand]: never };\n\n/**\n * Cast a string to InternalIssueId after validation.\n * Use this when you've validated that a string is a valid internal ID.\n */\nexport function asInternalId(id: string): InternalIssueId {\n return id as InternalIssueId;\n}\n\n/**\n * Cast a string to DisplayIssueId.\n * Use this when formatting an ID for display.\n */\nexport function asDisplayId(id: string): DisplayIssueId {\n return id as DisplayIssueId;\n}\n\n/**\n * Prefix for internal IDs (ULID-based).\n * All internal IDs are formatted as: {INTERNAL_ID_PREFIX}-{ulid}\n */\nexport const INTERNAL_ID_PREFIX = 'is';\n\n/**\n * Length of internal ID prefix including the hyphen (e.g., \"is-\" = 3).\n */\nexport const INTERNAL_ID_PREFIX_LENGTH = INTERNAL_ID_PREFIX.length + 1;\n\n/**\n * Construct an internal ID from a ULID.\n *\n * @param ulidValue - The ULID (26 chars)\n * @returns Internal ID in format {prefix}-{ulid}\n */\nexport function makeInternalId(ulidValue: string): InternalIssueId {\n return `${INTERNAL_ID_PREFIX}-${ulidValue.toLowerCase()}` as InternalIssueId;\n}\n\n/**\n * Generate a unique internal ID using ULID.\n * Format: is-{ulid} (26 lowercase alphanumeric chars)\n * Example: is-01hx5zzkbkactav9wevgemmvrz\n *\n * ULID provides:\n * - Time-ordered sorting (48-bit timestamp)\n * - 80-bit randomness (no collisions)\n * - Lexicographic sort = chronological order\n */\nexport function generateInternalId(): InternalIssueId {\n return makeInternalId(ulid());\n}\n\n/**\n * Generate a short ID for external display.\n * Format: base36 characters (a-z, 0-9)\n * Example: a7k2\n *\n * @param length - Number of characters (default 4)\n */\nexport function generateShortId(length = 4): string {\n const chars = '0123456789abcdefghijklmnopqrstuvwxyz';\n let result = '';\n const bytes = randomBytes(length);\n for (let i = 0; i < length; i++) {\n result += chars[bytes[i]! % 36];\n }\n return result;\n}\n\n// Regex pattern for validating internal IDs - built from prefix constant\nconst INTERNAL_ID_PATTERN = new RegExp(`^${INTERNAL_ID_PREFIX}-[0-9a-z]{26}$`);\n\n// Expected length of a full internal ID (prefix + hyphen + 26-char ULID)\nconst INTERNAL_ID_LENGTH = INTERNAL_ID_PREFIX_LENGTH + 26;\n\n/**\n * Validate an internal issue ID matches the ULID format.\n * Format: {prefix}-{26 lowercase alphanumeric chars}\n */\nexport function validateIssueId(id: string): boolean {\n return INTERNAL_ID_PATTERN.test(id);\n}\n\n/**\n * Validate a short/external ID format.\n * Format: 1+ base36 characters (typically 4 for new IDs, but imports may preserve longer IDs).\n */\nexport function validateShortId(id: string): boolean {\n return /^[0-9a-z]+$/.test(id);\n}\n\n/**\n * Check if an input looks like an internal ID (ULID-based).\n */\nexport function isInternalId(input: string): boolean {\n const lower = input.toLowerCase();\n // Check if it starts with the internal prefix and has correct length\n const prefixWithHyphen = `${INTERNAL_ID_PREFIX}-`;\n if (lower.startsWith(prefixWithHyphen) && lower.length === INTERNAL_ID_LENGTH) {\n return INTERNAL_ID_PATTERN.test(lower);\n }\n return false;\n}\n\n/**\n * Check if an input looks like a short/external ID.\n * Returns true for IDs like \"a7k2\", \"bd-a7k2\", \"100\", \"tbd-100\".\n * Short IDs are 16 characters or less (ULIDs are 26 characters).\n */\nexport function isShortId(input: string): boolean {\n const lower = input.toLowerCase();\n // Strip prefix if present\n const stripped = lower.replace(/^[a-z]+-/, '');\n // Must be 1-16 alphanumeric chars (short IDs, not ULIDs which are 26 chars)\n return /^[0-9a-z]+$/.test(stripped) && stripped.length >= 1 && stripped.length <= 16;\n}\n\n/**\n * Extract the short ID portion from an external ID.\n * Examples:\n * \"tbd-100\" -> \"100\"\n * \"bd-a7k2\" -> \"a7k2\"\n * \"a7k2\" -> \"a7k2\"\n * \"100\" -> \"100\"\n */\nexport function extractShortId(externalId: string): string {\n return externalId.toLowerCase().replace(/^[a-z]+-/, '');\n}\n\n/**\n * Extract the prefix portion from an external ID.\n * Returns the prefix (letters before the hyphen) or null if no prefix found.\n * Examples:\n * \"tbd-100\" -> \"tbd\"\n * \"bd-a7k2\" -> \"bd\"\n * \"TBD-100\" -> \"tbd\" (normalized to lowercase)\n * \"a7k2\" -> null (no prefix)\n * \"100\" -> null (no prefix)\n */\nexport function extractPrefix(externalId: string): string | null {\n const match = /^([a-zA-Z]+)-/.exec(externalId);\n return match?.[1]?.toLowerCase() ?? null;\n}\n\n/**\n * Extract the ULID portion from an internal ID.\n *\n * Internal IDs have the format: {prefix}-{ulid}\n * This function strips any prefix to return just the ULID.\n *\n * Examples:\n * \"is-01hx5zzkbkactav9wevgemmvrz\" -> \"01hx5zzkbkactav9wevgemmvrz\"\n * \"01hx5zzkbkactav9wevgemmvrz\" -> \"01hx5zzkbkactav9wevgemmvrz\" (no prefix)\n *\n * @param internalId - The internal ID (with or without prefix)\n * @returns The ULID portion without any prefix\n */\nexport function extractUlidFromInternalId(internalId: string): string {\n // Strip any prefix in format {letters}- (e.g., \"is-\", \"bd-\")\n return internalId.toLowerCase().replace(/^[a-z]+-/, '');\n}\n\n/** Prefix used in Beads for compatibility */\nconst BEADS_COMPAT_PREFIX = 'bd';\n\n/**\n * Normalize an internal issue ID.\n *\n * This function expects a full internal ID ({prefix}-{ulid}).\n * If given a short ID, it won't be able to resolve it without\n * access to the ID mapping.\n *\n * Handles:\n * - Uppercase (converts to lowercase)\n * - Ensures internal ID prefix\n * - Beads compatibility (bd- prefix)\n */\nexport function normalizeIssueId(input: string): string {\n const lower = input.toLowerCase();\n const internalPrefixWithHyphen = `${INTERNAL_ID_PREFIX}-`;\n const beadsPrefixWithHyphen = `${BEADS_COMPAT_PREFIX}-`;\n\n // If already a valid internal ID, return as-is\n if (validateIssueId(lower)) {\n return lower;\n }\n\n // If it starts with internal prefix but wrong length, might be corrupted\n if (lower.startsWith(internalPrefixWithHyphen)) {\n return lower; // Return as-is, let validation fail later\n }\n\n // If it starts with bd- (Beads compat), convert prefix\n if (lower.startsWith(beadsPrefixWithHyphen)) {\n const rest = lower.slice(beadsPrefixWithHyphen.length);\n if (rest.length === 26) {\n return makeInternalId(rest);\n }\n // Short ID - can't resolve without mapping\n return lower;\n }\n\n // Bare ID without prefix\n if (lower.length === 26 && /^[0-9a-z]{26}$/.test(lower)) {\n return makeInternalId(lower);\n }\n\n // Can't normalize - return as-is\n return lower;\n}\n\nimport type { IdMapping } from '../file/id-mapping.js';\n\n/**\n * Format an internal ID for display with the configured prefix.\n *\n * Uses the short ID (4 chars) from the mapping.\n * Throws an error if the mapping is missing or doesn't contain the ID.\n *\n * IMPORTANT: All user-facing output MUST use short IDs, never internal ULIDs.\n * If you see a ULID in user output, it's a bug.\n *\n * @param internalId - The internal ID (is-{ulid})\n * @param mapping - ID mapping for short ID lookup (required)\n * @param prefix - Display prefix (should come from config.display.id_prefix; defaults to 'tbd' as fallback)\n * @throws Error if mapping is missing or ID not found in mapping\n */\nexport function formatDisplayId(\n internalId: InternalIssueId | string,\n mapping: IdMapping,\n prefix = 'tbd',\n): DisplayIssueId {\n // Extract the ULID portion\n const ulidPart = extractUlidFromInternalId(internalId);\n\n // Get short ID from mapping\n const shortId = mapping.ulidToShort.get(ulidPart);\n if (!shortId) {\n throw new Error(\n `No short ID mapping found for internal ID: ${internalId}. ` +\n `This is a bug - all issues must have a short ID mapping.`,\n );\n }\n\n return `${prefix}-${shortId}` as DisplayIssueId;\n}\n\n/**\n * Format an ID for debug output, showing both public and internal IDs.\n *\n * @param internalId - The internal ID (is-{ulid})\n * @param mapping - ID mapping for short ID lookup\n * @param prefix - Display prefix (should come from config.display.id_prefix; defaults to 'tbd' as fallback)\n */\nexport function formatDebugId(\n internalId: InternalIssueId | string,\n mapping: IdMapping,\n prefix = 'tbd',\n): string {\n const displayId = formatDisplayId(internalId, mapping, prefix);\n return `${displayId} (${internalId})`;\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 all files in parallel for better I/O performance\n const fileContents = await Promise.all(\n mdFiles.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 // Parse issues (filter out failed reads)\n const issues: Issue[] = [];\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 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 * Natural sort utilities.\n *\n * Provides alphanumeric sorting where numeric portions are sorted numerically.\n * Similar to `sort -V` (version sort) or `sort -n` for numbers.\n *\n * Examples:\n * [\"1\", \"2\", \"9\", \"10\", \"11\"] instead of [\"1\", \"10\", \"11\", \"2\", \"9\"]\n * [\"a1\", \"a2\", \"a10\"] instead of [\"a1\", \"a10\", \"a2\"]\n * [\"file1.txt\", \"file2.txt\", \"file10.txt\"] instead of [\"file1.txt\", \"file10.txt\", \"file2.txt\"]\n */\n\n/**\n * Split a string into alternating runs of digits and non-digits.\n * @param str - The string to split\n * @returns Array of [isNumeric, value] tuples\n */\nfunction splitIntoChunks(str: string): [boolean, string][] {\n const chunks: [boolean, string][] = [];\n let current = '';\n let currentIsNumeric: boolean | null = null;\n\n for (const char of str) {\n const isDigit = char >= '0' && char <= '9';\n\n if (currentIsNumeric === null) {\n // First character\n currentIsNumeric = isDigit;\n current = char;\n } else if (isDigit === currentIsNumeric) {\n // Same type, continue accumulating\n current += char;\n } else {\n // Type changed, push current chunk and start new one\n chunks.push([currentIsNumeric, current]);\n currentIsNumeric = isDigit;\n current = char;\n }\n }\n\n // Push final chunk\n if (current) {\n chunks.push([currentIsNumeric!, current]);\n }\n\n return chunks;\n}\n\n/**\n * Compare two strings using natural (alphanumeric) ordering.\n *\n * Numeric portions are compared numerically, non-numeric portions are\n * compared lexicographically (case-insensitive). Numbers sort before letters\n * when they appear at the same position in mixed comparisons, matching\n * the behavior of `sort -V` (version sort).\n *\n * @param a - First string\n * @param b - Second string\n * @returns Negative if a < b, positive if a > b, zero if equal\n */\nexport function naturalCompare(a: string, b: string): number {\n // Handle empty strings\n if (!a && !b) return 0;\n if (!a) return -1;\n if (!b) return 1;\n\n const chunksA = splitIntoChunks(a);\n const chunksB = splitIntoChunks(b);\n\n const minLen = Math.min(chunksA.length, chunksB.length);\n\n for (let i = 0; i < minLen; i++) {\n const [isNumericA, valueA] = chunksA[i]!;\n const [isNumericB, valueB] = chunksB[i]!;\n\n if (isNumericA && isNumericB) {\n // Both are numeric - compare as numbers\n const numA = parseInt(valueA, 10);\n const numB = parseInt(valueB, 10);\n if (numA !== numB) {\n return numA - numB;\n }\n // If numerically equal but different strings (e.g., \"01\" vs \"1\"),\n // prefer shorter (fewer leading zeros)\n if (valueA.length !== valueB.length) {\n return valueA.length - valueB.length;\n }\n } else if (!isNumericA && !isNumericB) {\n // Both are non-numeric - compare lexicographically (case-insensitive)\n const lowerA = valueA.toLowerCase();\n const lowerB = valueB.toLowerCase();\n if (lowerA !== lowerB) {\n return lowerA.localeCompare(lowerB);\n }\n // Same when lowercased - they're equal for sorting purposes\n } else {\n // Mixed: numeric comes before non-numeric\n // (so \"1\" comes before \"a\" at the same position)\n // This matches `sort -V` behavior\n return isNumericA ? -1 : 1;\n }\n }\n\n // All compared chunks are equal, shorter string comes first\n return chunksA.length - chunksB.length;\n}\n\n/**\n * Sort an array of strings using natural (alphanumeric) ordering.\n *\n * @param arr - Array to sort\n * @returns New sorted array (does not mutate original)\n */\nexport function naturalSort(arr: readonly string[]): string[] {\n return [...arr].sort(naturalCompare);\n}\n\n/**\n * Sort an array of objects by a string key using natural ordering.\n *\n * @param arr - Array to sort\n * @param keyFn - Function to extract the sort key from each element\n * @returns New sorted array (does not mutate original)\n */\nexport function naturalSortBy<T>(arr: readonly T[], keyFn: (item: T) => string): T[] {\n return [...arr].sort((a, b) => naturalCompare(keyFn(a), keyFn(b)));\n}\n","/**\n * ID mapping management for short public IDs.\n *\n * Maps 4-char base36 short IDs to 26-char ULIDs.\n * Stored in .tbd/data-sync/mappings/ids.yml\n *\n * See: tbd-design.md §2.5 ID Generation\n */\n\nimport { readFile, mkdir } from 'node:fs/promises';\nimport { join, dirname } from 'node:path';\nimport { writeFile } from 'atomically';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\n\nimport {\n generateShortId,\n extractUlidFromInternalId,\n makeInternalId,\n isInternalId,\n extractShortId,\n asInternalId,\n type InternalIssueId,\n} from '../lib/ids.js';\nimport { naturalSort } from '../lib/sort.js';\n\n/**\n * ID mapping from short ID to ULID.\n * Format in ids.yml:\n * a7k2: 01hx5zzkbkactav9wevgemmvrz\n * b3m9: 01hx5zzkbkbctav9wevgemmvrz\n */\nexport interface IdMapping {\n shortToUlid: Map<string, string>;\n ulidToShort: Map<string, string>;\n}\n\n/**\n * Get the path to the ids.yml mapping file.\n */\nfunction getMappingPath(baseDir: string): string {\n return join(baseDir, 'mappings', 'ids.yml');\n}\n\n/**\n * Load the ID mapping from disk.\n * Returns empty mapping if file doesn't exist.\n */\nexport async function loadIdMapping(baseDir: string): Promise<IdMapping> {\n const filePath = getMappingPath(baseDir);\n\n let content: string;\n try {\n content = await readFile(filePath, 'utf-8');\n } catch {\n // File doesn't exist - return empty mapping\n return {\n shortToUlid: new Map(),\n ulidToShort: new Map(),\n };\n }\n\n const data = (parseYaml(content) as Record<string, string>) || {};\n\n const shortToUlid = new Map<string, string>();\n const ulidToShort = new Map<string, string>();\n\n for (const [shortId, ulid] of Object.entries(data)) {\n shortToUlid.set(shortId, ulid);\n ulidToShort.set(ulid, shortId);\n }\n\n return { shortToUlid, ulidToShort };\n}\n\n/**\n * Save the ID mapping to disk.\n */\nexport async function saveIdMapping(baseDir: string, mapping: IdMapping): Promise<void> {\n const filePath = getMappingPath(baseDir);\n\n // Ensure directory exists\n await mkdir(dirname(filePath), { recursive: true });\n\n // Convert Map to sorted object for deterministic output\n // Use natural sort so \"1\", \"2\", \"10\" sorts correctly (not \"1\", \"10\", \"2\")\n const data: Record<string, string> = {};\n const sortedKeys = naturalSort(Array.from(mapping.shortToUlid.keys()));\n for (const key of sortedKeys) {\n data[key] = mapping.shortToUlid.get(key)!;\n }\n\n const content = stringifyYaml(data);\n await writeFile(filePath, content);\n}\n\n/**\n * Calculate the optimal short ID length based on existing ID count.\n *\n * At 50K issues, switches from 4-char to 5-char IDs to keep\n * collision probability low (~3% per attempt with 4 chars at 50K).\n *\n * With 10 retries per length, actual failure probability is astronomically low.\n */\nexport function calculateOptimalLength(existingCount: number): number {\n return existingCount < 50_000 ? 4 : 5;\n}\n\n/**\n * Generate a unique short ID that doesn't collide with existing ones.\n *\n * Calculates optimal length (4 or 5 chars) based on existing ID count,\n * then retries with the next length if collisions occur.\n *\n * @returns The new short ID\n * @throws If unable to generate a unique ID after max attempts\n */\nexport function generateUniqueShortId(mapping: IdMapping): string {\n const ATTEMPTS_PER_LENGTH = 10;\n const existingCount = mapping.shortToUlid.size;\n const optimalLength = calculateOptimalLength(existingCount);\n\n // Try optimal length first, then fall back to longer if needed\n for (const length of [optimalLength, optimalLength + 1]) {\n for (let attempt = 0; attempt < ATTEMPTS_PER_LENGTH; attempt++) {\n const shortId = generateShortId(length);\n if (!mapping.shortToUlid.has(shortId)) {\n return shortId;\n }\n }\n }\n\n throw new Error(\n `Failed to generate unique short ID after 20 attempts with ${existingCount} existing IDs. ` +\n `This should be extremely rare - please report if you see this error.`,\n );\n}\n\n/**\n * Register a new ID mapping.\n * @param ulid - The ULID (without is- prefix)\n * @param shortId - The short ID (4 chars)\n */\nexport function addIdMapping(mapping: IdMapping, ulid: string, shortId: string): void {\n mapping.shortToUlid.set(shortId, ulid);\n mapping.ulidToShort.set(ulid, shortId);\n}\n\n/**\n * Get the short ID for a ULID.\n * @param ulid - The ULID (without is- prefix)\n * @returns The short ID, or undefined if not found\n */\nexport function getShortId(mapping: IdMapping, ulid: string): string | undefined {\n return mapping.ulidToShort.get(ulid);\n}\n\n/**\n * Get the ULID for a short ID.\n * @param shortId - The short ID\n * @returns The ULID (without is- prefix), or undefined if not found\n */\nexport function getUlid(mapping: IdMapping, shortId: string): string | undefined {\n return mapping.shortToUlid.get(shortId);\n}\n\n/**\n * Check if a short ID exists in the mapping.\n */\nexport function hasShortId(mapping: IdMapping, shortId: string): boolean {\n return mapping.shortToUlid.has(shortId);\n}\n\n/**\n * Create a short ID mapping for a new internal ID.\n * Generates a unique short ID and registers it in the mapping.\n *\n * @param internalId - The internal ID (is-{ulid})\n * @param mapping - The ID mapping to update\n * @returns The generated short ID\n */\nexport function createShortIdMapping(internalId: string, mapping: IdMapping): string {\n // Extract ULID from internal ID (remove prefix)\n const ulid = extractUlidFromInternalId(internalId);\n\n // Check if already mapped\n const existing = mapping.ulidToShort.get(ulid);\n if (existing) {\n return existing;\n }\n\n // Generate unique short ID\n const shortId = generateUniqueShortId(mapping);\n\n // Register mapping\n addIdMapping(mapping, ulid, shortId);\n\n return shortId;\n}\n\n/**\n * Resolve any ID input to an internal ID ({prefix}-{ulid}).\n *\n * Handles:\n * - Internal IDs: {prefix}-{ulid} -> {prefix}-{ulid}\n * - Short IDs: a7k2 -> {prefix}-{ulid from mapping}\n * - Prefixed short IDs: bd-a7k2 -> {prefix}-{ulid from mapping}\n *\n * @param input - The ID input (short ID, prefixed short ID, or internal ID)\n * @param mapping - The ID mapping for short ID resolution\n * @returns The internal ID ({prefix}-{ulid})\n * @throws If the short ID is not found in the mapping\n */\nexport function resolveToInternalId(input: string, mapping: IdMapping): InternalIssueId {\n const lower = input.toLowerCase();\n\n // If it's already an internal ID, return it\n if (isInternalId(lower)) {\n return asInternalId(lower);\n }\n\n // Extract the short ID portion (strips any prefix like \"bd-\" or \"is-\")\n const shortId = extractShortId(lower);\n\n // If it's a full ULID (26 chars), it might be a bare internal ID\n if (shortId.length === 26 && /^[0-9a-z]{26}$/.test(shortId)) {\n return makeInternalId(shortId);\n }\n\n // Must be a short ID - look it up in the mapping\n const ulid = mapping.shortToUlid.get(shortId);\n if (!ulid) {\n throw new Error(`Unknown issue ID: ${input}. ` + `Short ID \"${shortId}\" not found in mapping.`);\n }\n\n return makeInternalId(ulid);\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 {\n throw new CLIError(`Failed to read description from file: ${options.file}`);\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 * Comparison chain utilities for complex multi-field sorting.\n *\n * Inspired by Google Guava's ComparisonChain, this provides a fluent API\n * for building comparators with multiple sort keys, null handling, and\n * custom orderings.\n *\n * Example:\n *\n * items.sort(comparisonChain<Item>()\n * .compare(item => item.title, ordering.nullsLast)\n * .compare(item => item.url)\n * .result());\n */\n\nexport type Selector<T, K> = (item: T) => K;\nexport type Comparator<T> = (a: T, b: T) => number;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst defaultCompare: Comparator<any> = (a, b) => {\n if (typeof a === 'string' && typeof b === 'string') {\n return a.localeCompare(b);\n }\n if (a < b) return -1;\n if (a > b) return 1;\n return 0;\n};\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst nullLastCompare: Comparator<any> = (a, b) => {\n if (a == null) return b == null ? 0 : 1;\n if (b == null) return -1;\n return defaultCompare(a, b);\n};\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst nullFirstCompare: Comparator<any> = (a, b) => {\n if (a == null) return b == null ? 0 : -1;\n if (b == null) return 1;\n return defaultCompare(a, b);\n};\n\n/**\n * A Google Guava-style comparison chain to make complex sorting, such as secondary\n * sorts, significantly easier.\n *\n * For example:\n *\n * items.sort(comparisonChain<Item>()\n * .compare(item => item.title, ordering.nullsLast)\n * .compare(item => item.url)\n * .result());\n */\nexport const comparisonChain = <T>() => {\n let compare: Comparator<T> = () => 0;\n\n const chain: {\n compare: <K>(selector: Selector<T, K>, comparator?: Comparator<K>) => typeof chain;\n result: () => Comparator<T>;\n } = {\n compare: <K>(selector: Selector<T, K>, comparator: Comparator<K> = defaultCompare) => {\n const prevCompare = compare;\n compare = (a, b) => prevCompare(a, b) || comparator(selector(a), selector(b));\n return chain;\n },\n result: () => compare,\n };\n\n return chain;\n};\n\n/**\n * Reverse a comparator's ordering.\n */\nexport const reverse =\n <T>(comparator: Comparator<T>) =>\n (a: T, b: T) =>\n comparator(b, a);\n\n/**\n * Create a comparator that sorts values in a manually specified order.\n * Values not in the order array are placed at the end.\n */\nconst manualOrderComparator = <T>(order: readonly T[]): Comparator<T> => {\n const orderMap = new Map(order.map((value, index) => [value, index]));\n\n return (a: T, b: T): number => {\n const indexA = orderMap.get(a);\n const indexB = orderMap.get(b);\n\n // Values not in the manually ordered array go at the end.\n if (indexA === undefined) return indexB === undefined ? 0 : 1;\n if (indexB === undefined) return -1;\n\n return indexA - indexB;\n };\n};\n\n/**\n * Common ordering strategies for use with comparisonChain.\n */\nexport const ordering = {\n nullsLast: nullLastCompare,\n nullsFirst: nullFirstCompare,\n default: defaultCompare,\n reversed: reverse(defaultCompare),\n manual: manualOrderComparator,\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 const storedFilename = basename(normalizedStored);\n const queryFilename = basename(normalizedQuery);\n\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('/') && storedFilename === normalizedQuery) {\n return true;\n }\n\n // Also match if both have same filename and query is just a filename\n if (!normalizedQuery.includes('/') && storedFilename === queryFilename) {\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, CLIError } from '../lib/errors.js';\nimport { loadDataContext, type TbdDataContext } 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 { naturalCompare } from '../../lib/sort.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 let issues: Issue[];\n let dataCtx: TbdDataContext;\n\n try {\n // Load shared data context (dataSyncDir, mapping, config, prefix)\n dataCtx = await loadDataContext(tbdRoot);\n issues = await listIssues(dataCtx.dataSyncDir);\n } catch {\n throw new CLIError('Failed to read issues');\n }\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 // Helper to get short ID for secondary sort\n const getShortId = (issue: Issue): string => {\n const ulid = extractUlidFromInternalId(issue.id);\n return mapping.ulidToShort.get(ulid) ?? ulid;\n };\n\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 .compare(getShortId, (a, b) => naturalCompare(a, b))\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 type { IssueStatusType } from '../../lib/types.js';\n\ninterface ShowOptions {\n showOrder?: boolean;\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 // 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 };\n\n this.output.data(displayIssue, () => {\n const colors = this.output.getColors();\n\n // Output as YAML+Markdown format (same as storage format)\n const serialized = serializeIssue(issue);\n\n // Add some color highlighting for text output\n const lines = serialized.split('\\n');\n for (const line of lines) {\n if (line === '---') {\n console.log(colors.dim(line));\n } else if (line.startsWith('id:')) {\n console.log(`${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 console.log(`${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 console.log(`${colors.dim('priority:')} ${priorityColor(formatPriority(priority))}`);\n } else if (line.startsWith('title:')) {\n console.log(`${colors.dim('title:')} ${colors.bold(line.slice(7))}`);\n } else if (line.startsWith('spec_path:')) {\n console.log(`${colors.dim('spec_path:')} ${colors.id(line.slice(11))}`);\n } else if (line.startsWith('## Notes')) {\n console.log(colors.bold(line));\n } else if (line.startsWith(' - ')) {\n console.log(` - ${colors.label(line.slice(4))}`);\n } else {\n console.log(line);\n }\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 .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_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/commit-code.md')\n * // => { type: 'internal', location: 'shortcuts/standard/commit-code.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_PREFIX)) {\n return {\n type: 'internal',\n location: source.slice(INTERNAL_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_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/** Prefix for internal bundled doc sources */\nconst INTERNAL_SOURCE_PREFIX = 'internal:';\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 * `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} 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';\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}\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, options.force);\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(syncBranch, remote, 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 }\n\n /**\n * Show git log --stat output in debug mode.\n * Used to display commits that were synced.\n */\n private async showGitLogDebug(range: string, label: string): Promise<void> {\n try {\n const logOutput = await git('log', '--stat', '--oneline', range);\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(syncBranch: string, remote: string, _force?: boolean): 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 // 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 if (headBeforeMerge) {\n await this.showGitLogDebug(`${headBeforeMerge}..HEAD`, 'Commits received');\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 // 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 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 await this.showGitLogDebug(`-${aheadCommits}`, 'Commits sent');\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 - don't silently swallow it\n if (pushFailed) {\n this.output.data(\n {\n summary,\n conflicts: conflicts.length,\n pushFailed,\n pushError,\n unpushedCommits: aheadCommits,\n },\n () => {\n // Extract meaningful error from git output (look for HTTP errors, permission issues, etc.)\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 this.output.error(`Push failed: ${displayError}`);\n console.log(` ${aheadCommits} commit(s) not pushed to remote.`);\n console.log(` Run 'tbd sync' to retry or 'tbd sync --status' to check status.`);\n console.log(` To preserve changes locally: tbd save --outbox`);\n },\n );\n return;\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\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 .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';\nimport { readFile } from 'node:fs/promises';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\n\nimport { writeFile } from 'atomically';\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 { IssueStatus } from '../../lib/schemas.js';\nimport type { Issue, IssueStatusType, LocalState } from '../../lib/types.js';\nimport { STATE_FILE } from '../../lib/paths.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\n/**\n * Read local state file.\n */\nasync function readState(): Promise<LocalState> {\n try {\n const content = await readFile(STATE_FILE, 'utf-8');\n return parseYaml(content) as LocalState;\n } catch {\n return {};\n }\n}\n\n/**\n * Update local state file.\n */\nasync function updateState(updates: Partial<LocalState>): Promise<void> {\n const state = await readState();\n const newState = { ...state, ...updates };\n await writeFile(STATE_FILE, stringifyYaml(newState));\n}\n\n/**\n * Check if worktree is stale and needs refresh.\n */\nasync function isWorktreeStale(): Promise<boolean> {\n const state = await readState();\n if (!state.last_sync_at) {\n return true; // Never synced\n }\n\n const lastSync = new Date(state.last_sync_at).getTime();\n const now = Date.now();\n return now - lastSync > STALE_THRESHOLD_MS;\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 readState();\n const stale = await isWorktreeStale();\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 // Update state to mark as fresh (in a full implementation, would actually sync)\n await updateState({ 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}\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 *\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): string => {\n const padding = ' '.repeat(maxLabelLen - label.length + 1);\n return ` ${label}:${padding}${value}`;\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 console.log(formatLine('Total', data.total));\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 * 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 { stringify as stringifyYaml } from 'yaml';\nimport { writeFile } from 'atomically';\n\nimport { listIssues, writeIssue, readIssue } from './storage.js';\nimport { parseIssue } from './parser.js';\nimport { mergeIssues, deepEqual, git, type ConflictEntry } from './git.js';\nimport { loadIdMapping, saveIdMapping, addIdMapping } 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 type { AtticEntry, Issue } 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}\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}\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 content differs from the remote version (modified issue)\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 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 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 - content differs from remote\n return !deepEqual(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 const content = stringifyYaml(entry, { sortMapEntries: true });\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 targetDir = getTargetDir(tbdRoot, options);\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 const allSourceIssues = await listIssues(dataSyncDir);\n const totalSource = allSourceIssues.length;\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 {\n // Fetch and compare with remote tbd-sync\n await git('-C', tbdRoot, 'fetch', 'origin', 'tbd-sync');\n const remoteIssues = await readRemoteIssues(tbdRoot, 'origin', 'tbd-sync');\n sourceIssues = getUpdatedIssues(allSourceIssues, remoteIssues);\n } catch {\n // If fetch fails (offline, remote doesn't exist, etc.), save all issues\n // This is the fallback behavior mentioned in the spec\n }\n }\n\n let saved = 0;\n let conflicts = 0;\n\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\n // Use null base since we don't track common ancestor\n // mergeIssues uses created_at as tiebreaker, so put newer version as \"local\" to win\n const sourceTime = new Date(sourceIssue.updated_at).getTime();\n const targetTime = new Date(targetIssue.updated_at).getTime();\n\n let result;\n let winnerSource: 'local' | 'remote';\n if (sourceTime >= targetTime) {\n // Source (worktree) is newer - put as local so it wins\n result = mergeIssues(null, sourceIssue, targetIssue);\n winnerSource = 'local';\n } else {\n // Target (workspace) is newer - put as local so it wins\n result = mergeIssues(null, targetIssue, sourceIssue);\n winnerSource = 'remote';\n }\n\n // Save merged issue\n await writeIssue(targetDir, result.merged);\n saved++;\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\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 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 sourceDir = resolveWorkspaceDir(tbdRoot, options);\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 const sourceIssues = await listIssues(sourceDir);\n\n let imported = 0;\n let conflicts = 0;\n\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\n // Use null base since we don't track common ancestor\n // mergeIssues uses created_at as tiebreaker, so put newer version as \"local\" to win\n const sourceTime = new Date(sourceIssue.updated_at).getTime();\n const targetTime = new Date(targetIssue.updated_at).getTime();\n\n let result;\n let winnerSource: 'local' | 'remote';\n if (sourceTime >= targetTime) {\n // Source (workspace) is newer - put as local so it wins\n result = mergeIssues(null, sourceIssue, targetIssue);\n winnerSource = 'local';\n } else {\n // Target (worktree) is newer - put as local so it wins\n result = mergeIssues(null, targetIssue, sourceIssue);\n winnerSource = 'remote';\n }\n\n // Save merged issue\n await writeIssue(dataSyncDir, result.merged);\n imported++;\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\n // Merge ID mappings from source (workspace) to target (worktree) - union operation\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 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 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 await requireInit();\n\n // Load all issues\n let issues: Issue[];\n try {\n const dataSyncDir = await resolveDataSyncDir();\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 {\n getClaudePaths,\n getAgentsMdPath,\n CLAUDE_SKILL_REL,\n AGENTS_MD_REL,\n} from '../../lib/integration-paths.js';\nimport { validateIssueId } from '../../lib/ids.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} 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}\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\n const statsInfo = 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 6: Orphaned temp files\n healthChecks.push(await this.checkTempFiles(options.fix));\n\n // Check 7: Issue validity\n healthChecks.push(this.checkIssueValidity(this.issues));\n\n // Check 8: Worktree health (with fix support)\n healthChecks.push(await this.checkWorktree(options.fix));\n\n // Check 9: Data location (issues in wrong path, with fix support)\n healthChecks.push(await this.checkDataLocation(options.fix));\n\n // Check 10: Local sync branch health\n healthChecks.push(await this.checkLocalSyncBranch());\n\n // Check 11: Remote sync branch health\n healthChecks.push(await this.checkRemoteSyncBranch());\n\n // Check 12: Local has data but remote empty (ai-trade-arena bug detection)\n healthChecks.push(await this.checkLocalVsRemoteData());\n\n // Check 13: Multi-user/clone scenario detection\n healthChecks.push(await this.checkCloneScenarios());\n\n // Check 14: 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 gatherStatsInfo(): {\n total: number;\n ready: number;\n inProgress: number;\n blocked: number;\n open: number;\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 return {\n total: this.issues.length,\n ready: readyCount,\n inProgress: byStatus.in_progress,\n blocked: blockedIds.size,\n open: byStatus.open,\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 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 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\n const result = await migrateDataToWorktree(this.cwd);\n\n if (result.success) {\n const message = result.backupPath\n ? `migrated ${result.migratedCount} file(s), backed up to ${result.backupPath}`\n : `migrated ${result.migratedCount} file(s)`;\n return { name: 'Data location', status: 'ok', message, path: wrongIssuesPath };\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: 'warn',\n message: `${consistency.localAhead} commit(s) ahead of remote`,\n suggestion: 'Run: tbd sync to push changes',\n };\n }\n\n if (consistency.localBehind > 0) {\n return {\n name: 'Sync consistency',\n status: 'warn',\n message: `${consistency.localBehind} commit(s) behind remote`,\n suggestion: 'Run: tbd sync to pull changes',\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 .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 await requireInit();\n\n let config: Config;\n try {\n config = await readConfig('.');\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 await requireInit();\n\n let config: Config;\n try {\n config = await readConfig('.');\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('.', 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 await requireInit();\n\n let config: Config;\n try {\n config = await readConfig('.');\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';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\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';\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(filterById?: string): Promise<AtticEntry[]> {\n const atticPath = await resolveAtticDir();\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 content = await readFile(join(atticPath, file), 'utf-8');\n const entry = parseYaml(content) as AtticEntry;\n entries.push(entry);\n } catch {\n // Skip invalid files\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(entry: AtticEntry): Promise<void> {\n const atticPath = await resolveAtticDir();\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 const content = stringifyYaml(entry, { sortMapEntries: true });\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(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(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(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;\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 * 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: beads.priority ?? 2,\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\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 await requireInit();\n this.dataSyncDir = await resolveDataSyncDir();\n await this.validateImport(options);\n return;\n }\n\n // File import requires initialization\n if (file) {\n await requireInit();\n this.dataSyncDir = await resolveDataSyncDir();\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 const tbdRoot = await requireInit();\n this.dataSyncDir = await resolveDataSyncDir(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\n const result = await this.execute(async () => {\n return await importFromWorkspace(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 ((beads.priority ?? 2) !== tbdIssue.priority) {\n fieldIssues.push(`priority mismatch: ${tbdIssue.priority} vs ${beads.priority ?? 2}`);\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 const cwd = process.cwd();\n try {\n const config = await readConfig(cwd);\n if (config.display.id_prefix !== detectedPrefix) {\n const oldPrefix = config.display.id_prefix;\n config.display.id_prefix = detectedPrefix;\n await writeConfig(cwd, 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 { 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 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\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 /**\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 commit-code 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 { CLIError } from '../lib/errors.js';\nimport { renderMarkdown } 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\n console.log(renderMarkdown(content, this.ctx.color));\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 } from 'node:path';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { NotInitializedError, CLIError } from '../lib/errors.js';\nimport { 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 // Check if tbd is initialized\n try {\n await access('.tbd');\n } catch {\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('.');\n } catch {\n config = null;\n }\n\n const syncBranch = config?.sync.branch ?? SYNC_BRANCH;\n const remote = config?.sync.remote ?? 'origin';\n const worktreePath = join('.tbd', 'data-sync-worktree');\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: ${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('.tbd');\n items.push(` - Directory: .tbd/ (${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('.tbd', { recursive: true, force: true });\n console.log(` ${colors.success('✓')} Removed .tbd directory`);\n } catch {\n throw new CLIError('Failed to remove .tbd directory');\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 /** 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 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 * // | commit-code | 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, ['skill', 'skill-brief', 'shortcut-explanation']);\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 { 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.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 console.log(primeContent);\n\n // Shortcut directory\n const shortcutDir = await this.getShortcutDirectory(tbdRoot);\n if (shortcutDir) {\n console.log('');\n console.log(shortcutDir);\n }\n\n console.log('');\n console.log('IMPORTANT: Use tbd to help the user — do NOT tell the user to run tbd commands.');\n console.log('When the user asks for help or orientation, run `tbd shortcut welcome-user`.');\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\n console.log(BRIEF_SKILL_CONTENT);\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\n console.log(VALUE_PROPOSITION);\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 (default when running `tbd`)')\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 { 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 if (options.brief) {\n // Brief mode: just output skill-brief.md\n const content = await loadDocContent('skill-brief.md');\n console.log(content);\n return;\n }\n\n // Full mode: compose header + skill.md + shortcut directory\n const content = await this.composeFullSkill();\n console.log(content);\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.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.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 inferred from shortcut names.\n */\ntype ShortcutCategory = 'planning' | 'implementation' | 'quality' | 'shipping';\n\n/**\n * Infer category from shortcut name.\n * Returns undefined if no category matches.\n */\nfunction inferCategory(name: string): ShortcutCategory | undefined {\n // Planning: specs, architecture, research, planning docs\n if (\n name.includes('plan-spec') ||\n name.includes('architecture') ||\n name.includes('research') ||\n name.includes('validation-spec') ||\n name.includes('implementation-spec') ||\n name.includes('beads-from-spec') ||\n name.includes('update-spec') ||\n name.includes('refine-spec') ||\n name.includes('revise-')\n ) {\n return 'planning';\n }\n\n // Implementation: coding, implementing\n if (name.includes('implement-') || name.includes('coding-spike')) {\n return 'implementation';\n }\n\n // Quality: review, testing, precommit, cleanup\n if (\n name.includes('review-') ||\n name.includes('precommit') ||\n name.includes('cleanup-') ||\n name.includes('validation-plan')\n ) {\n return 'quality';\n }\n\n // Shipping: commit, PR, merge\n if (name.includes('commit-') || name.includes('pr-') || name.includes('merge-')) {\n return 'shipping';\n }\n\n return undefined;\n}\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) => d.name !== 'skill' && d.name !== 'skill-brief' && 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\n if (category) {\n docs = docs.filter((d) => {\n const docCategory = inferCategory(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 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, implementation, quality, shipping',\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 { 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 } 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 const header = this.getAgentHeader();\n if (header) {\n console.log(header + '\\n');\n }\n console.log(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 const header = this.getAgentHeader();\n if (header) {\n console.log(header + '\\n');\n }\n console.log(best.doc.content);\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 ) {\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 * 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 = 10;\n\n/** Minimum length for a valid prefix */\nconst MIN_PREFIX_LENGTH = 1;\n\n/**\n * Normalize a prefix string.\n * - Lowercases\n * - Removes invalid characters (keeps only alphanumeric)\n * - Truncates to max length\n */\nexport function normalizePrefix(s: string): string {\n if (!s) return '';\n\n // Lowercase and remove non-alphanumeric characters\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.\n * - Must be 1-10 characters\n * - Must start with a letter\n * - Must be alphanumeric only (lowercase)\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 // Must match: starts with letter, followed by alphanumeric (lowercase)\n return /^[a-z][a-z0-9]*$/.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 * `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, 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 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) && !options.prefix) {\n // Beads migration flow (unless prefix override given)\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 );\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 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-4 letters recommended):\\n' +\n ' tbd setup --auto --prefix=tbd',\n );\n }\n\n if (!isValidPrefix(prefix)) {\n throw new CLIError(\n 'Invalid prefix format.\\n' +\n 'Prefix must be 1-10 lowercase alphanumeric characters, starting with a letter.\\n' +\n 'Recommended: 2-4 letters for clear, readable issue IDs.\\n' +\n 'Please specify a valid prefix:\\n' +\n ' tbd setup --auto --prefix=tbd',\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 if (!isValidPrefix(prefix)) {\n throw new CLIError(\n 'Invalid prefix format.\\n' +\n 'Prefix must be 1-10 lowercase alphanumeric characters, starting with a letter.\\n' +\n 'Recommended: 2-4 letters for clear, readable issue IDs.\\n\\n' +\n 'Example:\\n' +\n ' tbd setup --auto --prefix=tbd',\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 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 // 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('--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 (e.g., \"tbd\", \"myapp\")');\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\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('--non-interactive', 'Disable all prompts, fail if input required')\n .option('--yes', 'Assume yes to confirmation prompts')\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 * Output error in the appropriate format (JSON or text).\n */\nfunction outputError(message: string, error?: Error): void {\n if (isJsonMode()) {\n const errorObj: { error: string; type?: string; details?: string } = { error: message };\n if (error instanceof CLIError) {\n errorObj.type = error.name;\n }\n if (error && error.message !== message) {\n errorObj.details = error.message;\n }\n console.error(JSON.stringify(errorObj));\n } else {\n console.error(`Error: ${message}`);\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 if (hasNoCommand() && !isHelpOrVersion) {\n // Insert 'prime' as the command\n process.argv.splice(2, 0, 'prime');\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","WorktreeMissingError","parseYaml","parsePath","parseYaml","execFileAsync","normalizePath","addCommand","removeCommand","parseYaml"],"sources":["../src/cli/lib/version.ts","../src/cli/lib/context.ts","../src/cli/lib/output.ts","../src/lib/paths.ts","../src/lib/tbd-format.ts","../src/file/config.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/lib/ids.ts","../src/file/storage.ts","../src/lib/sort.ts","../src/file/id-mapping.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/comparison-chain.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/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/file/workspace.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 nonInteractive: boolean;\n yes: boolean;\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 const isCI = Boolean(process.env.CI);\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 nonInteractive: opts.nonInteractive ?? (!process.stdin.isTTY || isCI),\n yes: opts.yes ?? false,\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 running in interactive mode.\n */\nexport function isInteractive(ctx: CommandContext): boolean {\n return !ctx.nonInteractive && process.stdin.isTTY === true && !process.env.CI;\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';\n\nimport type { CommandContext, ColorOption } from './context.js';\nimport { shouldColorize } from './context.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 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('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 ` For interactive setup: ${colors.dim('tbd setup --interactive')}`,\n ` For manual control: ${colors.dim('tbd init --help')}`,\n '',\n colors.bold('Orientation:'),\n ` For workflow guidance, run: ${colors.green('tbd prime')}`,\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 * 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 * 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 * Centralized path constants for tbd.\n *\n * Directory structure (per spec):\n *\n * On main/dev branches:\n * .tbd/\n * config.yml - Project configuration (tracked)\n * state.yml - Local state (gitignored)\n * .gitignore - Ignores docs/, data-sync-worktree/, data-sync/\n * docs/ - Installed documentation (gitignored, regenerated on setup)\n * data-sync-worktree/ - Hidden worktree checkout of tbd-sync branch\n * .tbd/\n * data-sync/\n * issues/\n * mappings/\n * attic/\n * meta.yml\n *\n * On tbd-sync branch:\n * .tbd/\n * data-sync/\n * issues/\n * mappings/\n * attic/\n * meta.yml\n */\n\nimport { join } from 'node:path';\n\n/** The tbd configuration directory on main branch */\nexport const TBD_DIR = '.tbd';\n\n/** The config file path */\nexport const CONFIG_FILE = join(TBD_DIR, 'config.yml');\n\n/** The local state file (gitignored) */\nexport const STATE_FILE = join(TBD_DIR, 'state.yml');\n\n/** The worktree directory name */\nexport const WORKTREE_DIR_NAME = 'data-sync-worktree';\n\n/** The worktree path (gitignored) */\nexport const WORKTREE_DIR = join(TBD_DIR, WORKTREE_DIR_NAME);\n\n/** The data directory name on the sync branch */\nexport const DATA_SYNC_DIR_NAME = 'data-sync';\n\n/**\n * The base directory for synced data.\n *\n * NOTE: This is currently pointing directly to .tbd/data-sync/ which is WRONG\n * per the spec. The correct path should be via the worktree:\n * .tbd/data-sync-worktree/.tbd/data-sync/\n *\n * TODO(tbd-208): Update this to use the worktree path once worktree\n * management is implemented.\n */\nexport const DATA_SYNC_DIR = join(TBD_DIR, DATA_SYNC_DIR_NAME);\n\n/**\n * The correct path for synced data via worktree (per spec).\n * Use this once worktree management is implemented.\n */\nexport const DATA_SYNC_DIR_VIA_WORKTREE = join(WORKTREE_DIR, TBD_DIR, DATA_SYNC_DIR_NAME);\n\n/** Issues directory */\nexport const ISSUES_DIR = join(DATA_SYNC_DIR, 'issues');\n\n/** Mappings directory */\nexport const MAPPINGS_DIR = join(DATA_SYNC_DIR, 'mappings');\n\n/** Attic directory for conflict resolution */\nexport const ATTIC_DIR = join(DATA_SYNC_DIR, 'attic');\n\n/** Meta file for schema version */\nexport const META_FILE = join(DATA_SYNC_DIR, 'meta.yml');\n\n/** The sync branch name */\nexport const SYNC_BRANCH = 'tbd-sync';\n\n// =============================================================================\n// Workspace Paths (for sync failure recovery, backups, bulk editing)\n// =============================================================================\n\n/** The workspaces directory name within .tbd/ */\nexport const WORKSPACES_DIR_NAME = 'workspaces';\n\n/** Full path to workspaces directory: .tbd/workspaces/ */\nexport const WORKSPACES_DIR = join(TBD_DIR, WORKSPACES_DIR_NAME);\n\n/**\n * Get the path to a named workspace directory.\n *\n * Workspaces are stored at: .tbd/workspaces/{name}/\n *\n * @param workspaceName - The name of the workspace (e.g., 'outbox', 'my-feature')\n * @returns Path to the workspace directory\n */\nexport function getWorkspaceDir(workspaceName: string): string {\n return join(WORKSPACES_DIR, workspaceName);\n}\n\n/**\n * Get the path to a workspace's issues directory.\n *\n * @param workspaceName - The name of the workspace\n * @returns Path to the workspace's issues directory\n */\nexport function getWorkspaceIssuesDir(workspaceName: string): string {\n return join(getWorkspaceDir(workspaceName), 'issues');\n}\n\n/**\n * Get the path to a workspace's mappings directory.\n *\n * @param workspaceName - The name of the workspace\n * @returns Path to the workspace's mappings directory\n */\nexport function getWorkspaceMappingsDir(workspaceName: string): string {\n return join(getWorkspaceDir(workspaceName), 'mappings');\n}\n\n/**\n * Get the path to a workspace's attic directory.\n *\n * The attic stores conflict backups during workspace save operations.\n *\n * @param workspaceName - The name of the workspace\n * @returns Path to the workspace's attic directory\n */\nexport function getWorkspaceAtticDir(workspaceName: string): string {\n return join(getWorkspaceDir(workspaceName), 'attic');\n}\n\n/**\n * Validate a workspace name.\n *\n * Valid workspace names:\n * - Lowercase alphanumeric characters\n * - Hyphens and underscores allowed\n * - Must not be empty\n * - Must not contain path separators or dots at start\n *\n * @param name - The workspace name to validate\n * @returns true if the name is valid\n */\nexport function isValidWorkspaceName(name: string): boolean {\n if (!name || name.length === 0) {\n return false;\n }\n\n // Must not start with dot (hidden files)\n if (name.startsWith('.')) {\n return false;\n }\n\n // Only allow lowercase alphanumeric, hyphens, and underscores\n // No spaces, path separators, or special characters\n const validPattern = /^[a-z0-9][a-z0-9_-]*$/;\n return validPattern.test(name);\n}\n\n// =============================================================================\n// Documentation/Shortcuts Paths\n// =============================================================================\n\n/** Docs directory name within .tbd/ */\nexport const DOCS_DIR = 'docs';\n\n/** Shortcuts directory name within docs/ */\nexport const SHORTCUTS_DIR = 'shortcuts';\n\n/** System shortcuts directory name (core docs like skill.md) */\nexport const SYSTEM_DIR = 'system';\n\n/** Standard shortcuts directory name (workflow shortcuts) */\nexport const STANDARD_DIR = 'standard';\n\n/** Guidelines directory name (coding rules and best practices) */\nexport const GUIDELINES_DIR = 'guidelines';\n\n/** Templates directory name (document templates) */\nexport const TEMPLATES_DIR = 'templates';\n\n/** Full path to docs directory: .tbd/docs/ */\nexport const TBD_DOCS_DIR = join(TBD_DIR, DOCS_DIR);\n\n/** Full path to shortcuts directory: .tbd/docs/shortcuts/ */\nexport const TBD_SHORTCUTS_DIR = join(TBD_DOCS_DIR, SHORTCUTS_DIR);\n\n/** Full path to system shortcuts: .tbd/docs/shortcuts/system/ */\nexport const TBD_SHORTCUTS_SYSTEM = join(TBD_SHORTCUTS_DIR, SYSTEM_DIR);\n\n/** Full path to standard shortcuts: .tbd/docs/shortcuts/standard/ */\nexport const TBD_SHORTCUTS_STANDARD = join(TBD_SHORTCUTS_DIR, STANDARD_DIR);\n\n/** Full path to guidelines: .tbd/docs/guidelines/ (top-level, not under shortcuts) */\nexport const TBD_GUIDELINES_DIR = join(TBD_DOCS_DIR, GUIDELINES_DIR);\n\n/** Full path to templates: .tbd/docs/templates/ (top-level, not under shortcuts) */\nexport const TBD_TEMPLATES_DIR = join(TBD_DOCS_DIR, TEMPLATES_DIR);\n\n/** @deprecated Use TBD_GUIDELINES_DIR instead */\nexport const TBD_SHORTCUTS_GUIDELINES = TBD_GUIDELINES_DIR;\n\n/** @deprecated Use TBD_TEMPLATES_DIR instead */\nexport const TBD_SHORTCUTS_TEMPLATES = TBD_TEMPLATES_DIR;\n\n/** Built-in docs source paths (relative to package docs/) */\nexport const BUILTIN_SHORTCUTS_SYSTEM = join(SHORTCUTS_DIR, SYSTEM_DIR);\nexport const BUILTIN_SHORTCUTS_STANDARD = join(SHORTCUTS_DIR, STANDARD_DIR);\n\n/** Built-in guidelines source path (relative to package docs/) */\nexport const BUILTIN_GUIDELINES_DIR = GUIDELINES_DIR;\n\n/** Built-in templates source path (relative to package docs/) */\nexport const BUILTIN_TEMPLATES_DIR = TEMPLATES_DIR;\n\n/** Install directory name (header files for tool-specific installation) */\nexport const INSTALL_DIR = 'install';\n\n/** Built-in install source path (relative to package docs/) */\nexport const BUILTIN_INSTALL_DIR = INSTALL_DIR;\n\n/**\n * Default shortcut lookup paths (searched in order, relative to tbd root).\n * Earlier paths take precedence over later paths.\n * Note: Guidelines and templates are now separate top-level directories.\n */\nexport const DEFAULT_SHORTCUT_PATHS = [\n TBD_SHORTCUTS_SYSTEM, // .tbd/docs/shortcuts/system/\n TBD_SHORTCUTS_STANDARD, // .tbd/docs/shortcuts/standard/\n];\n\n/**\n * Default guidelines lookup paths (relative to tbd root).\n */\nexport const DEFAULT_GUIDELINES_PATHS = [\n TBD_GUIDELINES_DIR, // .tbd/docs/guidelines/\n];\n\n/**\n * Default template lookup paths (relative to tbd root).\n */\nexport const DEFAULT_TEMPLATE_PATHS = [\n TBD_TEMPLATES_DIR, // .tbd/docs/templates/\n];\n\n/**\n * Get the full path to an issue file.\n */\nexport function getIssuePath(issueId: string): string {\n return join(ISSUES_DIR, `${issueId}.md`);\n}\n\n/**\n * Get the full path to a mapping file.\n */\nexport function getMappingPath(name: string): string {\n return join(MAPPINGS_DIR, `${name}.yml`);\n}\n\n/**\n * Get the full path to an attic entry.\n */\nexport function getAtticPath(issueId: string, filename: string): string {\n return join(ATTIC_DIR, 'conflicts', issueId, filename);\n}\n\n// =============================================================================\n// Dynamic Path Resolution\n// =============================================================================\n\nimport { access } from 'node:fs/promises';\n\n/**\n * Options for resolveDataSyncDir.\n */\nexport interface ResolveDataSyncDirOptions {\n /**\n * Allow fallback to direct path when worktree is missing.\n * Set to true for test environments or diagnostic tools.\n * Default: true. When false and worktree is missing, throws WorktreeMissingError.\n */\n allowFallback?: boolean;\n}\n\n/**\n * Error thrown when worktree is missing and fallback is not allowed.\n * Defined inline to avoid circular dependency with errors.ts.\n */\nexport class WorktreeMissingError extends Error {\n constructor(\n message = \"Worktree not found at .tbd/data-sync-worktree/. Run 'tbd doctor --fix' to repair.\",\n ) {\n super(message);\n this.name = 'WorktreeMissingError';\n }\n}\n\n/**\n * Cache for resolved data sync directory.\n * Reset when baseDir changes.\n */\nlet _resolvedDataSyncDir: string | null = null;\nlet _resolvedBaseDir: string | null = null;\nlet _resolvedAllowFallback: boolean | null = null;\n\n/**\n * Resolve the actual data sync directory path.\n *\n * This function detects whether we're running with a git worktree\n * (production) or in a test environment without worktree.\n *\n * Order of preference:\n * 1. Worktree path if worktree exists: .tbd/data-sync-worktree/.tbd/data-sync/\n * 2. Direct path as fallback (only if allowFallback: true)\n *\n * @param baseDir - The base directory of the repository (default: process.cwd())\n * @param options - Options for path resolution\n * @returns Resolved data sync directory path\n * @throws WorktreeMissingError if worktree missing and allowFallback is false\n *\n * See: plan-2026-01-28-sync-worktree-recovery-and-hardening.md\n */\nexport async function resolveDataSyncDir(\n baseDir: string = process.cwd(),\n options?: ResolveDataSyncDirOptions,\n): Promise<string> {\n const allowFallback = options?.allowFallback ?? true;\n\n // Return cached result if baseDir and options haven't changed\n if (\n _resolvedDataSyncDir &&\n _resolvedBaseDir === baseDir &&\n _resolvedAllowFallback === allowFallback\n ) {\n return _resolvedDataSyncDir;\n }\n\n const worktreePath = join(baseDir, DATA_SYNC_DIR_VIA_WORKTREE);\n const directPath = join(baseDir, DATA_SYNC_DIR);\n\n // Check if worktree path exists\n try {\n await access(worktreePath);\n _resolvedDataSyncDir = worktreePath;\n _resolvedBaseDir = baseDir;\n _resolvedAllowFallback = allowFallback;\n return worktreePath;\n } catch {\n // Worktree doesn't exist\n if (!allowFallback) {\n throw new WorktreeMissingError();\n }\n\n // Fallback to direct path (test mode or diagnostic tools)\n // Note: In production, sync.ts checks worktree health before calling this\n // Debug warning to help detect unintended fallback usage\n if (process.env.DEBUG || process.env.TBD_DEBUG) {\n console.warn(\n '[tbd:paths] resolveDataSyncDir: worktree not found, falling back to direct path',\n );\n }\n _resolvedDataSyncDir = directPath;\n _resolvedBaseDir = baseDir;\n _resolvedAllowFallback = allowFallback;\n return directPath;\n }\n}\n\n/**\n * Resolve issues directory path.\n */\nexport async function resolveIssuesDir(\n baseDir: string = process.cwd(),\n options?: ResolveDataSyncDirOptions,\n): Promise<string> {\n const dataSyncDir = await resolveDataSyncDir(baseDir, options);\n return join(dataSyncDir, 'issues');\n}\n\n/**\n * Resolve mappings directory path.\n */\nexport async function resolveMappingsDir(\n baseDir: string = process.cwd(),\n options?: ResolveDataSyncDirOptions,\n): Promise<string> {\n const dataSyncDir = await resolveDataSyncDir(baseDir, options);\n return join(dataSyncDir, 'mappings');\n}\n\n/**\n * Resolve attic directory path.\n */\nexport async function resolveAtticDir(\n baseDir: string = process.cwd(),\n options?: ResolveDataSyncDirOptions,\n): Promise<string> {\n const dataSyncDir = await resolveDataSyncDir(baseDir, options);\n return join(dataSyncDir, 'attic');\n}\n\n/**\n * Clear the resolved path cache.\n * Call this when the repository state changes (e.g., after init).\n */\nexport function clearPathCache(): void {\n _resolvedDataSyncDir = null;\n _resolvedBaseDir = null;\n _resolvedAllowFallback = null;\n}\n\n// =============================================================================\n// Doc Path Resolution\n// =============================================================================\n\nimport { isAbsolute } from 'node:path';\nimport { homedir } from 'node:os';\n\n/**\n * Resolve a doc path for consistent handling across the codebase.\n *\n * Path resolution rules:\n * - Absolute paths (starting with /): used as-is\n * - Home directory paths (starting with ~/): expanded to user home directory\n * - Relative paths: resolved from tbd root (baseDir)\n *\n * @param docPath - The path to resolve\n * @param baseDir - The tbd root directory (parent of .tbd/), defaults to cwd\n * @returns Resolved absolute path\n *\n * @example\n * // Absolute path - returned as-is\n * resolveDocPath('/usr/local/docs/file.md') // => '/usr/local/docs/file.md'\n *\n * // Home path - expanded\n * resolveDocPath('~/docs/file.md') // => '/Users/username/docs/file.md'\n *\n * // Relative path - resolved from baseDir\n * resolveDocPath('docs/file.md', '/project') // => '/project/docs/file.md'\n */\nexport function resolveDocPath(docPath: string, baseDir: string = process.cwd()): string {\n // Handle home directory expansion\n if (docPath.startsWith('~/')) {\n return join(homedir(), docPath.slice(2));\n }\n\n // Absolute paths used as-is\n if (isAbsolute(docPath)) {\n return docPath;\n }\n\n // Relative paths resolved from baseDir (tbd root)\n return join(baseDir, docPath);\n}\n\n// =============================================================================\n// Token Estimation Settings\n// =============================================================================\n\n/**\n * Characters per token ratio for estimating token counts.\n *\n * Based on research of OpenAI (tiktoken) and Claude tokenizers:\n * - Pure English prose: ~4-5 chars/token\n * - Code and symbols: ~3 chars/token\n * - Mixed markdown/code docs: ~3.5 chars/token\n *\n * We use 3.5 as our docs are markdown with code examples.\n * This provides ~15-20% accuracy, sufficient for cost estimation.\n */\nexport const CHARS_PER_TOKEN = 3.5;\n","/**\n * tbd Directory Format Versioning\n * ================================\n *\n * This file is the SINGLE SOURCE OF TRUTH for .tbd/ directory format versions.\n *\n * WHEN TO BUMP THE FORMAT VERSION:\n * - Bump when changes REQUIRE migration (deleting files, changing formats, moving files)\n * - **Bump when changing config schema** (adding, removing, or modifying fields)\n * - Do NOT bump for additive changes that don't affect config.yml (new directories, etc.)\n *\n * HOW TO ADD A NEW FORMAT VERSION:\n * 1. Add entry to FORMAT_HISTORY with detailed description\n * 2. Implement migrate_fXX_to_fYY() function\n * 3. Add case to migrateToLatest()\n * 4. Update CURRENT_FORMAT\n * 5. Add tests for the migration path\n *\n * FORWARD COMPATIBILITY POLICY:\n * ConfigSchema uses Zod's strip() mode, which discards unknown fields. To prevent\n * data loss when users mix tbd versions:\n *\n * 1. When changing config schema, bump the format version (e.g., f03 → f04)\n * 2. config.ts checks format compatibility via isCompatibleFormat()\n * 3. Older tbd versions will error with \"format 'fXX' is from a newer tbd version\"\n * 4. The error tells users to upgrade: npm install -g get-tbd@latest\n *\n * This ensures older versions fail fast rather than silently corrupting config.\n * See ConfigSchema in schemas.ts and checkFormatCompatibility() in config.ts.\n */\n\n// =============================================================================\n// Format Constants\n// =============================================================================\n\n/**\n * Current format version.\n * Bump this ONLY for breaking changes that require migration.\n */\nexport const CURRENT_FORMAT = 'f03';\n\n/**\n * Initial format version for configs that don't have tbd_format field.\n */\nexport const INITIAL_FORMAT = 'f01';\n\n// =============================================================================\n// Format History\n// =============================================================================\n\n/**\n * Complete history of format versions with their changes.\n * This serves as documentation and enables version detection.\n */\nexport const FORMAT_HISTORY = {\n f01: {\n introduced: '0.1.0',\n description: 'Initial format',\n structure: {\n 'config.yml': 'Project configuration',\n 'state.yml': 'Local state (gitignored)',\n 'docs/': 'Documentation cache (gitignored)',\n 'issues/': 'Issue YAML files',\n },\n },\n f02: {\n introduced: '0.1.5',\n description: 'Adds configurable doc_cache',\n changes: [\n 'Added doc_cache: key to config.yml for configurable doc sources',\n 'Added settings.doc_auto_sync_hours for automatic doc refresh',\n 'Added last_doc_sync_at to state.yml for tracking sync time',\n ],\n migration: 'Populates default doc_cache config from bundled docs',\n },\n f03: {\n introduced: '0.1.6',\n description: 'Consolidates docs_cache config structure',\n changes: [\n 'Consolidated doc_cache: and docs: into single docs_cache: key',\n 'Moved doc_cache: -> docs_cache.files:',\n 'Moved docs.paths: -> docs_cache.lookup_path:',\n 'Removed separate docs: key',\n ],\n migration: 'Migrates old config keys to new docs_cache structure',\n },\n} as const;\n\nexport type FormatVersion = keyof typeof FORMAT_HISTORY;\n\n// =============================================================================\n// Migration Types\n// =============================================================================\n\n/**\n * Raw config data before parsing/validation.\n * Used during migration when we need to work with potentially old formats.\n */\nexport interface RawConfig {\n tbd_format?: string;\n tbd_version?: string;\n sync?: {\n branch?: string;\n remote?: string;\n };\n display?: {\n id_prefix?: string;\n };\n settings?: {\n auto_sync?: boolean;\n doc_auto_sync_hours?: number;\n };\n // Old format (f02 and earlier)\n docs?: {\n paths?: string[];\n };\n doc_cache?: Record<string, string>;\n // New format (f03+)\n docs_cache?: {\n files?: Record<string, string>;\n lookup_path?: string[];\n };\n}\n\n/**\n * Result of a migration operation.\n */\nexport interface MigrationResult {\n /** The migrated config */\n config: RawConfig;\n /** Format version before migration */\n fromFormat: FormatVersion;\n /** Format version after migration */\n toFormat: FormatVersion;\n /** Whether any changes were made */\n changed: boolean;\n /** Description of changes made */\n changes: string[];\n}\n\n// =============================================================================\n// Migration Functions\n// =============================================================================\n\n/**\n * Migrate from f01 to f02.\n * - Adds tbd_format field\n * - Adds doc_auto_sync_hours setting (default: 24)\n * - doc_cache will be populated separately during setup (requires file system access)\n */\nfunction migrate_f01_to_f02(config: RawConfig): MigrationResult {\n const changes: string[] = [];\n const migrated = { ...config };\n\n // Add format version\n migrated.tbd_format = 'f02';\n changes.push('Added tbd_format: f02');\n\n // Ensure settings exists and add doc_auto_sync_hours\n migrated.settings ??= {};\n if (migrated.settings.doc_auto_sync_hours === undefined) {\n migrated.settings.doc_auto_sync_hours = 24;\n changes.push('Added settings.doc_auto_sync_hours: 24');\n }\n\n // Note: doc_cache is intentionally NOT added here.\n // It will be populated during setup when we have access to the file system\n // and can enumerate the bundled docs.\n\n return {\n config: migrated,\n fromFormat: 'f01',\n toFormat: 'f02',\n changed: changes.length > 0,\n changes,\n };\n}\n\n/**\n * Migrate from f02 to f03.\n * - Consolidates doc_cache: and docs: into docs_cache:\n * - Moves doc_cache: -> docs_cache.files:\n * - Moves docs.paths: -> docs_cache.lookup_path:\n * - Removes separate docs: and doc_cache: keys\n */\nfunction migrate_f02_to_f03(config: RawConfig): MigrationResult {\n const changes: string[] = [];\n const migrated = { ...config };\n\n // Update format version\n migrated.tbd_format = 'f03';\n changes.push('Updated tbd_format: f03');\n\n // Initialize docs_cache if it doesn't exist\n migrated.docs_cache ??= {};\n\n // Migrate doc_cache -> docs_cache.files\n if (migrated.doc_cache && Object.keys(migrated.doc_cache).length > 0) {\n migrated.docs_cache.files = { ...migrated.doc_cache };\n changes.push('Moved doc_cache: -> docs_cache.files:');\n delete migrated.doc_cache;\n }\n\n // Migrate docs.paths -> docs_cache.lookup_path\n if (migrated.docs?.paths && migrated.docs.paths.length > 0) {\n migrated.docs_cache.lookup_path = [...migrated.docs.paths];\n changes.push('Moved docs.paths: -> docs_cache.lookup_path:');\n }\n\n // Remove old docs: key\n if (migrated.docs) {\n delete migrated.docs;\n changes.push('Removed docs: key');\n }\n\n return {\n config: migrated,\n fromFormat: 'f02',\n toFormat: 'f03',\n changed: changes.length > 0,\n changes,\n };\n}\n\n// =============================================================================\n// Public API\n// =============================================================================\n\n/**\n * Detect the format version of a config.\n * Returns INITIAL_FORMAT ('f01') if no tbd_format field is present.\n */\nexport function detectFormat(config: RawConfig): FormatVersion {\n const format = config.tbd_format;\n if (!format) {\n return INITIAL_FORMAT;\n }\n if (format in FORMAT_HISTORY) {\n return format as FormatVersion;\n }\n // Unknown format - treat as latest (will fail validation if incompatible)\n return CURRENT_FORMAT;\n}\n\n/**\n * Check if a config needs migration.\n */\nexport function needsMigration(config: RawConfig): boolean {\n const currentFormat = detectFormat(config);\n return currentFormat !== CURRENT_FORMAT;\n}\n\n/**\n * Migrate a config to the latest format version.\n *\n * This function applies all necessary migrations in sequence.\n * It does NOT populate doc_cache - that requires file system access\n * and should be done separately during setup.\n *\n * @param config - The raw config to migrate\n * @returns Migration result with the migrated config and change log\n */\nexport function migrateToLatest(config: RawConfig): MigrationResult {\n const fromFormat = detectFormat(config);\n\n if (fromFormat === CURRENT_FORMAT) {\n return {\n config,\n fromFormat,\n toFormat: CURRENT_FORMAT,\n changed: false,\n changes: [],\n };\n }\n\n let current = config;\n let currentFormat: FormatVersion = fromFormat;\n const allChanges: string[] = [];\n\n // Apply migrations in sequence\n if (currentFormat === 'f01') {\n const result = migrate_f01_to_f02(current);\n current = result.config;\n currentFormat = 'f02' as FormatVersion;\n allChanges.push(...result.changes);\n }\n\n if (currentFormat === 'f02') {\n const result = migrate_f02_to_f03(current);\n current = result.config;\n currentFormat = 'f03' as FormatVersion;\n allChanges.push(...result.changes);\n }\n\n // Add more migrations here as new format versions are added\n\n return {\n config: current,\n fromFormat,\n toFormat: currentFormat,\n changed: allChanges.length > 0,\n changes: allChanges,\n };\n}\n\n/**\n * Check if a format version is compatible with the current tbd version.\n * Future format versions are considered incompatible (would need tbd upgrade).\n */\nexport function isCompatibleFormat(format: string): boolean {\n const formatVersions = Object.keys(FORMAT_HISTORY);\n const currentIndex = formatVersions.indexOf(CURRENT_FORMAT);\n const checkIndex = formatVersions.indexOf(format);\n\n if (checkIndex === -1) {\n // Unknown format - might be from a newer tbd version\n return false;\n }\n\n // Compatible if same or older format (we can migrate up)\n return checkIndex <= currentIndex;\n}\n\n/**\n * Get a human-readable description of what migrations will be applied.\n */\nexport function describeMigration(fromFormat: FormatVersion): string[] {\n const descriptions: string[] = [];\n let current = fromFormat;\n\n if (current === 'f01') {\n descriptions.push('f01 → f02: Add doc_cache configuration support');\n current = 'f02';\n }\n\n if (current === 'f02') {\n descriptions.push('f02 → f03: Consolidate doc_cache and docs into docs_cache');\n current = 'f03';\n }\n\n // Add more migration descriptions here\n\n return descriptions;\n}\n","/**\n * Config file operations.\n *\n * Config is stored at .tbd/config.yml and contains project-level settings.\n *\n * ⚠️ FORMAT VERSIONING: See tbd-format.ts for version history and migration rules.\n *\n * See: tbd-design.md §2.2.2 Config File\n */\n\nimport { readFile, mkdir, access } from 'node:fs/promises';\nimport { join, dirname, parse as parsePath } from 'node:path';\nimport { writeFile } from 'atomically';\nimport { parse as parseYaml } from 'yaml';\n\nimport { stringifyYaml } from '../utils/yaml-utils.js';\nimport type { Config, LocalState } from '../lib/types.js';\nimport { ConfigSchema, LocalStateSchema } from '../lib/schemas.js';\nimport { CONFIG_FILE, STATE_FILE, SYNC_BRANCH } from '../lib/paths.js';\nimport {\n CURRENT_FORMAT,\n needsMigration,\n migrateToLatest,\n isCompatibleFormat,\n type RawConfig,\n} from '../lib/tbd-format.js';\n\n/**\n * Error thrown when the config format version is from a newer tbd version.\n * This prevents older tbd versions from silently stripping new config fields.\n */\nexport class IncompatibleFormatError extends Error {\n constructor(\n public readonly foundFormat: string,\n public readonly supportedFormat: string,\n ) {\n super(\n `Config format '${foundFormat}' is from a newer tbd version.\\n` +\n `This tbd version supports up to format '${supportedFormat}'.\\n` +\n `Please upgrade tbd: npm install -g get-tbd@latest`,\n );\n this.name = 'IncompatibleFormatError';\n }\n}\n\n/**\n * Check if config format is compatible, throw if not.\n * This prevents older tbd versions from silently stripping fields added by newer versions.\n */\nfunction checkFormatCompatibility(data: RawConfig): void {\n const format = data.tbd_format;\n if (format && !isCompatibleFormat(format)) {\n throw new IncompatibleFormatError(format, CURRENT_FORMAT);\n }\n}\n\n/**\n * Create default config for a new project.\n * @param prefix - Required: the project prefix for display IDs (e.g., \"proj\", \"myapp\")\n */\nfunction createDefaultConfig(version: string, prefix: string): Config {\n return ConfigSchema.parse({\n tbd_format: CURRENT_FORMAT,\n tbd_version: version,\n sync: {\n branch: SYNC_BRANCH,\n remote: 'origin',\n },\n display: {\n id_prefix: prefix,\n },\n settings: {\n auto_sync: false,\n doc_auto_sync_hours: 24,\n },\n });\n}\n\n/**\n * Initialize a new config file with default settings.\n * Creates .tbd directory if it doesn't exist.\n * @param prefix - Required: the project prefix for display IDs (e.g., \"proj\", \"myapp\")\n */\nexport async function initConfig(\n baseDir: string,\n version: string,\n prefix: string,\n): Promise<Config> {\n const tbdDir = join(baseDir, '.tbd');\n await mkdir(tbdDir, { recursive: true });\n\n const config = createDefaultConfig(version, prefix);\n await writeConfig(baseDir, config);\n\n return config;\n}\n\n/**\n * Read config from file with automatic migration if needed.\n *\n * ⚠️ FORMAT VERSIONING: See tbd-format.ts for version history and migration rules.\n *\n * @throws {IncompatibleFormatError} If config is from a newer tbd version.\n * @throws If config file doesn't exist or is invalid.\n */\nexport async function readConfig(baseDir: string): Promise<Config> {\n const configPath = join(baseDir, CONFIG_FILE);\n const content = await readFile(configPath, 'utf-8');\n const data = parseYaml(content) as RawConfig;\n\n // Check for incompatible (future) format versions first\n checkFormatCompatibility(data);\n\n // Check if migration is needed (for older formats)\n if (needsMigration(data)) {\n const result = migrateToLatest(data);\n // Note: We don't automatically write the migrated config here.\n // Migration writes should be explicit via writeConfig() after setup.\n return ConfigSchema.parse(result.config);\n }\n\n return ConfigSchema.parse(data);\n}\n\n/**\n * Read config from file, returning migration info if a migration was applied.\n * Use this when you need to know if the config was migrated.\n *\n * @throws {IncompatibleFormatError} If config is from a newer tbd version.\n */\nexport async function readConfigWithMigration(\n baseDir: string,\n): Promise<{ config: Config; migrated: boolean; changes: string[] }> {\n const configPath = join(baseDir, CONFIG_FILE);\n const content = await readFile(configPath, 'utf-8');\n const data = parseYaml(content) as RawConfig;\n\n // Check for incompatible (future) format versions first\n checkFormatCompatibility(data);\n\n if (needsMigration(data)) {\n const result = migrateToLatest(data);\n return {\n config: ConfigSchema.parse(result.config),\n migrated: result.changed,\n changes: result.changes,\n };\n }\n\n return {\n config: ConfigSchema.parse(data),\n migrated: false,\n changes: [],\n };\n}\n\n/**\n * Write config to file with explanatory comments.\n */\nexport async function writeConfig(baseDir: string, config: Config): Promise<void> {\n const configPath = join(baseDir, CONFIG_FILE);\n\n // Use lineWidth: 0 for compact config output (sortMapEntries is in defaults)\n const yaml = stringifyYaml(config, { lineWidth: 0 });\n\n // Add explanatory comments for docs_cache section\n let content = yaml;\n if (config.docs_cache && Object.keys(config.docs_cache).length > 0) {\n const docsCacheComment = `# Documentation cache configuration.\n# files: Maps destination paths (relative to .tbd/docs/) to source locations.\n# Sources can be:\n# - internal: prefix for bundled docs (e.g., \"internal:shortcuts/standard/code-review-and-commit.md\")\n# - Full URL for external docs (e.g., \"https://raw.githubusercontent.com/org/repo/main/file.md\")\n# lookup_path: Search paths for doc lookup (like shell $PATH). Earlier paths take precedence.\n#\n# To sync docs: tbd sync --docs\n# To check status: tbd sync --status\n#\n# Auto-sync: Docs are automatically synced when stale (default: every 24 hours).\n# Configure with settings.doc_auto_sync_hours (0 = disabled).\n`;\n content = content.replace('docs_cache:', docsCacheComment + 'docs_cache:');\n }\n\n await writeFile(configPath, content);\n}\n\n/**\n * Check if tbd is initialized in the given directory (immediate check only).\n * Returns true if .tbd/ directory exists directly in baseDir.\n */\nasync function hasTbdDir(dir: string): Promise<boolean> {\n const tbdDir = join(dir, '.tbd');\n try {\n await access(tbdDir);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Find the tbd repository root by walking up the directory tree.\n * Similar to how git finds .git/ directories.\n *\n * @param startDir - Directory to start searching from\n * @returns The tbd root directory path, or null if not found\n */\nexport async function findTbdRoot(startDir: string): Promise<string | null> {\n let currentDir = startDir;\n const { root } = parsePath(startDir);\n\n while (currentDir !== root) {\n if (await hasTbdDir(currentDir)) {\n return currentDir;\n }\n currentDir = dirname(currentDir);\n }\n\n // Check root directory as well\n if (await hasTbdDir(root)) {\n return root;\n }\n\n return null;\n}\n\n/**\n * Check if tbd is initialized in the given directory or any parent directory.\n * Walks up the directory tree looking for .tbd/.\n */\nexport async function isInitialized(baseDir: string): Promise<boolean> {\n const root = await findTbdRoot(baseDir);\n return root !== null;\n}\n\n// =============================================================================\n// Local State Operations\n// =============================================================================\n\n/**\n * Read local state from .tbd/state.yml\n * Returns empty state if file doesn't exist.\n */\nexport async function readLocalState(baseDir: string): Promise<LocalState> {\n const statePath = join(baseDir, STATE_FILE);\n try {\n const content = await readFile(statePath, 'utf-8');\n const data: unknown = parseYaml(content);\n return LocalStateSchema.parse(data ?? {});\n } catch {\n // File doesn't exist or is invalid - return empty state\n return {};\n }\n}\n\n/**\n * Write local state to .tbd/state.yml\n */\nexport async function writeLocalState(baseDir: string, state: LocalState): Promise<void> {\n const statePath = join(baseDir, STATE_FILE);\n\n // Ensure .tbd directory exists\n await mkdir(join(baseDir, '.tbd'), { recursive: true });\n\n // Use lineWidth: 0 for compact state output (sortMapEntries is in defaults)\n const yaml = stringifyYaml(state, { lineWidth: 0 });\n\n await writeFile(statePath, yaml);\n}\n\n/**\n * Update specific fields in local state (merge with existing).\n */\nexport async function updateLocalState(\n baseDir: string,\n updates: Partial<LocalState>,\n): Promise<LocalState> {\n const current = await readLocalState(baseDir);\n const updated = { ...current, ...updates };\n await writeLocalState(baseDir, updated);\n return updated;\n}\n\n// =============================================================================\n// Welcome State Operations\n// =============================================================================\n\n/**\n * Check if the user has seen the welcome message.\n */\nexport async function hasSeenWelcome(baseDir: string): Promise<boolean> {\n const state = await readLocalState(baseDir);\n return state.welcome_seen === true;\n}\n\n/**\n * Mark the welcome message as seen.\n */\nexport async function markWelcomeSeen(baseDir: string): Promise<void> {\n await updateLocalState(baseDir, { welcome_seen: true });\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 * 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 */\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 this.output.error(errorMessage, error instanceof Error ? error : undefined);\n throw new CLIError(errorMessage);\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 * 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, one was created independently - LWW based on created_at\n if (!base) {\n const localTime = new Date(local.created_at).getTime();\n const remoteTime = new Date(remote.created_at).getTime();\n\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 // 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 // Compare 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 // Always increment version after merge\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 */\nexport async function pushWithRetry(\n syncBranch: string,\n remote: string,\n onMergeNeeded: () => Promise<ConflictEntry[]>,\n): Promise<PushResult> {\n for (let attempt = 1; attempt <= MAX_PUSH_RETRIES; attempt++) {\n try {\n // Try to push\n await git('push', remote, syncBranch);\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('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 // 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 * 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 for (const file of mappingFiles) {\n await cp(join(wrongMappingsPath, file), join(correctMappingsPath, file));\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 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 const cwd = process.cwd();\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 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 * ID generation and validation utilities.\n *\n * The system uses dual IDs for usability:\n * - Internal ID: is-{ulid} - ULID-based (26 lowercase chars), stored in files\n * - External ID: {prefix}-{short} - 4-5 base36 chars for CLI display/input\n *\n * For Beads compatibility, bd- prefix is accepted on input for external IDs.\n *\n * See: tbd-design.md §2.5 ID Generation\n */\n\nimport { ulid } from 'ulid';\nimport { randomBytes } from 'node:crypto';\n\n// =============================================================================\n// Branded Types for Type-Safe ID Handling\n// =============================================================================\n\n/**\n * Branded type for internal issue IDs (is-{ulid} format).\n *\n * Internal IDs are stored in files and used as the canonical identifier.\n * Format: is-{26 lowercase alphanumeric chars}\n * Example: is-01hx5zzkbkactav9wevgemmvrz\n *\n * Use this type when:\n * - Reading/writing issue files\n * - Storing parent_id, dependencies, child_order_hints\n * - Passing IDs between internal functions\n */\ndeclare const InternalIssueIdBrand: unique symbol;\nexport type InternalIssueId = string & { [InternalIssueIdBrand]: never };\n\n/**\n * Branded type for display issue IDs ({prefix}-{short} format).\n *\n * Display IDs are shown to users and accepted as CLI input.\n * Format: {prefix}-{short} where short is typically 4 base36 chars\n * Example: tbd-a7k2, bd-100\n *\n * Use this type when:\n * - Formatting output for users\n * - Accepting user input (before resolution)\n * - Building tree views for display\n */\ndeclare const DisplayIssueIdBrand: unique symbol;\nexport type DisplayIssueId = string & { [DisplayIssueIdBrand]: never };\n\n/**\n * Cast a string to InternalIssueId after validation.\n * Use this when you've validated that a string is a valid internal ID.\n */\nexport function asInternalId(id: string): InternalIssueId {\n return id as InternalIssueId;\n}\n\n/**\n * Cast a string to DisplayIssueId.\n * Use this when formatting an ID for display.\n */\nexport function asDisplayId(id: string): DisplayIssueId {\n return id as DisplayIssueId;\n}\n\n/**\n * Prefix for internal IDs (ULID-based).\n * All internal IDs are formatted as: {INTERNAL_ID_PREFIX}-{ulid}\n */\nexport const INTERNAL_ID_PREFIX = 'is';\n\n/**\n * Length of internal ID prefix including the hyphen (e.g., \"is-\" = 3).\n */\nexport const INTERNAL_ID_PREFIX_LENGTH = INTERNAL_ID_PREFIX.length + 1;\n\n/**\n * Construct an internal ID from a ULID.\n *\n * @param ulidValue - The ULID (26 chars)\n * @returns Internal ID in format {prefix}-{ulid}\n */\nexport function makeInternalId(ulidValue: string): InternalIssueId {\n return `${INTERNAL_ID_PREFIX}-${ulidValue.toLowerCase()}` as InternalIssueId;\n}\n\n/**\n * Generate a unique internal ID using ULID.\n * Format: is-{ulid} (26 lowercase alphanumeric chars)\n * Example: is-01hx5zzkbkactav9wevgemmvrz\n *\n * ULID provides:\n * - Time-ordered sorting (48-bit timestamp)\n * - 80-bit randomness (no collisions)\n * - Lexicographic sort = chronological order\n */\nexport function generateInternalId(): InternalIssueId {\n return makeInternalId(ulid());\n}\n\n/**\n * Generate a short ID for external display.\n * Format: base36 characters (a-z, 0-9)\n * Example: a7k2\n *\n * @param length - Number of characters (default 4)\n */\nexport function generateShortId(length = 4): string {\n const chars = '0123456789abcdefghijklmnopqrstuvwxyz';\n let result = '';\n const bytes = randomBytes(length);\n for (let i = 0; i < length; i++) {\n result += chars[bytes[i]! % 36];\n }\n return result;\n}\n\n// Regex pattern for validating internal IDs - built from prefix constant\nconst INTERNAL_ID_PATTERN = new RegExp(`^${INTERNAL_ID_PREFIX}-[0-9a-z]{26}$`);\n\n// Expected length of a full internal ID (prefix + hyphen + 26-char ULID)\nconst INTERNAL_ID_LENGTH = INTERNAL_ID_PREFIX_LENGTH + 26;\n\n/**\n * Validate an internal issue ID matches the ULID format.\n * Format: {prefix}-{26 lowercase alphanumeric chars}\n */\nexport function validateIssueId(id: string): boolean {\n return INTERNAL_ID_PATTERN.test(id);\n}\n\n/**\n * Validate a short/external ID format.\n * Format: 1+ base36 characters (typically 4 for new IDs, but imports may preserve longer IDs).\n */\nexport function validateShortId(id: string): boolean {\n return /^[0-9a-z]+$/.test(id);\n}\n\n/**\n * Check if an input looks like an internal ID (ULID-based).\n */\nexport function isInternalId(input: string): boolean {\n const lower = input.toLowerCase();\n // Check if it starts with the internal prefix and has correct length\n const prefixWithHyphen = `${INTERNAL_ID_PREFIX}-`;\n if (lower.startsWith(prefixWithHyphen) && lower.length === INTERNAL_ID_LENGTH) {\n return INTERNAL_ID_PATTERN.test(lower);\n }\n return false;\n}\n\n/**\n * Check if an input looks like a short/external ID.\n * Returns true for IDs like \"a7k2\", \"bd-a7k2\", \"100\", \"tbd-100\".\n * Short IDs are 16 characters or less (ULIDs are 26 characters).\n */\nexport function isShortId(input: string): boolean {\n const lower = input.toLowerCase();\n // Strip prefix if present\n const stripped = lower.replace(/^[a-z]+-/, '');\n // Must be 1-16 alphanumeric chars (short IDs, not ULIDs which are 26 chars)\n return /^[0-9a-z]+$/.test(stripped) && stripped.length >= 1 && stripped.length <= 16;\n}\n\n/**\n * Extract the short ID portion from an external ID.\n * Examples:\n * \"tbd-100\" -> \"100\"\n * \"bd-a7k2\" -> \"a7k2\"\n * \"a7k2\" -> \"a7k2\"\n * \"100\" -> \"100\"\n */\nexport function extractShortId(externalId: string): string {\n return externalId.toLowerCase().replace(/^[a-z]+-/, '');\n}\n\n/**\n * Extract the prefix portion from an external ID.\n * Returns the prefix (letters before the hyphen) or null if no prefix found.\n * Examples:\n * \"tbd-100\" -> \"tbd\"\n * \"bd-a7k2\" -> \"bd\"\n * \"TBD-100\" -> \"tbd\" (normalized to lowercase)\n * \"a7k2\" -> null (no prefix)\n * \"100\" -> null (no prefix)\n */\nexport function extractPrefix(externalId: string): string | null {\n const match = /^([a-zA-Z]+)-/.exec(externalId);\n return match?.[1]?.toLowerCase() ?? null;\n}\n\n/**\n * Extract the ULID portion from an internal ID.\n *\n * Internal IDs have the format: {prefix}-{ulid}\n * This function strips any prefix to return just the ULID.\n *\n * Examples:\n * \"is-01hx5zzkbkactav9wevgemmvrz\" -> \"01hx5zzkbkactav9wevgemmvrz\"\n * \"01hx5zzkbkactav9wevgemmvrz\" -> \"01hx5zzkbkactav9wevgemmvrz\" (no prefix)\n *\n * @param internalId - The internal ID (with or without prefix)\n * @returns The ULID portion without any prefix\n */\nexport function extractUlidFromInternalId(internalId: string): string {\n // Strip any prefix in format {letters}- (e.g., \"is-\", \"bd-\")\n return internalId.toLowerCase().replace(/^[a-z]+-/, '');\n}\n\n/** Prefix used in Beads for compatibility */\nconst BEADS_COMPAT_PREFIX = 'bd';\n\n/**\n * Normalize an internal issue ID.\n *\n * This function expects a full internal ID ({prefix}-{ulid}).\n * If given a short ID, it won't be able to resolve it without\n * access to the ID mapping.\n *\n * Handles:\n * - Uppercase (converts to lowercase)\n * - Ensures internal ID prefix\n * - Beads compatibility (bd- prefix)\n */\nexport function normalizeIssueId(input: string): string {\n const lower = input.toLowerCase();\n const internalPrefixWithHyphen = `${INTERNAL_ID_PREFIX}-`;\n const beadsPrefixWithHyphen = `${BEADS_COMPAT_PREFIX}-`;\n\n // If already a valid internal ID, return as-is\n if (validateIssueId(lower)) {\n return lower;\n }\n\n // If it starts with internal prefix but wrong length, might be corrupted\n if (lower.startsWith(internalPrefixWithHyphen)) {\n return lower; // Return as-is, let validation fail later\n }\n\n // If it starts with bd- (Beads compat), convert prefix\n if (lower.startsWith(beadsPrefixWithHyphen)) {\n const rest = lower.slice(beadsPrefixWithHyphen.length);\n if (rest.length === 26) {\n return makeInternalId(rest);\n }\n // Short ID - can't resolve without mapping\n return lower;\n }\n\n // Bare ID without prefix\n if (lower.length === 26 && /^[0-9a-z]{26}$/.test(lower)) {\n return makeInternalId(lower);\n }\n\n // Can't normalize - return as-is\n return lower;\n}\n\nimport type { IdMapping } from '../file/id-mapping.js';\n\n/**\n * Format an internal ID for display with the configured prefix.\n *\n * Uses the short ID (4 chars) from the mapping.\n * Throws an error if the mapping is missing or doesn't contain the ID.\n *\n * IMPORTANT: All user-facing output MUST use short IDs, never internal ULIDs.\n * If you see a ULID in user output, it's a bug.\n *\n * @param internalId - The internal ID (is-{ulid})\n * @param mapping - ID mapping for short ID lookup (required)\n * @param prefix - Display prefix (should come from config.display.id_prefix; defaults to 'tbd' as fallback)\n * @throws Error if mapping is missing or ID not found in mapping\n */\nexport function formatDisplayId(\n internalId: InternalIssueId | string,\n mapping: IdMapping,\n prefix = 'tbd',\n): DisplayIssueId {\n // Extract the ULID portion\n const ulidPart = extractUlidFromInternalId(internalId);\n\n // Get short ID from mapping\n const shortId = mapping.ulidToShort.get(ulidPart);\n if (!shortId) {\n throw new Error(\n `No short ID mapping found for internal ID: ${internalId}. ` +\n `This is a bug - all issues must have a short ID mapping.`,\n );\n }\n\n return `${prefix}-${shortId}` as DisplayIssueId;\n}\n\n/**\n * Format an ID for debug output, showing both public and internal IDs.\n *\n * @param internalId - The internal ID (is-{ulid})\n * @param mapping - ID mapping for short ID lookup\n * @param prefix - Display prefix (should come from config.display.id_prefix; defaults to 'tbd' as fallback)\n */\nexport function formatDebugId(\n internalId: InternalIssueId | string,\n mapping: IdMapping,\n prefix = 'tbd',\n): string {\n const displayId = formatDisplayId(internalId, mapping, prefix);\n return `${displayId} (${internalId})`;\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 all files in parallel for better I/O performance\n const fileContents = await Promise.all(\n mdFiles.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 // Parse issues (filter out failed reads)\n const issues: Issue[] = [];\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 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 * Natural sort utilities.\n *\n * Provides alphanumeric sorting where numeric portions are sorted numerically.\n * Similar to `sort -V` (version sort) or `sort -n` for numbers.\n *\n * Examples:\n * [\"1\", \"2\", \"9\", \"10\", \"11\"] instead of [\"1\", \"10\", \"11\", \"2\", \"9\"]\n * [\"a1\", \"a2\", \"a10\"] instead of [\"a1\", \"a10\", \"a2\"]\n * [\"file1.txt\", \"file2.txt\", \"file10.txt\"] instead of [\"file1.txt\", \"file10.txt\", \"file2.txt\"]\n */\n\n/**\n * Split a string into alternating runs of digits and non-digits.\n * @param str - The string to split\n * @returns Array of [isNumeric, value] tuples\n */\nfunction splitIntoChunks(str: string): [boolean, string][] {\n const chunks: [boolean, string][] = [];\n let current = '';\n let currentIsNumeric: boolean | null = null;\n\n for (const char of str) {\n const isDigit = char >= '0' && char <= '9';\n\n if (currentIsNumeric === null) {\n // First character\n currentIsNumeric = isDigit;\n current = char;\n } else if (isDigit === currentIsNumeric) {\n // Same type, continue accumulating\n current += char;\n } else {\n // Type changed, push current chunk and start new one\n chunks.push([currentIsNumeric, current]);\n currentIsNumeric = isDigit;\n current = char;\n }\n }\n\n // Push final chunk\n if (current) {\n chunks.push([currentIsNumeric!, current]);\n }\n\n return chunks;\n}\n\n/**\n * Compare two strings using natural (alphanumeric) ordering.\n *\n * Numeric portions are compared numerically, non-numeric portions are\n * compared lexicographically (case-insensitive). Numbers sort before letters\n * when they appear at the same position in mixed comparisons, matching\n * the behavior of `sort -V` (version sort).\n *\n * @param a - First string\n * @param b - Second string\n * @returns Negative if a < b, positive if a > b, zero if equal\n */\nexport function naturalCompare(a: string, b: string): number {\n // Handle empty strings\n if (!a && !b) return 0;\n if (!a) return -1;\n if (!b) return 1;\n\n const chunksA = splitIntoChunks(a);\n const chunksB = splitIntoChunks(b);\n\n const minLen = Math.min(chunksA.length, chunksB.length);\n\n for (let i = 0; i < minLen; i++) {\n const [isNumericA, valueA] = chunksA[i]!;\n const [isNumericB, valueB] = chunksB[i]!;\n\n if (isNumericA && isNumericB) {\n // Both are numeric - compare as numbers\n const numA = parseInt(valueA, 10);\n const numB = parseInt(valueB, 10);\n if (numA !== numB) {\n return numA - numB;\n }\n // If numerically equal but different strings (e.g., \"01\" vs \"1\"),\n // prefer shorter (fewer leading zeros)\n if (valueA.length !== valueB.length) {\n return valueA.length - valueB.length;\n }\n } else if (!isNumericA && !isNumericB) {\n // Both are non-numeric - compare lexicographically (case-insensitive)\n const lowerA = valueA.toLowerCase();\n const lowerB = valueB.toLowerCase();\n if (lowerA !== lowerB) {\n return lowerA.localeCompare(lowerB);\n }\n // Same when lowercased - they're equal for sorting purposes\n } else {\n // Mixed: numeric comes before non-numeric\n // (so \"1\" comes before \"a\" at the same position)\n // This matches `sort -V` behavior\n return isNumericA ? -1 : 1;\n }\n }\n\n // All compared chunks are equal, shorter string comes first\n return chunksA.length - chunksB.length;\n}\n\n/**\n * Sort an array of strings using natural (alphanumeric) ordering.\n *\n * @param arr - Array to sort\n * @returns New sorted array (does not mutate original)\n */\nexport function naturalSort(arr: readonly string[]): string[] {\n return [...arr].sort(naturalCompare);\n}\n\n/**\n * Sort an array of objects by a string key using natural ordering.\n *\n * @param arr - Array to sort\n * @param keyFn - Function to extract the sort key from each element\n * @returns New sorted array (does not mutate original)\n */\nexport function naturalSortBy<T>(arr: readonly T[], keyFn: (item: T) => string): T[] {\n return [...arr].sort((a, b) => naturalCompare(keyFn(a), keyFn(b)));\n}\n","/**\n * ID mapping management for short public IDs.\n *\n * Maps 4-char base36 short IDs to 26-char ULIDs.\n * Stored in .tbd/data-sync/mappings/ids.yml\n *\n * See: tbd-design.md §2.5 ID Generation\n */\n\nimport { readFile, mkdir } from 'node:fs/promises';\nimport { join, dirname } from 'node:path';\nimport { writeFile } from 'atomically';\n\nimport { parseYamlWithConflictDetection, stringifyYaml } from '../utils/yaml-utils.js';\n\nimport {\n generateShortId,\n extractUlidFromInternalId,\n makeInternalId,\n isInternalId,\n extractShortId,\n asInternalId,\n type InternalIssueId,\n} from '../lib/ids.js';\nimport { naturalSort } from '../lib/sort.js';\nimport { IdMappingYamlSchema } from '../lib/schemas.js';\n\n/**\n * ID mapping from short ID to ULID.\n * Format in ids.yml:\n * a7k2: 01hx5zzkbkactav9wevgemmvrz\n * b3m9: 01hx5zzkbkbctav9wevgemmvrz\n */\nexport interface IdMapping {\n shortToUlid: Map<string, string>;\n ulidToShort: Map<string, string>;\n}\n\n/**\n * Get the path to the ids.yml mapping file.\n */\nfunction getMappingPath(baseDir: string): string {\n return join(baseDir, 'mappings', 'ids.yml');\n}\n\n/**\n * Load the ID mapping from disk.\n * Returns empty mapping if file doesn't exist.\n */\nexport async function loadIdMapping(baseDir: string): Promise<IdMapping> {\n const filePath = getMappingPath(baseDir);\n\n let content: string;\n try {\n content = await readFile(filePath, 'utf-8');\n } catch {\n // File doesn't exist - return empty mapping\n return {\n shortToUlid: new Map(),\n ulidToShort: new Map(),\n };\n }\n\n // Parse with conflict detection - provides helpful error if file has merge markers\n const rawData = parseYamlWithConflictDetection<unknown>(content, filePath) ?? {};\n\n // Validate with Zod schema - ensures all keys are valid short IDs and values are ULIDs\n const parseResult = IdMappingYamlSchema.safeParse(rawData);\n if (!parseResult.success) {\n throw new Error(`Invalid ID mapping format in ${filePath}: ${parseResult.error.message}`);\n }\n const data = parseResult.data;\n\n const shortToUlid = new Map<string, string>();\n const ulidToShort = new Map<string, string>();\n\n for (const [shortId, ulid] of Object.entries(data)) {\n shortToUlid.set(shortId, ulid);\n ulidToShort.set(ulid, shortId);\n }\n\n return { shortToUlid, ulidToShort };\n}\n\n/**\n * Save the ID mapping to disk.\n */\nexport async function saveIdMapping(baseDir: string, mapping: IdMapping): Promise<void> {\n const filePath = getMappingPath(baseDir);\n\n // Ensure directory exists\n await mkdir(dirname(filePath), { recursive: true });\n\n // Convert Map to sorted object for deterministic output\n // Use natural sort so \"1\", \"2\", \"10\" sorts correctly (not \"1\", \"10\", \"2\")\n const data: Record<string, string> = {};\n const sortedKeys = naturalSort(Array.from(mapping.shortToUlid.keys()));\n for (const key of sortedKeys) {\n data[key] = mapping.shortToUlid.get(key)!;\n }\n\n const content = stringifyYaml(data);\n await writeFile(filePath, content);\n}\n\n/**\n * Calculate the optimal short ID length based on existing ID count.\n *\n * At 50K issues, switches from 4-char to 5-char IDs to keep\n * collision probability low (~3% per attempt with 4 chars at 50K).\n *\n * With 10 retries per length, actual failure probability is astronomically low.\n */\nexport function calculateOptimalLength(existingCount: number): number {\n return existingCount < 50_000 ? 4 : 5;\n}\n\n/**\n * Generate a unique short ID that doesn't collide with existing ones.\n *\n * Calculates optimal length (4 or 5 chars) based on existing ID count,\n * then retries with the next length if collisions occur.\n *\n * @returns The new short ID\n * @throws If unable to generate a unique ID after max attempts\n */\nexport function generateUniqueShortId(mapping: IdMapping): string {\n const ATTEMPTS_PER_LENGTH = 10;\n const existingCount = mapping.shortToUlid.size;\n const optimalLength = calculateOptimalLength(existingCount);\n\n // Try optimal length first, then fall back to longer if needed\n for (const length of [optimalLength, optimalLength + 1]) {\n for (let attempt = 0; attempt < ATTEMPTS_PER_LENGTH; attempt++) {\n const shortId = generateShortId(length);\n if (!mapping.shortToUlid.has(shortId)) {\n return shortId;\n }\n }\n }\n\n throw new Error(\n `Failed to generate unique short ID after 20 attempts with ${existingCount} existing IDs. ` +\n `This should be extremely rare - please report if you see this error.`,\n );\n}\n\n/**\n * Register a new ID mapping.\n * @param ulid - The ULID (without is- prefix)\n * @param shortId - The short ID (4 chars)\n */\nexport function addIdMapping(mapping: IdMapping, ulid: string, shortId: string): void {\n mapping.shortToUlid.set(shortId, ulid);\n mapping.ulidToShort.set(ulid, shortId);\n}\n\n/**\n * Get the short ID for a ULID.\n * @param ulid - The ULID (without is- prefix)\n * @returns The short ID, or undefined if not found\n */\nexport function getShortId(mapping: IdMapping, ulid: string): string | undefined {\n return mapping.ulidToShort.get(ulid);\n}\n\n/**\n * Get the ULID for a short ID.\n * @param shortId - The short ID\n * @returns The ULID (without is- prefix), or undefined if not found\n */\nexport function getUlid(mapping: IdMapping, shortId: string): string | undefined {\n return mapping.shortToUlid.get(shortId);\n}\n\n/**\n * Check if a short ID exists in the mapping.\n */\nexport function hasShortId(mapping: IdMapping, shortId: string): boolean {\n return mapping.shortToUlid.has(shortId);\n}\n\n/**\n * Create a short ID mapping for a new internal ID.\n * Generates a unique short ID and registers it in the mapping.\n *\n * @param internalId - The internal ID (is-{ulid})\n * @param mapping - The ID mapping to update\n * @returns The generated short ID\n */\nexport function createShortIdMapping(internalId: string, mapping: IdMapping): string {\n // Extract ULID from internal ID (remove prefix)\n const ulid = extractUlidFromInternalId(internalId);\n\n // Check if already mapped\n const existing = mapping.ulidToShort.get(ulid);\n if (existing) {\n return existing;\n }\n\n // Generate unique short ID\n const shortId = generateUniqueShortId(mapping);\n\n // Register mapping\n addIdMapping(mapping, ulid, shortId);\n\n return shortId;\n}\n\n/**\n * Resolve any ID input to an internal ID ({prefix}-{ulid}).\n *\n * Handles:\n * - Internal IDs: {prefix}-{ulid} -> {prefix}-{ulid}\n * - Short IDs: a7k2 -> {prefix}-{ulid from mapping}\n * - Prefixed short IDs: bd-a7k2 -> {prefix}-{ulid from mapping}\n *\n * @param input - The ID input (short ID, prefixed short ID, or internal ID)\n * @param mapping - The ID mapping for short ID resolution\n * @returns The internal ID ({prefix}-{ulid})\n * @throws If the short ID is not found in the mapping\n */\nexport function resolveToInternalId(input: string, mapping: IdMapping): InternalIssueId {\n const lower = input.toLowerCase();\n\n // If it's already an internal ID, return it\n if (isInternalId(lower)) {\n return asInternalId(lower);\n }\n\n // Extract the short ID portion (strips any prefix like \"bd-\" or \"is-\")\n const shortId = extractShortId(lower);\n\n // If it's a full ULID (26 chars), it might be a bare internal ID\n if (shortId.length === 26 && /^[0-9a-z]{26}$/.test(shortId)) {\n return makeInternalId(shortId);\n }\n\n // Must be a short ID - look it up in the mapping\n const ulid = mapping.shortToUlid.get(shortId);\n if (!ulid) {\n throw new Error(`Unknown issue ID: ${input}. ` + `Short ID \"${shortId}\" not found in mapping.`);\n }\n\n return makeInternalId(ulid);\n}\n\n/**\n * Parse an ID mapping from raw YAML content.\n * Used for loading mappings from git show output during conflict resolution.\n *\n * @throws MergeConflictError if content contains merge conflict markers\n */\nexport function parseIdMappingFromYaml(content: string): IdMapping {\n // Parse with conflict detection - throws MergeConflictError if markers found\n const rawData = parseYamlWithConflictDetection<unknown>(content) ?? {};\n\n // Validate with Zod schema\n const parseResult = IdMappingYamlSchema.safeParse(rawData);\n if (!parseResult.success) {\n throw new Error(`Invalid ID mapping format: ${parseResult.error.message}`);\n }\n const data = parseResult.data;\n\n const shortToUlid = new Map<string, string>();\n const ulidToShort = new Map<string, string>();\n\n for (const [shortId, ulid] of Object.entries(data)) {\n shortToUlid.set(shortId, ulid);\n ulidToShort.set(ulid, shortId);\n }\n\n return { shortToUlid, ulidToShort };\n}\n\n/**\n * Merge two ID mappings by combining all entries from both.\n * ID mappings are always additive (new IDs are only added, never removed),\n * so merging simply unions all key-value pairs.\n *\n * If the same short ID maps to different ULIDs in each mapping (a conflict),\n * the local mapping takes precedence (caller should log a warning).\n *\n * @param local - The local ID mapping\n * @param remote - The remote ID mapping\n * @returns Merged mapping with all entries from both\n */\nexport function mergeIdMappings(local: IdMapping, remote: IdMapping): IdMapping {\n const merged: IdMapping = {\n shortToUlid: new Map(local.shortToUlid),\n ulidToShort: new Map(local.ulidToShort),\n };\n\n // Add all remote entries that don't conflict\n for (const [shortId, ulid] of remote.shortToUlid) {\n if (!merged.shortToUlid.has(shortId)) {\n merged.shortToUlid.set(shortId, ulid);\n merged.ulidToShort.set(ulid, shortId);\n }\n // If shortId already exists with different ulid, keep local (conflict resolution)\n }\n\n // Also check for ULIDs that exist in remote but not in local\n // (different short ID for same ULID - shouldn't happen but handle gracefully)\n for (const [ulid, shortId] of remote.ulidToShort) {\n if (!merged.ulidToShort.has(ulid) && !merged.shortToUlid.has(shortId)) {\n merged.shortToUlid.set(shortId, ulid);\n merged.ulidToShort.set(ulid, shortId);\n }\n }\n\n return merged;\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 * Comparison chain utilities for complex multi-field sorting.\n *\n * Inspired by Google Guava's ComparisonChain, this provides a fluent API\n * for building comparators with multiple sort keys, null handling, and\n * custom orderings.\n *\n * Example:\n *\n * items.sort(comparisonChain<Item>()\n * .compare(item => item.title, ordering.nullsLast)\n * .compare(item => item.url)\n * .result());\n */\n\nexport type Selector<T, K> = (item: T) => K;\nexport type Comparator<T> = (a: T, b: T) => number;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst defaultCompare: Comparator<any> = (a, b) => {\n if (typeof a === 'string' && typeof b === 'string') {\n return a.localeCompare(b);\n }\n if (a < b) return -1;\n if (a > b) return 1;\n return 0;\n};\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst nullLastCompare: Comparator<any> = (a, b) => {\n if (a == null) return b == null ? 0 : 1;\n if (b == null) return -1;\n return defaultCompare(a, b);\n};\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst nullFirstCompare: Comparator<any> = (a, b) => {\n if (a == null) return b == null ? 0 : -1;\n if (b == null) return 1;\n return defaultCompare(a, b);\n};\n\n/**\n * A Google Guava-style comparison chain to make complex sorting, such as secondary\n * sorts, significantly easier.\n *\n * For example:\n *\n * items.sort(comparisonChain<Item>()\n * .compare(item => item.title, ordering.nullsLast)\n * .compare(item => item.url)\n * .result());\n */\nexport const comparisonChain = <T>() => {\n let compare: Comparator<T> = () => 0;\n\n const chain: {\n compare: <K>(selector: Selector<T, K>, comparator?: Comparator<K>) => typeof chain;\n result: () => Comparator<T>;\n } = {\n compare: <K>(selector: Selector<T, K>, comparator: Comparator<K> = defaultCompare) => {\n const prevCompare = compare;\n compare = (a, b) => prevCompare(a, b) || comparator(selector(a), selector(b));\n return chain;\n },\n result: () => compare,\n };\n\n return chain;\n};\n\n/**\n * Reverse a comparator's ordering.\n */\nexport const reverse =\n <T>(comparator: Comparator<T>) =>\n (a: T, b: T) =>\n comparator(b, a);\n\n/**\n * Create a comparator that sorts values in a manually specified order.\n * Values not in the order array are placed at the end.\n */\nconst manualOrderComparator = <T>(order: readonly T[]): Comparator<T> => {\n const orderMap = new Map(order.map((value, index) => [value, index]));\n\n return (a: T, b: T): number => {\n const indexA = orderMap.get(a);\n const indexB = orderMap.get(b);\n\n // Values not in the manually ordered array go at the end.\n if (indexA === undefined) return indexB === undefined ? 0 : 1;\n if (indexB === undefined) return -1;\n\n return indexA - indexB;\n };\n};\n\n/**\n * Common ordering strategies for use with comparisonChain.\n */\nexport const ordering = {\n nullsLast: nullLastCompare,\n nullsFirst: nullFirstCompare,\n default: defaultCompare,\n reversed: reverse(defaultCompare),\n manual: manualOrderComparator,\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 const storedFilename = basename(normalizedStored);\n const queryFilename = basename(normalizedQuery);\n\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('/') && storedFilename === normalizedQuery) {\n return true;\n }\n\n // Also match if both have same filename and query is just a filename\n if (!normalizedQuery.includes('/') && storedFilename === queryFilename) {\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 { naturalCompare } from '../../lib/sort.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 // Helper to get short ID for secondary sort\n const getShortId = (issue: Issue): string => {\n const ulid = extractUlidFromInternalId(issue.id);\n return mapping.ulidToShort.get(ulid) ?? ulid;\n };\n\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 .compare(getShortId, (a, b) => naturalCompare(a, b))\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 type { IssueStatusType } from '../../lib/types.js';\n\ninterface ShowOptions {\n showOrder?: boolean;\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 // 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 };\n\n this.output.data(displayIssue, () => {\n const colors = this.output.getColors();\n\n // Output as YAML+Markdown format (same as storage format)\n const serialized = serializeIssue(issue);\n\n // Add some color highlighting for text output\n const lines = serialized.split('\\n');\n for (const line of lines) {\n if (line === '---') {\n console.log(colors.dim(line));\n } else if (line.startsWith('id:')) {\n console.log(`${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 console.log(`${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 console.log(`${colors.dim('priority:')} ${priorityColor(formatPriority(priority))}`);\n } else if (line.startsWith('title:')) {\n console.log(`${colors.dim('title:')} ${colors.bold(line.slice(7))}`);\n } else if (line.startsWith('spec_path:')) {\n console.log(`${colors.dim('spec_path:')} ${colors.id(line.slice(11))}`);\n } else if (line.startsWith('## Notes')) {\n console.log(colors.bold(line));\n } else if (line.startsWith(' - ')) {\n console.log(` - ${colors.label(line.slice(4))}`);\n } else {\n console.log(line);\n }\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 .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_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_PREFIX)) {\n return {\n type: 'internal',\n location: source.slice(INTERNAL_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_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/** Prefix for internal bundled doc sources */\nconst INTERNAL_SOURCE_PREFIX = 'internal:';\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 * `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} 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} from '../../file/id-mapping.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}\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, options.force);\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(syncBranch, remote, 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 }\n\n /**\n * Show git log --stat output in debug mode.\n * Used to display commits that were synced.\n */\n private async showGitLogDebug(range: string, label: string): Promise<void> {\n try {\n const logOutput = await git('log', '--stat', '--oneline', range);\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(syncBranch: string, remote: string, _force?: boolean): 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 // 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 if (headBeforeMerge) {\n await this.showGitLogDebug(`${headBeforeMerge}..HEAD`, 'Commits received');\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 try {\n const remoteIdsContent = await git(\n 'show',\n `${remote}/${syncBranch}:${DATA_SYNC_DIR}/mappings/ids.yml`,\n );\n if (remoteIdsContent) {\n const localMapping = await loadIdMapping(this.dataSyncDir);\n const remoteMapping = parseIdMappingFromYaml(remoteIdsContent);\n const mergedMapping = mergeIdMappings(localMapping, remoteMapping);\n await saveIdMapping(this.dataSyncDir, mergedMapping);\n this.output.debug(\n `Merged ID mappings: ${localMapping.shortToUlid.size} local + ${remoteMapping.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 // 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 await this.showGitLogDebug(`-${aheadCommits}`, 'Commits sent');\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 - don't silently swallow it\n if (pushFailed) {\n this.output.data(\n {\n summary,\n conflicts: conflicts.length,\n pushFailed,\n pushError,\n unpushedCommits: aheadCommits,\n },\n () => {\n // Extract meaningful error from git output (look for HTTP errors, permission issues, etc.)\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 this.output.error(`Push failed: ${displayError}`);\n console.log(` ${aheadCommits} commit(s) not pushed to remote.`);\n console.log(` Run 'tbd sync' to retry or 'tbd sync --status' to check status.`);\n console.log(` To preserve changes locally: tbd save --outbox`);\n },\n );\n return;\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\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 .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';\nimport { readFile } from 'node:fs/promises';\nimport { parse as parseYaml } from 'yaml';\nimport { writeFile } from 'atomically';\n\nimport { stringifyYaml } from '../../utils/yaml-utils.js';\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 { IssueStatus } from '../../lib/schemas.js';\nimport type { Issue, IssueStatusType, LocalState } from '../../lib/types.js';\nimport { STATE_FILE } from '../../lib/paths.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\n/**\n * Read local state file.\n */\nasync function readState(): Promise<LocalState> {\n try {\n const content = await readFile(STATE_FILE, 'utf-8');\n return parseYaml(content) as LocalState;\n } catch {\n return {};\n }\n}\n\n/**\n * Update local state file.\n */\nasync function updateState(updates: Partial<LocalState>): Promise<void> {\n const state = await readState();\n const newState = { ...state, ...updates };\n await writeFile(STATE_FILE, stringifyYaml(newState));\n}\n\n/**\n * Check if worktree is stale and needs refresh.\n */\nasync function isWorktreeStale(): Promise<boolean> {\n const state = await readState();\n if (!state.last_sync_at) {\n return true; // Never synced\n }\n\n const lastSync = new Date(state.last_sync_at).getTime();\n const now = Date.now();\n return now - lastSync > STALE_THRESHOLD_MS;\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 readState();\n const stale = await isWorktreeStale();\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 // Update state to mark as fresh (in a full implementation, would actually sync)\n await updateState({ 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}\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 *\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): string => {\n const padding = ' '.repeat(maxLabelLen - label.length + 1);\n return ` ${label}:${padding}${value}`;\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 console.log(formatLine('Total', data.total));\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 * 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 { stringifyYaml } from '../utils/yaml-utils.js';\n\nimport { listIssues, writeIssue, readIssue } from './storage.js';\nimport { parseIssue } from './parser.js';\nimport { mergeIssues, deepEqual, git, type ConflictEntry } from './git.js';\nimport { loadIdMapping, saveIdMapping, addIdMapping } 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 type { AtticEntry, Issue } 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}\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}\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 content differs from the remote version (modified issue)\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 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 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 - content differs from remote\n return !deepEqual(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 // Uses default options which include sortMapEntries: true\n const content = stringifyYaml(entry);\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 targetDir = getTargetDir(tbdRoot, options);\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 const allSourceIssues = await listIssues(dataSyncDir);\n const totalSource = allSourceIssues.length;\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 {\n // Fetch and compare with remote tbd-sync\n await git('-C', tbdRoot, 'fetch', 'origin', 'tbd-sync');\n const remoteIssues = await readRemoteIssues(tbdRoot, 'origin', 'tbd-sync');\n sourceIssues = getUpdatedIssues(allSourceIssues, remoteIssues);\n } catch {\n // If fetch fails (offline, remote doesn't exist, etc.), save all issues\n // This is the fallback behavior mentioned in the spec\n }\n }\n\n let saved = 0;\n let conflicts = 0;\n\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\n // Use null base since we don't track common ancestor\n // mergeIssues uses created_at as tiebreaker, so put newer version as \"local\" to win\n const sourceTime = new Date(sourceIssue.updated_at).getTime();\n const targetTime = new Date(targetIssue.updated_at).getTime();\n\n let result;\n let winnerSource: 'local' | 'remote';\n if (sourceTime >= targetTime) {\n // Source (worktree) is newer - put as local so it wins\n result = mergeIssues(null, sourceIssue, targetIssue);\n winnerSource = 'local';\n } else {\n // Target (workspace) is newer - put as local so it wins\n result = mergeIssues(null, targetIssue, sourceIssue);\n winnerSource = 'remote';\n }\n\n // Save merged issue\n await writeIssue(targetDir, result.merged);\n saved++;\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\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 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 sourceDir = resolveWorkspaceDir(tbdRoot, options);\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 const sourceIssues = await listIssues(sourceDir);\n\n let imported = 0;\n let conflicts = 0;\n\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\n // Use null base since we don't track common ancestor\n // mergeIssues uses created_at as tiebreaker, so put newer version as \"local\" to win\n const sourceTime = new Date(sourceIssue.updated_at).getTime();\n const targetTime = new Date(targetIssue.updated_at).getTime();\n\n let result;\n let winnerSource: 'local' | 'remote';\n if (sourceTime >= targetTime) {\n // Source (workspace) is newer - put as local so it wins\n result = mergeIssues(null, sourceIssue, targetIssue);\n winnerSource = 'local';\n } else {\n // Target (worktree) is newer - put as local so it wins\n result = mergeIssues(null, targetIssue, sourceIssue);\n winnerSource = 'remote';\n }\n\n // Save merged issue\n await writeIssue(dataSyncDir, result.merged);\n imported++;\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\n // Merge ID mappings from source (workspace) to target (worktree) - union operation\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 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 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 await requireInit();\n\n // Load all issues\n let issues: Issue[];\n try {\n const dataSyncDir = await resolveDataSyncDir();\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 {\n getClaudePaths,\n getAgentsMdPath,\n CLAUDE_SKILL_REL,\n AGENTS_MD_REL,\n} from '../../lib/integration-paths.js';\nimport { validateIssueId } from '../../lib/ids.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} 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}\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\n const statsInfo = 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 6: Orphaned temp files\n healthChecks.push(await this.checkTempFiles(options.fix));\n\n // Check 7: Issue validity\n healthChecks.push(this.checkIssueValidity(this.issues));\n\n // Check 8: Worktree health (with fix support)\n healthChecks.push(await this.checkWorktree(options.fix));\n\n // Check 9: Data location (issues in wrong path, with fix support)\n healthChecks.push(await this.checkDataLocation(options.fix));\n\n // Check 10: Local sync branch health\n healthChecks.push(await this.checkLocalSyncBranch());\n\n // Check 11: Remote sync branch health\n healthChecks.push(await this.checkRemoteSyncBranch());\n\n // Check 12: Local has data but remote empty (ai-trade-arena bug detection)\n healthChecks.push(await this.checkLocalVsRemoteData());\n\n // Check 13: Multi-user/clone scenario detection\n healthChecks.push(await this.checkCloneScenarios());\n\n // Check 14: 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 gatherStatsInfo(): {\n total: number;\n ready: number;\n inProgress: number;\n blocked: number;\n open: number;\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 return {\n total: this.issues.length,\n ready: readyCount,\n inProgress: byStatus.in_progress,\n blocked: blockedIds.size,\n open: byStatus.open,\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 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 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\n const result = await migrateDataToWorktree(this.cwd);\n\n if (result.success) {\n const message = result.backupPath\n ? `migrated ${result.migratedCount} file(s), backed up to ${result.backupPath}`\n : `migrated ${result.migratedCount} file(s)`;\n return { name: 'Data location', status: 'ok', message, path: wrongIssuesPath };\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: 'warn',\n message: `${consistency.localAhead} commit(s) ahead of remote`,\n suggestion: 'Run: tbd sync to push changes',\n };\n }\n\n if (consistency.localBehind > 0) {\n return {\n name: 'Sync consistency',\n status: 'warn',\n message: `${consistency.localBehind} commit(s) behind remote`,\n suggestion: 'Run: tbd sync to pull changes',\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 .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 await requireInit();\n\n let config: Config;\n try {\n config = await readConfig('.');\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 await requireInit();\n\n let config: Config;\n try {\n config = await readConfig('.');\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('.', 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 await requireInit();\n\n let config: Config;\n try {\n config = await readConfig('.');\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, 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 } 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(filterById?: string): Promise<AtticEntry[]> {\n const atticPath = await resolveAtticDir();\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(entry: AtticEntry): Promise<void> {\n const atticPath = await resolveAtticDir();\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 // Uses default options which include sortMapEntries: true\n const content = stringifyYaml(entry);\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(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(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(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;\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 * 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: beads.priority ?? 2,\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\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 await requireInit();\n this.dataSyncDir = await resolveDataSyncDir();\n await this.validateImport(options);\n return;\n }\n\n // File import requires initialization\n if (file) {\n await requireInit();\n this.dataSyncDir = await resolveDataSyncDir();\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 const tbdRoot = await requireInit();\n this.dataSyncDir = await resolveDataSyncDir(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\n const result = await this.execute(async () => {\n return await importFromWorkspace(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 ((beads.priority ?? 2) !== tbdIssue.priority) {\n fieldIssues.push(`priority mismatch: ${tbdIssue.priority} vs ${beads.priority ?? 2}`);\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 const cwd = process.cwd();\n try {\n const config = await readConfig(cwd);\n if (config.display.id_prefix !== detectedPrefix) {\n const oldPrefix = config.display.id_prefix;\n config.display.id_prefix = detectedPrefix;\n await writeConfig(cwd, 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 { 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 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\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 /**\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 { CLIError } from '../lib/errors.js';\nimport { renderMarkdown } 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\n console.log(renderMarkdown(content, this.ctx.color));\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 } from 'node:path';\n\nimport { BaseCommand } from '../lib/base-command.js';\nimport { NotInitializedError, CLIError } from '../lib/errors.js';\nimport { 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 // Check if tbd is initialized\n try {\n await access('.tbd');\n } catch {\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('.');\n } catch {\n config = null;\n }\n\n const syncBranch = config?.sync.branch ?? SYNC_BRANCH;\n const remote = config?.sync.remote ?? 'origin';\n const worktreePath = join('.tbd', 'data-sync-worktree');\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: ${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('.tbd');\n items.push(` - Directory: .tbd/ (${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('.tbd', { 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, ['skill', 'skill-brief', 'shortcut-explanation']);\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 { 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.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 console.log(primeContent);\n\n // Shortcut directory\n const shortcutDir = await this.getShortcutDirectory(tbdRoot);\n if (shortcutDir) {\n console.log('');\n console.log(shortcutDir);\n }\n\n console.log('');\n console.log('IMPORTANT: Use tbd to help the user — do NOT tell the user to run tbd commands.');\n console.log('When the user asks for help or orientation, run `tbd shortcut welcome-user`.');\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\n console.log(BRIEF_SKILL_CONTENT);\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\n console.log(VALUE_PROPOSITION);\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 (default when running `tbd`)')\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 { 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 if (options.brief) {\n // Brief mode: just output skill-brief.md\n const content = await loadDocContent('skill-brief.md');\n console.log(content);\n return;\n }\n\n // Full mode: compose header + skill.md + shortcut directory\n const content = await this.composeFullSkill();\n console.log(content);\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.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.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) => d.name !== 'skill' && d.name !== 'skill-brief' && 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 { 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 } 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 const header = this.getAgentHeader();\n if (header) {\n console.log(header + '\\n');\n }\n console.log(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 const header = this.getAgentHeader();\n if (header) {\n console.log(header + '\\n');\n }\n console.log(best.doc.content);\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 ) {\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) && !options.prefix) {\n // Beads migration flow (unless prefix override given)\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 );\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 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 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 // 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\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('--non-interactive', 'Disable all prompts, fail if input required')\n .option('--yes', 'Assume yes to confirmation prompts')\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 and stack trace.\n */\nfunction outputError(message: string, error?: Error): void {\n const debugMode = isDebugMode();\n\n if (isJsonMode()) {\n const errorObj: { error: string; type?: string; details?: string; stack?: string } = {\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 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 }\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 if (hasNoCommand() && !isHelpOrVersion) {\n // Insert 'prime' as the command\n process.argv.splice(2, 0, 'prime');\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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"}