[
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n  parser: '@typescript-eslint/parser',\n  plugins: ['@typescript-eslint'],\n  extends: [\n    'eslint:recommended',\n  ],\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n  env: {\n    node: true,\n    es2022: true,\n  },\n  rules: {\n    '@typescript-eslint/no-unused-vars': 'off',\n    'no-unused-vars': 'off',\n  },\n};"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    \n    strategy:\n      matrix:\n        node-version: [18.x, 20.x]\n    \n    steps:\n    - uses: actions/checkout@v3\n    \n    - name: Use Node.js ${{ matrix.node-version }}\n      uses: actions/setup-node@v3\n      with:\n        node-version: ${{ matrix.node-version }}\n        cache: 'npm'\n    \n    - name: Install dependencies\n      run: npm ci\n    \n    - name: Run lint\n      run: npm run lint\n    \n    - name: Run type check\n      run: npm run check\n    \n    - name: Run tests\n      run: npm test\n    \n    - name: Build\n      run: npm run build\n\n  release:\n    needs: test\n    runs-on: ubuntu-latest\n    if: github.ref == 'refs/heads/main' && github.event_name == 'push'\n    \n    steps:\n    - uses: actions/checkout@v3\n    \n    - name: Use Node.js 20.x\n      uses: actions/setup-node@v3\n      with:\n        node-version: 20.x\n        cache: 'npm'\n        registry-url: 'https://registry.npmjs.org'\n    \n    - name: Install dependencies\n      run: npm ci\n    \n    - name: Build\n      run: npm run build\n    \n    - name: Check if version changed\n      id: version\n      run: |\n        CURRENT_VERSION=$(node -p \"require('./package.json').version\")\n        echo \"current_version=$CURRENT_VERSION\" >> $GITHUB_OUTPUT\n        \n        # Check if this version exists on npm\n        if npm view repomirror@$CURRENT_VERSION version 2>/dev/null; then\n          echo \"version_exists=true\" >> $GITHUB_OUTPUT\n        else\n          echo \"version_exists=false\" >> $GITHUB_OUTPUT\n        fi\n    \n    - name: Publish to npm\n      if: steps.version.outputs.version_exists == 'false'\n      run: npm publish\n      env:\n        NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}"
  },
  {
    "path": ".gitignore",
    "content": "dist/\nclaude_output.jsonl\ndraft.md\nimages/\n*-draft.md"
  },
  {
    "path": ".repomirror/.gitignore",
    "content": "claude_output.jsonl\n"
  },
  {
    "path": ".repomirror/prompt.md",
    "content": "Your job is to port repomirror (TypeScript) to repomirror-py (Python) and maintain the repository.\n\nYou have access to the current ./ repository as well as the target /tmp/test-target2 repository.\n\nMake a commit and push your changes after every single file edit.\n\nUse the /tmp/test-target2/agent/ directory as a scratchpad for your work. Store long term plans and todo lists there.\n\nThe original project was mostly tested by manually running the code. When porting, you will need to write end to end and unit tests for the project. But make sure to spend most of your time on the actual porting, not on the testing. A good heuristic is to spend 80% of your time on the actual porting, and 20% on the testing."
  },
  {
    "path": ".repomirror/ralph.sh",
    "content": "#!/bin/bash\nwhile :; do\n  ./.repomirror/sync.sh\n  echo -e \"===SLEEP===\\n===SLEEP===\\n\"; echo 'looping';\n  sleep 10;\ndone"
  },
  {
    "path": ".repomirror/sync.sh",
    "content": "#!/bin/bash\ncat .repomirror/prompt.md | \\\n        claude -p --output-format=stream-json --verbose --dangerously-skip-permissions --add-dir /tmp/test-target2 | \\\n        tee -a .repomirror/claude_output.jsonl | \\\n        npx repomirror visualize --debug;"
  },
  {
    "path": "@IMPLEMENTATION_PLAN.md",
    "content": "# RepoMirror Implementation Plan\n\n## PRIORITY -1: finish rename ✅\n- [x] change simonsays to repomirror everywhere, files, docs, names referenfces, everything ✅\n  - Updated package.json binary name from \"simonsays\" to \"repomirror\"\n  - Renamed simonsays.yaml to repomirror.yaml \n  - Updated CLI name and all help text references throughout src/cli.ts\n  - Changed all \".simonsays\" directory references to \".repomirror\"\n  - Renamed createSimonSaysFiles function to createRepoMirrorFiles\n  - Updated all configuration file references from \"simonsays.yaml\" to \"repomirror.yaml\"\n  - Updated all \"npx simonsays\" command references to \"npx repomirror\"\n  - Migrated existing .simonsays directory to .repomirror with proper script updates\n  - Updated all test files to use new naming conventions\n  - Updated README.md and documentation\n  - Removed orphaned test file for non-existent issue-fixer command\n  - All 277 tests passing with TypeScript compilation successful\n\n## PRIORITY 0: fix critical bugs ✅\n- [x] sync-forever doesn't exit on ctrl+c ✅\n  - Fixed signal handling in sync-forever command for both legacy ralph.sh and new approach\n  - Added proper subprocess management with SIGINT/SIGTERM handlers\n- [x] check other commands for ctrl+c handling as well ✅\n  - Added signal handling to sync command\n  - Added signal handling to init command (Claude SDK query)\n  - Added signal handling to issue-fixer command (Claude SDK query)\n  - Added signal handling to pull command (for both sync and sync-forever)\n  - Added signal handling to visualize command\n  - All long-running commands now properly handle Ctrl+C\n\n## Priority 1: Core Infrastructure ✅\n- [x] Create implementation plan\n- [x] Initialize npm project structure\n- [x] Create basic CLI entry point\n\n## Priority 2: Init Command ✅\n- [x] Implement `npx repomirror init` command\n- [x] Generate transformation prompt using Claude SDK\n- [x] Perform preflight checks (git, claude, directories)\n- [x] Create .repomirror/ directory with scripts\n- [x] Ensure all preflight checks have verbose output\n- [x] Ensure all prompts/cli flags are stashed to a repomirror.yaml during setup, and defaults are populated from the yaml file if present (instead of core defaults)\n- [x] FIX CRITICAL BUG: `npx repomirror init` hangs in generateTransformationPrompt function\n  - The Claude SDK async iterator needs proper handling to avoid infinite loops\n  - Must break after receiving result (currently only breaks on non-error results)\n  - Must handle ALL message types, not just \"result\" type\n  - See updated spec.md for correct implementation pattern\n- [x] improve the cli init output to match the spec ✅\n  - Updated output format to match spec exactly (removed bullet points, added file list)\n  - Fixed typo \"repositorty\" → \"repository\" in prompt examples\n- [x] update transformation prompt to match the spec ✅\n\n## Priority 3: Sync Commands ✅\n- [x] Implement `sync` command to run sync.sh\n- [x] Implement `sync-one` command (alias for sync)\n- [x] Implement `sync-forever` command to run ralph.sh\n- [x] Implement `visualize` command for output formatting\n\n## Priority 4: Advanced Features\n- [x] Add comprehensive tests for all commands ✅\n- [x] Add remote repo support (push/pull) ✅\n  - Implemented `remote` command for managing remote repositories\n  - Implemented `push` command with auto-commit and multi-remote support\n  - Implemented `pull` command with source sync integration\n  - Enhanced sync commands with auto-push capabilities\n- [x] Add validation script for init command ✅\n  - Created `hack/ralph-validate.sh` for automated testing\n  - Added SKIP_CLAUDE_TEST environment variable for testing mode\n- [x] GitHub Actions integration ✅\n  - Implemented `github-actions` command for workflow generation\n  - Creates customizable GitHub Actions workflow for automated syncing\n  - Supports scheduled runs, manual triggers, and push-triggered syncs\n  - Fixed linting error with escaped characters in workflow template\n- [x] Issue fixer functionality ✅\n  - Implemented `issue-fixer` command for automatic issue detection and fixing\n  - Detects build, test, lint, and type checking issues across multiple languages\n  - Supports Node/TypeScript, Python, and Go projects\n  - Uses Claude SDK to intelligently fix detected issues\n  - Interactive mode for selective issue fixing\n  - Comprehensive test suite with 268 passing tests\n\n## Priority 5: GitHub Actions PR Sync Features ✅\n- [x] Implement `setup-github-pr-sync` command ✅\n  - Creates GitHub Actions workflow for PR-triggered syncing\n  - Configurable loop iterations (1-10 times) for sync-one command\n  - Persists settings to repomirror.yaml for future use\n  - Overwrite protection with user confirmation\n  - Creates `.github/workflows/repomirror.yml` workflow file\n  - Comprehensive test coverage (8 tests)\n- [x] Implement `dispatch-sync` command ✅\n  - Dispatches workflow runs using GitHub CLI\n  - Automatic repository detection from git remotes\n  - User confirmation with `--yes` flag to skip\n  - Quiet mode with `--quiet` flag (requires `--yes`)\n  - Comprehensive error handling for missing prerequisites\n  - Full test coverage (17 tests)\n\n## Current Status\n✅ **FULLY IMPLEMENTED** - All planned features completed successfully:\n- All CLI commands implemented and working\n- Init command creates proper .repomirror/ structure with **template-based shell script generation**\n- Sync commands execute shell scripts correctly\n- Visualize command provides colored output\n- Remote repository management (add/list/remove remotes)\n- Push command with intelligent commit messages and multi-remote support\n- Pull command with auto-sync integration\n- Auto-push capability after sync operations\n- Validation script for testing init command functionality\n- Test mode support with SKIP_CLAUDE_TEST environment variable\n- GitHub Actions workflow generation for CI/CD\n- Issue fixer command for automatic issue detection and resolution\n- GitHub Actions PR sync commands (`setup-github-pr-sync` and `dispatch-sync`)\n- **Shell script templates properly bundled in dist/ package** (matches spec requirement)\n- Comprehensive test suite with 293 tests passing (2 skipped for interactive mode)\n- TypeScript build passing with full type safety\n- All linting checks passing\n- **Ready for production usage with complete feature set**\n\n## Recent Improvements\n\n### Template-Based Shell Script Generation - IMPLEMENTED ✅\n**Issue**: The spec required \"The shell scripts are included in the npm dist/ bundle and baked into the package so they can be copied out of the package root by `npx repomirror init`\" but the implementation was generating scripts inline.\n\n**Solution Implemented**:\n- Created template files in `src/templates/`: `sync.sh.template`, `ralph.sh.template`, `gitignore.template`\n- Updated build process in `package.json` to copy templates to `dist/templates/`\n- Modified `src/commands/init.ts` to read templates from package and substitute variables\n- Added template location resolution (tries `dist/templates` first, falls back to `src/templates`)\n- Supports `${targetRepo}` variable substitution in sync script template\n- All 293 tests continue to pass\n- Now fully compliant with spec requirement\n\n**Files Modified**:\n- `src/templates/sync.sh.template` (new)\n- `src/templates/ralph.sh.template` (new)  \n- `src/templates/gitignore.template` (new)\n- `package.json` (updated build process and files array)\n- `src/commands/init.ts` (replaced inline generation with template-based)\n- `tests/commands/init.test.ts` (updated mocks for template handling)\n\n## Known Issues & Critical Fixes Needed\n\n### 1. Init Command Hangs (CRITICAL) - FIXED ✅\n**Problem**: The `repomirror init` command was hanging forever during prompt generation.\n**Root Cause**: The `generateTransformationPrompt` function (src/commands/init.ts:256-318) was using an async iterator incorrectly, not handling error cases properly.\n\n**Fix Applied**: Updated the async iterator loop to properly handle both error and success cases:\n- Now throws an error immediately when Claude SDK returns an error\n- Properly breaks the loop after receiving ANY result type\n- Added more descriptive error messages for debugging\n\n**Testing Completed**:\n- All 230 tests passing (comprehensive test coverage added)\n- TypeScript build successful\n- Ready for production use\n\n## Testing Instructions\n\nFor testing the init command without calling Claude SDK:\n- Set `SKIP_CLAUDE_TEST=true` environment variable\n- This will skip the Claude Code preflight check and use a test prompt template\n- The validation script `hack/ralph-validate.sh` uses this flag for automated testing\n"
  },
  {
    "path": "README.md",
    "content": "# repomirror\n\nA tool to perform transformations on code repositories with AI. Inspired by [@ghuntley](https://github.com/ghuntley)'s [ralph wiggum](https://ghuntley.com/ralph).\n\n\nBuilt by [@yonom](https://github.com/yonom), [@dexhorthy](https://github.com/dexhorthy), [@lantos1618](https://github.com/lantos1618) and [@AVGVSTVS96](https://github.com/AVGVSTVS96)\n\nRead more about the project at [repomirror.md](./repomirror.md)\n\n## Projects\n\nSome example projects maintained by repomirror:\n\n- [better-use](https://github.com/yonom/browser-use-ts) - A port of browser-use to typescript\n- [ai-sdk-python](https://github.com/yonom/ai-sdk-python) - Port of vercel AI sdk to python\n- [assistant-ui-vue](https://github.com/yonom/assistant-ui-vue) - Port of assistant-ui-react to vue.js\n- [open-dedalus](https://github.com/yonom/open-dedalus) - open source clone of dedalus using `llms-full.txt`\n- [better-ui](https://github.com/lantos1618/better-ui/tree/lantos-aui) - Assistant UI + TRPC (ai ui with frontend and backend State)\n- [lynlan](https://github.com/lantos1618/lynlang) - GO + RUST + Haskel\n"
  },
  {
    "path": "VALIDATION_PLAN.md",
    "content": "✅ validate init: [COMPLETED]\n\n- create a temp dir for source repo and add a \"hello.ts\" file in it ✅\n- create a temp dir for target and init a git repo in it ✅\n- use `repomirror init` to test, use a \"translate this repo to python\" ✅\n- ensure that the command succeeds and generated the correct files ✅\n\nValidation completed successfully using hack/ralph-validate.sh script.\nResults:\n- repomirror.yaml created in source directory\n- .repomirror/ directory created with all required files (prompt.md, sync.sh, ralph.sh, .gitignore)\n- Command handles CLI flags correctly (--source, --target, --instructions)\n- Test mode support added with SKIP_CLAUDE_TEST environment variable for automated testing\n\n\nBUG - CTRL + C does not work in sync-forever command\n"
  },
  {
    "path": "coverage/base.css",
    "content": "body, html {\n  margin:0; padding: 0;\n  height: 100%;\n}\nbody {\n    font-family: Helvetica Neue, Helvetica, Arial;\n    font-size: 14px;\n    color:#333;\n}\n.small { font-size: 12px; }\n*, *:after, *:before {\n  -webkit-box-sizing:border-box;\n     -moz-box-sizing:border-box;\n          box-sizing:border-box;\n  }\nh1 { font-size: 20px; margin: 0;}\nh2 { font-size: 14px; }\npre {\n    font: 12px/1.4 Consolas, \"Liberation Mono\", Menlo, Courier, monospace;\n    margin: 0;\n    padding: 0;\n    -moz-tab-size: 2;\n    -o-tab-size:  2;\n    tab-size: 2;\n}\na { color:#0074D9; text-decoration:none; }\na:hover { text-decoration:underline; }\n.strong { font-weight: bold; }\n.space-top1 { padding: 10px 0 0 0; }\n.pad2y { padding: 20px 0; }\n.pad1y { padding: 10px 0; }\n.pad2x { padding: 0 20px; }\n.pad2 { padding: 20px; }\n.pad1 { padding: 10px; }\n.space-left2 { padding-left:55px; }\n.space-right2 { padding-right:20px; }\n.center { text-align:center; }\n.clearfix { display:block; }\n.clearfix:after {\n  content:'';\n  display:block;\n  height:0;\n  clear:both;\n  visibility:hidden;\n  }\n.fl { float: left; }\n@media only screen and (max-width:640px) {\n  .col3 { width:100%; max-width:100%; }\n  .hide-mobile { display:none!important; }\n}\n\n.quiet {\n  color: #7f7f7f;\n  color: rgba(0,0,0,0.5);\n}\n.quiet a { opacity: 0.7; }\n\n.fraction {\n  font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;\n  font-size: 10px;\n  color: #555;\n  background: #E8E8E8;\n  padding: 4px 5px;\n  border-radius: 3px;\n  vertical-align: middle;\n}\n\ndiv.path a:link, div.path a:visited { color: #333; }\ntable.coverage {\n  border-collapse: collapse;\n  margin: 10px 0 0 0;\n  padding: 0;\n}\n\ntable.coverage td {\n  margin: 0;\n  padding: 0;\n  vertical-align: top;\n}\ntable.coverage td.line-count {\n    text-align: right;\n    padding: 0 5px 0 20px;\n}\ntable.coverage td.line-coverage {\n    text-align: right;\n    padding-right: 10px;\n    min-width:20px;\n}\n\ntable.coverage td span.cline-any {\n    display: inline-block;\n    padding: 0 5px;\n    width: 100%;\n}\n.missing-if-branch {\n    display: inline-block;\n    margin-right: 5px;\n    border-radius: 3px;\n    position: relative;\n    padding: 0 4px;\n    background: #333;\n    color: yellow;\n}\n\n.skip-if-branch {\n    display: none;\n    margin-right: 10px;\n    position: relative;\n    padding: 0 4px;\n    background: #ccc;\n    color: white;\n}\n.missing-if-branch .typ, .skip-if-branch .typ {\n    color: inherit !important;\n}\n.coverage-summary {\n  border-collapse: collapse;\n  width: 100%;\n}\n.coverage-summary tr { border-bottom: 1px solid #bbb; }\n.keyline-all { border: 1px solid #ddd; }\n.coverage-summary td, .coverage-summary th { padding: 10px; }\n.coverage-summary tbody { border: 1px solid #bbb; }\n.coverage-summary td { border-right: 1px solid #bbb; }\n.coverage-summary td:last-child { border-right: none; }\n.coverage-summary th {\n  text-align: left;\n  font-weight: normal;\n  white-space: nowrap;\n}\n.coverage-summary th.file { border-right: none !important; }\n.coverage-summary th.pct { }\n.coverage-summary th.pic,\n.coverage-summary th.abs,\n.coverage-summary td.pct,\n.coverage-summary td.abs { text-align: right; }\n.coverage-summary td.file { white-space: nowrap;  }\n.coverage-summary td.pic { min-width: 120px !important;  }\n.coverage-summary tfoot td { }\n\n.coverage-summary .sorter {\n    height: 10px;\n    width: 7px;\n    display: inline-block;\n    margin-left: 0.5em;\n    background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent;\n}\n.coverage-summary .sorted .sorter {\n    background-position: 0 -20px;\n}\n.coverage-summary .sorted-desc .sorter {\n    background-position: 0 -10px;\n}\n.status-line {  height: 10px; }\n/* yellow */\n.cbranch-no { background: yellow !important; color: #111; }\n/* dark red */\n.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 }\n.low .chart { border:1px solid #C21F39 }\n.highlighted,\n.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{\n  background: #C21F39 !important;\n}\n/* medium red */\n.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE }\n/* light red */\n.low, .cline-no { background:#FCE1E5 }\n/* light green */\n.high, .cline-yes { background:rgb(230,245,208) }\n/* medium green */\n.cstat-yes { background:rgb(161,215,106) }\n/* dark green */\n.status-line.high, .high .cover-fill { background:rgb(77,146,33) }\n.high .chart { border:1px solid rgb(77,146,33) }\n/* dark yellow (gold) */\n.status-line.medium, .medium .cover-fill { background: #f9cd0b; }\n.medium .chart { border:1px solid #f9cd0b; }\n/* light yellow */\n.medium { background: #fff4c2; }\n\n.cstat-skip { background: #ddd; color: #111; }\n.fstat-skip { background: #ddd; color: #111 !important; }\n.cbranch-skip { background: #ddd !important; color: #111; }\n\nspan.cline-neutral { background: #eaeaea; }\n\n.coverage-summary td.empty {\n    opacity: .5;\n    padding-top: 4px;\n    padding-bottom: 4px;\n    line-height: 1;\n    color: #888;\n}\n\n.cover-fill, .cover-empty {\n  display:inline-block;\n  height: 12px;\n}\n.chart {\n  line-height: 0;\n}\n.cover-empty {\n    background: white;\n}\n.cover-full {\n    border-right: none !important;\n}\npre.prettyprint {\n    border: none !important;\n    padding: 0 !important;\n    margin: 0 !important;\n}\n.com { color: #999 !important; }\n.ignore-none { color: #999; font-weight: normal; }\n\n.wrapper {\n  min-height: 100%;\n  height: auto !important;\n  height: 100%;\n  margin: 0 auto -48px;\n}\n.footer, .push {\n  height: 48px;\n}\n"
  },
  {
    "path": "coverage/block-navigation.js",
    "content": "/* eslint-disable */\nvar jumpToCode = (function init() {\n    // Classes of code we would like to highlight in the file view\n    var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no'];\n\n    // Elements to highlight in the file listing view\n    var fileListingElements = ['td.pct.low'];\n\n    // We don't want to select elements that are direct descendants of another match\n    var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > `\n\n    // Selector that finds elements on the page to which we can jump\n    var selector =\n        fileListingElements.join(', ') +\n        ', ' +\n        notSelector +\n        missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b`\n\n    // The NodeList of matching elements\n    var missingCoverageElements = document.querySelectorAll(selector);\n\n    var currentIndex;\n\n    function toggleClass(index) {\n        missingCoverageElements\n            .item(currentIndex)\n            .classList.remove('highlighted');\n        missingCoverageElements.item(index).classList.add('highlighted');\n    }\n\n    function makeCurrent(index) {\n        toggleClass(index);\n        currentIndex = index;\n        missingCoverageElements.item(index).scrollIntoView({\n            behavior: 'smooth',\n            block: 'center',\n            inline: 'center'\n        });\n    }\n\n    function goToPrevious() {\n        var nextIndex = 0;\n        if (typeof currentIndex !== 'number' || currentIndex === 0) {\n            nextIndex = missingCoverageElements.length - 1;\n        } else if (missingCoverageElements.length > 1) {\n            nextIndex = currentIndex - 1;\n        }\n\n        makeCurrent(nextIndex);\n    }\n\n    function goToNext() {\n        var nextIndex = 0;\n\n        if (\n            typeof currentIndex === 'number' &&\n            currentIndex < missingCoverageElements.length - 1\n        ) {\n            nextIndex = currentIndex + 1;\n        }\n\n        makeCurrent(nextIndex);\n    }\n\n    return function jump(event) {\n        if (\n            document.getElementById('fileSearch') === document.activeElement &&\n            document.activeElement != null\n        ) {\n            // if we're currently focused on the search input, we don't want to navigate\n            return;\n        }\n\n        switch (event.which) {\n            case 78: // n\n            case 74: // j\n                goToNext();\n                break;\n            case 66: // b\n            case 75: // k\n            case 80: // p\n                goToPrevious();\n                break;\n        }\n    };\n})();\nwindow.addEventListener('keydown', jumpToCode);\n"
  },
  {
    "path": "coverage/coverage-final.json",
    "content": "{\"/Users/dex/go/src/github.com/dexhorthy/repomirror/src/cli.ts\": {\"path\":\"/Users/dex/go/src/github.com/dexhorthy/repomirror/src/cli.ts\",\"all\":true,\"statementMap\":{\"0\":{\"start\":{\"line\":1,\"column\":0},\"end\":{\"line\":1,\"column\":19}},\"1\":{\"start\":{\"line\":2,\"column\":0},\"end\":{\"line\":2,\"column\":36}},\"2\":{\"start\":{\"line\":3,\"column\":0},\"end\":{\"line\":3,\"column\":39}},\"3\":{\"start\":{\"line\":4,\"column\":0},\"end\":{\"line\":4,\"column\":46}},\"4\":{\"start\":{\"line\":5,\"column\":0},\"end\":{\"line\":5,\"column\":39}},\"5\":{\"start\":{\"line\":6,\"column\":0},\"end\":{\"line\":6,\"column\":54}},\"6\":{\"start\":{\"line\":7,\"column\":0},\"end\":{\"line\":7,\"column\":49}},\"7\":{\"start\":{\"line\":8,\"column\":0},\"end\":{\"line\":8,\"column\":0}},\"8\":{\"start\":{\"line\":9,\"column\":0},\"end\":{\"line\":9,\"column\":30}},\"9\":{\"start\":{\"line\":10,\"column\":0},\"end\":{\"line\":10,\"column\":0}},\"10\":{\"start\":{\"line\":11,\"column\":0},\"end\":{\"line\":11,\"column\":7}},\"11\":{\"start\":{\"line\":12,\"column\":0},\"end\":{\"line\":12,\"column\":21}},\"12\":{\"start\":{\"line\":13,\"column\":0},\"end\":{\"line\":13,\"column\":65}},\"13\":{\"start\":{\"line\":14,\"column\":0},\"end\":{\"line\":14,\"column\":20}},\"14\":{\"start\":{\"line\":15,\"column\":0},\"end\":{\"line\":15,\"column\":0}},\"15\":{\"start\":{\"line\":16,\"column\":0},\"end\":{\"line\":16,\"column\":7}},\"16\":{\"start\":{\"line\":17,\"column\":0},\"end\":{\"line\":17,\"column\":18}},\"17\":{\"start\":{\"line\":18,\"column\":0},\"end\":{\"line\":18,\"column\":60}},\"18\":{\"start\":{\"line\":19,\"column\":0},\"end\":{\"line\":19,\"column\":16}},\"19\":{\"start\":{\"line\":20,\"column\":0},\"end\":{\"line\":20,\"column\":0}},\"20\":{\"start\":{\"line\":21,\"column\":0},\"end\":{\"line\":21,\"column\":75}},\"21\":{\"start\":{\"line\":22,\"column\":0},\"end\":{\"line\":22,\"column\":0}},\"22\":{\"start\":{\"line\":23,\"column\":0},\"end\":{\"line\":23,\"column\":7}},\"23\":{\"start\":{\"line\":24,\"column\":0},\"end\":{\"line\":24,\"column\":22}},\"24\":{\"start\":{\"line\":25,\"column\":0},\"end\":{\"line\":25,\"column\":57}},\"25\":{\"start\":{\"line\":26,\"column\":0},\"end\":{\"line\":26,\"column\":19}},\"26\":{\"start\":{\"line\":27,\"column\":0},\"end\":{\"line\":27,\"column\":0}},\"27\":{\"start\":{\"line\":28,\"column\":0},\"end\":{\"line\":28,\"column\":7}},\"28\":{\"start\":{\"line\":29,\"column\":0},\"end\":{\"line\":29,\"column\":26}},\"29\":{\"start\":{\"line\":30,\"column\":0},\"end\":{\"line\":30,\"column\":39}},\"30\":{\"start\":{\"line\":31,\"column\":0},\"end\":{\"line\":31,\"column\":23}},\"31\":{\"start\":{\"line\":32,\"column\":0},\"end\":{\"line\":32,\"column\":0}},\"32\":{\"start\":{\"line\":33,\"column\":0},\"end\":{\"line\":33,\"column\":7}},\"33\":{\"start\":{\"line\":34,\"column\":0},\"end\":{\"line\":34,\"column\":23}},\"34\":{\"start\":{\"line\":35,\"column\":0},\"end\":{\"line\":35,\"column\":48}},\"35\":{\"start\":{\"line\":36,\"column\":0},\"end\":{\"line\":36,\"column\":45}},\"36\":{\"start\":{\"line\":37,\"column\":0},\"end\":{\"line\":37,\"column\":43}},\"37\":{\"start\":{\"line\":38,\"column\":0},\"end\":{\"line\":38,\"column\":0}},\"38\":{\"start\":{\"line\":39,\"column\":0},\"end\":{\"line\":39,\"column\":16}}},\"s\":{\"0\":0,\"1\":0,\"2\":0,\"3\":0,\"4\":0,\"5\":0,\"6\":0,\"7\":0,\"8\":0,\"9\":0,\"10\":0,\"11\":0,\"12\":0,\"13\":0,\"14\":0,\"15\":0,\"16\":0,\"17\":0,\"18\":0,\"19\":0,\"20\":0,\"21\":0,\"22\":0,\"23\":0,\"24\":0,\"25\":0,\"26\":0,\"27\":0,\"28\":0,\"29\":0,\"30\":0,\"31\":0,\"32\":0,\"33\":0,\"34\":0,\"35\":0,\"36\":0,\"37\":0,\"38\":0},\"branchMap\":{\"0\":{\"type\":\"branch\",\"line\":1,\"loc\":{\"start\":{\"line\":1,\"column\":0},\"end\":{\"line\":39,\"column\":-423}},\"locations\":[{\"start\":{\"line\":1,\"column\":0},\"end\":{\"line\":39,\"column\":-423}}]}},\"b\":{\"0\":[0]},\"fnMap\":{\"0\":{\"name\":\"(empty-report)\",\"decl\":{\"start\":{\"line\":1,\"column\":0},\"end\":{\"line\":39,\"column\":-423}},\"loc\":{\"start\":{\"line\":1,\"column\":0},\"end\":{\"line\":39,\"column\":-423}},\"line\":1}},\"f\":{\"0\":0}}\n,\"/Users/dex/go/src/github.com/dexhorthy/repomirror/src/commands/init.ts\": {\"path\":\"/Users/dex/go/src/github.com/dexhorthy/repomirror/src/commands/init.ts\",\"all\":true,\"statementMap\":{\"0\":{\"start\":{\"line\":1,\"column\":0},\"end\":{\"line\":1,\"column\":36}},\"1\":{\"start\":{\"line\":2,\"column\":0},\"end\":{\"line\":2,\"column\":38}},\"2\":{\"start\":{\"line\":3,\"column\":0},\"end\":{\"line\":3,\"column\":32}},\"3\":{\"start\":{\"line\":4,\"column\":0},\"end\":{\"line\":4,\"column\":26}},\"4\":{\"start\":{\"line\":5,\"column\":0},\"end\":{\"line\":5,\"column\":22}},\"5\":{\"start\":{\"line\":6,\"column\":0},\"end\":{\"line\":6,\"column\":50}},\"6\":{\"start\":{\"line\":7,\"column\":0},\"end\":{\"line\":7,\"column\":30}},\"7\":{\"start\":{\"line\":8,\"column\":0},\"end\":{\"line\":8,\"column\":0}},\"8\":{\"start\":{\"line\":9,\"column\":0},\"end\":{\"line\":9,\"column\":23}},\"9\":{\"start\":{\"line\":10,\"column\":0},\"end\":{\"line\":10,\"column\":21}},\"10\":{\"start\":{\"line\":11,\"column\":0},\"end\":{\"line\":11,\"column\":21}},\"11\":{\"start\":{\"line\":12,\"column\":0},\"end\":{\"line\":12,\"column\":37}},\"12\":{\"start\":{\"line\":13,\"column\":0},\"end\":{\"line\":13,\"column\":1}},\"13\":{\"start\":{\"line\":14,\"column\":0},\"end\":{\"line\":14,\"column\":0}},\"14\":{\"start\":{\"line\":15,\"column\":0},\"end\":{\"line\":15,\"column\":45}},\"15\":{\"start\":{\"line\":16,\"column\":0},\"end\":{\"line\":16,\"column\":14}},\"16\":{\"start\":{\"line\":17,\"column\":0},\"end\":{\"line\":17,\"column\":76}},\"17\":{\"start\":{\"line\":18,\"column\":0},\"end\":{\"line\":18,\"column\":4}},\"18\":{\"start\":{\"line\":19,\"column\":0},\"end\":{\"line\":19,\"column\":0}},\"19\":{\"start\":{\"line\":20,\"column\":0},\"end\":{\"line\":20,\"column\":50}},\"20\":{\"start\":{\"line\":21,\"column\":0},\"end\":{\"line\":21,\"column\":35}},\"21\":{\"start\":{\"line\":22,\"column\":0},\"end\":{\"line\":22,\"column\":40}},\"22\":{\"start\":{\"line\":23,\"column\":0},\"end\":{\"line\":23,\"column\":53}},\"23\":{\"start\":{\"line\":24,\"column\":0},\"end\":{\"line\":24,\"column\":0}},\"24\":{\"start\":{\"line\":25,\"column\":0},\"end\":{\"line\":25,\"column\":54}},\"25\":{\"start\":{\"line\":26,\"column\":0},\"end\":{\"line\":26,\"column\":5}},\"26\":{\"start\":{\"line\":27,\"column\":0},\"end\":{\"line\":27,\"column\":20}},\"27\":{\"start\":{\"line\":28,\"column\":0},\"end\":{\"line\":28,\"column\":25}},\"28\":{\"start\":{\"line\":29,\"column\":0},\"end\":{\"line\":29,\"column\":52}},\"29\":{\"start\":{\"line\":30,\"column\":0},\"end\":{\"line\":30,\"column\":20}},\"30\":{\"start\":{\"line\":31,\"column\":0},\"end\":{\"line\":31,\"column\":6}},\"31\":{\"start\":{\"line\":32,\"column\":0},\"end\":{\"line\":32,\"column\":5}},\"32\":{\"start\":{\"line\":33,\"column\":0},\"end\":{\"line\":33,\"column\":20}},\"33\":{\"start\":{\"line\":34,\"column\":0},\"end\":{\"line\":34,\"column\":25}},\"34\":{\"start\":{\"line\":35,\"column\":0},\"end\":{\"line\":35,\"column\":57}},\"35\":{\"start\":{\"line\":36,\"column\":0},\"end\":{\"line\":36,\"column\":29}},\"36\":{\"start\":{\"line\":37,\"column\":0},\"end\":{\"line\":37,\"column\":6}},\"37\":{\"start\":{\"line\":38,\"column\":0},\"end\":{\"line\":38,\"column\":5}},\"38\":{\"start\":{\"line\":39,\"column\":0},\"end\":{\"line\":39,\"column\":20}},\"39\":{\"start\":{\"line\":40,\"column\":0},\"end\":{\"line\":40,\"column\":41}},\"40\":{\"start\":{\"line\":41,\"column\":0},\"end\":{\"line\":41,\"column\":51}},\"41\":{\"start\":{\"line\":42,\"column\":0},\"end\":{\"line\":42,\"column\":58}},\"42\":{\"start\":{\"line\":43,\"column\":0},\"end\":{\"line\":43,\"column\":6}},\"43\":{\"start\":{\"line\":44,\"column\":0},\"end\":{\"line\":44,\"column\":5}},\"44\":{\"start\":{\"line\":45,\"column\":0},\"end\":{\"line\":45,\"column\":0}},\"45\":{\"start\":{\"line\":46,\"column\":0},\"end\":{\"line\":46,\"column\":29}},\"46\":{\"start\":{\"line\":47,\"column\":0},\"end\":{\"line\":47,\"column\":51}},\"47\":{\"start\":{\"line\":48,\"column\":0},\"end\":{\"line\":48,\"column\":0}},\"48\":{\"start\":{\"line\":49,\"column\":0},\"end\":{\"line\":49,\"column\":52}},\"49\":{\"start\":{\"line\":50,\"column\":0},\"end\":{\"line\":50,\"column\":69}},\"50\":{\"start\":{\"line\":51,\"column\":0},\"end\":{\"line\":51,\"column\":0}},\"51\":{\"start\":{\"line\":52,\"column\":0},\"end\":{\"line\":52,\"column\":7}},\"52\":{\"start\":{\"line\":53,\"column\":0},\"end\":{\"line\":53,\"column\":63}},\"53\":{\"start\":{\"line\":54,\"column\":0},\"end\":{\"line\":54,\"column\":25}},\"54\":{\"start\":{\"line\":55,\"column\":0},\"end\":{\"line\":55,\"column\":25}},\"55\":{\"start\":{\"line\":56,\"column\":0},\"end\":{\"line\":56,\"column\":41}},\"56\":{\"start\":{\"line\":57,\"column\":0},\"end\":{\"line\":57,\"column\":6}},\"57\":{\"start\":{\"line\":58,\"column\":0},\"end\":{\"line\":58,\"column\":0}},\"58\":{\"start\":{\"line\":59,\"column\":0},\"end\":{\"line\":59,\"column\":55}},\"59\":{\"start\":{\"line\":60,\"column\":0},\"end\":{\"line\":60,\"column\":0}},\"60\":{\"start\":{\"line\":61,\"column\":0},\"end\":{\"line\":61,\"column\":45}},\"61\":{\"start\":{\"line\":62,\"column\":0},\"end\":{\"line\":62,\"column\":32}},\"62\":{\"start\":{\"line\":63,\"column\":0},\"end\":{\"line\":63,\"column\":25}},\"63\":{\"start\":{\"line\":64,\"column\":0},\"end\":{\"line\":64,\"column\":25}},\"64\":{\"start\":{\"line\":65,\"column\":0},\"end\":{\"line\":65,\"column\":22}},\"65\":{\"start\":{\"line\":66,\"column\":0},\"end\":{\"line\":66,\"column\":6}},\"66\":{\"start\":{\"line\":67,\"column\":0},\"end\":{\"line\":67,\"column\":0}},\"67\":{\"start\":{\"line\":68,\"column\":0},\"end\":{\"line\":68,\"column\":73}},\"68\":{\"start\":{\"line\":69,\"column\":0},\"end\":{\"line\":69,\"column\":45}},\"69\":{\"start\":{\"line\":70,\"column\":0},\"end\":{\"line\":70,\"column\":16}},\"70\":{\"start\":{\"line\":71,\"column\":0},\"end\":{\"line\":71,\"column\":18}},\"71\":{\"start\":{\"line\":72,\"column\":0},\"end\":{\"line\":72,\"column\":78}},\"72\":{\"start\":{\"line\":73,\"column\":0},\"end\":{\"line\":73,\"column\":8}},\"73\":{\"start\":{\"line\":74,\"column\":0},\"end\":{\"line\":74,\"column\":6}},\"74\":{\"start\":{\"line\":75,\"column\":0},\"end\":{\"line\":75,\"column\":16}},\"75\":{\"start\":{\"line\":76,\"column\":0},\"end\":{\"line\":76,\"column\":18}},\"76\":{\"start\":{\"line\":77,\"column\":0},\"end\":{\"line\":77,\"column\":128}},\"77\":{\"start\":{\"line\":78,\"column\":0},\"end\":{\"line\":78,\"column\":8}},\"78\":{\"start\":{\"line\":79,\"column\":0},\"end\":{\"line\":79,\"column\":6}},\"79\":{\"start\":{\"line\":80,\"column\":0},\"end\":{\"line\":80,\"column\":19}},\"80\":{\"start\":{\"line\":81,\"column\":0},\"end\":{\"line\":81,\"column\":61}},\"81\":{\"start\":{\"line\":82,\"column\":0},\"end\":{\"line\":82,\"column\":18}},\"82\":{\"start\":{\"line\":83,\"column\":0},\"end\":{\"line\":83,\"column\":16}},\"83\":{\"start\":{\"line\":84,\"column\":0},\"end\":{\"line\":84,\"column\":75}},\"84\":{\"start\":{\"line\":85,\"column\":0},\"end\":{\"line\":85,\"column\":8}},\"85\":{\"start\":{\"line\":86,\"column\":0},\"end\":{\"line\":86,\"column\":6}},\"86\":{\"start\":{\"line\":87,\"column\":0},\"end\":{\"line\":87,\"column\":20}},\"87\":{\"start\":{\"line\":88,\"column\":0},\"end\":{\"line\":88,\"column\":3}},\"88\":{\"start\":{\"line\":89,\"column\":0},\"end\":{\"line\":89,\"column\":1}},\"89\":{\"start\":{\"line\":90,\"column\":0},\"end\":{\"line\":90,\"column\":0}},\"90\":{\"start\":{\"line\":91,\"column\":0},\"end\":{\"line\":91,\"column\":74}},\"91\":{\"start\":{\"line\":92,\"column\":0},\"end\":{\"line\":92,\"column\":64}},\"92\":{\"start\":{\"line\":93,\"column\":0},\"end\":{\"line\":93,\"column\":0}},\"93\":{\"start\":{\"line\":94,\"column\":0},\"end\":{\"line\":94,\"column\":7}},\"94\":{\"start\":{\"line\":95,\"column\":0},\"end\":{\"line\":95,\"column\":39}},\"95\":{\"start\":{\"line\":96,\"column\":0},\"end\":{\"line\":96,\"column\":9}},\"96\":{\"start\":{\"line\":97,\"column\":0},\"end\":{\"line\":97,\"column\":34}},\"97\":{\"start\":{\"line\":98,\"column\":0},\"end\":{\"line\":98,\"column\":13}},\"98\":{\"start\":{\"line\":99,\"column\":0},\"end\":{\"line\":99,\"column\":68}},\"99\":{\"start\":{\"line\":100,\"column\":0},\"end\":{\"line\":100,\"column\":22}},\"100\":{\"start\":{\"line\":101,\"column\":0},\"end\":{\"line\":101,\"column\":5}},\"101\":{\"start\":{\"line\":102,\"column\":0},\"end\":{\"line\":102,\"column\":0}},\"102\":{\"start\":{\"line\":103,\"column\":0},\"end\":{\"line\":103,\"column\":46}},\"103\":{\"start\":{\"line\":104,\"column\":0},\"end\":{\"line\":104,\"column\":9}},\"104\":{\"start\":{\"line\":105,\"column\":0},\"end\":{\"line\":105,\"column\":74}},\"105\":{\"start\":{\"line\":106,\"column\":0},\"end\":{\"line\":106,\"column\":13}},\"106\":{\"start\":{\"line\":107,\"column\":0},\"end\":{\"line\":107,\"column\":77}},\"107\":{\"start\":{\"line\":108,\"column\":0},\"end\":{\"line\":108,\"column\":22}},\"108\":{\"start\":{\"line\":109,\"column\":0},\"end\":{\"line\":109,\"column\":5}},\"109\":{\"start\":{\"line\":110,\"column\":0},\"end\":{\"line\":110,\"column\":0}},\"110\":{\"start\":{\"line\":111,\"column\":0},\"end\":{\"line\":111,\"column\":58}},\"111\":{\"start\":{\"line\":112,\"column\":0},\"end\":{\"line\":112,\"column\":9}},\"112\":{\"start\":{\"line\":113,\"column\":0},\"end\":{\"line\":113,\"column\":63}},\"113\":{\"start\":{\"line\":114,\"column\":0},\"end\":{\"line\":114,\"column\":24}},\"114\":{\"start\":{\"line\":115,\"column\":0},\"end\":{\"line\":115,\"column\":9}},\"115\":{\"start\":{\"line\":116,\"column\":0},\"end\":{\"line\":116,\"column\":27}},\"116\":{\"start\":{\"line\":117,\"column\":0},\"end\":{\"line\":117,\"column\":21}},\"117\":{\"start\":{\"line\":118,\"column\":0},\"end\":{\"line\":118,\"column\":73}},\"118\":{\"start\":{\"line\":119,\"column\":0},\"end\":{\"line\":119,\"column\":10}},\"119\":{\"start\":{\"line\":120,\"column\":0},\"end\":{\"line\":120,\"column\":24}},\"120\":{\"start\":{\"line\":121,\"column\":0},\"end\":{\"line\":121,\"column\":7}},\"121\":{\"start\":{\"line\":122,\"column\":0},\"end\":{\"line\":122,\"column\":13}},\"122\":{\"start\":{\"line\":123,\"column\":0},\"end\":{\"line\":123,\"column\":67}},\"123\":{\"start\":{\"line\":124,\"column\":0},\"end\":{\"line\":124,\"column\":22}},\"124\":{\"start\":{\"line\":125,\"column\":0},\"end\":{\"line\":125,\"column\":5}},\"125\":{\"start\":{\"line\":126,\"column\":0},\"end\":{\"line\":126,\"column\":0}},\"126\":{\"start\":{\"line\":127,\"column\":0},\"end\":{\"line\":127,\"column\":41}},\"127\":{\"start\":{\"line\":128,\"column\":0},\"end\":{\"line\":128,\"column\":9}},\"128\":{\"start\":{\"line\":129,\"column\":0},\"end\":{\"line\":129,\"column\":65}},\"129\":{\"start\":{\"line\":130,\"column\":0},\"end\":{\"line\":130,\"column\":49}},\"130\":{\"start\":{\"line\":131,\"column\":0},\"end\":{\"line\":131,\"column\":80}},\"131\":{\"start\":{\"line\":132,\"column\":0},\"end\":{\"line\":132,\"column\":24}},\"132\":{\"start\":{\"line\":133,\"column\":0},\"end\":{\"line\":133,\"column\":7}},\"133\":{\"start\":{\"line\":134,\"column\":0},\"end\":{\"line\":134,\"column\":13}},\"134\":{\"start\":{\"line\":135,\"column\":0},\"end\":{\"line\":135,\"column\":19}},\"135\":{\"start\":{\"line\":136,\"column\":0},\"end\":{\"line\":136,\"column\":93}},\"136\":{\"start\":{\"line\":137,\"column\":0},\"end\":{\"line\":137,\"column\":8}},\"137\":{\"start\":{\"line\":138,\"column\":0},\"end\":{\"line\":138,\"column\":22}},\"138\":{\"start\":{\"line\":139,\"column\":0},\"end\":{\"line\":139,\"column\":5}},\"139\":{\"start\":{\"line\":140,\"column\":0},\"end\":{\"line\":140,\"column\":0}},\"140\":{\"start\":{\"line\":141,\"column\":0},\"end\":{\"line\":141,\"column\":51}},\"141\":{\"start\":{\"line\":142,\"column\":0},\"end\":{\"line\":142,\"column\":19}},\"142\":{\"start\":{\"line\":143,\"column\":0},\"end\":{\"line\":143,\"column\":17}},\"143\":{\"start\":{\"line\":144,\"column\":0},\"end\":{\"line\":144,\"column\":90}},\"144\":{\"start\":{\"line\":145,\"column\":0},\"end\":{\"line\":145,\"column\":6}},\"145\":{\"start\":{\"line\":146,\"column\":0},\"end\":{\"line\":146,\"column\":20}},\"146\":{\"start\":{\"line\":147,\"column\":0},\"end\":{\"line\":147,\"column\":3}},\"147\":{\"start\":{\"line\":148,\"column\":0},\"end\":{\"line\":148,\"column\":1}},\"148\":{\"start\":{\"line\":149,\"column\":0},\"end\":{\"line\":149,\"column\":0}},\"149\":{\"start\":{\"line\":150,\"column\":0},\"end\":{\"line\":150,\"column\":44}},\"150\":{\"start\":{\"line\":151,\"column\":0},\"end\":{\"line\":151,\"column\":21}},\"151\":{\"start\":{\"line\":152,\"column\":0},\"end\":{\"line\":152,\"column\":21}},\"152\":{\"start\":{\"line\":153,\"column\":0},\"end\":{\"line\":153,\"column\":37}},\"153\":{\"start\":{\"line\":154,\"column\":0},\"end\":{\"line\":154,\"column\":20}},\"154\":{\"start\":{\"line\":155,\"column\":0},\"end\":{\"line\":155,\"column\":149}},\"155\":{\"start\":{\"line\":156,\"column\":0},\"end\":{\"line\":156,\"column\":0}},\"156\":{\"start\":{\"line\":157,\"column\":0},\"end\":{\"line\":157,\"column\":11}},\"157\":{\"start\":{\"line\":158,\"column\":0},\"end\":{\"line\":158,\"column\":110}},\"158\":{\"start\":{\"line\":159,\"column\":0},\"end\":{\"line\":159,\"column\":0}},\"159\":{\"start\":{\"line\":160,\"column\":0},\"end\":{\"line\":160,\"column\":97}},\"160\":{\"start\":{\"line\":161,\"column\":0},\"end\":{\"line\":161,\"column\":0}},\"161\":{\"start\":{\"line\":162,\"column\":0},\"end\":{\"line\":162,\"column\":65}},\"162\":{\"start\":{\"line\":163,\"column\":0},\"end\":{\"line\":163,\"column\":0}},\"163\":{\"start\":{\"line\":164,\"column\":0},\"end\":{\"line\":164,\"column\":113}},\"164\":{\"start\":{\"line\":165,\"column\":0},\"end\":{\"line\":165,\"column\":0}},\"165\":{\"start\":{\"line\":166,\"column\":0},\"end\":{\"line\":166,\"column\":325}},\"166\":{\"start\":{\"line\":167,\"column\":0},\"end\":{\"line\":167,\"column\":12}},\"167\":{\"start\":{\"line\":168,\"column\":0},\"end\":{\"line\":168,\"column\":0}},\"168\":{\"start\":{\"line\":169,\"column\":0},\"end\":{\"line\":169,\"column\":11}},\"169\":{\"start\":{\"line\":170,\"column\":0},\"end\":{\"line\":170,\"column\":109}},\"170\":{\"start\":{\"line\":171,\"column\":0},\"end\":{\"line\":171,\"column\":0}},\"171\":{\"start\":{\"line\":172,\"column\":0},\"end\":{\"line\":172,\"column\":104}},\"172\":{\"start\":{\"line\":173,\"column\":0},\"end\":{\"line\":173,\"column\":0}},\"173\":{\"start\":{\"line\":174,\"column\":0},\"end\":{\"line\":174,\"column\":65}},\"174\":{\"start\":{\"line\":175,\"column\":0},\"end\":{\"line\":175,\"column\":0}},\"175\":{\"start\":{\"line\":176,\"column\":0},\"end\":{\"line\":176,\"column\":113}},\"176\":{\"start\":{\"line\":177,\"column\":0},\"end\":{\"line\":177,\"column\":0}},\"177\":{\"start\":{\"line\":178,\"column\":0},\"end\":{\"line\":178,\"column\":325}},\"178\":{\"start\":{\"line\":179,\"column\":0},\"end\":{\"line\":179,\"column\":12}},\"179\":{\"start\":{\"line\":180,\"column\":0},\"end\":{\"line\":180,\"column\":0}},\"180\":{\"start\":{\"line\":181,\"column\":0},\"end\":{\"line\":181,\"column\":46}},\"181\":{\"start\":{\"line\":182,\"column\":0},\"end\":{\"line\":182,\"column\":0}},\"182\":{\"start\":{\"line\":183,\"column\":0},\"end\":{\"line\":183,\"column\":19}},\"183\":{\"start\":{\"line\":184,\"column\":0},\"end\":{\"line\":184,\"column\":29}},\"184\":{\"start\":{\"line\":185,\"column\":0},\"end\":{\"line\":185,\"column\":20}},\"185\":{\"start\":{\"line\":186,\"column\":0},\"end\":{\"line\":186,\"column\":0}},\"186\":{\"start\":{\"line\":187,\"column\":0},\"end\":{\"line\":187,\"column\":9}},\"187\":{\"start\":{\"line\":188,\"column\":0},\"end\":{\"line\":188,\"column\":0}},\"188\":{\"start\":{\"line\":189,\"column\":0},\"end\":{\"line\":189,\"column\":138}},\"189\":{\"start\":{\"line\":190,\"column\":0},\"end\":{\"line\":190,\"column\":0}},\"190\":{\"start\":{\"line\":191,\"column\":0},\"end\":{\"line\":191,\"column\":316}},\"191\":{\"start\":{\"line\":192,\"column\":0},\"end\":{\"line\":192,\"column\":0}},\"192\":{\"start\":{\"line\":193,\"column\":0},\"end\":{\"line\":193,\"column\":18}},\"193\":{\"start\":{\"line\":194,\"column\":0},\"end\":{\"line\":194,\"column\":37}},\"194\":{\"start\":{\"line\":195,\"column\":0},\"end\":{\"line\":195,\"column\":23}},\"195\":{\"start\":{\"line\":196,\"column\":0},\"end\":{\"line\":196,\"column\":7}},\"196\":{\"start\":{\"line\":197,\"column\":0},\"end\":{\"line\":197,\"column\":57}},\"197\":{\"start\":{\"line\":198,\"column\":0},\"end\":{\"line\":198,\"column\":45}},\"198\":{\"start\":{\"line\":199,\"column\":0},\"end\":{\"line\":199,\"column\":12}},\"199\":{\"start\":{\"line\":200,\"column\":0},\"end\":{\"line\":200,\"column\":5}},\"200\":{\"start\":{\"line\":201,\"column\":0},\"end\":{\"line\":201,\"column\":3}},\"201\":{\"start\":{\"line\":202,\"column\":0},\"end\":{\"line\":202,\"column\":0}},\"202\":{\"start\":{\"line\":203,\"column\":0},\"end\":{\"line\":203,\"column\":16}},\"203\":{\"start\":{\"line\":204,\"column\":0},\"end\":{\"line\":204,\"column\":64}},\"204\":{\"start\":{\"line\":205,\"column\":0},\"end\":{\"line\":205,\"column\":3}},\"205\":{\"start\":{\"line\":206,\"column\":0},\"end\":{\"line\":206,\"column\":0}},\"206\":{\"start\":{\"line\":207,\"column\":0},\"end\":{\"line\":207,\"column\":43}},\"207\":{\"start\":{\"line\":208,\"column\":0},\"end\":{\"line\":208,\"column\":15}},\"208\":{\"start\":{\"line\":209,\"column\":0},\"end\":{\"line\":209,\"column\":44}},\"209\":{\"start\":{\"line\":210,\"column\":0},\"end\":{\"line\":210,\"column\":44}},\"210\":{\"start\":{\"line\":211,\"column\":0},\"end\":{\"line\":211,\"column\":45}},\"211\":{\"start\":{\"line\":212,\"column\":0},\"end\":{\"line\":212,\"column\":1}},\"212\":{\"start\":{\"line\":213,\"column\":0},\"end\":{\"line\":213,\"column\":0}},\"213\":{\"start\":{\"line\":214,\"column\":0},\"end\":{\"line\":214,\"column\":37}},\"214\":{\"start\":{\"line\":215,\"column\":0},\"end\":{\"line\":215,\"column\":21}},\"215\":{\"start\":{\"line\":216,\"column\":0},\"end\":{\"line\":216,\"column\":21}},\"216\":{\"start\":{\"line\":217,\"column\":0},\"end\":{\"line\":217,\"column\":26}},\"217\":{\"start\":{\"line\":218,\"column\":0},\"end\":{\"line\":218,\"column\":18}},\"218\":{\"start\":{\"line\":219,\"column\":0},\"end\":{\"line\":219,\"column\":59}},\"219\":{\"start\":{\"line\":220,\"column\":0},\"end\":{\"line\":220,\"column\":0}},\"220\":{\"start\":{\"line\":221,\"column\":0},\"end\":{\"line\":221,\"column\":33}},\"221\":{\"start\":{\"line\":222,\"column\":0},\"end\":{\"line\":222,\"column\":53}},\"222\":{\"start\":{\"line\":223,\"column\":0},\"end\":{\"line\":223,\"column\":0}},\"223\":{\"start\":{\"line\":224,\"column\":0},\"end\":{\"line\":224,\"column\":21}},\"224\":{\"start\":{\"line\":225,\"column\":0},\"end\":{\"line\":225,\"column\":72}},\"225\":{\"start\":{\"line\":226,\"column\":0},\"end\":{\"line\":226,\"column\":0}},\"226\":{\"start\":{\"line\":227,\"column\":0},\"end\":{\"line\":227,\"column\":19}},\"227\":{\"start\":{\"line\":228,\"column\":0},\"end\":{\"line\":228,\"column\":33}},\"228\":{\"start\":{\"line\":229,\"column\":0},\"end\":{\"line\":229,\"column\":30}},\"229\":{\"start\":{\"line\":230,\"column\":0},\"end\":{\"line\":230,\"column\":115}},\"230\":{\"start\":{\"line\":231,\"column\":0},\"end\":{\"line\":231,\"column\":51}},\"231\":{\"start\":{\"line\":232,\"column\":0},\"end\":{\"line\":232,\"column\":43}},\"232\":{\"start\":{\"line\":233,\"column\":0},\"end\":{\"line\":233,\"column\":0}},\"233\":{\"start\":{\"line\":234,\"column\":0},\"end\":{\"line\":234,\"column\":66}},\"234\":{\"start\":{\"line\":235,\"column\":0},\"end\":{\"line\":235,\"column\":16}},\"235\":{\"start\":{\"line\":236,\"column\":0},\"end\":{\"line\":236,\"column\":5}},\"236\":{\"start\":{\"line\":237,\"column\":0},\"end\":{\"line\":237,\"column\":0}},\"237\":{\"start\":{\"line\":238,\"column\":0},\"end\":{\"line\":238,\"column\":20}},\"238\":{\"start\":{\"line\":239,\"column\":0},\"end\":{\"line\":239,\"column\":34}},\"239\":{\"start\":{\"line\":240,\"column\":0},\"end\":{\"line\":240,\"column\":11}},\"240\":{\"start\":{\"line\":241,\"column\":0},\"end\":{\"line\":241,\"column\":23}},\"241\":{\"start\":{\"line\":242,\"column\":0},\"end\":{\"line\":242,\"column\":57}},\"242\":{\"start\":{\"line\":243,\"column\":0},\"end\":{\"line\":243,\"column\":11}},\"243\":{\"start\":{\"line\":244,\"column\":0},\"end\":{\"line\":244,\"column\":6}},\"244\":{\"start\":{\"line\":245,\"column\":0},\"end\":{\"line\":245,\"column\":0}},\"245\":{\"start\":{\"line\":246,\"column\":0},\"end\":{\"line\":246,\"column\":68}},\"246\":{\"start\":{\"line\":247,\"column\":0},\"end\":{\"line\":247,\"column\":16}},\"247\":{\"start\":{\"line\":248,\"column\":0},\"end\":{\"line\":248,\"column\":5}},\"248\":{\"start\":{\"line\":249,\"column\":0},\"end\":{\"line\":249,\"column\":0}},\"249\":{\"start\":{\"line\":250,\"column\":0},\"end\":{\"line\":250,\"column\":22}},\"250\":{\"start\":{\"line\":251,\"column\":0},\"end\":{\"line\":251,\"column\":21}},\"251\":{\"start\":{\"line\":252,\"column\":0},\"end\":{\"line\":252,\"column\":38}},\"252\":{\"start\":{\"line\":253,\"column\":0},\"end\":{\"line\":253,\"column\":28}},\"253\":{\"start\":{\"line\":254,\"column\":0},\"end\":{\"line\":254,\"column\":4}},\"254\":{\"start\":{\"line\":255,\"column\":0},\"end\":{\"line\":255,\"column\":1}}},\"s\":{\"0\":0,\"1\":0,\"2\":0,\"3\":0,\"4\":0,\"5\":0,\"6\":0,\"7\":0,\"8\":0,\"9\":0,\"10\":0,\"11\":0,\"12\":0,\"13\":0,\"14\":0,\"15\":0,\"16\":0,\"17\":0,\"18\":0,\"19\":0,\"20\":0,\"21\":0,\"22\":0,\"23\":0,\"24\":0,\"25\":0,\"26\":0,\"27\":0,\"28\":0,\"29\":0,\"30\":0,\"31\":0,\"32\":0,\"33\":0,\"34\":0,\"35\":0,\"36\":0,\"37\":0,\"38\":0,\"39\":0,\"40\":0,\"41\":0,\"42\":0,\"43\":0,\"44\":0,\"45\":0,\"46\":0,\"47\":0,\"48\":0,\"49\":0,\"50\":0,\"51\":0,\"52\":0,\"53\":0,\"54\":0,\"55\":0,\"56\":0,\"57\":0,\"58\":0,\"59\":0,\"60\":0,\"61\":0,\"62\":0,\"63\":0,\"64\":0,\"65\":0,\"66\":0,\"67\":0,\"68\":0,\"69\":0,\"70\":0,\"71\":0,\"72\":0,\"73\":0,\"74\":0,\"75\":0,\"76\":0,\"77\":0,\"78\":0,\"79\":0,\"80\":0,\"81\":0,\"82\":0,\"83\":0,\"84\":0,\"85\":0,\"86\":0,\"87\":0,\"88\":0,\"89\":0,\"90\":0,\"91\":0,\"92\":0,\"93\":0,\"94\":0,\"95\":0,\"96\":0,\"97\":0,\"98\":0,\"99\":0,\"100\":0,\"101\":0,\"102\":0,\"103\":0,\"104\":0,\"105\":0,\"106\":0,\"107\":0,\"108\":0,\"109\":0,\"110\":0,\"111\":0,\"112\":0,\"113\":0,\"114\":0,\"115\":0,\"116\":0,\"117\":0,\"118\":0,\"119\":0,\"120\":0,\"121\":0,\"122\":0,\"123\":0,\"124\":0,\"125\":0,\"126\":0,\"127\":0,\"128\":0,\"129\":0,\"130\":0,\"131\":0,\"132\":0,\"133\":0,\"134\":0,\"135\":0,\"136\":0,\"137\":0,\"138\":0,\"139\":0,\"140\":0,\"141\":0,\"142\":0,\"143\":0,\"144\":0,\"145\":0,\"146\":0,\"147\":0,\"148\":0,\"149\":0,\"150\":0,\"151\":0,\"152\":0,\"153\":0,\"154\":0,\"155\":0,\"156\":0,\"157\":0,\"158\":0,\"159\":0,\"160\":0,\"161\":0,\"162\":0,\"163\":0,\"164\":0,\"165\":0,\"166\":0,\"167\":0,\"168\":0,\"169\":0,\"170\":0,\"171\":0,\"172\":0,\"173\":0,\"174\":0,\"175\":0,\"176\":0,\"177\":0,\"178\":0,\"179\":0,\"180\":0,\"181\":0,\"182\":0,\"183\":0,\"184\":0,\"185\":0,\"186\":0,\"187\":0,\"188\":0,\"189\":0,\"190\":0,\"191\":0,\"192\":0,\"193\":0,\"194\":0,\"195\":0,\"196\":0,\"197\":0,\"198\":0,\"199\":0,\"200\":0,\"201\":0,\"202\":0,\"203\":0,\"204\":0,\"205\":0,\"206\":0,\"207\":0,\"208\":0,\"209\":0,\"210\":0,\"211\":0,\"212\":0,\"213\":0,\"214\":0,\"215\":0,\"216\":0,\"217\":0,\"218\":0,\"219\":0,\"220\":0,\"221\":0,\"222\":0,\"223\":0,\"224\":0,\"225\":0,\"226\":0,\"227\":0,\"228\":0,\"229\":0,\"230\":0,\"231\":0,\"232\":0,\"233\":0,\"234\":0,\"235\":0,\"236\":0,\"237\":0,\"238\":0,\"239\":0,\"240\":0,\"241\":0,\"242\":0,\"243\":0,\"244\":0,\"245\":0,\"246\":0,\"247\":0,\"248\":0,\"249\":0,\"250\":0,\"251\":0,\"252\":0,\"253\":0,\"254\":0},\"branchMap\":{\"0\":{\"type\":\"branch\",\"line\":1,\"loc\":{\"start\":{\"line\":1,\"column\":0},\"end\":{\"line\":255,\"column\":-326}},\"locations\":[{\"start\":{\"line\":1,\"column\":0},\"end\":{\"line\":255,\"column\":-326}}]}},\"b\":{\"0\":[0]},\"fnMap\":{\"0\":{\"name\":\"(empty-report)\",\"decl\":{\"start\":{\"line\":1,\"column\":0},\"end\":{\"line\":255,\"column\":-326}},\"loc\":{\"start\":{\"line\":1,\"column\":0},\"end\":{\"line\":255,\"column\":-326}},\"line\":1}},\"f\":{\"0\":0}}\n,\"/Users/dex/go/src/github.com/dexhorthy/repomirror/src/commands/sync-forever.ts\": {\"path\":\"/Users/dex/go/src/github.com/dexhorthy/repomirror/src/commands/sync-forever.ts\",\"all\":true,\"statementMap\":{\"0\":{\"start\":{\"line\":1,\"column\":0},\"end\":{\"line\":1,\"column\":30}},\"1\":{\"start\":{\"line\":2,\"column\":0},\"end\":{\"line\":2,\"column\":26}},\"2\":{\"start\":{\"line\":3,\"column\":0},\"end\":{\"line\":3,\"column\":28}},\"3\":{\"start\":{\"line\":4,\"column\":0},\"end\":{\"line\":4,\"column\":36}},\"4\":{\"start\":{\"line\":5,\"column\":0},\"end\":{\"line\":5,\"column\":0}},\"5\":{\"start\":{\"line\":6,\"column\":0},\"end\":{\"line\":6,\"column\":52}},\"6\":{\"start\":{\"line\":7,\"column\":0},\"end\":{\"line\":7,\"column\":69}},\"7\":{\"start\":{\"line\":8,\"column\":0},\"end\":{\"line\":8,\"column\":0}},\"8\":{\"start\":{\"line\":9,\"column\":0},\"end\":{\"line\":9,\"column\":7}},\"9\":{\"start\":{\"line\":10,\"column\":0},\"end\":{\"line\":10,\"column\":31}},\"10\":{\"start\":{\"line\":11,\"column\":0},\"end\":{\"line\":11,\"column\":33}},\"11\":{\"start\":{\"line\":12,\"column\":0},\"end\":{\"line\":12,\"column\":11}},\"12\":{\"start\":{\"line\":13,\"column\":0},\"end\":{\"line\":13,\"column\":18}},\"13\":{\"start\":{\"line\":14,\"column\":0},\"end\":{\"line\":14,\"column\":16}},\"14\":{\"start\":{\"line\":15,\"column\":0},\"end\":{\"line\":15,\"column\":82}},\"15\":{\"start\":{\"line\":16,\"column\":0},\"end\":{\"line\":16,\"column\":8}},\"16\":{\"start\":{\"line\":17,\"column\":0},\"end\":{\"line\":17,\"column\":6}},\"17\":{\"start\":{\"line\":18,\"column\":0},\"end\":{\"line\":18,\"column\":20}},\"18\":{\"start\":{\"line\":19,\"column\":0},\"end\":{\"line\":19,\"column\":3}},\"19\":{\"start\":{\"line\":20,\"column\":0},\"end\":{\"line\":20,\"column\":0}},\"20\":{\"start\":{\"line\":21,\"column\":0},\"end\":{\"line\":21,\"column\":67}},\"21\":{\"start\":{\"line\":22,\"column\":0},\"end\":{\"line\":22,\"column\":52}},\"22\":{\"start\":{\"line\":23,\"column\":0},\"end\":{\"line\":23,\"column\":0}},\"23\":{\"start\":{\"line\":24,\"column\":0},\"end\":{\"line\":24,\"column\":7}},\"24\":{\"start\":{\"line\":25,\"column\":0},\"end\":{\"line\":25,\"column\":40}},\"25\":{\"start\":{\"line\":26,\"column\":0},\"end\":{\"line\":26,\"column\":23}},\"26\":{\"start\":{\"line\":27,\"column\":0},\"end\":{\"line\":27,\"column\":25}},\"27\":{\"start\":{\"line\":28,\"column\":0},\"end\":{\"line\":28,\"column\":7}},\"28\":{\"start\":{\"line\":29,\"column\":0},\"end\":{\"line\":29,\"column\":19}},\"29\":{\"start\":{\"line\":30,\"column\":0},\"end\":{\"line\":30,\"column\":71}},\"30\":{\"start\":{\"line\":31,\"column\":0},\"end\":{\"line\":31,\"column\":53}},\"31\":{\"start\":{\"line\":32,\"column\":0},\"end\":{\"line\":32,\"column\":12}},\"32\":{\"start\":{\"line\":33,\"column\":0},\"end\":{\"line\":33,\"column\":20}},\"33\":{\"start\":{\"line\":34,\"column\":0},\"end\":{\"line\":34,\"column\":18}},\"34\":{\"start\":{\"line\":35,\"column\":0},\"end\":{\"line\":35,\"column\":91}},\"35\":{\"start\":{\"line\":36,\"column\":0},\"end\":{\"line\":36,\"column\":10}},\"36\":{\"start\":{\"line\":37,\"column\":0},\"end\":{\"line\":37,\"column\":8}},\"37\":{\"start\":{\"line\":38,\"column\":0},\"end\":{\"line\":38,\"column\":22}},\"38\":{\"start\":{\"line\":39,\"column\":0},\"end\":{\"line\":39,\"column\":5}},\"39\":{\"start\":{\"line\":40,\"column\":0},\"end\":{\"line\":40,\"column\":3}},\"40\":{\"start\":{\"line\":41,\"column\":0},\"end\":{\"line\":41,\"column\":1}}},\"s\":{\"0\":0,\"1\":0,\"2\":0,\"3\":0,\"4\":0,\"5\":0,\"6\":0,\"7\":0,\"8\":0,\"9\":0,\"10\":0,\"11\":0,\"12\":0,\"13\":0,\"14\":0,\"15\":0,\"16\":0,\"17\":0,\"18\":0,\"19\":0,\"20\":0,\"21\":0,\"22\":0,\"23\":0,\"24\":0,\"25\":0,\"26\":0,\"27\":0,\"28\":0,\"29\":0,\"30\":0,\"31\":0,\"32\":0,\"33\":0,\"34\":0,\"35\":0,\"36\":0,\"37\":0,\"38\":0,\"39\":0,\"40\":0},\"branchMap\":{\"0\":{\"type\":\"branch\",\"line\":1,\"loc\":{\"start\":{\"line\":1,\"column\":0},\"end\":{\"line\":41,\"column\":-333}},\"locations\":[{\"start\":{\"line\":1,\"column\":0},\"end\":{\"line\":41,\"column\":-333}}]}},\"b\":{\"0\":[0]},\"fnMap\":{\"0\":{\"name\":\"(empty-report)\",\"decl\":{\"start\":{\"line\":1,\"column\":0},\"end\":{\"line\":41,\"column\":-333}},\"loc\":{\"start\":{\"line\":1,\"column\":0},\"end\":{\"line\":41,\"column\":-333}},\"line\":1}},\"f\":{\"0\":0}}\n,\"/Users/dex/go/src/github.com/dexhorthy/repomirror/src/commands/sync-one.ts\": {\"path\":\"/Users/dex/go/src/github.com/dexhorthy/repomirror/src/commands/sync-one.ts\",\"all\":true,\"statementMap\":{\"0\":{\"start\":{\"line\":1,\"column\":0},\"end\":{\"line\":1,\"column\":30}},\"1\":{\"start\":{\"line\":2,\"column\":0},\"end\":{\"line\":2,\"column\":0}},\"2\":{\"start\":{\"line\":3,\"column\":0},\"end\":{\"line\":3,\"column\":37}},\"3\":{\"start\":{\"line\":4,\"column\":0},\"end\":{\"line\":4,\"column\":48}},\"4\":{\"start\":{\"line\":5,\"column\":0},\"end\":{\"line\":5,\"column\":15}},\"5\":{\"start\":{\"line\":6,\"column\":0},\"end\":{\"line\":6,\"column\":1}}},\"s\":{\"0\":0,\"1\":0,\"2\":0,\"3\":0,\"4\":0,\"5\":0},\"branchMap\":{\"0\":{\"type\":\"branch\",\"line\":1,\"loc\":{\"start\":{\"line\":1,\"column\":0},\"end\":{\"line\":6,\"column\":-17}},\"locations\":[{\"start\":{\"line\":1,\"column\":0},\"end\":{\"line\":6,\"column\":-17}}]}},\"b\":{\"0\":[0]},\"fnMap\":{\"0\":{\"name\":\"(empty-report)\",\"decl\":{\"start\":{\"line\":1,\"column\":0},\"end\":{\"line\":6,\"column\":-17}},\"loc\":{\"start\":{\"line\":1,\"column\":0},\"end\":{\"line\":6,\"column\":-17}},\"line\":1}},\"f\":{\"0\":0}}\n,\"/Users/dex/go/src/github.com/dexhorthy/repomirror/src/commands/sync.ts\": {\"path\":\"/Users/dex/go/src/github.com/dexhorthy/repomirror/src/commands/sync.ts\",\"all\":true,\"statementMap\":{\"0\":{\"start\":{\"line\":1,\"column\":0},\"end\":{\"line\":1,\"column\":30}},\"1\":{\"start\":{\"line\":2,\"column\":0},\"end\":{\"line\":2,\"column\":26}},\"2\":{\"start\":{\"line\":3,\"column\":0},\"end\":{\"line\":3,\"column\":28}},\"3\":{\"start\":{\"line\":4,\"column\":0},\"end\":{\"line\":4,\"column\":36}},\"4\":{\"start\":{\"line\":5,\"column\":0},\"end\":{\"line\":5,\"column\":0}},\"5\":{\"start\":{\"line\":6,\"column\":0},\"end\":{\"line\":6,\"column\":45}},\"6\":{\"start\":{\"line\":7,\"column\":0},\"end\":{\"line\":7,\"column\":67}},\"7\":{\"start\":{\"line\":8,\"column\":0},\"end\":{\"line\":8,\"column\":0}},\"8\":{\"start\":{\"line\":9,\"column\":0},\"end\":{\"line\":9,\"column\":7}},\"9\":{\"start\":{\"line\":10,\"column\":0},\"end\":{\"line\":10,\"column\":30}},\"10\":{\"start\":{\"line\":11,\"column\":0},\"end\":{\"line\":11,\"column\":32}},\"11\":{\"start\":{\"line\":12,\"column\":0},\"end\":{\"line\":12,\"column\":11}},\"12\":{\"start\":{\"line\":13,\"column\":0},\"end\":{\"line\":13,\"column\":18}},\"13\":{\"start\":{\"line\":14,\"column\":0},\"end\":{\"line\":14,\"column\":16}},\"14\":{\"start\":{\"line\":15,\"column\":0},\"end\":{\"line\":15,\"column\":81}},\"15\":{\"start\":{\"line\":16,\"column\":0},\"end\":{\"line\":16,\"column\":8}},\"16\":{\"start\":{\"line\":17,\"column\":0},\"end\":{\"line\":17,\"column\":6}},\"17\":{\"start\":{\"line\":18,\"column\":0},\"end\":{\"line\":18,\"column\":20}},\"18\":{\"start\":{\"line\":19,\"column\":0},\"end\":{\"line\":19,\"column\":3}},\"19\":{\"start\":{\"line\":20,\"column\":0},\"end\":{\"line\":20,\"column\":0}},\"20\":{\"start\":{\"line\":21,\"column\":0},\"end\":{\"line\":21,\"column\":48}},\"21\":{\"start\":{\"line\":22,\"column\":0},\"end\":{\"line\":22,\"column\":0}},\"22\":{\"start\":{\"line\":23,\"column\":0},\"end\":{\"line\":23,\"column\":7}},\"23\":{\"start\":{\"line\":24,\"column\":0},\"end\":{\"line\":24,\"column\":39}},\"24\":{\"start\":{\"line\":25,\"column\":0},\"end\":{\"line\":25,\"column\":23}},\"25\":{\"start\":{\"line\":26,\"column\":0},\"end\":{\"line\":26,\"column\":25}},\"26\":{\"start\":{\"line\":27,\"column\":0},\"end\":{\"line\":27,\"column\":7}},\"27\":{\"start\":{\"line\":28,\"column\":0},\"end\":{\"line\":28,\"column\":0}},\"28\":{\"start\":{\"line\":29,\"column\":0},\"end\":{\"line\":29,\"column\":60}},\"29\":{\"start\":{\"line\":30,\"column\":0},\"end\":{\"line\":30,\"column\":19}},\"30\":{\"start\":{\"line\":31,\"column\":0},\"end\":{\"line\":31,\"column\":18}},\"31\":{\"start\":{\"line\":32,\"column\":0},\"end\":{\"line\":32,\"column\":16}},\"32\":{\"start\":{\"line\":33,\"column\":0},\"end\":{\"line\":33,\"column\":81}},\"33\":{\"start\":{\"line\":34,\"column\":0},\"end\":{\"line\":34,\"column\":8}},\"34\":{\"start\":{\"line\":35,\"column\":0},\"end\":{\"line\":35,\"column\":6}},\"35\":{\"start\":{\"line\":36,\"column\":0},\"end\":{\"line\":36,\"column\":20}},\"36\":{\"start\":{\"line\":37,\"column\":0},\"end\":{\"line\":37,\"column\":3}},\"37\":{\"start\":{\"line\":38,\"column\":0},\"end\":{\"line\":38,\"column\":1}}},\"s\":{\"0\":0,\"1\":0,\"2\":0,\"3\":0,\"4\":0,\"5\":0,\"6\":0,\"7\":0,\"8\":0,\"9\":0,\"10\":0,\"11\":0,\"12\":0,\"13\":0,\"14\":0,\"15\":0,\"16\":0,\"17\":0,\"18\":0,\"19\":0,\"20\":0,\"21\":0,\"22\":0,\"23\":0,\"24\":0,\"25\":0,\"26\":0,\"27\":0,\"28\":0,\"29\":0,\"30\":0,\"31\":0,\"32\":0,\"33\":0,\"34\":0,\"35\":0,\"36\":0,\"37\":0},\"branchMap\":{\"0\":{\"type\":\"branch\",\"line\":1,\"loc\":{\"start\":{\"line\":1,\"column\":0},\"end\":{\"line\":38,\"column\":-315}},\"locations\":[{\"start\":{\"line\":1,\"column\":0},\"end\":{\"line\":38,\"column\":-315}}]}},\"b\":{\"0\":[0]},\"fnMap\":{\"0\":{\"name\":\"(empty-report)\",\"decl\":{\"start\":{\"line\":1,\"column\":0},\"end\":{\"line\":38,\"column\":-315}},\"loc\":{\"start\":{\"line\":1,\"column\":0},\"end\":{\"line\":38,\"column\":-315}},\"line\":1}},\"f\":{\"0\":0}}\n,\"/Users/dex/go/src/github.com/dexhorthy/repomirror/src/commands/visualize.ts\": {\"path\":\"/Users/dex/go/src/github.com/dexhorthy/repomirror/src/commands/visualize.ts\",\"all\":true,\"statementMap\":{\"0\":{\"start\":{\"line\":1,\"column\":0},\"end\":{\"line\":1,\"column\":48}},\"1\":{\"start\":{\"line\":2,\"column\":0},\"end\":{\"line\":2,\"column\":0}},\"2\":{\"start\":{\"line\":3,\"column\":0},\"end\":{\"line\":3,\"column\":16}},\"3\":{\"start\":{\"line\":4,\"column\":0},\"end\":{\"line\":4,\"column\":19}},\"4\":{\"start\":{\"line\":5,\"column\":0},\"end\":{\"line\":5,\"column\":20}},\"5\":{\"start\":{\"line\":6,\"column\":0},\"end\":{\"line\":6,\"column\":17}},\"6\":{\"start\":{\"line\":7,\"column\":0},\"end\":{\"line\":7,\"column\":18}},\"7\":{\"start\":{\"line\":8,\"column\":0},\"end\":{\"line\":8,\"column\":20}},\"8\":{\"start\":{\"line\":9,\"column\":0},\"end\":{\"line\":9,\"column\":21}},\"9\":{\"start\":{\"line\":10,\"column\":0},\"end\":{\"line\":10,\"column\":19}},\"10\":{\"start\":{\"line\":11,\"column\":0},\"end\":{\"line\":11,\"column\":22}},\"11\":{\"start\":{\"line\":12,\"column\":0},\"end\":{\"line\":12,\"column\":19}},\"12\":{\"start\":{\"line\":13,\"column\":0},\"end\":{\"line\":13,\"column\":2}},\"13\":{\"start\":{\"line\":14,\"column\":0},\"end\":{\"line\":14,\"column\":0}},\"14\":{\"start\":{\"line\":15,\"column\":0},\"end\":{\"line\":15,\"column\":45}},\"15\":{\"start\":{\"line\":16,\"column\":0},\"end\":{\"line\":16,\"column\":17}},\"16\":{\"start\":{\"line\":17,\"column\":0},\"end\":{\"line\":17,\"column\":18}},\"17\":{\"start\":{\"line\":18,\"column\":0},\"end\":{\"line\":18,\"column\":28}},\"18\":{\"start\":{\"line\":19,\"column\":0},\"end\":{\"line\":19,\"column\":16}},\"19\":{\"start\":{\"line\":20,\"column\":0},\"end\":{\"line\":20,\"column\":25}},\"20\":{\"start\":{\"line\":21,\"column\":0},\"end\":{\"line\":21,\"column\":21}},\"21\":{\"start\":{\"line\":22,\"column\":0},\"end\":{\"line\":22,\"column\":26}},\"22\":{\"start\":{\"line\":23,\"column\":0},\"end\":{\"line\":23,\"column\":20}},\"23\":{\"start\":{\"line\":24,\"column\":0},\"end\":{\"line\":24,\"column\":25}},\"24\":{\"start\":{\"line\":25,\"column\":0},\"end\":{\"line\":25,\"column\":23}},\"25\":{\"start\":{\"line\":26,\"column\":0},\"end\":{\"line\":26,\"column\":27}},\"26\":{\"start\":{\"line\":27,\"column\":0},\"end\":{\"line\":27,\"column\":19}},\"27\":{\"start\":{\"line\":28,\"column\":0},\"end\":{\"line\":28,\"column\":24}},\"28\":{\"start\":{\"line\":29,\"column\":0},\"end\":{\"line\":29,\"column\":16}},\"29\":{\"start\":{\"line\":30,\"column\":0},\"end\":{\"line\":30,\"column\":26}},\"30\":{\"start\":{\"line\":31,\"column\":0},\"end\":{\"line\":31,\"column\":12}},\"31\":{\"start\":{\"line\":32,\"column\":0},\"end\":{\"line\":32,\"column\":26}},\"32\":{\"start\":{\"line\":33,\"column\":0},\"end\":{\"line\":33,\"column\":3}},\"33\":{\"start\":{\"line\":34,\"column\":0},\"end\":{\"line\":34,\"column\":1}},\"34\":{\"start\":{\"line\":35,\"column\":0},\"end\":{\"line\":35,\"column\":0}},\"35\":{\"start\":{\"line\":36,\"column\":0},\"end\":{\"line\":36,\"column\":16}},\"36\":{\"start\":{\"line\":37,\"column\":0},\"end\":{\"line\":37,\"column\":17}},\"37\":{\"start\":{\"line\":38,\"column\":0},\"end\":{\"line\":38,\"column\":18}},\"38\":{\"start\":{\"line\":39,\"column\":0},\"end\":{\"line\":39,\"column\":20}},\"39\":{\"start\":{\"line\":40,\"column\":0},\"end\":{\"line\":40,\"column\":1}},\"40\":{\"start\":{\"line\":41,\"column\":0},\"end\":{\"line\":41,\"column\":0}},\"41\":{\"start\":{\"line\":42,\"column\":0},\"end\":{\"line\":42,\"column\":48}},\"42\":{\"start\":{\"line\":43,\"column\":0},\"end\":{\"line\":43,\"column\":84}},\"43\":{\"start\":{\"line\":44,\"column\":0},\"end\":{\"line\":44,\"column\":0}},\"44\":{\"start\":{\"line\":45,\"column\":0},\"end\":{\"line\":45,\"column\":24}},\"45\":{\"start\":{\"line\":46,\"column\":0},\"end\":{\"line\":46,\"column\":41}},\"46\":{\"start\":{\"line\":47,\"column\":0},\"end\":{\"line\":47,\"column\":47}},\"47\":{\"start\":{\"line\":48,\"column\":0},\"end\":{\"line\":48,\"column\":26}},\"48\":{\"start\":{\"line\":49,\"column\":0},\"end\":{\"line\":49,\"column\":4}},\"49\":{\"start\":{\"line\":50,\"column\":0},\"end\":{\"line\":50,\"column\":0}},\"50\":{\"start\":{\"line\":51,\"column\":0},\"end\":{\"line\":51,\"column\":23}},\"51\":{\"start\":{\"line\":52,\"column\":0},\"end\":{\"line\":52,\"column\":19}},\"52\":{\"start\":{\"line\":53,\"column\":0},\"end\":{\"line\":53,\"column\":22}},\"53\":{\"start\":{\"line\":54,\"column\":0},\"end\":{\"line\":54,\"column\":18}},\"54\":{\"start\":{\"line\":55,\"column\":0},\"end\":{\"line\":55,\"column\":4}},\"55\":{\"start\":{\"line\":56,\"column\":0},\"end\":{\"line\":56,\"column\":0}},\"56\":{\"start\":{\"line\":57,\"column\":0},\"end\":{\"line\":57,\"column\":26}},\"57\":{\"start\":{\"line\":58,\"column\":0},\"end\":{\"line\":58,\"column\":21}},\"58\":{\"start\":{\"line\":59,\"column\":0},\"end\":{\"line\":59,\"column\":26}},\"59\":{\"start\":{\"line\":60,\"column\":0},\"end\":{\"line\":60,\"column\":20}},\"60\":{\"start\":{\"line\":61,\"column\":0},\"end\":{\"line\":61,\"column\":4}},\"61\":{\"start\":{\"line\":62,\"column\":0},\"end\":{\"line\":62,\"column\":0}},\"62\":{\"start\":{\"line\":63,\"column\":0},\"end\":{\"line\":63,\"column\":34}},\"63\":{\"start\":{\"line\":64,\"column\":0},\"end\":{\"line\":64,\"column\":23}},\"64\":{\"start\":{\"line\":65,\"column\":0},\"end\":{\"line\":65,\"column\":77}},\"65\":{\"start\":{\"line\":66,\"column\":0},\"end\":{\"line\":66,\"column\":22}},\"66\":{\"start\":{\"line\":67,\"column\":0},\"end\":{\"line\":67,\"column\":66}},\"67\":{\"start\":{\"line\":68,\"column\":0},\"end\":{\"line\":68,\"column\":25}},\"68\":{\"start\":{\"line\":69,\"column\":0},\"end\":{\"line\":69,\"column\":69}},\"69\":{\"start\":{\"line\":70,\"column\":0},\"end\":{\"line\":70,\"column\":19}},\"70\":{\"start\":{\"line\":71,\"column\":0},\"end\":{\"line\":71,\"column\":62}},\"71\":{\"start\":{\"line\":72,\"column\":0},\"end\":{\"line\":72,\"column\":0}},\"72\":{\"start\":{\"line\":73,\"column\":0},\"end\":{\"line\":73,\"column\":89}},\"73\":{\"start\":{\"line\":74,\"column\":0},\"end\":{\"line\":74,\"column\":67}},\"74\":{\"start\":{\"line\":75,\"column\":0},\"end\":{\"line\":75,\"column\":0}},\"75\":{\"start\":{\"line\":76,\"column\":0},\"end\":{\"line\":76,\"column\":40}},\"76\":{\"start\":{\"line\":77,\"column\":0},\"end\":{\"line\":77,\"column\":75}},\"77\":{\"start\":{\"line\":78,\"column\":0},\"end\":{\"line\":78,\"column\":5}},\"78\":{\"start\":{\"line\":79,\"column\":0},\"end\":{\"line\":79,\"column\":0}},\"79\":{\"start\":{\"line\":80,\"column\":0},\"end\":{\"line\":80,\"column\":19}},\"80\":{\"start\":{\"line\":81,\"column\":0},\"end\":{\"line\":81,\"column\":5}},\"81\":{\"start\":{\"line\":82,\"column\":0},\"end\":{\"line\":82,\"column\":0}},\"82\":{\"start\":{\"line\":83,\"column\":0},\"end\":{\"line\":83,\"column\":22}},\"83\":{\"start\":{\"line\":84,\"column\":0},\"end\":{\"line\":84,\"column\":73}},\"84\":{\"start\":{\"line\":85,\"column\":0},\"end\":{\"line\":85,\"column\":76}},\"85\":{\"start\":{\"line\":86,\"column\":0},\"end\":{\"line\":86,\"column\":69}},\"86\":{\"start\":{\"line\":87,\"column\":0},\"end\":{\"line\":87,\"column\":0}},\"87\":{\"start\":{\"line\":88,\"column\":0},\"end\":{\"line\":88,\"column\":97}},\"88\":{\"start\":{\"line\":89,\"column\":0},\"end\":{\"line\":89,\"column\":81}},\"89\":{\"start\":{\"line\":90,\"column\":0},\"end\":{\"line\":90,\"column\":78}},\"90\":{\"start\":{\"line\":91,\"column\":0},\"end\":{\"line\":91,\"column\":99}},\"91\":{\"start\":{\"line\":92,\"column\":0},\"end\":{\"line\":92,\"column\":0}},\"92\":{\"start\":{\"line\":93,\"column\":0},\"end\":{\"line\":93,\"column\":16}},\"93\":{\"start\":{\"line\":94,\"column\":0},\"end\":{\"line\":94,\"column\":1}},\"94\":{\"start\":{\"line\":95,\"column\":0},\"end\":{\"line\":95,\"column\":0}},\"95\":{\"start\":{\"line\":96,\"column\":0},\"end\":{\"line\":96,\"column\":43}},\"96\":{\"start\":{\"line\":97,\"column\":0},\"end\":{\"line\":97,\"column\":38}},\"97\":{\"start\":{\"line\":98,\"column\":0},\"end\":{\"line\":98,\"column\":39}},\"98\":{\"start\":{\"line\":99,\"column\":0},\"end\":{\"line\":99,\"column\":0}},\"99\":{\"start\":{\"line\":100,\"column\":0},\"end\":{\"line\":100,\"column\":94}},\"100\":{\"start\":{\"line\":101,\"column\":0},\"end\":{\"line\":101,\"column\":0}},\"101\":{\"start\":{\"line\":102,\"column\":0},\"end\":{\"line\":102,\"column\":41}},\"102\":{\"start\":{\"line\":103,\"column\":0},\"end\":{\"line\":103,\"column\":6}},\"103\":{\"start\":{\"line\":104,\"column\":0},\"end\":{\"line\":104,\"column\":27}},\"104\":{\"start\":{\"line\":105,\"column\":0},\"end\":{\"line\":105,\"column\":52}},\"105\":{\"start\":{\"line\":106,\"column\":0},\"end\":{\"line\":106,\"column\":5}},\"106\":{\"start\":{\"line\":107,\"column\":0},\"end\":{\"line\":107,\"column\":52}},\"107\":{\"start\":{\"line\":108,\"column\":0},\"end\":{\"line\":108,\"column\":61}},\"108\":{\"start\":{\"line\":109,\"column\":0},\"end\":{\"line\":109,\"column\":45}},\"109\":{\"start\":{\"line\":110,\"column\":0},\"end\":{\"line\":110,\"column\":5}},\"110\":{\"start\":{\"line\":111,\"column\":0},\"end\":{\"line\":111,\"column\":3}},\"111\":{\"start\":{\"line\":112,\"column\":0},\"end\":{\"line\":112,\"column\":0}},\"112\":{\"start\":{\"line\":113,\"column\":0},\"end\":{\"line\":113,\"column\":30}},\"113\":{\"start\":{\"line\":114,\"column\":0},\"end\":{\"line\":114,\"column\":65}},\"114\":{\"start\":{\"line\":115,\"column\":0},\"end\":{\"line\":115,\"column\":50}},\"115\":{\"start\":{\"line\":116,\"column\":0},\"end\":{\"line\":116,\"column\":52}},\"116\":{\"start\":{\"line\":117,\"column\":0},\"end\":{\"line\":117,\"column\":0}},\"117\":{\"start\":{\"line\":118,\"column\":0},\"end\":{\"line\":118,\"column\":42}},\"118\":{\"start\":{\"line\":119,\"column\":0},\"end\":{\"line\":119,\"column\":65}},\"119\":{\"start\":{\"line\":120,\"column\":0},\"end\":{\"line\":120,\"column\":0}},\"120\":{\"start\":{\"line\":121,\"column\":0},\"end\":{\"line\":121,\"column\":20}},\"121\":{\"start\":{\"line\":122,\"column\":0},\"end\":{\"line\":122,\"column\":25}},\"122\":{\"start\":{\"line\":123,\"column\":0},\"end\":{\"line\":123,\"column\":0}},\"123\":{\"start\":{\"line\":124,\"column\":0},\"end\":{\"line\":124,\"column\":63}},\"124\":{\"start\":{\"line\":125,\"column\":0},\"end\":{\"line\":125,\"column\":65}},\"125\":{\"start\":{\"line\":126,\"column\":0},\"end\":{\"line\":126,\"column\":60}},\"126\":{\"start\":{\"line\":127,\"column\":0},\"end\":{\"line\":127,\"column\":73}},\"127\":{\"start\":{\"line\":128,\"column\":0},\"end\":{\"line\":128,\"column\":66}},\"128\":{\"start\":{\"line\":129,\"column\":0},\"end\":{\"line\":129,\"column\":58}},\"129\":{\"start\":{\"line\":130,\"column\":0},\"end\":{\"line\":130,\"column\":69}},\"130\":{\"start\":{\"line\":131,\"column\":0},\"end\":{\"line\":131,\"column\":74}},\"131\":{\"start\":{\"line\":132,\"column\":0},\"end\":{\"line\":132,\"column\":32}},\"132\":{\"start\":{\"line\":133,\"column\":0},\"end\":{\"line\":133,\"column\":66}},\"133\":{\"start\":{\"line\":134,\"column\":0},\"end\":{\"line\":134,\"column\":58}},\"134\":{\"start\":{\"line\":135,\"column\":0},\"end\":{\"line\":135,\"column\":0}},\"135\":{\"start\":{\"line\":136,\"column\":0},\"end\":{\"line\":136,\"column\":31}},\"136\":{\"start\":{\"line\":137,\"column\":0},\"end\":{\"line\":137,\"column\":71}},\"137\":{\"start\":{\"line\":138,\"column\":0},\"end\":{\"line\":138,\"column\":7}},\"138\":{\"start\":{\"line\":139,\"column\":0},\"end\":{\"line\":139,\"column\":5}},\"139\":{\"start\":{\"line\":140,\"column\":0},\"end\":{\"line\":140,\"column\":0}},\"140\":{\"start\":{\"line\":141,\"column\":0},\"end\":{\"line\":141,\"column\":32}},\"141\":{\"start\":{\"line\":142,\"column\":0},\"end\":{\"line\":142,\"column\":0}},\"142\":{\"start\":{\"line\":143,\"column\":0},\"end\":{\"line\":143,\"column\":64}},\"143\":{\"start\":{\"line\":144,\"column\":0},\"end\":{\"line\":144,\"column\":20}},\"144\":{\"start\":{\"line\":145,\"column\":0},\"end\":{\"line\":145,\"column\":32}},\"145\":{\"start\":{\"line\":146,\"column\":0},\"end\":{\"line\":146,\"column\":0}},\"146\":{\"start\":{\"line\":147,\"column\":0},\"end\":{\"line\":147,\"column\":49}},\"147\":{\"start\":{\"line\":148,\"column\":0},\"end\":{\"line\":148,\"column\":53}},\"148\":{\"start\":{\"line\":149,\"column\":0},\"end\":{\"line\":149,\"column\":7}},\"149\":{\"start\":{\"line\":150,\"column\":0},\"end\":{\"line\":150,\"column\":76}},\"150\":{\"start\":{\"line\":151,\"column\":0},\"end\":{\"line\":151,\"column\":79}},\"151\":{\"start\":{\"line\":152,\"column\":0},\"end\":{\"line\":152,\"column\":28}},\"152\":{\"start\":{\"line\":153,\"column\":0},\"end\":{\"line\":153,\"column\":61}},\"153\":{\"start\":{\"line\":154,\"column\":0},\"end\":{\"line\":154,\"column\":57}},\"154\":{\"start\":{\"line\":155,\"column\":0},\"end\":{\"line\":155,\"column\":28}},\"155\":{\"start\":{\"line\":156,\"column\":0},\"end\":{\"line\":156,\"column\":115}},\"156\":{\"start\":{\"line\":157,\"column\":0},\"end\":{\"line\":157,\"column\":10}},\"157\":{\"start\":{\"line\":158,\"column\":0},\"end\":{\"line\":158,\"column\":7}},\"158\":{\"start\":{\"line\":159,\"column\":0},\"end\":{\"line\":159,\"column\":28}},\"159\":{\"start\":{\"line\":160,\"column\":0},\"end\":{\"line\":160,\"column\":63}},\"160\":{\"start\":{\"line\":161,\"column\":0},\"end\":{\"line\":161,\"column\":0}},\"161\":{\"start\":{\"line\":162,\"column\":0},\"end\":{\"line\":162,\"column\":38}},\"162\":{\"start\":{\"line\":163,\"column\":0},\"end\":{\"line\":163,\"column\":84}},\"163\":{\"start\":{\"line\":164,\"column\":0},\"end\":{\"line\":164,\"column\":7}},\"164\":{\"start\":{\"line\":165,\"column\":0},\"end\":{\"line\":165,\"column\":5}},\"165\":{\"start\":{\"line\":166,\"column\":0},\"end\":{\"line\":166,\"column\":51}},\"166\":{\"start\":{\"line\":167,\"column\":0},\"end\":{\"line\":167,\"column\":60}},\"167\":{\"start\":{\"line\":168,\"column\":0},\"end\":{\"line\":168,\"column\":61}},\"168\":{\"start\":{\"line\":169,\"column\":0},\"end\":{\"line\":169,\"column\":44}},\"169\":{\"start\":{\"line\":170,\"column\":0},\"end\":{\"line\":170,\"column\":41}},\"170\":{\"start\":{\"line\":171,\"column\":0},\"end\":{\"line\":171,\"column\":51}},\"171\":{\"start\":{\"line\":172,\"column\":0},\"end\":{\"line\":172,\"column\":62}},\"172\":{\"start\":{\"line\":173,\"column\":0},\"end\":{\"line\":173,\"column\":0}},\"173\":{\"start\":{\"line\":174,\"column\":0},\"end\":{\"line\":174,\"column\":46}},\"174\":{\"start\":{\"line\":175,\"column\":0},\"end\":{\"line\":175,\"column\":28}},\"175\":{\"start\":{\"line\":176,\"column\":0},\"end\":{\"line\":176,\"column\":26}},\"176\":{\"start\":{\"line\":177,\"column\":0},\"end\":{\"line\":177,\"column\":45}},\"177\":{\"start\":{\"line\":178,\"column\":0},\"end\":{\"line\":178,\"column\":29}},\"178\":{\"start\":{\"line\":179,\"column\":0},\"end\":{\"line\":179,\"column\":46}},\"179\":{\"start\":{\"line\":180,\"column\":0},\"end\":{\"line\":180,\"column\":45}},\"180\":{\"start\":{\"line\":181,\"column\":0},\"end\":{\"line\":181,\"column\":40}},\"181\":{\"start\":{\"line\":182,\"column\":0},\"end\":{\"line\":182,\"column\":93}},\"182\":{\"start\":{\"line\":183,\"column\":0},\"end\":{\"line\":183,\"column\":31}},\"183\":{\"start\":{\"line\":184,\"column\":0},\"end\":{\"line\":184,\"column\":57}},\"184\":{\"start\":{\"line\":185,\"column\":0},\"end\":{\"line\":185,\"column\":9}},\"185\":{\"start\":{\"line\":186,\"column\":0},\"end\":{\"line\":186,\"column\":0}},\"186\":{\"start\":{\"line\":187,\"column\":0},\"end\":{\"line\":187,\"column\":40}},\"187\":{\"start\":{\"line\":188,\"column\":0},\"end\":{\"line\":188,\"column\":50}},\"188\":{\"start\":{\"line\":189,\"column\":0},\"end\":{\"line\":189,\"column\":71}},\"189\":{\"start\":{\"line\":190,\"column\":0},\"end\":{\"line\":190,\"column\":9}},\"190\":{\"start\":{\"line\":191,\"column\":0},\"end\":{\"line\":191,\"column\":50}},\"191\":{\"start\":{\"line\":192,\"column\":0},\"end\":{\"line\":192,\"column\":70}},\"192\":{\"start\":{\"line\":193,\"column\":0},\"end\":{\"line\":193,\"column\":9}},\"193\":{\"start\":{\"line\":194,\"column\":0},\"end\":{\"line\":194,\"column\":7}},\"194\":{\"start\":{\"line\":195,\"column\":0},\"end\":{\"line\":195,\"column\":30}},\"195\":{\"start\":{\"line\":196,\"column\":0},\"end\":{\"line\":196,\"column\":49}},\"196\":{\"start\":{\"line\":197,\"column\":0},\"end\":{\"line\":197,\"column\":90}},\"197\":{\"start\":{\"line\":198,\"column\":0},\"end\":{\"line\":198,\"column\":5}},\"198\":{\"start\":{\"line\":199,\"column\":0},\"end\":{\"line\":199,\"column\":49}},\"199\":{\"start\":{\"line\":200,\"column\":0},\"end\":{\"line\":200,\"column\":62}},\"200\":{\"start\":{\"line\":201,\"column\":0},\"end\":{\"line\":201,\"column\":3}},\"201\":{\"start\":{\"line\":202,\"column\":0},\"end\":{\"line\":202,\"column\":0}},\"202\":{\"start\":{\"line\":203,\"column\":0},\"end\":{\"line\":203,\"column\":48}},\"203\":{\"start\":{\"line\":204,\"column\":0},\"end\":{\"line\":204,\"column\":54}},\"204\":{\"start\":{\"line\":205,\"column\":0},\"end\":{\"line\":205,\"column\":50}},\"205\":{\"start\":{\"line\":206,\"column\":0},\"end\":{\"line\":206,\"column\":36}},\"206\":{\"start\":{\"line\":207,\"column\":0},\"end\":{\"line\":207,\"column\":6}},\"207\":{\"start\":{\"line\":208,\"column\":0},\"end\":{\"line\":208,\"column\":28}},\"208\":{\"start\":{\"line\":209,\"column\":0},\"end\":{\"line\":209,\"column\":83}},\"209\":{\"start\":{\"line\":210,\"column\":0},\"end\":{\"line\":210,\"column\":67}},\"210\":{\"start\":{\"line\":211,\"column\":0},\"end\":{\"line\":211,\"column\":29}},\"211\":{\"start\":{\"line\":212,\"column\":0},\"end\":{\"line\":212,\"column\":68}},\"212\":{\"start\":{\"line\":213,\"column\":0},\"end\":{\"line\":213,\"column\":7}},\"213\":{\"start\":{\"line\":214,\"column\":0},\"end\":{\"line\":214,\"column\":29}},\"214\":{\"start\":{\"line\":215,\"column\":0},\"end\":{\"line\":215,\"column\":68}},\"215\":{\"start\":{\"line\":216,\"column\":0},\"end\":{\"line\":216,\"column\":7}},\"216\":{\"start\":{\"line\":217,\"column\":0},\"end\":{\"line\":217,\"column\":52}},\"217\":{\"start\":{\"line\":218,\"column\":0},\"end\":{\"line\":218,\"column\":60}},\"218\":{\"start\":{\"line\":219,\"column\":0},\"end\":{\"line\":219,\"column\":7}},\"219\":{\"start\":{\"line\":220,\"column\":0},\"end\":{\"line\":220,\"column\":5}},\"220\":{\"start\":{\"line\":221,\"column\":0},\"end\":{\"line\":221,\"column\":3}},\"221\":{\"start\":{\"line\":222,\"column\":0},\"end\":{\"line\":222,\"column\":0}},\"222\":{\"start\":{\"line\":223,\"column\":0},\"end\":{\"line\":223,\"column\":21}},\"223\":{\"start\":{\"line\":224,\"column\":0},\"end\":{\"line\":224,\"column\":19}},\"224\":{\"start\":{\"line\":225,\"column\":0},\"end\":{\"line\":225,\"column\":28}},\"225\":{\"start\":{\"line\":226,\"column\":0},\"end\":{\"line\":226,\"column\":37}},\"226\":{\"start\":{\"line\":227,\"column\":0},\"end\":{\"line\":227,\"column\":78}},\"227\":{\"start\":{\"line\":228,\"column\":0},\"end\":{\"line\":228,\"column\":62}},\"228\":{\"start\":{\"line\":229,\"column\":0},\"end\":{\"line\":229,\"column\":51}},\"229\":{\"start\":{\"line\":230,\"column\":0},\"end\":{\"line\":230,\"column\":45}},\"230\":{\"start\":{\"line\":231,\"column\":0},\"end\":{\"line\":231,\"column\":61}},\"231\":{\"start\":{\"line\":232,\"column\":0},\"end\":{\"line\":232,\"column\":34}},\"232\":{\"start\":{\"line\":233,\"column\":0},\"end\":{\"line\":233,\"column\":53}},\"233\":{\"start\":{\"line\":234,\"column\":0},\"end\":{\"line\":234,\"column\":3}},\"234\":{\"start\":{\"line\":235,\"column\":0},\"end\":{\"line\":235,\"column\":0}},\"235\":{\"start\":{\"line\":236,\"column\":0},\"end\":{\"line\":236,\"column\":16}},\"236\":{\"start\":{\"line\":237,\"column\":0},\"end\":{\"line\":237,\"column\":62}},\"237\":{\"start\":{\"line\":238,\"column\":0},\"end\":{\"line\":238,\"column\":3}},\"238\":{\"start\":{\"line\":239,\"column\":0},\"end\":{\"line\":239,\"column\":0}},\"239\":{\"start\":{\"line\":240,\"column\":0},\"end\":{\"line\":240,\"column\":16}},\"240\":{\"start\":{\"line\":241,\"column\":0},\"end\":{\"line\":241,\"column\":1}},\"241\":{\"start\":{\"line\":242,\"column\":0},\"end\":{\"line\":242,\"column\":0}},\"242\":{\"start\":{\"line\":243,\"column\":0},\"end\":{\"line\":243,\"column\":35}},\"243\":{\"start\":{\"line\":244,\"column\":0},\"end\":{\"line\":244,\"column\":16}},\"244\":{\"start\":{\"line\":245,\"column\":0},\"end\":{\"line\":245,\"column\":20}},\"245\":{\"start\":{\"line\":246,\"column\":0},\"end\":{\"line\":246,\"column\":22}},\"246\":{\"start\":{\"line\":247,\"column\":0},\"end\":{\"line\":247,\"column\":24}},\"247\":{\"start\":{\"line\":248,\"column\":0},\"end\":{\"line\":248,\"column\":26}},\"248\":{\"start\":{\"line\":249,\"column\":0},\"end\":{\"line\":249,\"column\":3}},\"249\":{\"start\":{\"line\":250,\"column\":0},\"end\":{\"line\":250,\"column\":33}},\"250\":{\"start\":{\"line\":251,\"column\":0},\"end\":{\"line\":251,\"column\":75}},\"251\":{\"start\":{\"line\":252,\"column\":0},\"end\":{\"line\":252,\"column\":0}},\"252\":{\"start\":{\"line\":253,\"column\":0},\"end\":{\"line\":253,\"column\":23}},\"253\":{\"start\":{\"line\":254,\"column\":0},\"end\":{\"line\":254,\"column\":55}},\"254\":{\"start\":{\"line\":255,\"column\":0},\"end\":{\"line\":255,\"column\":38}},\"255\":{\"start\":{\"line\":256,\"column\":0},\"end\":{\"line\":256,\"column\":41}},\"256\":{\"start\":{\"line\":257,\"column\":0},\"end\":{\"line\":257,\"column\":58}},\"257\":{\"start\":{\"line\":258,\"column\":0},\"end\":{\"line\":258,\"column\":0}},\"258\":{\"start\":{\"line\":259,\"column\":0},\"end\":{\"line\":259,\"column\":23}},\"259\":{\"start\":{\"line\":260,\"column\":0},\"end\":{\"line\":260,\"column\":81}},\"260\":{\"start\":{\"line\":261,\"column\":0},\"end\":{\"line\":261,\"column\":4}},\"261\":{\"start\":{\"line\":262,\"column\":0},\"end\":{\"line\":262,\"column\":0}},\"262\":{\"start\":{\"line\":263,\"column\":0},\"end\":{\"line\":263,\"column\":27}},\"263\":{\"start\":{\"line\":264,\"column\":0},\"end\":{\"line\":264,\"column\":22}},\"264\":{\"start\":{\"line\":265,\"column\":0},\"end\":{\"line\":265,\"column\":44}},\"265\":{\"start\":{\"line\":266,\"column\":0},\"end\":{\"line\":266,\"column\":28}},\"266\":{\"start\":{\"line\":267,\"column\":0},\"end\":{\"line\":267,\"column\":45}},\"267\":{\"start\":{\"line\":268,\"column\":0},\"end\":{\"line\":268,\"column\":41}},\"268\":{\"start\":{\"line\":269,\"column\":0},\"end\":{\"line\":269,\"column\":36}},\"269\":{\"start\":{\"line\":270,\"column\":0},\"end\":{\"line\":270,\"column\":0}},\"270\":{\"start\":{\"line\":271,\"column\":0},\"end\":{\"line\":271,\"column\":25}},\"271\":{\"start\":{\"line\":272,\"column\":0},\"end\":{\"line\":272,\"column\":77}},\"272\":{\"start\":{\"line\":273,\"column\":0},\"end\":{\"line\":273,\"column\":6}},\"273\":{\"start\":{\"line\":274,\"column\":0},\"end\":{\"line\":274,\"column\":0}},\"274\":{\"start\":{\"line\":275,\"column\":0},\"end\":{\"line\":275,\"column\":18}},\"275\":{\"start\":{\"line\":276,\"column\":0},\"end\":{\"line\":276,\"column\":65}},\"276\":{\"start\":{\"line\":277,\"column\":0},\"end\":{\"line\":277,\"column\":5}},\"277\":{\"start\":{\"line\":278,\"column\":0},\"end\":{\"line\":278,\"column\":0}},\"278\":{\"start\":{\"line\":279,\"column\":0},\"end\":{\"line\":279,\"column\":37}},\"279\":{\"start\":{\"line\":280,\"column\":0},\"end\":{\"line\":280,\"column\":50}},\"280\":{\"start\":{\"line\":281,\"column\":0},\"end\":{\"line\":281,\"column\":43}},\"281\":{\"start\":{\"line\":282,\"column\":0},\"end\":{\"line\":282,\"column\":28}},\"282\":{\"start\":{\"line\":283,\"column\":0},\"end\":{\"line\":283,\"column\":62}},\"283\":{\"start\":{\"line\":284,\"column\":0},\"end\":{\"line\":284,\"column\":80}},\"284\":{\"start\":{\"line\":285,\"column\":0},\"end\":{\"line\":285,\"column\":7}},\"285\":{\"start\":{\"line\":286,\"column\":0},\"end\":{\"line\":286,\"column\":5}},\"286\":{\"start\":{\"line\":287,\"column\":0},\"end\":{\"line\":287,\"column\":0}},\"287\":{\"start\":{\"line\":288,\"column\":0},\"end\":{\"line\":288,\"column\":37}},\"288\":{\"start\":{\"line\":289,\"column\":0},\"end\":{\"line\":289,\"column\":27}},\"289\":{\"start\":{\"line\":290,\"column\":0},\"end\":{\"line\":290,\"column\":92}},\"290\":{\"start\":{\"line\":291,\"column\":0},\"end\":{\"line\":291,\"column\":8}},\"291\":{\"start\":{\"line\":292,\"column\":0},\"end\":{\"line\":292,\"column\":5}},\"292\":{\"start\":{\"line\":293,\"column\":0},\"end\":{\"line\":293,\"column\":3}},\"293\":{\"start\":{\"line\":294,\"column\":0},\"end\":{\"line\":294,\"column\":0}},\"294\":{\"start\":{\"line\":295,\"column\":0},\"end\":{\"line\":295,\"column\":31}},\"295\":{\"start\":{\"line\":296,\"column\":0},\"end\":{\"line\":296,\"column\":1}},\"296\":{\"start\":{\"line\":297,\"column\":0},\"end\":{\"line\":297,\"column\":0}},\"297\":{\"start\":{\"line\":298,\"column\":0},\"end\":{\"line\":298,\"column\":32}},\"298\":{\"start\":{\"line\":299,\"column\":0},\"end\":{\"line\":299,\"column\":36}},\"299\":{\"start\":{\"line\":300,\"column\":0},\"end\":{\"line\":300,\"column\":18}},\"300\":{\"start\":{\"line\":301,\"column\":0},\"end\":{\"line\":301,\"column\":30}},\"301\":{\"start\":{\"line\":302,\"column\":0},\"end\":{\"line\":302,\"column\":25}},\"302\":{\"start\":{\"line\":303,\"column\":0},\"end\":{\"line\":303,\"column\":24}},\"303\":{\"start\":{\"line\":304,\"column\":0},\"end\":{\"line\":304,\"column\":5}},\"304\":{\"start\":{\"line\":305,\"column\":0},\"end\":{\"line\":305,\"column\":0}},\"305\":{\"start\":{\"line\":306,\"column\":0},\"end\":{\"line\":306,\"column\":70}},\"306\":{\"start\":{\"line\":307,\"column\":0},\"end\":{\"line\":307,\"column\":62}},\"307\":{\"start\":{\"line\":308,\"column\":0},\"end\":{\"line\":308,\"column\":81}},\"308\":{\"start\":{\"line\":309,\"column\":0},\"end\":{\"line\":309,\"column\":74}},\"309\":{\"start\":{\"line\":310,\"column\":0},\"end\":{\"line\":310,\"column\":37}},\"310\":{\"start\":{\"line\":311,\"column\":0},\"end\":{\"line\":311,\"column\":0}},\"311\":{\"start\":{\"line\":312,\"column\":0},\"end\":{\"line\":312,\"column\":27}},\"312\":{\"start\":{\"line\":313,\"column\":0},\"end\":{\"line\":313,\"column\":22}},\"313\":{\"start\":{\"line\":314,\"column\":0},\"end\":{\"line\":314,\"column\":33}},\"314\":{\"start\":{\"line\":315,\"column\":0},\"end\":{\"line\":315,\"column\":70}},\"315\":{\"start\":{\"line\":316,\"column\":0},\"end\":{\"line\":316,\"column\":13}},\"316\":{\"start\":{\"line\":317,\"column\":0},\"end\":{\"line\":317,\"column\":0}},\"317\":{\"start\":{\"line\":318,\"column\":0},\"end\":{\"line\":318,\"column\":11}},\"318\":{\"start\":{\"line\":319,\"column\":0},\"end\":{\"line\":319,\"column\":38}},\"319\":{\"start\":{\"line\":320,\"column\":0},\"end\":{\"line\":320,\"column\":0}},\"320\":{\"start\":{\"line\":321,\"column\":0},\"end\":{\"line\":321,\"column\":39}},\"321\":{\"start\":{\"line\":322,\"column\":0},\"end\":{\"line\":322,\"column\":74}},\"322\":{\"start\":{\"line\":323,\"column\":0},\"end\":{\"line\":323,\"column\":51}},\"323\":{\"start\":{\"line\":324,\"column\":0},\"end\":{\"line\":324,\"column\":37}},\"324\":{\"start\":{\"line\":325,\"column\":0},\"end\":{\"line\":325,\"column\":0}},\"325\":{\"start\":{\"line\":326,\"column\":0},\"end\":{\"line\":326,\"column\":32}},\"326\":{\"start\":{\"line\":327,\"column\":0},\"end\":{\"line\":327,\"column\":33}},\"327\":{\"start\":{\"line\":328,\"column\":0},\"end\":{\"line\":328,\"column\":27}},\"328\":{\"start\":{\"line\":329,\"column\":0},\"end\":{\"line\":329,\"column\":33}},\"329\":{\"start\":{\"line\":330,\"column\":0},\"end\":{\"line\":330,\"column\":13}},\"330\":{\"start\":{\"line\":331,\"column\":0},\"end\":{\"line\":331,\"column\":0}},\"331\":{\"start\":{\"line\":332,\"column\":0},\"end\":{\"line\":332,\"column\":65}},\"332\":{\"start\":{\"line\":333,\"column\":0},\"end\":{\"line\":333,\"column\":43}},\"333\":{\"start\":{\"line\":334,\"column\":0},\"end\":{\"line\":334,\"column\":54}},\"334\":{\"start\":{\"line\":335,\"column\":0},\"end\":{\"line\":335,\"column\":38}},\"335\":{\"start\":{\"line\":336,\"column\":0},\"end\":{\"line\":336,\"column\":23}},\"336\":{\"start\":{\"line\":337,\"column\":0},\"end\":{\"line\":337,\"column\":19}},\"337\":{\"start\":{\"line\":338,\"column\":0},\"end\":{\"line\":338,\"column\":32}},\"338\":{\"start\":{\"line\":339,\"column\":0},\"end\":{\"line\":339,\"column\":31}},\"339\":{\"start\":{\"line\":340,\"column\":0},\"end\":{\"line\":340,\"column\":24}},\"340\":{\"start\":{\"line\":341,\"column\":0},\"end\":{\"line\":341,\"column\":14}},\"341\":{\"start\":{\"line\":342,\"column\":0},\"end\":{\"line\":342,\"column\":42}},\"342\":{\"start\":{\"line\":343,\"column\":0},\"end\":{\"line\":343,\"column\":18}},\"343\":{\"start\":{\"line\":344,\"column\":0},\"end\":{\"line\":344,\"column\":59}},\"344\":{\"start\":{\"line\":345,\"column\":0},\"end\":{\"line\":345,\"column\":73}},\"345\":{\"start\":{\"line\":346,\"column\":0},\"end\":{\"line\":346,\"column\":33}},\"346\":{\"start\":{\"line\":347,\"column\":0},\"end\":{\"line\":347,\"column\":75}},\"347\":{\"start\":{\"line\":348,\"column\":0},\"end\":{\"line\":348,\"column\":14}},\"348\":{\"start\":{\"line\":349,\"column\":0},\"end\":{\"line\":349,\"column\":11}},\"349\":{\"start\":{\"line\":350,\"column\":0},\"end\":{\"line\":350,\"column\":9}},\"350\":{\"start\":{\"line\":351,\"column\":0},\"end\":{\"line\":351,\"column\":41}},\"351\":{\"start\":{\"line\":352,\"column\":0},\"end\":{\"line\":352,\"column\":17}},\"352\":{\"start\":{\"line\":353,\"column\":0},\"end\":{\"line\":353,\"column\":33}},\"353\":{\"start\":{\"line\":354,\"column\":0},\"end\":{\"line\":354,\"column\":60}},\"354\":{\"start\":{\"line\":355,\"column\":0},\"end\":{\"line\":355,\"column\":11}},\"355\":{\"start\":{\"line\":356,\"column\":0},\"end\":{\"line\":356,\"column\":53}},\"356\":{\"start\":{\"line\":357,\"column\":0},\"end\":{\"line\":357,\"column\":48}},\"357\":{\"start\":{\"line\":358,\"column\":0},\"end\":{\"line\":358,\"column\":0}},\"358\":{\"start\":{\"line\":359,\"column\":0},\"end\":{\"line\":359,\"column\":38}},\"359\":{\"start\":{\"line\":360,\"column\":0},\"end\":{\"line\":360,\"column\":68}},\"360\":{\"start\":{\"line\":361,\"column\":0},\"end\":{\"line\":361,\"column\":49}},\"361\":{\"start\":{\"line\":362,\"column\":0},\"end\":{\"line\":362,\"column\":38}},\"362\":{\"start\":{\"line\":363,\"column\":0},\"end\":{\"line\":363,\"column\":49}},\"363\":{\"start\":{\"line\":364,\"column\":0},\"end\":{\"line\":364,\"column\":30}},\"364\":{\"start\":{\"line\":365,\"column\":0},\"end\":{\"line\":365,\"column\":19}},\"365\":{\"start\":{\"line\":366,\"column\":0},\"end\":{\"line\":366,\"column\":31}},\"366\":{\"start\":{\"line\":367,\"column\":0},\"end\":{\"line\":367,\"column\":24}},\"367\":{\"start\":{\"line\":368,\"column\":0},\"end\":{\"line\":368,\"column\":14}},\"368\":{\"start\":{\"line\":369,\"column\":0},\"end\":{\"line\":369,\"column\":37}},\"369\":{\"start\":{\"line\":370,\"column\":0},\"end\":{\"line\":370,\"column\":18}},\"370\":{\"start\":{\"line\":371,\"column\":0},\"end\":{\"line\":371,\"column\":58}},\"371\":{\"start\":{\"line\":372,\"column\":0},\"end\":{\"line\":372,\"column\":40}},\"372\":{\"start\":{\"line\":373,\"column\":0},\"end\":{\"line\":373,\"column\":31}},\"373\":{\"start\":{\"line\":374,\"column\":0},\"end\":{\"line\":374,\"column\":35}},\"374\":{\"start\":{\"line\":375,\"column\":0},\"end\":{\"line\":375,\"column\":15}},\"375\":{\"start\":{\"line\":376,\"column\":0},\"end\":{\"line\":376,\"column\":11}},\"376\":{\"start\":{\"line\":377,\"column\":0},\"end\":{\"line\":377,\"column\":9}},\"377\":{\"start\":{\"line\":378,\"column\":0},\"end\":{\"line\":378,\"column\":71}},\"378\":{\"start\":{\"line\":379,\"column\":0},\"end\":{\"line\":379,\"column\":57}},\"379\":{\"start\":{\"line\":380,\"column\":0},\"end\":{\"line\":380,\"column\":73}},\"380\":{\"start\":{\"line\":381,\"column\":0},\"end\":{\"line\":381,\"column\":31}},\"381\":{\"start\":{\"line\":382,\"column\":0},\"end\":{\"line\":382,\"column\":85}},\"382\":{\"start\":{\"line\":383,\"column\":0},\"end\":{\"line\":383,\"column\":12}},\"383\":{\"start\":{\"line\":384,\"column\":0},\"end\":{\"line\":384,\"column\":51}},\"384\":{\"start\":{\"line\":385,\"column\":0},\"end\":{\"line\":385,\"column\":9}},\"385\":{\"start\":{\"line\":386,\"column\":0},\"end\":{\"line\":386,\"column\":56}},\"386\":{\"start\":{\"line\":387,\"column\":0},\"end\":{\"line\":387,\"column\":14}},\"387\":{\"start\":{\"line\":388,\"column\":0},\"end\":{\"line\":388,\"column\":73}},\"388\":{\"start\":{\"line\":389,\"column\":0},\"end\":{\"line\":389,\"column\":9}},\"389\":{\"start\":{\"line\":390,\"column\":0},\"end\":{\"line\":390,\"column\":0}},\"390\":{\"start\":{\"line\":391,\"column\":0},\"end\":{\"line\":391,\"column\":60}},\"391\":{\"start\":{\"line\":392,\"column\":0},\"end\":{\"line\":392,\"column\":24}},\"392\":{\"start\":{\"line\":393,\"column\":0},\"end\":{\"line\":393,\"column\":32}},\"393\":{\"start\":{\"line\":394,\"column\":0},\"end\":{\"line\":394,\"column\":71}},\"394\":{\"start\":{\"line\":395,\"column\":0},\"end\":{\"line\":395,\"column\":24}},\"395\":{\"start\":{\"line\":396,\"column\":0},\"end\":{\"line\":396,\"column\":29}},\"396\":{\"start\":{\"line\":397,\"column\":0},\"end\":{\"line\":397,\"column\":68}},\"397\":{\"start\":{\"line\":398,\"column\":0},\"end\":{\"line\":398,\"column\":10}},\"398\":{\"start\":{\"line\":399,\"column\":0},\"end\":{\"line\":399,\"column\":29}},\"399\":{\"start\":{\"line\":400,\"column\":0},\"end\":{\"line\":400,\"column\":77}},\"400\":{\"start\":{\"line\":401,\"column\":0},\"end\":{\"line\":401,\"column\":10}},\"401\":{\"start\":{\"line\":402,\"column\":0},\"end\":{\"line\":402,\"column\":7}},\"402\":{\"start\":{\"line\":403,\"column\":0},\"end\":{\"line\":403,\"column\":5}},\"403\":{\"start\":{\"line\":404,\"column\":0},\"end\":{\"line\":404,\"column\":5}},\"404\":{\"start\":{\"line\":405,\"column\":0},\"end\":{\"line\":405,\"column\":0}},\"405\":{\"start\":{\"line\":406,\"column\":0},\"end\":{\"line\":406,\"column\":24}},\"406\":{\"start\":{\"line\":407,\"column\":0},\"end\":{\"line\":407,\"column\":95}},\"407\":{\"start\":{\"line\":408,\"column\":0},\"end\":{\"line\":408,\"column\":74}},\"408\":{\"start\":{\"line\":409,\"column\":0},\"end\":{\"line\":409,\"column\":27}},\"409\":{\"start\":{\"line\":410,\"column\":0},\"end\":{\"line\":410,\"column\":94}},\"410\":{\"start\":{\"line\":411,\"column\":0},\"end\":{\"line\":411,\"column\":8}},\"411\":{\"start\":{\"line\":412,\"column\":0},\"end\":{\"line\":412,\"column\":68}},\"412\":{\"start\":{\"line\":413,\"column\":0},\"end\":{\"line\":413,\"column\":5}},\"413\":{\"start\":{\"line\":414,\"column\":0},\"end\":{\"line\":414,\"column\":5}},\"414\":{\"start\":{\"line\":415,\"column\":0},\"end\":{\"line\":415,\"column\":1}}},\"s\":{\"0\":0,\"1\":0,\"2\":0,\"3\":0,\"4\":0,\"5\":0,\"6\":0,\"7\":0,\"8\":0,\"9\":0,\"10\":0,\"11\":0,\"12\":0,\"13\":0,\"14\":0,\"15\":0,\"16\":0,\"17\":0,\"18\":0,\"19\":0,\"20\":0,\"21\":0,\"22\":0,\"23\":0,\"24\":0,\"25\":0,\"26\":0,\"27\":0,\"28\":0,\"29\":0,\"30\":0,\"31\":0,\"32\":0,\"33\":0,\"34\":0,\"35\":0,\"36\":0,\"37\":0,\"38\":0,\"39\":0,\"40\":0,\"41\":0,\"42\":0,\"43\":0,\"44\":0,\"45\":0,\"46\":0,\"47\":0,\"48\":0,\"49\":0,\"50\":0,\"51\":0,\"52\":0,\"53\":0,\"54\":0,\"55\":0,\"56\":0,\"57\":0,\"58\":0,\"59\":0,\"60\":0,\"61\":0,\"62\":0,\"63\":0,\"64\":0,\"65\":0,\"66\":0,\"67\":0,\"68\":0,\"69\":0,\"70\":0,\"71\":0,\"72\":0,\"73\":0,\"74\":0,\"75\":0,\"76\":0,\"77\":0,\"78\":0,\"79\":0,\"80\":0,\"81\":0,\"82\":0,\"83\":0,\"84\":0,\"85\":0,\"86\":0,\"87\":0,\"88\":0,\"89\":0,\"90\":0,\"91\":0,\"92\":0,\"93\":0,\"94\":0,\"95\":0,\"96\":0,\"97\":0,\"98\":0,\"99\":0,\"100\":0,\"101\":0,\"102\":0,\"103\":0,\"104\":0,\"105\":0,\"106\":0,\"107\":0,\"108\":0,\"109\":0,\"110\":0,\"111\":0,\"112\":0,\"113\":0,\"114\":0,\"115\":0,\"116\":0,\"117\":0,\"118\":0,\"119\":0,\"120\":0,\"121\":0,\"122\":0,\"123\":0,\"124\":0,\"125\":0,\"126\":0,\"127\":0,\"128\":0,\"129\":0,\"130\":0,\"131\":0,\"132\":0,\"133\":0,\"134\":0,\"135\":0,\"136\":0,\"137\":0,\"138\":0,\"139\":0,\"140\":0,\"141\":0,\"142\":0,\"143\":0,\"144\":0,\"145\":0,\"146\":0,\"147\":0,\"148\":0,\"149\":0,\"150\":0,\"151\":0,\"152\":0,\"153\":0,\"154\":0,\"155\":0,\"156\":0,\"157\":0,\"158\":0,\"159\":0,\"160\":0,\"161\":0,\"162\":0,\"163\":0,\"164\":0,\"165\":0,\"166\":0,\"167\":0,\"168\":0,\"169\":0,\"170\":0,\"171\":0,\"172\":0,\"173\":0,\"174\":0,\"175\":0,\"176\":0,\"177\":0,\"178\":0,\"179\":0,\"180\":0,\"181\":0,\"182\":0,\"183\":0,\"184\":0,\"185\":0,\"186\":0,\"187\":0,\"188\":0,\"189\":0,\"190\":0,\"191\":0,\"192\":0,\"193\":0,\"194\":0,\"195\":0,\"196\":0,\"197\":0,\"198\":0,\"199\":0,\"200\":0,\"201\":0,\"202\":0,\"203\":0,\"204\":0,\"205\":0,\"206\":0,\"207\":0,\"208\":0,\"209\":0,\"210\":0,\"211\":0,\"212\":0,\"213\":0,\"214\":0,\"215\":0,\"216\":0,\"217\":0,\"218\":0,\"219\":0,\"220\":0,\"221\":0,\"222\":0,\"223\":0,\"224\":0,\"225\":0,\"226\":0,\"227\":0,\"228\":0,\"229\":0,\"230\":0,\"231\":0,\"232\":0,\"233\":0,\"234\":0,\"235\":0,\"236\":0,\"237\":0,\"238\":0,\"239\":0,\"240\":0,\"241\":0,\"242\":0,\"243\":0,\"244\":0,\"245\":0,\"246\":0,\"247\":0,\"248\":0,\"249\":0,\"250\":0,\"251\":0,\"252\":0,\"253\":0,\"254\":0,\"255\":0,\"256\":0,\"257\":0,\"258\":0,\"259\":0,\"260\":0,\"261\":0,\"262\":0,\"263\":0,\"264\":0,\"265\":0,\"266\":0,\"267\":0,\"268\":0,\"269\":0,\"270\":0,\"271\":0,\"272\":0,\"273\":0,\"274\":0,\"275\":0,\"276\":0,\"277\":0,\"278\":0,\"279\":0,\"280\":0,\"281\":0,\"282\":0,\"283\":0,\"284\":0,\"285\":0,\"286\":0,\"287\":0,\"288\":0,\"289\":0,\"290\":0,\"291\":0,\"292\":0,\"293\":0,\"294\":0,\"295\":0,\"296\":0,\"297\":0,\"298\":0,\"299\":0,\"300\":0,\"301\":0,\"302\":0,\"303\":0,\"304\":0,\"305\":0,\"306\":0,\"307\":0,\"308\":0,\"309\":0,\"310\":0,\"311\":0,\"312\":0,\"313\":0,\"314\":0,\"315\":0,\"316\":0,\"317\":0,\"318\":0,\"319\":0,\"320\":0,\"321\":0,\"322\":0,\"323\":0,\"324\":0,\"325\":0,\"326\":0,\"327\":0,\"328\":0,\"329\":0,\"330\":0,\"331\":0,\"332\":0,\"333\":0,\"334\":0,\"335\":0,\"336\":0,\"337\":0,\"338\":0,\"339\":0,\"340\":0,\"341\":0,\"342\":0,\"343\":0,\"344\":0,\"345\":0,\"346\":0,\"347\":0,\"348\":0,\"349\":0,\"350\":0,\"351\":0,\"352\":0,\"353\":0,\"354\":0,\"355\":0,\"356\":0,\"357\":0,\"358\":0,\"359\":0,\"360\":0,\"361\":0,\"362\":0,\"363\":0,\"364\":0,\"365\":0,\"366\":0,\"367\":0,\"368\":0,\"369\":0,\"370\":0,\"371\":0,\"372\":0,\"373\":0,\"374\":0,\"375\":0,\"376\":0,\"377\":0,\"378\":0,\"379\":0,\"380\":0,\"381\":0,\"382\":0,\"383\":0,\"384\":0,\"385\":0,\"386\":0,\"387\":0,\"388\":0,\"389\":0,\"390\":0,\"391\":0,\"392\":0,\"393\":0,\"394\":0,\"395\":0,\"396\":0,\"397\":0,\"398\":0,\"399\":0,\"400\":0,\"401\":0,\"402\":0,\"403\":0,\"404\":0,\"405\":0,\"406\":0,\"407\":0,\"408\":0,\"409\":0,\"410\":0,\"411\":0,\"412\":0,\"413\":0,\"414\":0},\"branchMap\":{\"0\":{\"type\":\"branch\",\"line\":1,\"loc\":{\"start\":{\"line\":1,\"column\":13794},\"end\":{\"line\":415,\"column\":1}},\"locations\":[{\"start\":{\"line\":1,\"column\":13794},\"end\":{\"line\":415,\"column\":1}}]}},\"b\":{\"0\":[0]},\"fnMap\":{\"0\":{\"name\":\"(empty-report)\",\"decl\":{\"start\":{\"line\":1,\"column\":13794},\"end\":{\"line\":415,\"column\":1}},\"loc\":{\"start\":{\"line\":1,\"column\":13794},\"end\":{\"line\":415,\"column\":1}},\"line\":1}},\"f\":{\"0\":0}}\n}\n"
  },
  {
    "path": "coverage/index.html",
    "content": "\n<!doctype html>\n<html lang=\"en\">\n\n<head>\n    <title>Code coverage report for All files</title>\n    <meta charset=\"utf-8\" />\n    <link rel=\"stylesheet\" href=\"prettify.css\" />\n    <link rel=\"stylesheet\" href=\"base.css\" />\n    <link rel=\"shortcut icon\" type=\"image/x-icon\" href=\"favicon.png\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <style type='text/css'>\n        .coverage-summary .sorter {\n            background-image: url(sort-arrow-sprite.png);\n        }\n    </style>\n</head>\n    \n<body>\n<div class='wrapper'>\n    <div class='pad1'>\n        <h1>All files</h1>\n        <div class='clearfix'>\n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Statements</span>\n                <span class='fraction'>0/794</span>\n            </div>\n        \n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Branches</span>\n                <span class='fraction'>0/6</span>\n            </div>\n        \n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Functions</span>\n                <span class='fraction'>0/6</span>\n            </div>\n        \n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Lines</span>\n                <span class='fraction'>0/794</span>\n            </div>\n        \n            \n        </div>\n        <p class=\"quiet\">\n            Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.\n        </p>\n        <template id=\"filterTemplate\">\n            <div class=\"quiet\">\n                Filter:\n                <input type=\"search\" id=\"fileSearch\">\n            </div>\n        </template>\n    </div>\n    <div class='status-line low'></div>\n    <div class=\"pad1\">\n<table class=\"coverage-summary\">\n<thead>\n<tr>\n   <th data-col=\"file\" data-fmt=\"html\" data-html=\"true\" class=\"file\">File</th>\n   <th data-col=\"pic\" data-type=\"number\" data-fmt=\"html\" data-html=\"true\" class=\"pic\"></th>\n   <th data-col=\"statements\" data-type=\"number\" data-fmt=\"pct\" class=\"pct\">Statements</th>\n   <th data-col=\"statements_raw\" data-type=\"number\" data-fmt=\"html\" class=\"abs\"></th>\n   <th data-col=\"branches\" data-type=\"number\" data-fmt=\"pct\" class=\"pct\">Branches</th>\n   <th data-col=\"branches_raw\" data-type=\"number\" data-fmt=\"html\" class=\"abs\"></th>\n   <th data-col=\"functions\" data-type=\"number\" data-fmt=\"pct\" class=\"pct\">Functions</th>\n   <th data-col=\"functions_raw\" data-type=\"number\" data-fmt=\"html\" class=\"abs\"></th>\n   <th data-col=\"lines\" data-type=\"number\" data-fmt=\"pct\" class=\"pct\">Lines</th>\n   <th data-col=\"lines_raw\" data-type=\"number\" data-fmt=\"html\" class=\"abs\"></th>\n</tr>\n</thead>\n<tbody><tr>\n\t<td class=\"file low\" data-value=\"src\"><a href=\"src/index.html\">src</a></td>\n\t<td data-value=\"0\" class=\"pic low\">\n\t<div class=\"chart\"><div class=\"cover-fill\" style=\"width: 0%\"></div><div class=\"cover-empty\" style=\"width: 100%\"></div></div>\n\t</td>\n\t<td data-value=\"0\" class=\"pct low\">0%</td>\n\t<td data-value=\"39\" class=\"abs low\">0/39</td>\n\t<td data-value=\"0\" class=\"pct low\">0%</td>\n\t<td data-value=\"1\" class=\"abs low\">0/1</td>\n\t<td data-value=\"0\" class=\"pct low\">0%</td>\n\t<td data-value=\"1\" class=\"abs low\">0/1</td>\n\t<td data-value=\"0\" class=\"pct low\">0%</td>\n\t<td data-value=\"39\" class=\"abs low\">0/39</td>\n\t</tr>\n\n<tr>\n\t<td class=\"file low\" data-value=\"src/commands\"><a href=\"src/commands/index.html\">src/commands</a></td>\n\t<td data-value=\"0\" class=\"pic low\">\n\t<div class=\"chart\"><div class=\"cover-fill\" style=\"width: 0%\"></div><div class=\"cover-empty\" style=\"width: 100%\"></div></div>\n\t</td>\n\t<td data-value=\"0\" class=\"pct low\">0%</td>\n\t<td data-value=\"755\" class=\"abs low\">0/755</td>\n\t<td data-value=\"0\" class=\"pct low\">0%</td>\n\t<td data-value=\"5\" class=\"abs low\">0/5</td>\n\t<td data-value=\"0\" class=\"pct low\">0%</td>\n\t<td data-value=\"5\" class=\"abs low\">0/5</td>\n\t<td data-value=\"0\" class=\"pct low\">0%</td>\n\t<td data-value=\"755\" class=\"abs low\">0/755</td>\n\t</tr>\n\n</tbody>\n</table>\n</div>\n                <div class='push'></div><!-- for sticky footer -->\n            </div><!-- /wrapper -->\n            <div class='footer quiet pad2 space-top1 center small'>\n                Code coverage generated by\n                <a href=\"https://istanbul.js.org/\" target=\"_blank\" rel=\"noopener noreferrer\">istanbul</a>\n                at 2025-08-23T07:39:41.860Z\n            </div>\n        <script src=\"prettify.js\"></script>\n        <script>\n            window.onload = function () {\n                prettyPrint();\n            };\n        </script>\n        <script src=\"sorter.js\"></script>\n        <script src=\"block-navigation.js\"></script>\n    </body>\n</html>\n    "
  },
  {
    "path": "coverage/prettify.css",
    "content": ".pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee}\n"
  },
  {
    "path": "coverage/prettify.js",
    "content": "/* eslint-disable */\nwindow.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=[\"break,continue,do,else,for,if,return,while\"];var u=[h,\"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile\"];var p=[u,\"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof\"];var l=[p,\"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where\"];var x=[p,\"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient\"];var R=[x,\"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var\"];var r=\"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes\";var w=[p,\"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN\"];var s=\"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END\";var I=[h,\"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None\"];var f=[h,\"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END\"];var H=[h,\"case,done,elif,esac,eval,fi,function,in,local,set,then,until\"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\\d*)/;var C=\"str\";var z=\"kwd\";var j=\"com\";var O=\"typ\";var G=\"lit\";var L=\"pun\";var F=\"pln\";var m=\"tag\";var E=\"dec\";var J=\"src\";var P=\"atn\";var n=\"atv\";var N=\"nocode\";var M=\"(?:^^\\\\.?|[+-]|\\\\!|\\\\!=|\\\\!==|\\\\#|\\\\%|\\\\%=|&|&&|&&=|&=|\\\\(|\\\\*|\\\\*=|\\\\+=|\\\\,|\\\\-=|\\\\->|\\\\/|\\\\/=|:|::|\\\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\\\?|\\\\@|\\\\[|\\\\^|\\\\^=|\\\\^\\\\^|\\\\^\\\\^=|\\\\{|\\\\||\\\\|=|\\\\|\\\\||\\\\|\\\\|=|\\\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\\\s*\";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V<U;++V){var ae=Z[V];if(ae.ignoreCase){ac=true}else{if(/[a-z]/i.test(ae.source.replace(/\\\\u[0-9a-f]{4}|\\\\x[0-9a-f]{2}|\\\\[^ux]/gi,\"\"))){S=true;ac=false;break}}}var Y={b:8,t:9,n:10,v:11,f:12,r:13};function ab(ah){var ag=ah.charCodeAt(0);if(ag!==92){return ag}var af=ah.charAt(1);ag=Y[af];if(ag){return ag}else{if(\"0\"<=af&&af<=\"7\"){return parseInt(ah.substring(1),8)}else{if(af===\"u\"||af===\"x\"){return parseInt(ah.substring(2),16)}else{return ah.charCodeAt(1)}}}}function T(af){if(af<32){return(af<16?\"\\\\x0\":\"\\\\x\")+af.toString(16)}var ag=String.fromCharCode(af);if(ag===\"\\\\\"||ag===\"-\"||ag===\"[\"||ag===\"]\"){ag=\"\\\\\"+ag}return ag}function X(am){var aq=am.substring(1,am.length-1).match(new RegExp(\"\\\\\\\\u[0-9A-Fa-f]{4}|\\\\\\\\x[0-9A-Fa-f]{2}|\\\\\\\\[0-3][0-7]{0,2}|\\\\\\\\[0-7]{1,2}|\\\\\\\\[\\\\s\\\\S]|-|[^-\\\\\\\\]\",\"g\"));var ak=[];var af=[];var ao=aq[0]===\"^\";for(var ar=ao?1:0,aj=aq.length;ar<aj;++ar){var ah=aq[ar];if(/\\\\[bdsw]/i.test(ah)){ak.push(ah)}else{var ag=ab(ah);var al;if(ar+2<aj&&\"-\"===aq[ar+1]){al=ab(aq[ar+2]);ar+=2}else{al=ag}af.push([ag,al]);if(!(al<65||ag>122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;ar<af.length;++ar){var at=af[ar];if(at[0]<=ap[1]+1){ap[1]=Math.max(ap[1],at[1])}else{ai.push(ap=at)}}var an=[\"[\"];if(ao){an.push(\"^\")}an.push.apply(an,ak);for(var ar=0;ar<ai.length;++ar){var at=ai[ar];an.push(T(at[0]));if(at[1]>at[0]){if(at[1]+1>at[0]){an.push(\"-\")}an.push(T(at[1]))}}an.push(\"]\");return an.join(\"\")}function W(al){var aj=al.source.match(new RegExp(\"(?:\\\\[(?:[^\\\\x5C\\\\x5D]|\\\\\\\\[\\\\s\\\\S])*\\\\]|\\\\\\\\u[A-Fa-f0-9]{4}|\\\\\\\\x[A-Fa-f0-9]{2}|\\\\\\\\[0-9]+|\\\\\\\\[^ux0-9]|\\\\(\\\\?[:!=]|[\\\\(\\\\)\\\\^]|[^\\\\x5B\\\\x5C\\\\(\\\\)\\\\^]+)\",\"g\"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak<ah;++ak){var ag=aj[ak];if(ag===\"(\"){++am}else{if(\"\\\\\"===ag.charAt(0)){var af=+ag.substring(1);if(af&&af<=am){an[af]=-1}}}}for(var ak=1;ak<an.length;++ak){if(-1===an[ak]){an[ak]=++ad}}for(var ak=0,am=0;ak<ah;++ak){var ag=aj[ak];if(ag===\"(\"){++am;if(an[am]===undefined){aj[ak]=\"(?:\"}}else{if(\"\\\\\"===ag.charAt(0)){var af=+ag.substring(1);if(af&&af<=am){aj[ak]=\"\\\\\"+an[am]}}}}for(var ak=0,am=0;ak<ah;++ak){if(\"^\"===aj[ak]&&\"^\"!==aj[ak+1]){aj[ak]=\"\"}}if(al.ignoreCase&&S){for(var ak=0;ak<ah;++ak){var ag=aj[ak];var ai=ag.charAt(0);if(ag.length>=2&&ai===\"[\"){aj[ak]=X(ag)}else{if(ai!==\"\\\\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return\"[\"+String.fromCharCode(ap&~32,ap|32)+\"]\"})}}}}return aj.join(\"\")}var aa=[];for(var V=0,U=Z.length;V<U;++V){var ae=Z[V];if(ae.global||ae.multiline){throw new Error(\"\"+ae)}aa.push(\"(?:\"+W(ae)+\")\")}return new RegExp(aa.join(\"|\"),ac?\"gi\":\"g\")}function a(V){var U=/(?:^|\\s)nocode(?:\\s|$)/;var X=[];var T=0;var Z=[];var W=0;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=document.defaultView.getComputedStyle(V,null).getPropertyValue(\"white-space\")}}var Y=S&&\"pre\"===S.substring(0,3);function aa(ab){switch(ab.nodeType){case 1:if(U.test(ab.className)){return}for(var ae=ab.firstChild;ae;ae=ae.nextSibling){aa(ae)}var ad=ab.nodeName;if(\"BR\"===ad||\"LI\"===ad){X[W]=\"\\n\";Z[W<<1]=T++;Z[(W++<<1)|1]=ab}break;case 3:case 4:var ac=ab.nodeValue;if(ac.length){if(!Y){ac=ac.replace(/[ \\t\\r\\n]+/g,\" \")}else{ac=ac.replace(/\\r\\n?/g,\"\\n\")}X[W]=ac;Z[W<<1]=T;T+=ac.length;Z[(W++<<1)|1]=ab}break}}aa(V);return{sourceCode:X.join(\"\").replace(/\\n$/,\"\"),spans:Z}}function B(S,U,W,T){if(!U){return}var V={sourceCode:U,basePos:S};W(V);T.push.apply(T,V.decorations)}var v=/\\S/;function o(S){var V=undefined;for(var U=S.firstChild;U;U=U.nextSibling){var T=U.nodeType;V=(T===1)?(V?S:U):(T===3)?(v.test(U.nodeValue)?S:V):V}return V===S?undefined:V}function g(U,T){var S={};var V;(function(){var ad=U.concat(T);var ah=[];var ag={};for(var ab=0,Z=ad.length;ab<Z;++ab){var Y=ad[ab];var ac=Y[3];if(ac){for(var ae=ac.length;--ae>=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=\"\"+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\\0-\\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae<aq;++ae){var ag=an[ae];var ap=aj[ag];var ai=void 0;var am;if(typeof ap===\"string\"){am=false}else{var aa=S[ag.charAt(0)];if(aa){ai=ag.match(aa[1]);ap=aa[0]}else{for(var ao=0;ao<X;++ao){aa=T[ao];ai=ag.match(aa[1]);if(ai){ap=aa[0];break}}if(!ai){ap=F}}am=ap.length>=5&&\"lang-\"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]===\"string\")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\\'\\'\\'(?:[^\\'\\\\]|\\\\[\\s\\S]|\\'{1,2}(?=[^\\']))*(?:\\'\\'\\'|$)|\\\"\\\"\\\"(?:[^\\\"\\\\]|\\\\[\\s\\S]|\\\"{1,2}(?=[^\\\"]))*(?:\\\"\\\"\\\"|$)|\\'(?:[^\\\\\\']|\\\\[\\s\\S])*(?:\\'|$)|\\\"(?:[^\\\\\\\"]|\\\\[\\s\\S])*(?:\\\"|$))/,null,\"'\\\"\"])}else{if(T.multiLineStrings){W.push([C,/^(?:\\'(?:[^\\\\\\']|\\\\[\\s\\S])*(?:\\'|$)|\\\"(?:[^\\\\\\\"]|\\\\[\\s\\S])*(?:\\\"|$)|\\`(?:[^\\\\\\`]|\\\\[\\s\\S])*(?:\\`|$))/,null,\"'\\\"`\"])}else{W.push([C,/^(?:\\'(?:[^\\\\\\'\\r\\n]|\\\\.)*(?:\\'|$)|\\\"(?:[^\\\\\\\"\\r\\n]|\\\\.)*(?:\\\"|$))/,null,\"\\\"'\"])}}if(T.verbatimStrings){S.push([C,/^@\\\"(?:[^\\\"]|\\\"\\\")*(?:\\\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,\"#\"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\\b|[^\\r\\n]*)/,null,\"#\"])}S.push([C,/^<(?:(?:(?:\\.\\.\\/)*|\\/?)(?:[\\w-]+(?:\\/[\\w-]+)+)?[\\w-]+\\.h|[a-z]\\w*)>/,null])}else{W.push([j,/^#[^\\r\\n]*/,null,\"#\"])}}if(T.cStyleComments){S.push([j,/^\\/\\/[^\\r\\n]*/,null]);S.push([j,/^\\/\\*[\\s\\S]*?(?:\\*\\/|$)/,null])}if(T.regexLiterals){var X=(\"/(?=[^/*])(?:[^/\\\\x5B\\\\x5C]|\\\\x5C[\\\\s\\\\S]|\\\\x5B(?:[^\\\\x5C\\\\x5D]|\\\\x5C[\\\\s\\\\S])*(?:\\\\x5D|$))+/\");S.push([\"lang-regex\",new RegExp(\"^\"+M+\"(\"+X+\")\")])}var V=T.types;if(V){S.push([O,V])}var U=(\"\"+T.keywords).replace(/^ | $/g,\"\");if(U.length){S.push([z,new RegExp(\"^(?:\"+U.replace(/[\\s,]+/g,\"|\")+\")\\\\b\"),null])}W.push([F,/^\\s+/,null,\" \\r\\n\\t\\xA0\"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\\w+_t\\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp(\"^(?:0x[a-f0-9]+|(?:\\\\d(?:_\\\\d+)*\\\\d*(?:\\\\.\\\\d*)?|\\\\.\\\\d\\\\+)(?:e[+\\\\-]?\\\\d+)?)[a-z]*\",\"i\"),null,\"0123456789\"],[F,/^\\\\[\\s\\S]?/,null],[L,/^.[^\\s\\w\\.$@\\'\\\"\\`\\/\\#\\\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\\s)nocode(?:\\s|$)/;var ab=/\\r\\n?|\\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue(\"white-space\")}}var Z=S&&\"pre\"===S.substring(0,3);var af=ac.createElement(\"LI\");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if(\"BR\"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y<W.length;++Y){ae(W[Y])}if(ag===(ag|0)){W[0].setAttribute(\"value\",ag)}var aa=ac.createElement(\"OL\");aa.className=\"linenums\";var X=Math.max(0,((ag-1))|0)||0;for(var Y=0,T=W.length;Y<T;++Y){af=W[Y];af.className=\"L\"+((Y+X)%10);if(!af.firstChild){af.appendChild(ac.createTextNode(\"\\xA0\"))}aa.appendChild(af)}V.appendChild(aa)}function D(ac){var aj=/\\bMSIE\\b/.test(navigator.userAgent);var am=/\\n/g;var al=ac.sourceCode;var an=al.length;var V=0;var aa=ac.spans;var T=aa.length;var ah=0;var X=ac.decorations;var Y=X.length;var Z=0;X[Y]=an;var ar,aq;for(aq=ar=0;aq<Y;){if(X[aq]!==X[aq+2]){X[ar++]=X[aq++];X[ar++]=X[aq++]}else{aq+=2}}Y=ar;for(aq=ar=0;aq<Y;){var at=X[aq];var ab=X[aq+1];var W=aq+2;while(W+2<=Y&&X[W+1]===ab){W+=2}X[ar++]=at;X[ar++]=ab;aq=W}Y=X.length=ar;var ae=null;while(ah<T){var af=aa[ah];var S=aa[ah+2]||an;var ag=X[Z];var ap=X[Z+2]||an;var W=Math.min(S,ap);var ak=aa[ah+1];var U;if(ak.nodeType!==1&&(U=al.substring(V,W))){if(aj){U=U.replace(am,\"\\r\")}ak.nodeValue=U;var ai=ak.ownerDocument;var ao=ai.createElement(\"SPAN\");ao.className=X[Z+1];var ad=ak.parentNode;ad.replaceChild(ao,ak);ao.appendChild(ak);if(V<S){aa[ah+1]=ak=ai.createTextNode(al.substring(W,S));ad.insertBefore(ak,ao.nextSibling)}}V=W;if(V>=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn(\"cannot override language handler %s\",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\\s*</.test(S)?\"default-markup\":\"default-code\"}return t[T]}c(K,[\"default-code\"]);c(g([],[[F,/^[^<?]+/],[E,/^<!\\w[^>]*(?:>|$)/],[j,/^<\\!--[\\s\\S]*?(?:-\\->|$)/],[\"lang-\",/^<\\?([\\s\\S]+?)(?:\\?>|$)/],[\"lang-\",/^<%([\\s\\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],[\"lang-\",/^<xmp\\b[^>]*>([\\s\\S]+?)<\\/xmp\\b[^>]*>/i],[\"lang-js\",/^<script\\b[^>]*>([\\s\\S]*?)(<\\/script\\b[^>]*>)/i],[\"lang-css\",/^<style\\b[^>]*>([\\s\\S]*?)(<\\/style\\b[^>]*>)/i],[\"lang-in.tag\",/^(<\\/?[a-z][^<>]*>)/i]]),[\"default-markup\",\"htm\",\"html\",\"mxml\",\"xhtml\",\"xml\",\"xsl\"]);c(g([[F,/^[\\s]+/,null,\" \\t\\r\\n\"],[n,/^(?:\\\"[^\\\"]*\\\"?|\\'[^\\']*\\'?)/,null,\"\\\"'\"]],[[m,/^^<\\/?[a-z](?:[\\w.:-]*\\w)?|\\/?>$/i],[P,/^(?!style[\\s=]|on)[a-z](?:[\\w:-]*\\w)?/i],[\"lang-uq.val\",/^=\\s*([^>\\'\\\"\\s]*(?:[^>\\'\\\"\\s\\/]|\\/(?=\\s)))/],[L,/^[=<>\\/]+/],[\"lang-js\",/^on\\w+\\s*=\\s*\\\"([^\\\"]+)\\\"/i],[\"lang-js\",/^on\\w+\\s*=\\s*\\'([^\\']+)\\'/i],[\"lang-js\",/^on\\w+\\s*=\\s*([^\\\"\\'>\\s]+)/i],[\"lang-css\",/^style\\s*=\\s*\\\"([^\\\"]+)\\\"/i],[\"lang-css\",/^style\\s*=\\s*\\'([^\\']+)\\'/i],[\"lang-css\",/^style\\s*=\\s*([^\\\"\\'>\\s]+)/i]]),[\"in.tag\"]);c(g([],[[n,/^[\\s\\S]+/]]),[\"uq.val\"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),[\"c\",\"cc\",\"cpp\",\"cxx\",\"cyc\",\"m\"]);c(i({keywords:\"null,true,false\"}),[\"json\"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),[\"cs\"]);c(i({keywords:x,cStyleComments:true}),[\"java\"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),[\"bsh\",\"csh\",\"sh\"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),[\"cv\",\"py\"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),[\"perl\",\"pl\",\"pm\"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),[\"rb\"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),[\"js\"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),[\"coffee\"]);c(g([],[[C,/^[\\s\\S]+/]]),[\"regex\"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if(\"console\" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement(\"PRE\");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y(\"pre\"),Y(\"code\"),Y(\"xmp\")];var T=[];for(var aa=0;aa<ac.length;++aa){for(var Z=0,V=ac[aa].length;Z<V;++Z){T.push(ac[aa][Z])}}ac=null;var W=Date;if(!W.now){W={now:function(){return +(new Date)}}}var X=0;var S;var ab=/\\blang(?:uage)?-([\\w.]+)(?!\\S)/;var ae=/\\bprettyprint\\b/;function U(){var ag=(window.PR_SHOULD_USE_CONTINUATION?W.now()+250:Infinity);for(;X<T.length&&W.now()<ag;X++){var aj=T[X];var ai=aj.className;if(ai.indexOf(\"prettyprint\")>=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&\"CODE\"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName===\"pre\"||ak.tagName===\"code\"||ak.tagName===\"xmp\")&&ak.className&&ak.className.indexOf(\"prettyprint\")>=0){al=true;break}}if(!al){var af=aj.className.match(/\\blinenums\\b(?::(\\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X<T.length){setTimeout(U,250)}else{if(ad){ad()}}}U()}window.prettyPrintOne=y;window.prettyPrint=b;window.PR={createSimpleLexer:g,registerLangHandler:c,sourceDecorator:i,PR_ATTRIB_NAME:P,PR_ATTRIB_VALUE:n,PR_COMMENT:j,PR_DECLARATION:E,PR_KEYWORD:z,PR_LITERAL:G,PR_NOCODE:N,PR_PLAIN:F,PR_PUNCTUATION:L,PR_SOURCE:J,PR_STRING:C,PR_TAG:m,PR_TYPE:O}})();PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_DECLARATION,/^<!\\w[^>]*(?:>|$)/],[PR.PR_COMMENT,/^<\\!--[\\s\\S]*?(?:-\\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],[\"lang-\",/^<\\?([\\s\\S]+?)(?:\\?>|$)/],[\"lang-\",/^<%([\\s\\S]+?)(?:%>|$)/],[\"lang-\",/^<xmp\\b[^>]*>([\\s\\S]+?)<\\/xmp\\b[^>]*>/i],[\"lang-handlebars\",/^<script\\b[^>]*type\\s*=\\s*['\"]?text\\/x-handlebars-template['\"]?\\b[^>]*>([\\s\\S]*?)(<\\/script\\b[^>]*>)/i],[\"lang-js\",/^<script\\b[^>]*>([\\s\\S]*?)(<\\/script\\b[^>]*>)/i],[\"lang-css\",/^<style\\b[^>]*>([\\s\\S]*?)(<\\/style\\b[^>]*>)/i],[\"lang-in.tag\",/^(<\\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\\s*[\\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\\s*[\\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\\s*[\\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),[\"handlebars\",\"hbs\"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \\t\\r\\n\\f]+/,null,\" \\t\\r\\n\\f\"]],[[PR.PR_STRING,/^\\\"(?:[^\\n\\r\\f\\\\\\\"]|\\\\(?:\\r\\n?|\\n|\\f)|\\\\[\\s\\S])*\\\"/,null],[PR.PR_STRING,/^\\'(?:[^\\n\\r\\f\\\\\\']|\\\\(?:\\r\\n?|\\n|\\f)|\\\\[\\s\\S])*\\'/,null],[\"lang-css-str\",/^url\\(([^\\)\\\"\\']*)\\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\\!important|@import|@page|@media|@charset|inherit)(?=[^\\-\\w]|$)/i,null],[\"lang-css-kw\",/^(-?(?:[_a-z]|(?:\\\\[0-9a-f]+ ?))(?:[_a-z0-9\\-]|\\\\(?:\\\\[0-9a-f]+ ?))*)\\s*:/i],[PR.PR_COMMENT,/^\\/\\*[^*]*\\*+(?:[^\\/*][^*]*\\*+)*\\//],[PR.PR_COMMENT,/^(?:<!--|-->)/],[PR.PR_LITERAL,/^(?:\\d+|\\d*\\.\\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\\\[\\da-f]+ ?))(?:[_a-z\\d\\-]|\\\\(?:\\\\[\\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\\s\\w\\'\\\"]+/]]),[\"css\"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\\\[\\da-f]+ ?))(?:[_a-z\\d\\-]|\\\\(?:\\\\[\\da-f]+ ?))*/i]]),[\"css-kw\"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\\)\\\"\\']+/]]),[\"css-str\"]);\n"
  },
  {
    "path": "coverage/sorter.js",
    "content": "/* eslint-disable */\nvar addSorting = (function() {\n    'use strict';\n    var cols,\n        currentSort = {\n            index: 0,\n            desc: false\n        };\n\n    // returns the summary table element\n    function getTable() {\n        return document.querySelector('.coverage-summary');\n    }\n    // returns the thead element of the summary table\n    function getTableHeader() {\n        return getTable().querySelector('thead tr');\n    }\n    // returns the tbody element of the summary table\n    function getTableBody() {\n        return getTable().querySelector('tbody');\n    }\n    // returns the th element for nth column\n    function getNthColumn(n) {\n        return getTableHeader().querySelectorAll('th')[n];\n    }\n\n    function onFilterInput() {\n        const searchValue = document.getElementById('fileSearch').value;\n        const rows = document.getElementsByTagName('tbody')[0].children;\n\n        // Try to create a RegExp from the searchValue. If it fails (invalid regex),\n        // it will be treated as a plain text search\n        let searchRegex;\n        try {\n            searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive\n        } catch (error) {\n            searchRegex = null;\n        }\n\n        for (let i = 0; i < rows.length; i++) {\n            const row = rows[i];\n            let isMatch = false;\n\n            if (searchRegex) {\n                // If a valid regex was created, use it for matching\n                isMatch = searchRegex.test(row.textContent);\n            } else {\n                // Otherwise, fall back to the original plain text search\n                isMatch = row.textContent\n                    .toLowerCase()\n                    .includes(searchValue.toLowerCase());\n            }\n\n            row.style.display = isMatch ? '' : 'none';\n        }\n    }\n\n    // loads the search box\n    function addSearchBox() {\n        var template = document.getElementById('filterTemplate');\n        var templateClone = template.content.cloneNode(true);\n        templateClone.getElementById('fileSearch').oninput = onFilterInput;\n        template.parentElement.appendChild(templateClone);\n    }\n\n    // loads all columns\n    function loadColumns() {\n        var colNodes = getTableHeader().querySelectorAll('th'),\n            colNode,\n            cols = [],\n            col,\n            i;\n\n        for (i = 0; i < colNodes.length; i += 1) {\n            colNode = colNodes[i];\n            col = {\n                key: colNode.getAttribute('data-col'),\n                sortable: !colNode.getAttribute('data-nosort'),\n                type: colNode.getAttribute('data-type') || 'string'\n            };\n            cols.push(col);\n            if (col.sortable) {\n                col.defaultDescSort = col.type === 'number';\n                colNode.innerHTML =\n                    colNode.innerHTML + '<span class=\"sorter\"></span>';\n            }\n        }\n        return cols;\n    }\n    // attaches a data attribute to every tr element with an object\n    // of data values keyed by column name\n    function loadRowData(tableRow) {\n        var tableCols = tableRow.querySelectorAll('td'),\n            colNode,\n            col,\n            data = {},\n            i,\n            val;\n        for (i = 0; i < tableCols.length; i += 1) {\n            colNode = tableCols[i];\n            col = cols[i];\n            val = colNode.getAttribute('data-value');\n            if (col.type === 'number') {\n                val = Number(val);\n            }\n            data[col.key] = val;\n        }\n        return data;\n    }\n    // loads all row data\n    function loadData() {\n        var rows = getTableBody().querySelectorAll('tr'),\n            i;\n\n        for (i = 0; i < rows.length; i += 1) {\n            rows[i].data = loadRowData(rows[i]);\n        }\n    }\n    // sorts the table using the data for the ith column\n    function sortByIndex(index, desc) {\n        var key = cols[index].key,\n            sorter = function(a, b) {\n                a = a.data[key];\n                b = b.data[key];\n                return a < b ? -1 : a > b ? 1 : 0;\n            },\n            finalSorter = sorter,\n            tableBody = document.querySelector('.coverage-summary tbody'),\n            rowNodes = tableBody.querySelectorAll('tr'),\n            rows = [],\n            i;\n\n        if (desc) {\n            finalSorter = function(a, b) {\n                return -1 * sorter(a, b);\n            };\n        }\n\n        for (i = 0; i < rowNodes.length; i += 1) {\n            rows.push(rowNodes[i]);\n            tableBody.removeChild(rowNodes[i]);\n        }\n\n        rows.sort(finalSorter);\n\n        for (i = 0; i < rows.length; i += 1) {\n            tableBody.appendChild(rows[i]);\n        }\n    }\n    // removes sort indicators for current column being sorted\n    function removeSortIndicators() {\n        var col = getNthColumn(currentSort.index),\n            cls = col.className;\n\n        cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, '');\n        col.className = cls;\n    }\n    // adds sort indicators for current column being sorted\n    function addSortIndicators() {\n        getNthColumn(currentSort.index).className += currentSort.desc\n            ? ' sorted-desc'\n            : ' sorted';\n    }\n    // adds event listeners for all sorter widgets\n    function enableUI() {\n        var i,\n            el,\n            ithSorter = function ithSorter(i) {\n                var col = cols[i];\n\n                return function() {\n                    var desc = col.defaultDescSort;\n\n                    if (currentSort.index === i) {\n                        desc = !currentSort.desc;\n                    }\n                    sortByIndex(i, desc);\n                    removeSortIndicators();\n                    currentSort.index = i;\n                    currentSort.desc = desc;\n                    addSortIndicators();\n                };\n            };\n        for (i = 0; i < cols.length; i += 1) {\n            if (cols[i].sortable) {\n                // add the click event handler on the th so users\n                // dont have to click on those tiny arrows\n                el = getNthColumn(i).querySelector('.sorter').parentElement;\n                if (el.addEventListener) {\n                    el.addEventListener('click', ithSorter(i));\n                } else {\n                    el.attachEvent('onclick', ithSorter(i));\n                }\n            }\n        }\n    }\n    // adds sorting functionality to the UI\n    return function() {\n        if (!getTable()) {\n            return;\n        }\n        cols = loadColumns();\n        loadData();\n        addSearchBox();\n        addSortIndicators();\n        enableUI();\n    };\n})();\n\nwindow.addEventListener('load', addSorting);\n"
  },
  {
    "path": "coverage/src/cli.ts.html",
    "content": "\n<!doctype html>\n<html lang=\"en\">\n\n<head>\n    <title>Code coverage report for src/cli.ts</title>\n    <meta charset=\"utf-8\" />\n    <link rel=\"stylesheet\" href=\"../prettify.css\" />\n    <link rel=\"stylesheet\" href=\"../base.css\" />\n    <link rel=\"shortcut icon\" type=\"image/x-icon\" href=\"../favicon.png\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <style type='text/css'>\n        .coverage-summary .sorter {\n            background-image: url(../sort-arrow-sprite.png);\n        }\n    </style>\n</head>\n    \n<body>\n<div class='wrapper'>\n    <div class='pad1'>\n        <h1><a href=\"../index.html\">All files</a> / <a href=\"index.html\">src</a> cli.ts</h1>\n        <div class='clearfix'>\n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Statements</span>\n                <span class='fraction'>0/39</span>\n            </div>\n        \n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Branches</span>\n                <span class='fraction'>0/1</span>\n            </div>\n        \n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Functions</span>\n                <span class='fraction'>0/1</span>\n            </div>\n        \n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Lines</span>\n                <span class='fraction'>0/39</span>\n            </div>\n        \n            \n        </div>\n        <p class=\"quiet\">\n            Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.\n        </p>\n        <template id=\"filterTemplate\">\n            <div class=\"quiet\">\n                Filter:\n                <input type=\"search\" id=\"fileSearch\">\n            </div>\n        </template>\n    </div>\n    <div class='status-line low'></div>\n    <pre><table class=\"coverage\">\n<tr><td class=\"line-count quiet\"><a name='L1'></a><a href='#L1'>1</a>\n<a name='L2'></a><a href='#L2'>2</a>\n<a name='L3'></a><a href='#L3'>3</a>\n<a name='L4'></a><a href='#L4'>4</a>\n<a name='L5'></a><a href='#L5'>5</a>\n<a name='L6'></a><a href='#L6'>6</a>\n<a name='L7'></a><a href='#L7'>7</a>\n<a name='L8'></a><a href='#L8'>8</a>\n<a name='L9'></a><a href='#L9'>9</a>\n<a name='L10'></a><a href='#L10'>10</a>\n<a name='L11'></a><a href='#L11'>11</a>\n<a name='L12'></a><a href='#L12'>12</a>\n<a name='L13'></a><a href='#L13'>13</a>\n<a name='L14'></a><a href='#L14'>14</a>\n<a name='L15'></a><a href='#L15'>15</a>\n<a name='L16'></a><a href='#L16'>16</a>\n<a name='L17'></a><a href='#L17'>17</a>\n<a name='L18'></a><a href='#L18'>18</a>\n<a name='L19'></a><a href='#L19'>19</a>\n<a name='L20'></a><a href='#L20'>20</a>\n<a name='L21'></a><a href='#L21'>21</a>\n<a name='L22'></a><a href='#L22'>22</a>\n<a name='L23'></a><a href='#L23'>23</a>\n<a name='L24'></a><a href='#L24'>24</a>\n<a name='L25'></a><a href='#L25'>25</a>\n<a name='L26'></a><a href='#L26'>26</a>\n<a name='L27'></a><a href='#L27'>27</a>\n<a name='L28'></a><a href='#L28'>28</a>\n<a name='L29'></a><a href='#L29'>29</a>\n<a name='L30'></a><a href='#L30'>30</a>\n<a name='L31'></a><a href='#L31'>31</a>\n<a name='L32'></a><a href='#L32'>32</a>\n<a name='L33'></a><a href='#L33'>33</a>\n<a name='L34'></a><a href='#L34'>34</a>\n<a name='L35'></a><a href='#L35'>35</a>\n<a name='L36'></a><a href='#L36'>36</a>\n<a name='L37'></a><a href='#L37'>37</a>\n<a name='L38'></a><a href='#L38'>38</a>\n<a name='L39'></a><a href='#L39'>39</a>\n<a name='L40'></a><a href='#L40'>40</a></td><td class=\"line-coverage quiet\"><span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-neutral\">&nbsp;</span></td><td class=\"text\"><pre class=\"prettyprint lang-js\"><span class=\"cstat-no\" title=\"statement not covered\" ><span class=\"fstat-no\" title=\"function not covered\" ><span class=\"branch-0 cbranch-no\" title=\"branch not covered\" >#!/usr/bin/env node</span></span></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >import { Command } from \"commander\";</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >import { init } from \"./commands/init\";</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >import { syncOne } from \"./commands/sync-one\";</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >import { sync } from \"./commands/sync\";</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >import { syncForever } from \"./commands/sync-forever\";</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >import { visualize } from \"./commands/visualize\";</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >const program = new Command();</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >program</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  .name(\"repomirror\")</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  .description(\"Sync and transform repositories using AI agents\")</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  .version(\"0.1.0\");</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >program</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  .command(\"init\")</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  .description(\"Initialize repomirror in current directory\")</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  .action(init);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >program.command(\"sync\").description(\"Run one sync iteration\").action(sync);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >program</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  .command(\"sync-one\")</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  .description(\"Run one sync iteration (alias for sync)\")</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  .action(syncOne);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >program</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  .command(\"sync-forever\")</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  .description(\"Run sync continuously\")</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  .action(syncForever);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >program</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  .command(\"visualize\")</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  .description(\"Visualize Claude output stream\")</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  .option(\"--debug\", \"Show debug timestamps\")</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  .action((options) =&gt; visualize(options));</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >program.parse();</span>\n&nbsp;</pre></td></tr></table></pre>\n\n                <div class='push'></div><!-- for sticky footer -->\n            </div><!-- /wrapper -->\n            <div class='footer quiet pad2 space-top1 center small'>\n                Code coverage generated by\n                <a href=\"https://istanbul.js.org/\" target=\"_blank\" rel=\"noopener noreferrer\">istanbul</a>\n                at 2025-08-23T07:39:41.860Z\n            </div>\n        <script src=\"../prettify.js\"></script>\n        <script>\n            window.onload = function () {\n                prettyPrint();\n            };\n        </script>\n        <script src=\"../sorter.js\"></script>\n        <script src=\"../block-navigation.js\"></script>\n    </body>\n</html>\n    "
  },
  {
    "path": "coverage/src/commands/index.html",
    "content": "\n<!doctype html>\n<html lang=\"en\">\n\n<head>\n    <title>Code coverage report for src/commands</title>\n    <meta charset=\"utf-8\" />\n    <link rel=\"stylesheet\" href=\"../../prettify.css\" />\n    <link rel=\"stylesheet\" href=\"../../base.css\" />\n    <link rel=\"shortcut icon\" type=\"image/x-icon\" href=\"../../favicon.png\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <style type='text/css'>\n        .coverage-summary .sorter {\n            background-image: url(../../sort-arrow-sprite.png);\n        }\n    </style>\n</head>\n    \n<body>\n<div class='wrapper'>\n    <div class='pad1'>\n        <h1><a href=\"../../index.html\">All files</a> src/commands</h1>\n        <div class='clearfix'>\n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Statements</span>\n                <span class='fraction'>0/755</span>\n            </div>\n        \n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Branches</span>\n                <span class='fraction'>0/5</span>\n            </div>\n        \n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Functions</span>\n                <span class='fraction'>0/5</span>\n            </div>\n        \n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Lines</span>\n                <span class='fraction'>0/755</span>\n            </div>\n        \n            \n        </div>\n        <p class=\"quiet\">\n            Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.\n        </p>\n        <template id=\"filterTemplate\">\n            <div class=\"quiet\">\n                Filter:\n                <input type=\"search\" id=\"fileSearch\">\n            </div>\n        </template>\n    </div>\n    <div class='status-line low'></div>\n    <div class=\"pad1\">\n<table class=\"coverage-summary\">\n<thead>\n<tr>\n   <th data-col=\"file\" data-fmt=\"html\" data-html=\"true\" class=\"file\">File</th>\n   <th data-col=\"pic\" data-type=\"number\" data-fmt=\"html\" data-html=\"true\" class=\"pic\"></th>\n   <th data-col=\"statements\" data-type=\"number\" data-fmt=\"pct\" class=\"pct\">Statements</th>\n   <th data-col=\"statements_raw\" data-type=\"number\" data-fmt=\"html\" class=\"abs\"></th>\n   <th data-col=\"branches\" data-type=\"number\" data-fmt=\"pct\" class=\"pct\">Branches</th>\n   <th data-col=\"branches_raw\" data-type=\"number\" data-fmt=\"html\" class=\"abs\"></th>\n   <th data-col=\"functions\" data-type=\"number\" data-fmt=\"pct\" class=\"pct\">Functions</th>\n   <th data-col=\"functions_raw\" data-type=\"number\" data-fmt=\"html\" class=\"abs\"></th>\n   <th data-col=\"lines\" data-type=\"number\" data-fmt=\"pct\" class=\"pct\">Lines</th>\n   <th data-col=\"lines_raw\" data-type=\"number\" data-fmt=\"html\" class=\"abs\"></th>\n</tr>\n</thead>\n<tbody><tr>\n\t<td class=\"file low\" data-value=\"init.ts\"><a href=\"init.ts.html\">init.ts</a></td>\n\t<td data-value=\"0\" class=\"pic low\">\n\t<div class=\"chart\"><div class=\"cover-fill\" style=\"width: 0%\"></div><div class=\"cover-empty\" style=\"width: 100%\"></div></div>\n\t</td>\n\t<td data-value=\"0\" class=\"pct low\">0%</td>\n\t<td data-value=\"255\" class=\"abs low\">0/255</td>\n\t<td data-value=\"0\" class=\"pct low\">0%</td>\n\t<td data-value=\"1\" class=\"abs low\">0/1</td>\n\t<td data-value=\"0\" class=\"pct low\">0%</td>\n\t<td data-value=\"1\" class=\"abs low\">0/1</td>\n\t<td data-value=\"0\" class=\"pct low\">0%</td>\n\t<td data-value=\"255\" class=\"abs low\">0/255</td>\n\t</tr>\n\n<tr>\n\t<td class=\"file low\" data-value=\"sync-forever.ts\"><a href=\"sync-forever.ts.html\">sync-forever.ts</a></td>\n\t<td data-value=\"0\" class=\"pic low\">\n\t<div class=\"chart\"><div class=\"cover-fill\" style=\"width: 0%\"></div><div class=\"cover-empty\" style=\"width: 100%\"></div></div>\n\t</td>\n\t<td data-value=\"0\" class=\"pct low\">0%</td>\n\t<td data-value=\"41\" class=\"abs low\">0/41</td>\n\t<td data-value=\"0\" class=\"pct low\">0%</td>\n\t<td data-value=\"1\" class=\"abs low\">0/1</td>\n\t<td data-value=\"0\" class=\"pct low\">0%</td>\n\t<td data-value=\"1\" class=\"abs low\">0/1</td>\n\t<td data-value=\"0\" class=\"pct low\">0%</td>\n\t<td data-value=\"41\" class=\"abs low\">0/41</td>\n\t</tr>\n\n<tr>\n\t<td class=\"file low\" data-value=\"sync-one.ts\"><a href=\"sync-one.ts.html\">sync-one.ts</a></td>\n\t<td data-value=\"0\" class=\"pic low\">\n\t<div class=\"chart\"><div class=\"cover-fill\" style=\"width: 0%\"></div><div class=\"cover-empty\" style=\"width: 100%\"></div></div>\n\t</td>\n\t<td data-value=\"0\" class=\"pct low\">0%</td>\n\t<td data-value=\"6\" class=\"abs low\">0/6</td>\n\t<td data-value=\"0\" class=\"pct low\">0%</td>\n\t<td data-value=\"1\" class=\"abs low\">0/1</td>\n\t<td data-value=\"0\" class=\"pct low\">0%</td>\n\t<td data-value=\"1\" class=\"abs low\">0/1</td>\n\t<td data-value=\"0\" class=\"pct low\">0%</td>\n\t<td data-value=\"6\" class=\"abs low\">0/6</td>\n\t</tr>\n\n<tr>\n\t<td class=\"file low\" data-value=\"sync.ts\"><a href=\"sync.ts.html\">sync.ts</a></td>\n\t<td data-value=\"0\" class=\"pic low\">\n\t<div class=\"chart\"><div class=\"cover-fill\" style=\"width: 0%\"></div><div class=\"cover-empty\" style=\"width: 100%\"></div></div>\n\t</td>\n\t<td data-value=\"0\" class=\"pct low\">0%</td>\n\t<td data-value=\"38\" class=\"abs low\">0/38</td>\n\t<td data-value=\"0\" class=\"pct low\">0%</td>\n\t<td data-value=\"1\" class=\"abs low\">0/1</td>\n\t<td data-value=\"0\" class=\"pct low\">0%</td>\n\t<td data-value=\"1\" class=\"abs low\">0/1</td>\n\t<td data-value=\"0\" class=\"pct low\">0%</td>\n\t<td data-value=\"38\" class=\"abs low\">0/38</td>\n\t</tr>\n\n<tr>\n\t<td class=\"file low\" data-value=\"visualize.ts\"><a href=\"visualize.ts.html\">visualize.ts</a></td>\n\t<td data-value=\"0\" class=\"pic low\">\n\t<div class=\"chart\"><div class=\"cover-fill\" style=\"width: 0%\"></div><div class=\"cover-empty\" style=\"width: 100%\"></div></div>\n\t</td>\n\t<td data-value=\"0\" class=\"pct low\">0%</td>\n\t<td data-value=\"415\" class=\"abs low\">0/415</td>\n\t<td data-value=\"0\" class=\"pct low\">0%</td>\n\t<td data-value=\"1\" class=\"abs low\">0/1</td>\n\t<td data-value=\"0\" class=\"pct low\">0%</td>\n\t<td data-value=\"1\" class=\"abs low\">0/1</td>\n\t<td data-value=\"0\" class=\"pct low\">0%</td>\n\t<td data-value=\"415\" class=\"abs low\">0/415</td>\n\t</tr>\n\n</tbody>\n</table>\n</div>\n                <div class='push'></div><!-- for sticky footer -->\n            </div><!-- /wrapper -->\n            <div class='footer quiet pad2 space-top1 center small'>\n                Code coverage generated by\n                <a href=\"https://istanbul.js.org/\" target=\"_blank\" rel=\"noopener noreferrer\">istanbul</a>\n                at 2025-08-23T07:39:41.860Z\n            </div>\n        <script src=\"../../prettify.js\"></script>\n        <script>\n            window.onload = function () {\n                prettyPrint();\n            };\n        </script>\n        <script src=\"../../sorter.js\"></script>\n        <script src=\"../../block-navigation.js\"></script>\n    </body>\n</html>\n    "
  },
  {
    "path": "coverage/src/commands/init.ts.html",
    "content": "\n<!doctype html>\n<html lang=\"en\">\n\n<head>\n    <title>Code coverage report for src/commands/init.ts</title>\n    <meta charset=\"utf-8\" />\n    <link rel=\"stylesheet\" href=\"../../prettify.css\" />\n    <link rel=\"stylesheet\" href=\"../../base.css\" />\n    <link rel=\"shortcut icon\" type=\"image/x-icon\" href=\"../../favicon.png\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <style type='text/css'>\n        .coverage-summary .sorter {\n            background-image: url(../../sort-arrow-sprite.png);\n        }\n    </style>\n</head>\n    \n<body>\n<div class='wrapper'>\n    <div class='pad1'>\n        <h1><a href=\"../../index.html\">All files</a> / <a href=\"index.html\">src/commands</a> init.ts</h1>\n        <div class='clearfix'>\n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Statements</span>\n                <span class='fraction'>0/255</span>\n            </div>\n        \n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Branches</span>\n                <span class='fraction'>0/1</span>\n            </div>\n        \n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Functions</span>\n                <span class='fraction'>0/1</span>\n            </div>\n        \n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Lines</span>\n                <span class='fraction'>0/255</span>\n            </div>\n        \n            \n        </div>\n        <p class=\"quiet\">\n            Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.\n        </p>\n        <template id=\"filterTemplate\">\n            <div class=\"quiet\">\n                Filter:\n                <input type=\"search\" id=\"fileSearch\">\n            </div>\n        </template>\n    </div>\n    <div class='status-line low'></div>\n    <pre><table class=\"coverage\">\n<tr><td class=\"line-count quiet\"><a name='L1'></a><a href='#L1'>1</a>\n<a name='L2'></a><a href='#L2'>2</a>\n<a name='L3'></a><a href='#L3'>3</a>\n<a name='L4'></a><a href='#L4'>4</a>\n<a name='L5'></a><a href='#L5'>5</a>\n<a name='L6'></a><a href='#L6'>6</a>\n<a name='L7'></a><a href='#L7'>7</a>\n<a name='L8'></a><a href='#L8'>8</a>\n<a name='L9'></a><a href='#L9'>9</a>\n<a name='L10'></a><a href='#L10'>10</a>\n<a name='L11'></a><a href='#L11'>11</a>\n<a name='L12'></a><a href='#L12'>12</a>\n<a name='L13'></a><a href='#L13'>13</a>\n<a name='L14'></a><a href='#L14'>14</a>\n<a name='L15'></a><a href='#L15'>15</a>\n<a name='L16'></a><a href='#L16'>16</a>\n<a name='L17'></a><a href='#L17'>17</a>\n<a name='L18'></a><a href='#L18'>18</a>\n<a name='L19'></a><a href='#L19'>19</a>\n<a name='L20'></a><a href='#L20'>20</a>\n<a name='L21'></a><a href='#L21'>21</a>\n<a name='L22'></a><a href='#L22'>22</a>\n<a name='L23'></a><a href='#L23'>23</a>\n<a name='L24'></a><a href='#L24'>24</a>\n<a name='L25'></a><a href='#L25'>25</a>\n<a name='L26'></a><a href='#L26'>26</a>\n<a name='L27'></a><a href='#L27'>27</a>\n<a name='L28'></a><a href='#L28'>28</a>\n<a name='L29'></a><a href='#L29'>29</a>\n<a name='L30'></a><a href='#L30'>30</a>\n<a name='L31'></a><a href='#L31'>31</a>\n<a name='L32'></a><a href='#L32'>32</a>\n<a name='L33'></a><a href='#L33'>33</a>\n<a name='L34'></a><a href='#L34'>34</a>\n<a name='L35'></a><a href='#L35'>35</a>\n<a name='L36'></a><a href='#L36'>36</a>\n<a name='L37'></a><a href='#L37'>37</a>\n<a name='L38'></a><a href='#L38'>38</a>\n<a name='L39'></a><a href='#L39'>39</a>\n<a name='L40'></a><a href='#L40'>40</a>\n<a name='L41'></a><a href='#L41'>41</a>\n<a name='L42'></a><a href='#L42'>42</a>\n<a name='L43'></a><a href='#L43'>43</a>\n<a name='L44'></a><a href='#L44'>44</a>\n<a name='L45'></a><a href='#L45'>45</a>\n<a name='L46'></a><a href='#L46'>46</a>\n<a name='L47'></a><a href='#L47'>47</a>\n<a name='L48'></a><a href='#L48'>48</a>\n<a name='L49'></a><a href='#L49'>49</a>\n<a name='L50'></a><a href='#L50'>50</a>\n<a name='L51'></a><a href='#L51'>51</a>\n<a name='L52'></a><a href='#L52'>52</a>\n<a name='L53'></a><a href='#L53'>53</a>\n<a name='L54'></a><a href='#L54'>54</a>\n<a name='L55'></a><a href='#L55'>55</a>\n<a name='L56'></a><a href='#L56'>56</a>\n<a name='L57'></a><a href='#L57'>57</a>\n<a name='L58'></a><a href='#L58'>58</a>\n<a name='L59'></a><a href='#L59'>59</a>\n<a name='L60'></a><a href='#L60'>60</a>\n<a name='L61'></a><a href='#L61'>61</a>\n<a name='L62'></a><a href='#L62'>62</a>\n<a name='L63'></a><a href='#L63'>63</a>\n<a name='L64'></a><a href='#L64'>64</a>\n<a name='L65'></a><a href='#L65'>65</a>\n<a name='L66'></a><a href='#L66'>66</a>\n<a name='L67'></a><a href='#L67'>67</a>\n<a name='L68'></a><a href='#L68'>68</a>\n<a name='L69'></a><a href='#L69'>69</a>\n<a name='L70'></a><a href='#L70'>70</a>\n<a name='L71'></a><a href='#L71'>71</a>\n<a name='L72'></a><a href='#L72'>72</a>\n<a name='L73'></a><a href='#L73'>73</a>\n<a name='L74'></a><a href='#L74'>74</a>\n<a name='L75'></a><a href='#L75'>75</a>\n<a name='L76'></a><a href='#L76'>76</a>\n<a name='L77'></a><a href='#L77'>77</a>\n<a name='L78'></a><a href='#L78'>78</a>\n<a name='L79'></a><a href='#L79'>79</a>\n<a name='L80'></a><a href='#L80'>80</a>\n<a name='L81'></a><a href='#L81'>81</a>\n<a name='L82'></a><a href='#L82'>82</a>\n<a name='L83'></a><a href='#L83'>83</a>\n<a name='L84'></a><a href='#L84'>84</a>\n<a name='L85'></a><a href='#L85'>85</a>\n<a name='L86'></a><a href='#L86'>86</a>\n<a name='L87'></a><a href='#L87'>87</a>\n<a name='L88'></a><a href='#L88'>88</a>\n<a name='L89'></a><a href='#L89'>89</a>\n<a name='L90'></a><a href='#L90'>90</a>\n<a name='L91'></a><a href='#L91'>91</a>\n<a name='L92'></a><a href='#L92'>92</a>\n<a name='L93'></a><a href='#L93'>93</a>\n<a name='L94'></a><a href='#L94'>94</a>\n<a name='L95'></a><a href='#L95'>95</a>\n<a name='L96'></a><a href='#L96'>96</a>\n<a name='L97'></a><a href='#L97'>97</a>\n<a name='L98'></a><a href='#L98'>98</a>\n<a name='L99'></a><a href='#L99'>99</a>\n<a name='L100'></a><a href='#L100'>100</a>\n<a name='L101'></a><a href='#L101'>101</a>\n<a name='L102'></a><a href='#L102'>102</a>\n<a name='L103'></a><a href='#L103'>103</a>\n<a name='L104'></a><a href='#L104'>104</a>\n<a name='L105'></a><a href='#L105'>105</a>\n<a name='L106'></a><a href='#L106'>106</a>\n<a name='L107'></a><a href='#L107'>107</a>\n<a name='L108'></a><a href='#L108'>108</a>\n<a name='L109'></a><a href='#L109'>109</a>\n<a name='L110'></a><a href='#L110'>110</a>\n<a name='L111'></a><a href='#L111'>111</a>\n<a name='L112'></a><a href='#L112'>112</a>\n<a name='L113'></a><a href='#L113'>113</a>\n<a name='L114'></a><a href='#L114'>114</a>\n<a name='L115'></a><a href='#L115'>115</a>\n<a name='L116'></a><a href='#L116'>116</a>\n<a name='L117'></a><a href='#L117'>117</a>\n<a name='L118'></a><a href='#L118'>118</a>\n<a name='L119'></a><a href='#L119'>119</a>\n<a name='L120'></a><a href='#L120'>120</a>\n<a name='L121'></a><a href='#L121'>121</a>\n<a name='L122'></a><a href='#L122'>122</a>\n<a name='L123'></a><a href='#L123'>123</a>\n<a name='L124'></a><a href='#L124'>124</a>\n<a name='L125'></a><a href='#L125'>125</a>\n<a name='L126'></a><a href='#L126'>126</a>\n<a name='L127'></a><a href='#L127'>127</a>\n<a name='L128'></a><a href='#L128'>128</a>\n<a name='L129'></a><a href='#L129'>129</a>\n<a name='L130'></a><a href='#L130'>130</a>\n<a name='L131'></a><a href='#L131'>131</a>\n<a name='L132'></a><a href='#L132'>132</a>\n<a name='L133'></a><a href='#L133'>133</a>\n<a name='L134'></a><a href='#L134'>134</a>\n<a name='L135'></a><a href='#L135'>135</a>\n<a name='L136'></a><a href='#L136'>136</a>\n<a name='L137'></a><a href='#L137'>137</a>\n<a name='L138'></a><a href='#L138'>138</a>\n<a name='L139'></a><a href='#L139'>139</a>\n<a name='L140'></a><a href='#L140'>140</a>\n<a name='L141'></a><a href='#L141'>141</a>\n<a name='L142'></a><a href='#L142'>142</a>\n<a name='L143'></a><a href='#L143'>143</a>\n<a name='L144'></a><a href='#L144'>144</a>\n<a name='L145'></a><a href='#L145'>145</a>\n<a name='L146'></a><a href='#L146'>146</a>\n<a name='L147'></a><a href='#L147'>147</a>\n<a name='L148'></a><a href='#L148'>148</a>\n<a name='L149'></a><a href='#L149'>149</a>\n<a name='L150'></a><a href='#L150'>150</a>\n<a name='L151'></a><a href='#L151'>151</a>\n<a name='L152'></a><a href='#L152'>152</a>\n<a name='L153'></a><a href='#L153'>153</a>\n<a name='L154'></a><a href='#L154'>154</a>\n<a name='L155'></a><a href='#L155'>155</a>\n<a name='L156'></a><a href='#L156'>156</a>\n<a name='L157'></a><a href='#L157'>157</a>\n<a name='L158'></a><a href='#L158'>158</a>\n<a name='L159'></a><a href='#L159'>159</a>\n<a name='L160'></a><a href='#L160'>160</a>\n<a name='L161'></a><a href='#L161'>161</a>\n<a name='L162'></a><a href='#L162'>162</a>\n<a name='L163'></a><a href='#L163'>163</a>\n<a name='L164'></a><a href='#L164'>164</a>\n<a name='L165'></a><a href='#L165'>165</a>\n<a name='L166'></a><a href='#L166'>166</a>\n<a name='L167'></a><a href='#L167'>167</a>\n<a name='L168'></a><a href='#L168'>168</a>\n<a name='L169'></a><a href='#L169'>169</a>\n<a name='L170'></a><a href='#L170'>170</a>\n<a name='L171'></a><a href='#L171'>171</a>\n<a name='L172'></a><a href='#L172'>172</a>\n<a name='L173'></a><a href='#L173'>173</a>\n<a name='L174'></a><a href='#L174'>174</a>\n<a name='L175'></a><a href='#L175'>175</a>\n<a name='L176'></a><a href='#L176'>176</a>\n<a name='L177'></a><a href='#L177'>177</a>\n<a name='L178'></a><a href='#L178'>178</a>\n<a name='L179'></a><a href='#L179'>179</a>\n<a name='L180'></a><a href='#L180'>180</a>\n<a name='L181'></a><a href='#L181'>181</a>\n<a name='L182'></a><a href='#L182'>182</a>\n<a name='L183'></a><a href='#L183'>183</a>\n<a name='L184'></a><a href='#L184'>184</a>\n<a name='L185'></a><a href='#L185'>185</a>\n<a name='L186'></a><a href='#L186'>186</a>\n<a name='L187'></a><a href='#L187'>187</a>\n<a name='L188'></a><a href='#L188'>188</a>\n<a name='L189'></a><a href='#L189'>189</a>\n<a name='L190'></a><a href='#L190'>190</a>\n<a name='L191'></a><a href='#L191'>191</a>\n<a name='L192'></a><a href='#L192'>192</a>\n<a name='L193'></a><a href='#L193'>193</a>\n<a name='L194'></a><a href='#L194'>194</a>\n<a name='L195'></a><a href='#L195'>195</a>\n<a name='L196'></a><a href='#L196'>196</a>\n<a name='L197'></a><a href='#L197'>197</a>\n<a name='L198'></a><a href='#L198'>198</a>\n<a name='L199'></a><a href='#L199'>199</a>\n<a name='L200'></a><a href='#L200'>200</a>\n<a name='L201'></a><a href='#L201'>201</a>\n<a name='L202'></a><a href='#L202'>202</a>\n<a name='L203'></a><a href='#L203'>203</a>\n<a name='L204'></a><a href='#L204'>204</a>\n<a name='L205'></a><a href='#L205'>205</a>\n<a name='L206'></a><a href='#L206'>206</a>\n<a name='L207'></a><a href='#L207'>207</a>\n<a name='L208'></a><a href='#L208'>208</a>\n<a name='L209'></a><a href='#L209'>209</a>\n<a name='L210'></a><a href='#L210'>210</a>\n<a name='L211'></a><a href='#L211'>211</a>\n<a name='L212'></a><a href='#L212'>212</a>\n<a name='L213'></a><a href='#L213'>213</a>\n<a name='L214'></a><a href='#L214'>214</a>\n<a name='L215'></a><a href='#L215'>215</a>\n<a name='L216'></a><a href='#L216'>216</a>\n<a name='L217'></a><a href='#L217'>217</a>\n<a name='L218'></a><a href='#L218'>218</a>\n<a name='L219'></a><a href='#L219'>219</a>\n<a name='L220'></a><a href='#L220'>220</a>\n<a name='L221'></a><a href='#L221'>221</a>\n<a name='L222'></a><a href='#L222'>222</a>\n<a name='L223'></a><a href='#L223'>223</a>\n<a name='L224'></a><a href='#L224'>224</a>\n<a name='L225'></a><a href='#L225'>225</a>\n<a name='L226'></a><a href='#L226'>226</a>\n<a name='L227'></a><a href='#L227'>227</a>\n<a name='L228'></a><a href='#L228'>228</a>\n<a name='L229'></a><a href='#L229'>229</a>\n<a name='L230'></a><a href='#L230'>230</a>\n<a name='L231'></a><a href='#L231'>231</a>\n<a name='L232'></a><a href='#L232'>232</a>\n<a name='L233'></a><a href='#L233'>233</a>\n<a name='L234'></a><a href='#L234'>234</a>\n<a name='L235'></a><a href='#L235'>235</a>\n<a name='L236'></a><a href='#L236'>236</a>\n<a name='L237'></a><a href='#L237'>237</a>\n<a name='L238'></a><a href='#L238'>238</a>\n<a name='L239'></a><a href='#L239'>239</a>\n<a name='L240'></a><a href='#L240'>240</a>\n<a name='L241'></a><a href='#L241'>241</a>\n<a name='L242'></a><a href='#L242'>242</a>\n<a name='L243'></a><a href='#L243'>243</a>\n<a name='L244'></a><a href='#L244'>244</a>\n<a name='L245'></a><a href='#L245'>245</a>\n<a name='L246'></a><a href='#L246'>246</a>\n<a name='L247'></a><a href='#L247'>247</a>\n<a name='L248'></a><a href='#L248'>248</a>\n<a name='L249'></a><a href='#L249'>249</a>\n<a name='L250'></a><a href='#L250'>250</a>\n<a name='L251'></a><a href='#L251'>251</a>\n<a name='L252'></a><a href='#L252'>252</a>\n<a name='L253'></a><a href='#L253'>253</a>\n<a name='L254'></a><a href='#L254'>254</a>\n<a name='L255'></a><a href='#L255'>255</a>\n<a name='L256'></a><a href='#L256'>256</a></td><td class=\"line-coverage quiet\"><span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-neutral\">&nbsp;</span></td><td class=\"text\"><pre class=\"prettyprint lang-js\"><span class=\"cstat-no\" title=\"statement not covered\" ><span class=\"fstat-no\" title=\"function not covered\" ><span class=\"branch-0 cbranch-no\" title=\"branch not covered\" >import { promises as fs } from \"fs\";</span></span></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >import { join, basename } from \"path\";</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >import inquirer from \"inquirer\";</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >import chalk from \"chalk\";</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >import ora from \"ora\";</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >import { query } from \"@anthropic-ai/claude-code\";</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >import { execa } from \"execa\";</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >interface InitOptions {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  sourceRepo: string;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  targetRepo: string;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  transformationInstructions: string;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >}</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >export async function init(): Promise&lt;void&gt; {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  console.log(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    chalk.cyan(\"I'll help you maintain a transformed copy of this repo:\\n\"),</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  );</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  // Get current directory name for default target</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  const currentDir = process.cwd();</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  const repoName = basename(currentDir);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  const defaultTarget = `../${repoName}-transformed`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  const answers = await inquirer.prompt&lt;InitOptions&gt;([</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      type: \"input\",</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      name: \"sourceRepo\",</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      message: \"Source Repo you want to transform:\",</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      default: \"./\",</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    },</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      type: \"input\",</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      name: \"targetRepo\",</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      message: \"Where do you want to transform code to:\",</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      default: defaultTarget,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    },</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      type: \"input\",</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      name: \"transformationInstructions\",</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      message: \"What changes do you want to make:\",</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      default: \"translate this python repo to typescript\",</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    },</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  ]);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  // Perform preflight checks</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  await performPreflightChecks(answers.targetRepo);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  // Generate transformation prompt using Claude SDK</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  const spinner = ora(\"Generating transformation prompt...\").start();</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  try {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    const optimizedPrompt = await generateTransformationPrompt(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      answers.sourceRepo,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      answers.targetRepo,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      answers.transformationInstructions,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    );</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    spinner.succeed(\"Generated transformation prompt\");</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    // Create .repomirror directory and files</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    await createRepoMirrorFiles(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      answers.sourceRepo,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      answers.targetRepo,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      optimizedPrompt,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    );</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    console.log(chalk.green(\"\\n✅ repomirror initialized successfully!\"));</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    console.log(chalk.cyan(\"\\nNext steps:\"));</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    console.log(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      chalk.white(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        \"• Run `npx repomirror sync` - this will run the sync.sh script once\",</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      ),</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    );</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    console.log(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      chalk.white(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        \"• Run `npx repomirror sync-forever` - this will run the ralph.sh script, working forever to implement all the changes\",</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      ),</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    );</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  } catch (error) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    spinner.fail(\"Failed to generate transformation prompt\");</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    console.error(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      chalk.red(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        `Error: ${error instanceof Error ? error.message : String(error)}`,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      ),</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    );</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    process.exit(1);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >}</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >async function performPreflightChecks(targetRepo: string): Promise&lt;void&gt; {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  const spinner = ora(\"Performing preflight checks...\").start();</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  try {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    // Check if target directory exists</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    try {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      await fs.access(targetRepo);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    } catch {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      spinner.fail(`Target directory ${targetRepo} does not exist`);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      process.exit(1);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    // Check if target directory is a git repo</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    try {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      await execa(\"git\", [\"rev-parse\", \"--git-dir\"], { cwd: targetRepo });</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    } catch {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      spinner.fail(`Target directory ${targetRepo} is not a git repository`);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      process.exit(1);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    // Check if target directory has at least one upstream</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    try {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      const { stdout } = await execa(\"git\", [\"remote\", \"-v\"], {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        cwd: targetRepo,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      });</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      if (!stdout.trim()) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        spinner.fail(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >          `Target directory ${targetRepo} has no git remotes configured`,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        );</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        process.exit(1);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    } catch {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      spinner.fail(`Failed to check git remotes in ${targetRepo}`);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      process.exit(1);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    // Check if Claude Code is configured</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    try {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      const { stdout } = await execa(\"claude\", [\"-p\", \"say hi\"]);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      if (!stdout.toLowerCase().includes(\"hi\")) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        spinner.fail(\"Claude Code test failed - response doesn't contain 'hi'\");</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        process.exit(1);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    } catch {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      spinner.fail(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        \"Claude Code is not properly configured. Please run `claude` to set up your profile\",</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      );</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      process.exit(1);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    spinner.succeed(\"All preflight checks passed\");</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  } catch (error) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    spinner.fail(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      `Preflight check failed: ${error instanceof Error ? error.message : String(error)}`,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    );</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    process.exit(1);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >}</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >async function generateTransformationPrompt(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  sourceRepo: string,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  targetRepo: string,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  transformationInstructions: string,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >): Promise&lt;string&gt; {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  const metaPrompt = `your task is to generate an optimized prompt for repo transformation. The prompt should match the format of the examples below.</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >&lt;example 1&gt;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >Your job is to port [SOURCE PATH] monorepo (for react) to [TARGET PATH] (for vue) and maintain the repository.</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >You have access to the current [SOURCE PATH] repositorty as well as the [TARGET PATH] repository.</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >Make a commit and push your changes after every single file edit.</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >Use the [TARGET_PATH]/agent/ directory as a scratchpad for your work. Store long term plans and todo lists there.</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >The original project was mostly tested by manually running the code. When porting, you will need to write end to end and unit tests for the project. But make sure to spend most of your time on the actual porting, not on the testing. A good heuristic is to spend 80% of your time on the actual porting, and 20% on the testing.</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >&lt;/example 1&gt;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >&lt;example 2&gt;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >Your job is to port browser-use monorepo (Python) to browser-use-ts (Typescript) and maintain the repository.</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >You have access to the current [SOURCE PATH] repositorty as well as the target [TARGET_PATH] repository.</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >Make a commit and push your changes after every single file edit.</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >Use the [TARGET PATH]/agent/ directory as a scratchpad for your work. Store long term plans and todo lists there.</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >The original project was mostly tested by manually running the code. When porting, you will need to write end to end and unit tests for the project. But make sure to spend most of your time on the actual porting, not on the testing. A good heuristic is to spend 80% of your time on the actual porting, and 20% on the testing.</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >&lt;/example 2&gt;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >The users instructions for transformation are:</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >&lt;user instructions&gt;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >${transformationInstructions}</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >&lt;/user instructions&gt;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >Your Job:</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >When you are ready, respond with EXACTLY the prompt matching the example, tailored for following the users' instructions and nothing else.</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >You should follow the format EXACTLY, filling in information based on what you learn from a CURSORY exploration of the source repo (this directory). Ensure you ONLY use the read tools (Read, Search, Grep, LS, Glob, etc) to explore the repo. You only need enough sense to build a good prompt, so dont use subagents.`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  let result = \"\";</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  for await (const message of query({</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    prompt: metaPrompt,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  })) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    if (message.type === \"result\" &amp;&amp; !message.is_error) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      result = (message as any).result || \"\";</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      break;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  if (!result) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    throw new Error(\"Failed to generate transformation prompt\");</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  // Replace placeholders with actual paths</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  return result</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    .replace(/\\[SOURCE PATH\\]/g, sourceRepo)</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    .replace(/\\[TARGET PATH\\]/g, targetRepo)</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    .replace(/\\[TARGET_PATH\\]/g, targetRepo);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >}</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >async function createRepoMirrorFiles(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  sourceRepo: string,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  targetRepo: string,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  optimizedPrompt: string,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >): Promise&lt;void&gt; {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  const repoMirrorDir = join(process.cwd(), \".repomirror\");</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  // Create .repomirror directory</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  await fs.mkdir(repoMirrorDir, { recursive: true });</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  // Create prompt.md</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  await fs.writeFile(join(repoMirrorDir, \"prompt.md\"), optimizedPrompt);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  // Create sync.sh</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  const syncScript = `#!/bin/bash</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >cat .repomirror/prompt.md | \\\\</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        claude -p --output-format=stream-json --verbose --dangerously-skip-permissions --add-dir ${targetRepo} | \\\\</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        tee -a .repomirror/claude_output.jsonl | \\\\</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        npx repomirror visualize --debug;`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  await fs.writeFile(join(repoMirrorDir, \"sync.sh\"), syncScript, {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    mode: 0o755,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  });</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  // Create ralph.sh</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  const ralphScript = `#!/bin/bash</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >while :; do</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  ./.repomirror/sync.sh</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  echo -e \"===SLEEP===\\\\n===SLEEP===\\\\n\"; echo 'looping';</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  sleep 10;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >done`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  await fs.writeFile(join(repoMirrorDir, \"ralph.sh\"), ralphScript, {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    mode: 0o755,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  });</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  // Create .gitignore</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  await fs.writeFile(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    join(repoMirrorDir, \".gitignore\"),</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    \"claude_output.jsonl\\n\",</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  );</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >}</span>\n&nbsp;</pre></td></tr></table></pre>\n\n                <div class='push'></div><!-- for sticky footer -->\n            </div><!-- /wrapper -->\n            <div class='footer quiet pad2 space-top1 center small'>\n                Code coverage generated by\n                <a href=\"https://istanbul.js.org/\" target=\"_blank\" rel=\"noopener noreferrer\">istanbul</a>\n                at 2025-08-23T07:39:41.860Z\n            </div>\n        <script src=\"../../prettify.js\"></script>\n        <script>\n            window.onload = function () {\n                prettyPrint();\n            };\n        </script>\n        <script src=\"../../sorter.js\"></script>\n        <script src=\"../../block-navigation.js\"></script>\n    </body>\n</html>\n    "
  },
  {
    "path": "coverage/src/commands/sync-forever.ts.html",
    "content": "\n<!doctype html>\n<html lang=\"en\">\n\n<head>\n    <title>Code coverage report for src/commands/sync-forever.ts</title>\n    <meta charset=\"utf-8\" />\n    <link rel=\"stylesheet\" href=\"../../prettify.css\" />\n    <link rel=\"stylesheet\" href=\"../../base.css\" />\n    <link rel=\"shortcut icon\" type=\"image/x-icon\" href=\"../../favicon.png\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <style type='text/css'>\n        .coverage-summary .sorter {\n            background-image: url(../../sort-arrow-sprite.png);\n        }\n    </style>\n</head>\n    \n<body>\n<div class='wrapper'>\n    <div class='pad1'>\n        <h1><a href=\"../../index.html\">All files</a> / <a href=\"index.html\">src/commands</a> sync-forever.ts</h1>\n        <div class='clearfix'>\n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Statements</span>\n                <span class='fraction'>0/41</span>\n            </div>\n        \n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Branches</span>\n                <span class='fraction'>0/1</span>\n            </div>\n        \n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Functions</span>\n                <span class='fraction'>0/1</span>\n            </div>\n        \n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Lines</span>\n                <span class='fraction'>0/41</span>\n            </div>\n        \n            \n        </div>\n        <p class=\"quiet\">\n            Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.\n        </p>\n        <template id=\"filterTemplate\">\n            <div class=\"quiet\">\n                Filter:\n                <input type=\"search\" id=\"fileSearch\">\n            </div>\n        </template>\n    </div>\n    <div class='status-line low'></div>\n    <pre><table class=\"coverage\">\n<tr><td class=\"line-count quiet\"><a name='L1'></a><a href='#L1'>1</a>\n<a name='L2'></a><a href='#L2'>2</a>\n<a name='L3'></a><a href='#L3'>3</a>\n<a name='L4'></a><a href='#L4'>4</a>\n<a name='L5'></a><a href='#L5'>5</a>\n<a name='L6'></a><a href='#L6'>6</a>\n<a name='L7'></a><a href='#L7'>7</a>\n<a name='L8'></a><a href='#L8'>8</a>\n<a name='L9'></a><a href='#L9'>9</a>\n<a name='L10'></a><a href='#L10'>10</a>\n<a name='L11'></a><a href='#L11'>11</a>\n<a name='L12'></a><a href='#L12'>12</a>\n<a name='L13'></a><a href='#L13'>13</a>\n<a name='L14'></a><a href='#L14'>14</a>\n<a name='L15'></a><a href='#L15'>15</a>\n<a name='L16'></a><a href='#L16'>16</a>\n<a name='L17'></a><a href='#L17'>17</a>\n<a name='L18'></a><a href='#L18'>18</a>\n<a name='L19'></a><a href='#L19'>19</a>\n<a name='L20'></a><a href='#L20'>20</a>\n<a name='L21'></a><a href='#L21'>21</a>\n<a name='L22'></a><a href='#L22'>22</a>\n<a name='L23'></a><a href='#L23'>23</a>\n<a name='L24'></a><a href='#L24'>24</a>\n<a name='L25'></a><a href='#L25'>25</a>\n<a name='L26'></a><a href='#L26'>26</a>\n<a name='L27'></a><a href='#L27'>27</a>\n<a name='L28'></a><a href='#L28'>28</a>\n<a name='L29'></a><a href='#L29'>29</a>\n<a name='L30'></a><a href='#L30'>30</a>\n<a name='L31'></a><a href='#L31'>31</a>\n<a name='L32'></a><a href='#L32'>32</a>\n<a name='L33'></a><a href='#L33'>33</a>\n<a name='L34'></a><a href='#L34'>34</a>\n<a name='L35'></a><a href='#L35'>35</a>\n<a name='L36'></a><a href='#L36'>36</a>\n<a name='L37'></a><a href='#L37'>37</a>\n<a name='L38'></a><a href='#L38'>38</a>\n<a name='L39'></a><a href='#L39'>39</a>\n<a name='L40'></a><a href='#L40'>40</a>\n<a name='L41'></a><a href='#L41'>41</a>\n<a name='L42'></a><a href='#L42'>42</a></td><td class=\"line-coverage quiet\"><span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-neutral\">&nbsp;</span></td><td class=\"text\"><pre class=\"prettyprint lang-js\"><span class=\"cstat-no\" title=\"statement not covered\" ><span class=\"fstat-no\" title=\"function not covered\" ><span class=\"branch-0 cbranch-no\" title=\"branch not covered\" >import { execa } from \"execa\";</span></span></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >import chalk from \"chalk\";</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >import { join } from \"path\";</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >import { promises as fs } from \"fs\";</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >export async function syncForever(): Promise&lt;void&gt; {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  const ralphScript = join(process.cwd(), \".repomirror\", \"ralph.sh\");</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  try {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    // Check if ralph.sh exists</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    await fs.access(ralphScript);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  } catch {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    console.error(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      chalk.red(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        \"Error: .repomirror/ralph.sh not found. Run 'npx repomirror init' first.\",</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      ),</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    );</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    process.exit(1);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  console.log(chalk.cyan(\"Running ralph.sh (continuous sync)...\"));</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  console.log(chalk.yellow(\"Press Ctrl+C to stop\"));</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  try {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    await execa(\"bash\", [ralphScript], {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      stdio: \"inherit\",</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      cwd: process.cwd(),</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    });</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  } catch (error) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    if (error instanceof Error &amp;&amp; (error as any).signal === \"SIGINT\") {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      console.log(chalk.yellow(\"\\nStopped by user\"));</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    } else {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      console.error(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        chalk.red(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >          `Sync forever failed: ${error instanceof Error ? error.message : String(error)}`,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        ),</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      );</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      process.exit(1);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >}</span>\n&nbsp;</pre></td></tr></table></pre>\n\n                <div class='push'></div><!-- for sticky footer -->\n            </div><!-- /wrapper -->\n            <div class='footer quiet pad2 space-top1 center small'>\n                Code coverage generated by\n                <a href=\"https://istanbul.js.org/\" target=\"_blank\" rel=\"noopener noreferrer\">istanbul</a>\n                at 2025-08-23T07:39:41.860Z\n            </div>\n        <script src=\"../../prettify.js\"></script>\n        <script>\n            window.onload = function () {\n                prettyPrint();\n            };\n        </script>\n        <script src=\"../../sorter.js\"></script>\n        <script src=\"../../block-navigation.js\"></script>\n    </body>\n</html>\n    "
  },
  {
    "path": "coverage/src/commands/sync-one.ts.html",
    "content": "\n<!doctype html>\n<html lang=\"en\">\n\n<head>\n    <title>Code coverage report for src/commands/sync-one.ts</title>\n    <meta charset=\"utf-8\" />\n    <link rel=\"stylesheet\" href=\"../../prettify.css\" />\n    <link rel=\"stylesheet\" href=\"../../base.css\" />\n    <link rel=\"shortcut icon\" type=\"image/x-icon\" href=\"../../favicon.png\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <style type='text/css'>\n        .coverage-summary .sorter {\n            background-image: url(../../sort-arrow-sprite.png);\n        }\n    </style>\n</head>\n    \n<body>\n<div class='wrapper'>\n    <div class='pad1'>\n        <h1><a href=\"../../index.html\">All files</a> / <a href=\"index.html\">src/commands</a> sync-one.ts</h1>\n        <div class='clearfix'>\n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Statements</span>\n                <span class='fraction'>0/6</span>\n            </div>\n        \n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Branches</span>\n                <span class='fraction'>0/1</span>\n            </div>\n        \n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Functions</span>\n                <span class='fraction'>0/1</span>\n            </div>\n        \n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Lines</span>\n                <span class='fraction'>0/6</span>\n            </div>\n        \n            \n        </div>\n        <p class=\"quiet\">\n            Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.\n        </p>\n        <template id=\"filterTemplate\">\n            <div class=\"quiet\">\n                Filter:\n                <input type=\"search\" id=\"fileSearch\">\n            </div>\n        </template>\n    </div>\n    <div class='status-line low'></div>\n    <pre><table class=\"coverage\">\n<tr><td class=\"line-count quiet\"><a name='L1'></a><a href='#L1'>1</a>\n<a name='L2'></a><a href='#L2'>2</a>\n<a name='L3'></a><a href='#L3'>3</a>\n<a name='L4'></a><a href='#L4'>4</a>\n<a name='L5'></a><a href='#L5'>5</a>\n<a name='L6'></a><a href='#L6'>6</a>\n<a name='L7'></a><a href='#L7'>7</a></td><td class=\"line-coverage quiet\"><span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-neutral\">&nbsp;</span></td><td class=\"text\"><pre class=\"prettyprint lang-js\"><span class=\"cstat-no\" title=\"statement not covered\" ><span class=\"fstat-no\" title=\"function not covered\" ><span class=\"branch-0 cbranch-no\" title=\"branch not covered\" >import { sync } from \"./sync\";</span></span></span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >// sync-one is just an alias for sync</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >export async function syncOne(): Promise&lt;void&gt; {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  await sync();</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >}</span>\n&nbsp;</pre></td></tr></table></pre>\n\n                <div class='push'></div><!-- for sticky footer -->\n            </div><!-- /wrapper -->\n            <div class='footer quiet pad2 space-top1 center small'>\n                Code coverage generated by\n                <a href=\"https://istanbul.js.org/\" target=\"_blank\" rel=\"noopener noreferrer\">istanbul</a>\n                at 2025-08-23T07:39:41.860Z\n            </div>\n        <script src=\"../../prettify.js\"></script>\n        <script>\n            window.onload = function () {\n                prettyPrint();\n            };\n        </script>\n        <script src=\"../../sorter.js\"></script>\n        <script src=\"../../block-navigation.js\"></script>\n    </body>\n</html>\n    "
  },
  {
    "path": "coverage/src/commands/sync.ts.html",
    "content": "\n<!doctype html>\n<html lang=\"en\">\n\n<head>\n    <title>Code coverage report for src/commands/sync.ts</title>\n    <meta charset=\"utf-8\" />\n    <link rel=\"stylesheet\" href=\"../../prettify.css\" />\n    <link rel=\"stylesheet\" href=\"../../base.css\" />\n    <link rel=\"shortcut icon\" type=\"image/x-icon\" href=\"../../favicon.png\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <style type='text/css'>\n        .coverage-summary .sorter {\n            background-image: url(../../sort-arrow-sprite.png);\n        }\n    </style>\n</head>\n    \n<body>\n<div class='wrapper'>\n    <div class='pad1'>\n        <h1><a href=\"../../index.html\">All files</a> / <a href=\"index.html\">src/commands</a> sync.ts</h1>\n        <div class='clearfix'>\n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Statements</span>\n                <span class='fraction'>0/38</span>\n            </div>\n        \n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Branches</span>\n                <span class='fraction'>0/1</span>\n            </div>\n        \n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Functions</span>\n                <span class='fraction'>0/1</span>\n            </div>\n        \n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Lines</span>\n                <span class='fraction'>0/38</span>\n            </div>\n        \n            \n        </div>\n        <p class=\"quiet\">\n            Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.\n        </p>\n        <template id=\"filterTemplate\">\n            <div class=\"quiet\">\n                Filter:\n                <input type=\"search\" id=\"fileSearch\">\n            </div>\n        </template>\n    </div>\n    <div class='status-line low'></div>\n    <pre><table class=\"coverage\">\n<tr><td class=\"line-count quiet\"><a name='L1'></a><a href='#L1'>1</a>\n<a name='L2'></a><a href='#L2'>2</a>\n<a name='L3'></a><a href='#L3'>3</a>\n<a name='L4'></a><a href='#L4'>4</a>\n<a name='L5'></a><a href='#L5'>5</a>\n<a name='L6'></a><a href='#L6'>6</a>\n<a name='L7'></a><a href='#L7'>7</a>\n<a name='L8'></a><a href='#L8'>8</a>\n<a name='L9'></a><a href='#L9'>9</a>\n<a name='L10'></a><a href='#L10'>10</a>\n<a name='L11'></a><a href='#L11'>11</a>\n<a name='L12'></a><a href='#L12'>12</a>\n<a name='L13'></a><a href='#L13'>13</a>\n<a name='L14'></a><a href='#L14'>14</a>\n<a name='L15'></a><a href='#L15'>15</a>\n<a name='L16'></a><a href='#L16'>16</a>\n<a name='L17'></a><a href='#L17'>17</a>\n<a name='L18'></a><a href='#L18'>18</a>\n<a name='L19'></a><a href='#L19'>19</a>\n<a name='L20'></a><a href='#L20'>20</a>\n<a name='L21'></a><a href='#L21'>21</a>\n<a name='L22'></a><a href='#L22'>22</a>\n<a name='L23'></a><a href='#L23'>23</a>\n<a name='L24'></a><a href='#L24'>24</a>\n<a name='L25'></a><a href='#L25'>25</a>\n<a name='L26'></a><a href='#L26'>26</a>\n<a name='L27'></a><a href='#L27'>27</a>\n<a name='L28'></a><a href='#L28'>28</a>\n<a name='L29'></a><a href='#L29'>29</a>\n<a name='L30'></a><a href='#L30'>30</a>\n<a name='L31'></a><a href='#L31'>31</a>\n<a name='L32'></a><a href='#L32'>32</a>\n<a name='L33'></a><a href='#L33'>33</a>\n<a name='L34'></a><a href='#L34'>34</a>\n<a name='L35'></a><a href='#L35'>35</a>\n<a name='L36'></a><a href='#L36'>36</a>\n<a name='L37'></a><a href='#L37'>37</a>\n<a name='L38'></a><a href='#L38'>38</a>\n<a name='L39'></a><a href='#L39'>39</a></td><td class=\"line-coverage quiet\"><span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-neutral\">&nbsp;</span></td><td class=\"text\"><pre class=\"prettyprint lang-js\"><span class=\"cstat-no\" title=\"statement not covered\" ><span class=\"fstat-no\" title=\"function not covered\" ><span class=\"branch-0 cbranch-no\" title=\"branch not covered\" >import { execa } from \"execa\";</span></span></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >import chalk from \"chalk\";</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >import { join } from \"path\";</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >import { promises as fs } from \"fs\";</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >export async function sync(): Promise&lt;void&gt; {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  const syncScript = join(process.cwd(), \".repomirror\", \"sync.sh\");</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  try {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    // Check if sync.sh exists</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    await fs.access(syncScript);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  } catch {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    console.error(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      chalk.red(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        \"Error: .repomirror/sync.sh not found. Run 'npx repomirror init' first.\",</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      ),</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    );</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    process.exit(1);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  console.log(chalk.cyan(\"Running sync.sh...\"));</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  try {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    await execa(\"bash\", [syncScript], {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      stdio: \"inherit\",</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      cwd: process.cwd(),</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    });</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    console.log(chalk.green(\"Sync completed successfully\"));</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  } catch (error) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    console.error(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      chalk.red(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        `Sync failed: ${error instanceof Error ? error.message : String(error)}`,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      ),</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    );</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    process.exit(1);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >}</span>\n&nbsp;</pre></td></tr></table></pre>\n\n                <div class='push'></div><!-- for sticky footer -->\n            </div><!-- /wrapper -->\n            <div class='footer quiet pad2 space-top1 center small'>\n                Code coverage generated by\n                <a href=\"https://istanbul.js.org/\" target=\"_blank\" rel=\"noopener noreferrer\">istanbul</a>\n                at 2025-08-23T07:39:41.860Z\n            </div>\n        <script src=\"../../prettify.js\"></script>\n        <script>\n            window.onload = function () {\n                prettyPrint();\n            };\n        </script>\n        <script src=\"../../sorter.js\"></script>\n        <script src=\"../../block-navigation.js\"></script>\n    </body>\n</html>\n    "
  },
  {
    "path": "coverage/src/commands/visualize.ts.html",
    "content": "\n<!doctype html>\n<html lang=\"en\">\n\n<head>\n    <title>Code coverage report for src/commands/visualize.ts</title>\n    <meta charset=\"utf-8\" />\n    <link rel=\"stylesheet\" href=\"../../prettify.css\" />\n    <link rel=\"stylesheet\" href=\"../../base.css\" />\n    <link rel=\"shortcut icon\" type=\"image/x-icon\" href=\"../../favicon.png\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <style type='text/css'>\n        .coverage-summary .sorter {\n            background-image: url(../../sort-arrow-sprite.png);\n        }\n    </style>\n</head>\n    \n<body>\n<div class='wrapper'>\n    <div class='pad1'>\n        <h1><a href=\"../../index.html\">All files</a> / <a href=\"index.html\">src/commands</a> visualize.ts</h1>\n        <div class='clearfix'>\n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Statements</span>\n                <span class='fraction'>0/415</span>\n            </div>\n        \n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Branches</span>\n                <span class='fraction'>0/1</span>\n            </div>\n        \n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Functions</span>\n                <span class='fraction'>0/1</span>\n            </div>\n        \n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Lines</span>\n                <span class='fraction'>0/415</span>\n            </div>\n        \n            \n        </div>\n        <p class=\"quiet\">\n            Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.\n        </p>\n        <template id=\"filterTemplate\">\n            <div class=\"quiet\">\n                Filter:\n                <input type=\"search\" id=\"fileSearch\">\n            </div>\n        </template>\n    </div>\n    <div class='status-line low'></div>\n    <pre><table class=\"coverage\">\n<tr><td class=\"line-count quiet\"><a name='L1'></a><a href='#L1'>1</a>\n<a name='L2'></a><a href='#L2'>2</a>\n<a name='L3'></a><a href='#L3'>3</a>\n<a name='L4'></a><a href='#L4'>4</a>\n<a name='L5'></a><a href='#L5'>5</a>\n<a name='L6'></a><a href='#L6'>6</a>\n<a name='L7'></a><a href='#L7'>7</a>\n<a name='L8'></a><a href='#L8'>8</a>\n<a name='L9'></a><a href='#L9'>9</a>\n<a name='L10'></a><a href='#L10'>10</a>\n<a name='L11'></a><a href='#L11'>11</a>\n<a name='L12'></a><a href='#L12'>12</a>\n<a name='L13'></a><a href='#L13'>13</a>\n<a name='L14'></a><a href='#L14'>14</a>\n<a name='L15'></a><a href='#L15'>15</a>\n<a name='L16'></a><a href='#L16'>16</a>\n<a name='L17'></a><a href='#L17'>17</a>\n<a name='L18'></a><a href='#L18'>18</a>\n<a name='L19'></a><a href='#L19'>19</a>\n<a name='L20'></a><a href='#L20'>20</a>\n<a name='L21'></a><a href='#L21'>21</a>\n<a name='L22'></a><a href='#L22'>22</a>\n<a name='L23'></a><a href='#L23'>23</a>\n<a name='L24'></a><a href='#L24'>24</a>\n<a name='L25'></a><a href='#L25'>25</a>\n<a name='L26'></a><a href='#L26'>26</a>\n<a name='L27'></a><a href='#L27'>27</a>\n<a name='L28'></a><a href='#L28'>28</a>\n<a name='L29'></a><a href='#L29'>29</a>\n<a name='L30'></a><a href='#L30'>30</a>\n<a name='L31'></a><a href='#L31'>31</a>\n<a name='L32'></a><a href='#L32'>32</a>\n<a name='L33'></a><a href='#L33'>33</a>\n<a name='L34'></a><a href='#L34'>34</a>\n<a name='L35'></a><a href='#L35'>35</a>\n<a name='L36'></a><a href='#L36'>36</a>\n<a name='L37'></a><a href='#L37'>37</a>\n<a name='L38'></a><a href='#L38'>38</a>\n<a name='L39'></a><a href='#L39'>39</a>\n<a name='L40'></a><a href='#L40'>40</a>\n<a name='L41'></a><a href='#L41'>41</a>\n<a name='L42'></a><a href='#L42'>42</a>\n<a name='L43'></a><a href='#L43'>43</a>\n<a name='L44'></a><a href='#L44'>44</a>\n<a name='L45'></a><a href='#L45'>45</a>\n<a name='L46'></a><a href='#L46'>46</a>\n<a name='L47'></a><a href='#L47'>47</a>\n<a name='L48'></a><a href='#L48'>48</a>\n<a name='L49'></a><a href='#L49'>49</a>\n<a name='L50'></a><a href='#L50'>50</a>\n<a name='L51'></a><a href='#L51'>51</a>\n<a name='L52'></a><a href='#L52'>52</a>\n<a name='L53'></a><a href='#L53'>53</a>\n<a name='L54'></a><a href='#L54'>54</a>\n<a name='L55'></a><a href='#L55'>55</a>\n<a name='L56'></a><a href='#L56'>56</a>\n<a name='L57'></a><a href='#L57'>57</a>\n<a name='L58'></a><a href='#L58'>58</a>\n<a name='L59'></a><a href='#L59'>59</a>\n<a name='L60'></a><a href='#L60'>60</a>\n<a name='L61'></a><a href='#L61'>61</a>\n<a name='L62'></a><a href='#L62'>62</a>\n<a name='L63'></a><a href='#L63'>63</a>\n<a name='L64'></a><a href='#L64'>64</a>\n<a name='L65'></a><a href='#L65'>65</a>\n<a name='L66'></a><a href='#L66'>66</a>\n<a name='L67'></a><a href='#L67'>67</a>\n<a name='L68'></a><a href='#L68'>68</a>\n<a name='L69'></a><a href='#L69'>69</a>\n<a name='L70'></a><a href='#L70'>70</a>\n<a name='L71'></a><a href='#L71'>71</a>\n<a name='L72'></a><a href='#L72'>72</a>\n<a name='L73'></a><a href='#L73'>73</a>\n<a name='L74'></a><a href='#L74'>74</a>\n<a name='L75'></a><a href='#L75'>75</a>\n<a name='L76'></a><a href='#L76'>76</a>\n<a name='L77'></a><a href='#L77'>77</a>\n<a name='L78'></a><a href='#L78'>78</a>\n<a name='L79'></a><a href='#L79'>79</a>\n<a name='L80'></a><a href='#L80'>80</a>\n<a name='L81'></a><a href='#L81'>81</a>\n<a name='L82'></a><a href='#L82'>82</a>\n<a name='L83'></a><a href='#L83'>83</a>\n<a name='L84'></a><a href='#L84'>84</a>\n<a name='L85'></a><a href='#L85'>85</a>\n<a name='L86'></a><a href='#L86'>86</a>\n<a name='L87'></a><a href='#L87'>87</a>\n<a name='L88'></a><a href='#L88'>88</a>\n<a name='L89'></a><a href='#L89'>89</a>\n<a name='L90'></a><a href='#L90'>90</a>\n<a name='L91'></a><a href='#L91'>91</a>\n<a name='L92'></a><a href='#L92'>92</a>\n<a name='L93'></a><a href='#L93'>93</a>\n<a name='L94'></a><a href='#L94'>94</a>\n<a name='L95'></a><a href='#L95'>95</a>\n<a name='L96'></a><a href='#L96'>96</a>\n<a name='L97'></a><a href='#L97'>97</a>\n<a name='L98'></a><a href='#L98'>98</a>\n<a name='L99'></a><a href='#L99'>99</a>\n<a name='L100'></a><a href='#L100'>100</a>\n<a name='L101'></a><a href='#L101'>101</a>\n<a name='L102'></a><a href='#L102'>102</a>\n<a name='L103'></a><a href='#L103'>103</a>\n<a name='L104'></a><a href='#L104'>104</a>\n<a name='L105'></a><a href='#L105'>105</a>\n<a name='L106'></a><a href='#L106'>106</a>\n<a name='L107'></a><a href='#L107'>107</a>\n<a name='L108'></a><a href='#L108'>108</a>\n<a name='L109'></a><a href='#L109'>109</a>\n<a name='L110'></a><a href='#L110'>110</a>\n<a name='L111'></a><a href='#L111'>111</a>\n<a name='L112'></a><a href='#L112'>112</a>\n<a name='L113'></a><a href='#L113'>113</a>\n<a name='L114'></a><a href='#L114'>114</a>\n<a name='L115'></a><a href='#L115'>115</a>\n<a name='L116'></a><a href='#L116'>116</a>\n<a name='L117'></a><a href='#L117'>117</a>\n<a name='L118'></a><a href='#L118'>118</a>\n<a name='L119'></a><a href='#L119'>119</a>\n<a name='L120'></a><a href='#L120'>120</a>\n<a name='L121'></a><a href='#L121'>121</a>\n<a name='L122'></a><a href='#L122'>122</a>\n<a name='L123'></a><a href='#L123'>123</a>\n<a name='L124'></a><a href='#L124'>124</a>\n<a name='L125'></a><a href='#L125'>125</a>\n<a name='L126'></a><a href='#L126'>126</a>\n<a name='L127'></a><a href='#L127'>127</a>\n<a name='L128'></a><a href='#L128'>128</a>\n<a name='L129'></a><a href='#L129'>129</a>\n<a name='L130'></a><a href='#L130'>130</a>\n<a name='L131'></a><a href='#L131'>131</a>\n<a name='L132'></a><a href='#L132'>132</a>\n<a name='L133'></a><a href='#L133'>133</a>\n<a name='L134'></a><a href='#L134'>134</a>\n<a name='L135'></a><a href='#L135'>135</a>\n<a name='L136'></a><a href='#L136'>136</a>\n<a name='L137'></a><a href='#L137'>137</a>\n<a name='L138'></a><a href='#L138'>138</a>\n<a name='L139'></a><a href='#L139'>139</a>\n<a name='L140'></a><a href='#L140'>140</a>\n<a name='L141'></a><a href='#L141'>141</a>\n<a name='L142'></a><a href='#L142'>142</a>\n<a name='L143'></a><a href='#L143'>143</a>\n<a name='L144'></a><a href='#L144'>144</a>\n<a name='L145'></a><a href='#L145'>145</a>\n<a name='L146'></a><a href='#L146'>146</a>\n<a name='L147'></a><a href='#L147'>147</a>\n<a name='L148'></a><a href='#L148'>148</a>\n<a name='L149'></a><a href='#L149'>149</a>\n<a name='L150'></a><a href='#L150'>150</a>\n<a name='L151'></a><a href='#L151'>151</a>\n<a name='L152'></a><a href='#L152'>152</a>\n<a name='L153'></a><a href='#L153'>153</a>\n<a name='L154'></a><a href='#L154'>154</a>\n<a name='L155'></a><a href='#L155'>155</a>\n<a name='L156'></a><a href='#L156'>156</a>\n<a name='L157'></a><a href='#L157'>157</a>\n<a name='L158'></a><a href='#L158'>158</a>\n<a name='L159'></a><a href='#L159'>159</a>\n<a name='L160'></a><a href='#L160'>160</a>\n<a name='L161'></a><a href='#L161'>161</a>\n<a name='L162'></a><a href='#L162'>162</a>\n<a name='L163'></a><a href='#L163'>163</a>\n<a name='L164'></a><a href='#L164'>164</a>\n<a name='L165'></a><a href='#L165'>165</a>\n<a name='L166'></a><a href='#L166'>166</a>\n<a name='L167'></a><a href='#L167'>167</a>\n<a name='L168'></a><a href='#L168'>168</a>\n<a name='L169'></a><a href='#L169'>169</a>\n<a name='L170'></a><a href='#L170'>170</a>\n<a name='L171'></a><a href='#L171'>171</a>\n<a name='L172'></a><a href='#L172'>172</a>\n<a name='L173'></a><a href='#L173'>173</a>\n<a name='L174'></a><a href='#L174'>174</a>\n<a name='L175'></a><a href='#L175'>175</a>\n<a name='L176'></a><a href='#L176'>176</a>\n<a name='L177'></a><a href='#L177'>177</a>\n<a name='L178'></a><a href='#L178'>178</a>\n<a name='L179'></a><a href='#L179'>179</a>\n<a name='L180'></a><a href='#L180'>180</a>\n<a name='L181'></a><a href='#L181'>181</a>\n<a name='L182'></a><a href='#L182'>182</a>\n<a name='L183'></a><a href='#L183'>183</a>\n<a name='L184'></a><a href='#L184'>184</a>\n<a name='L185'></a><a href='#L185'>185</a>\n<a name='L186'></a><a href='#L186'>186</a>\n<a name='L187'></a><a href='#L187'>187</a>\n<a name='L188'></a><a href='#L188'>188</a>\n<a name='L189'></a><a href='#L189'>189</a>\n<a name='L190'></a><a href='#L190'>190</a>\n<a name='L191'></a><a href='#L191'>191</a>\n<a name='L192'></a><a href='#L192'>192</a>\n<a name='L193'></a><a href='#L193'>193</a>\n<a name='L194'></a><a href='#L194'>194</a>\n<a name='L195'></a><a href='#L195'>195</a>\n<a name='L196'></a><a href='#L196'>196</a>\n<a name='L197'></a><a href='#L197'>197</a>\n<a name='L198'></a><a href='#L198'>198</a>\n<a name='L199'></a><a href='#L199'>199</a>\n<a name='L200'></a><a href='#L200'>200</a>\n<a name='L201'></a><a href='#L201'>201</a>\n<a name='L202'></a><a href='#L202'>202</a>\n<a name='L203'></a><a href='#L203'>203</a>\n<a name='L204'></a><a href='#L204'>204</a>\n<a name='L205'></a><a href='#L205'>205</a>\n<a name='L206'></a><a href='#L206'>206</a>\n<a name='L207'></a><a href='#L207'>207</a>\n<a name='L208'></a><a href='#L208'>208</a>\n<a name='L209'></a><a href='#L209'>209</a>\n<a name='L210'></a><a href='#L210'>210</a>\n<a name='L211'></a><a href='#L211'>211</a>\n<a name='L212'></a><a href='#L212'>212</a>\n<a name='L213'></a><a href='#L213'>213</a>\n<a name='L214'></a><a href='#L214'>214</a>\n<a name='L215'></a><a href='#L215'>215</a>\n<a name='L216'></a><a href='#L216'>216</a>\n<a name='L217'></a><a href='#L217'>217</a>\n<a name='L218'></a><a href='#L218'>218</a>\n<a name='L219'></a><a href='#L219'>219</a>\n<a name='L220'></a><a href='#L220'>220</a>\n<a name='L221'></a><a href='#L221'>221</a>\n<a name='L222'></a><a href='#L222'>222</a>\n<a name='L223'></a><a href='#L223'>223</a>\n<a name='L224'></a><a href='#L224'>224</a>\n<a name='L225'></a><a href='#L225'>225</a>\n<a name='L226'></a><a href='#L226'>226</a>\n<a name='L227'></a><a href='#L227'>227</a>\n<a name='L228'></a><a href='#L228'>228</a>\n<a name='L229'></a><a href='#L229'>229</a>\n<a name='L230'></a><a href='#L230'>230</a>\n<a name='L231'></a><a href='#L231'>231</a>\n<a name='L232'></a><a href='#L232'>232</a>\n<a name='L233'></a><a href='#L233'>233</a>\n<a name='L234'></a><a href='#L234'>234</a>\n<a name='L235'></a><a href='#L235'>235</a>\n<a name='L236'></a><a href='#L236'>236</a>\n<a name='L237'></a><a href='#L237'>237</a>\n<a name='L238'></a><a href='#L238'>238</a>\n<a name='L239'></a><a href='#L239'>239</a>\n<a name='L240'></a><a href='#L240'>240</a>\n<a name='L241'></a><a href='#L241'>241</a>\n<a name='L242'></a><a href='#L242'>242</a>\n<a name='L243'></a><a href='#L243'>243</a>\n<a name='L244'></a><a href='#L244'>244</a>\n<a name='L245'></a><a href='#L245'>245</a>\n<a name='L246'></a><a href='#L246'>246</a>\n<a name='L247'></a><a href='#L247'>247</a>\n<a name='L248'></a><a href='#L248'>248</a>\n<a name='L249'></a><a href='#L249'>249</a>\n<a name='L250'></a><a href='#L250'>250</a>\n<a name='L251'></a><a href='#L251'>251</a>\n<a name='L252'></a><a href='#L252'>252</a>\n<a name='L253'></a><a href='#L253'>253</a>\n<a name='L254'></a><a href='#L254'>254</a>\n<a name='L255'></a><a href='#L255'>255</a>\n<a name='L256'></a><a href='#L256'>256</a>\n<a name='L257'></a><a href='#L257'>257</a>\n<a name='L258'></a><a href='#L258'>258</a>\n<a name='L259'></a><a href='#L259'>259</a>\n<a name='L260'></a><a href='#L260'>260</a>\n<a name='L261'></a><a href='#L261'>261</a>\n<a name='L262'></a><a href='#L262'>262</a>\n<a name='L263'></a><a href='#L263'>263</a>\n<a name='L264'></a><a href='#L264'>264</a>\n<a name='L265'></a><a href='#L265'>265</a>\n<a name='L266'></a><a href='#L266'>266</a>\n<a name='L267'></a><a href='#L267'>267</a>\n<a name='L268'></a><a href='#L268'>268</a>\n<a name='L269'></a><a href='#L269'>269</a>\n<a name='L270'></a><a href='#L270'>270</a>\n<a name='L271'></a><a href='#L271'>271</a>\n<a name='L272'></a><a href='#L272'>272</a>\n<a name='L273'></a><a href='#L273'>273</a>\n<a name='L274'></a><a href='#L274'>274</a>\n<a name='L275'></a><a href='#L275'>275</a>\n<a name='L276'></a><a href='#L276'>276</a>\n<a name='L277'></a><a href='#L277'>277</a>\n<a name='L278'></a><a href='#L278'>278</a>\n<a name='L279'></a><a href='#L279'>279</a>\n<a name='L280'></a><a href='#L280'>280</a>\n<a name='L281'></a><a href='#L281'>281</a>\n<a name='L282'></a><a href='#L282'>282</a>\n<a name='L283'></a><a href='#L283'>283</a>\n<a name='L284'></a><a href='#L284'>284</a>\n<a name='L285'></a><a href='#L285'>285</a>\n<a name='L286'></a><a href='#L286'>286</a>\n<a name='L287'></a><a href='#L287'>287</a>\n<a name='L288'></a><a href='#L288'>288</a>\n<a name='L289'></a><a href='#L289'>289</a>\n<a name='L290'></a><a href='#L290'>290</a>\n<a name='L291'></a><a href='#L291'>291</a>\n<a name='L292'></a><a href='#L292'>292</a>\n<a name='L293'></a><a href='#L293'>293</a>\n<a name='L294'></a><a href='#L294'>294</a>\n<a name='L295'></a><a href='#L295'>295</a>\n<a name='L296'></a><a href='#L296'>296</a>\n<a name='L297'></a><a href='#L297'>297</a>\n<a name='L298'></a><a href='#L298'>298</a>\n<a name='L299'></a><a href='#L299'>299</a>\n<a name='L300'></a><a href='#L300'>300</a>\n<a name='L301'></a><a href='#L301'>301</a>\n<a name='L302'></a><a href='#L302'>302</a>\n<a name='L303'></a><a href='#L303'>303</a>\n<a name='L304'></a><a href='#L304'>304</a>\n<a name='L305'></a><a href='#L305'>305</a>\n<a name='L306'></a><a href='#L306'>306</a>\n<a name='L307'></a><a href='#L307'>307</a>\n<a name='L308'></a><a href='#L308'>308</a>\n<a name='L309'></a><a href='#L309'>309</a>\n<a name='L310'></a><a href='#L310'>310</a>\n<a name='L311'></a><a href='#L311'>311</a>\n<a name='L312'></a><a href='#L312'>312</a>\n<a name='L313'></a><a href='#L313'>313</a>\n<a name='L314'></a><a href='#L314'>314</a>\n<a name='L315'></a><a href='#L315'>315</a>\n<a name='L316'></a><a href='#L316'>316</a>\n<a name='L317'></a><a href='#L317'>317</a>\n<a name='L318'></a><a href='#L318'>318</a>\n<a name='L319'></a><a href='#L319'>319</a>\n<a name='L320'></a><a href='#L320'>320</a>\n<a name='L321'></a><a href='#L321'>321</a>\n<a name='L322'></a><a href='#L322'>322</a>\n<a name='L323'></a><a href='#L323'>323</a>\n<a name='L324'></a><a href='#L324'>324</a>\n<a name='L325'></a><a href='#L325'>325</a>\n<a name='L326'></a><a href='#L326'>326</a>\n<a name='L327'></a><a href='#L327'>327</a>\n<a name='L328'></a><a href='#L328'>328</a>\n<a name='L329'></a><a href='#L329'>329</a>\n<a name='L330'></a><a href='#L330'>330</a>\n<a name='L331'></a><a href='#L331'>331</a>\n<a name='L332'></a><a href='#L332'>332</a>\n<a name='L333'></a><a href='#L333'>333</a>\n<a name='L334'></a><a href='#L334'>334</a>\n<a name='L335'></a><a href='#L335'>335</a>\n<a name='L336'></a><a href='#L336'>336</a>\n<a name='L337'></a><a href='#L337'>337</a>\n<a name='L338'></a><a href='#L338'>338</a>\n<a name='L339'></a><a href='#L339'>339</a>\n<a name='L340'></a><a href='#L340'>340</a>\n<a name='L341'></a><a href='#L341'>341</a>\n<a name='L342'></a><a href='#L342'>342</a>\n<a name='L343'></a><a href='#L343'>343</a>\n<a name='L344'></a><a href='#L344'>344</a>\n<a name='L345'></a><a href='#L345'>345</a>\n<a name='L346'></a><a href='#L346'>346</a>\n<a name='L347'></a><a href='#L347'>347</a>\n<a name='L348'></a><a href='#L348'>348</a>\n<a name='L349'></a><a href='#L349'>349</a>\n<a name='L350'></a><a href='#L350'>350</a>\n<a name='L351'></a><a href='#L351'>351</a>\n<a name='L352'></a><a href='#L352'>352</a>\n<a name='L353'></a><a href='#L353'>353</a>\n<a name='L354'></a><a href='#L354'>354</a>\n<a name='L355'></a><a href='#L355'>355</a>\n<a name='L356'></a><a href='#L356'>356</a>\n<a name='L357'></a><a href='#L357'>357</a>\n<a name='L358'></a><a href='#L358'>358</a>\n<a name='L359'></a><a href='#L359'>359</a>\n<a name='L360'></a><a href='#L360'>360</a>\n<a name='L361'></a><a href='#L361'>361</a>\n<a name='L362'></a><a href='#L362'>362</a>\n<a name='L363'></a><a href='#L363'>363</a>\n<a name='L364'></a><a href='#L364'>364</a>\n<a name='L365'></a><a href='#L365'>365</a>\n<a name='L366'></a><a href='#L366'>366</a>\n<a name='L367'></a><a href='#L367'>367</a>\n<a name='L368'></a><a href='#L368'>368</a>\n<a name='L369'></a><a href='#L369'>369</a>\n<a name='L370'></a><a href='#L370'>370</a>\n<a name='L371'></a><a href='#L371'>371</a>\n<a name='L372'></a><a href='#L372'>372</a>\n<a name='L373'></a><a href='#L373'>373</a>\n<a name='L374'></a><a href='#L374'>374</a>\n<a name='L375'></a><a href='#L375'>375</a>\n<a name='L376'></a><a href='#L376'>376</a>\n<a name='L377'></a><a href='#L377'>377</a>\n<a name='L378'></a><a href='#L378'>378</a>\n<a name='L379'></a><a href='#L379'>379</a>\n<a name='L380'></a><a href='#L380'>380</a>\n<a name='L381'></a><a href='#L381'>381</a>\n<a name='L382'></a><a href='#L382'>382</a>\n<a name='L383'></a><a href='#L383'>383</a>\n<a name='L384'></a><a href='#L384'>384</a>\n<a name='L385'></a><a href='#L385'>385</a>\n<a name='L386'></a><a href='#L386'>386</a>\n<a name='L387'></a><a href='#L387'>387</a>\n<a name='L388'></a><a href='#L388'>388</a>\n<a name='L389'></a><a href='#L389'>389</a>\n<a name='L390'></a><a href='#L390'>390</a>\n<a name='L391'></a><a href='#L391'>391</a>\n<a name='L392'></a><a href='#L392'>392</a>\n<a name='L393'></a><a href='#L393'>393</a>\n<a name='L394'></a><a href='#L394'>394</a>\n<a name='L395'></a><a href='#L395'>395</a>\n<a name='L396'></a><a href='#L396'>396</a>\n<a name='L397'></a><a href='#L397'>397</a>\n<a name='L398'></a><a href='#L398'>398</a>\n<a name='L399'></a><a href='#L399'>399</a>\n<a name='L400'></a><a href='#L400'>400</a>\n<a name='L401'></a><a href='#L401'>401</a>\n<a name='L402'></a><a href='#L402'>402</a>\n<a name='L403'></a><a href='#L403'>403</a>\n<a name='L404'></a><a href='#L404'>404</a>\n<a name='L405'></a><a href='#L405'>405</a>\n<a name='L406'></a><a href='#L406'>406</a>\n<a name='L407'></a><a href='#L407'>407</a>\n<a name='L408'></a><a href='#L408'>408</a>\n<a name='L409'></a><a href='#L409'>409</a>\n<a name='L410'></a><a href='#L410'>410</a>\n<a name='L411'></a><a href='#L411'>411</a>\n<a name='L412'></a><a href='#L412'>412</a>\n<a name='L413'></a><a href='#L413'>413</a>\n<a name='L414'></a><a href='#L414'>414</a>\n<a name='L415'></a><a href='#L415'>415</a>\n<a name='L416'></a><a href='#L416'>416</a></td><td class=\"line-coverage quiet\"><span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-no\">&nbsp;</span>\n<span class=\"cline-any cline-neutral\">&nbsp;</span></td><td class=\"text\"><pre class=\"prettyprint lang-js\"><span class=\"cstat-no\" title=\"statement not covered\" >import { createInterface } from \"node:readline\";<span class=\"fstat-no\" title=\"function not covered\" ><span class=\"branch-0 cbranch-no\" title=\"branch not covered\" ></span></span></span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >const colors = {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  reset: \"\\x1b[0m\",</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  bright: \"\\x1b[1m\",</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  dim: \"\\x1b[2m\",</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  red: \"\\x1b[31m\",</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  green: \"\\x1b[32m\",</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  yellow: \"\\x1b[33m\",</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  blue: \"\\x1b[34m\",</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  magenta: \"\\x1b[35m\",</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  cyan: \"\\x1b[36m\",</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >};</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >function getTypeColor(type: string): string {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  switch (type) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    case \"system\":</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      return colors.magenta;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    case \"user\":</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      return colors.blue;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    case \"assistant\":</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      return colors.green;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    case \"tool_use\":</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      return colors.cyan;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    case \"tool_result\":</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      return colors.yellow;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    case \"message\":</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      return colors.dim;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    case \"text\":</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      return colors.reset;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    default:</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      return colors.reset;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >}</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >interface Todo {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  status: string;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  content: string;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  priority?: string;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >}</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >function formatTodoList(todos: Todo[]): string {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  let output = `📋 ${colors.bright}${colors.cyan}Todo List Update${colors.reset}\\n`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  const statusColors = {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    completed: colors.dim + colors.green,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    in_progress: colors.bright + colors.yellow,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    pending: colors.reset,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  };</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  const statusIcons = {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    completed: \"✅\",</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    in_progress: \"🔄\",</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    pending: \"⏸️\",</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  };</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  const priorityColors = {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    high: colors.red,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    medium: colors.yellow,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    low: colors.dim,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  };</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  todos.forEach((todo, index) =&gt; {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    const statusColor =</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      statusColors[todo.status as keyof typeof statusColors] || colors.reset;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    const statusIcon =</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      statusIcons[todo.status as keyof typeof statusIcons] || \"❓\";</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    const priorityColor =</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      priorityColors[todo.priority as keyof typeof priorityColors] ||</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      colors.reset;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    const checkbox = todo.status === \"completed\" ? \"☑️\" : \"☐\";</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    output += `  ${checkbox} ${statusIcon} ${statusColor}${todo.content}${colors.reset}`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    output += ` ${priorityColor}[${todo.priority}]${colors.reset}`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    if (todo.status === \"in_progress\") {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      output += ` ${colors.bright}${colors.yellow}← ACTIVE${colors.reset}`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    output += \"\\n\";</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  });</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  // Add summary stats</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  const completed = todos.filter((t) =&gt; t.status === \"completed\").length;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  const inProgress = todos.filter((t) =&gt; t.status === \"in_progress\").length;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  const pending = todos.filter((t) =&gt; t.status === \"pending\").length;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  output += `\\n  ${colors.dim}📊 Progress: ${colors.green}${completed} completed${colors.reset}`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  output += `${colors.dim}, ${colors.yellow}${inProgress} active${colors.reset}`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  output += `${colors.dim}, ${colors.reset}${pending} pending${colors.reset}`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  output += `${colors.dim} (${Math.round((completed / todos.length) * 100)}% done)${colors.reset}`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  return output;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >}</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >function formatConcise(json: any): string {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  const type = json.type || \"unknown\";</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  const typeColor = getTypeColor(type);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  let output = `⏺ ${typeColor}${type.charAt(0).toUpperCase() + type.slice(1)}${colors.reset}`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  // Special handling for TodoWrite calls</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  if (</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    type === \"assistant\" &amp;&amp;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    json.message?.content?.[0]?.name === \"TodoWrite\"</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  ) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    const toolInput = json.message.content[0].input;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    if (toolInput?.todos &amp;&amp; Array.isArray(toolInput.todos)) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      return formatTodoList(toolInput.todos);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  // Add context based on type</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  if (type === \"assistant\" &amp;&amp; json.message?.content?.[0]?.name) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    const toolName = json.message.content[0].name;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    const toolInput = json.message.content[0].input;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    // Format tool name with key arguments</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    let toolDisplay = `${colors.cyan}${toolName}${colors.reset}`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    if (toolInput) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      const keyArgs = [];</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      // Extract the most important argument for each tool type</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      if (toolInput.file_path) keyArgs.push(toolInput.file_path);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      else if (toolInput.path) keyArgs.push(toolInput.path);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      else if (toolInput.pattern) keyArgs.push(`\"${toolInput.pattern}\"`);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      else if (toolInput.command) keyArgs.push(toolInput.command);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      else if (toolInput.cmd) keyArgs.push(toolInput.cmd);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      else if (toolInput.query) keyArgs.push(`\"${toolInput.query}\"`);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      else if (toolInput.description) keyArgs.push(toolInput.description);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      else if (toolInput.prompt)</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        keyArgs.push(`\"${toolInput.prompt.substring(0, 30)}...\"`);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      else if (toolInput.url) keyArgs.push(toolInput.url);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      if (keyArgs.length &gt; 0) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        toolDisplay += `(${colors.green}${keyArgs[0]}${colors.reset})`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    output = `⏺ ${toolDisplay}`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    // Show additional arguments on next lines for complex tools</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    if (toolInput) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      const additionalArgs = [];</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      if (toolName === \"Bash\" &amp;&amp; toolInput.cwd) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        additionalArgs.push(`cwd: ${toolInput.cwd}`);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      if (toolInput.limit) additionalArgs.push(`limit: ${toolInput.limit}`);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      if (toolInput.offset) additionalArgs.push(`offset: ${toolInput.offset}`);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      if (toolInput.include)</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        additionalArgs.push(`include: ${toolInput.include}`);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      if (toolInput.old_string &amp;&amp; toolInput.new_string) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        additionalArgs.push(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >          `replace: \"${toolInput.old_string.substring(0, 20)}...\" → \"${toolInput.new_string.substring(0, 20)}...\"`,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        );</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      if (toolInput.timeout)</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        additionalArgs.push(`timeout: ${toolInput.timeout}ms`);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      if (additionalArgs.length &gt; 0) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        output += `\\n  ⎿  ${colors.dim}${additionalArgs.join(\", \")}${colors.reset}`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  } else if (type === \"tool_result\" &amp;&amp; json.name) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    output += `(${colors.cyan}${json.name}${colors.reset})`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  } else if (type === \"user\" &amp;&amp; json.message?.content?.[0]) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    const content = json.message.content[0];</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    if (content.type === \"tool_result\") {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      // Override the type display for tool results</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      output = `⏺ ${colors.yellow}Tool Result${colors.reset}`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      // Show result summary and first 2 lines</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      if (content.content) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        const resultText =</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >          typeof content.content === \"string\"</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >            ? content.content</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >            : JSON.stringify(content.content);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        const lines = resultText.split(\"\\n\");</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        const chars = resultText.length;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        output += `\\n  ⎿  ${colors.dim}${lines.length} lines, ${chars} chars${colors.reset}`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        if (content.is_error) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >          output += ` ${colors.red}ERROR${colors.reset}`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        // Show first 2 lines of content</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        if (lines.length &gt; 0 &amp;&amp; lines[0].trim()) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >          output += `\\n  ⎿  ${colors.reset}${lines[0]}${colors.reset}`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        if (lines.length &gt; 1 &amp;&amp; lines[1].trim()) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >          output += `\\n      ${colors.dim}${lines[1]}${colors.reset}`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    } else if (content.text) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      const text = content.text.substring(0, 50);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      output += `: ${colors.dim}${text}${text.length === 50 ? \"...\" : \"\"}${colors.reset}`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  } else if (type === \"system\" &amp;&amp; json.subtype) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    output += `(${colors.dim}${json.subtype}${colors.reset})`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  // Show assistant message content if it exists</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  if (type === \"assistant\" &amp;&amp; json.message?.content) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    const textContent = json.message.content.find(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      (c: any) =&gt; c.type === \"text\",</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    );</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    if (textContent?.text) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      const lines = textContent.text.split(\"\\n\").slice(0, 3); // Show first 3 lines</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      output += `\\n  ⎿  ${colors.reset}${lines[0]}${colors.reset}`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      if (lines.length &gt; 1) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        output += `\\n      ${colors.dim}${lines[1]}${colors.reset}`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      if (lines.length &gt; 2) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        output += `\\n      ${colors.dim}${lines[2]}${colors.reset}`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      if (textContent.text.split(\"\\n\").length &gt; 3) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        output += `\\n      ${colors.dim}...${colors.reset}`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  // Add summary line</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  let summary = \"\";</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  if (json.message?.usage) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    const usage = json.message.usage;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    summary = `${usage.input_tokens || 0}/${usage.output_tokens || 0} tokens`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  } else if (json.output &amp;&amp; typeof json.output === \"string\") {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    summary = `${json.output.length} chars output`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  } else if (json.message?.content?.length) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    summary = `${json.message.content.length} content items`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  } else if (json.tools?.length) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    summary = `${json.tools.length} tools available`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  if (summary) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    output += `\\n  ⎿  ${colors.dim}${summary}${colors.reset}`;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  return output;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >}</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >function displayToolCallWithResult(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  toolCall: any,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  toolCallJson: any,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  toolResultJson: any,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  callTimestamp: string,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  resultTimestamp: string,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  // Display the tool call header</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  process.stdout.write(`${callTimestamp}${formatConcise(toolCallJson)}\\n`);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  // Display the result</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  const toolResult = toolResultJson.message.content[0];</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  const isError = toolResult.is_error;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  const resultIcon = isError ? \"❌\" : \"✅\";</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  const resultColor = isError ? colors.red : colors.green;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  process.stdout.write(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    `  ${resultTimestamp}${resultIcon} ${resultColor}Tool Result${colors.reset}`,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  );</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  if (toolResult.content) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    const resultText =</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      typeof toolResult.content === \"string\"</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        ? toolResult.content</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        : JSON.stringify(toolResult.content);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    const lines = resultText.split(\"\\n\");</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    const chars = resultText.length;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    process.stdout.write(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      ` ${colors.dim}(${lines.length} lines, ${chars} chars)${colors.reset}`,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    );</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    if (isError) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      process.stdout.write(` ${colors.red}ERROR${colors.reset}`);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    // Show first few lines of result</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    const linesToShow = Math.min(3, lines.length);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    for (let i = 0; i &lt; linesToShow; i++) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      if (lines[i].trim()) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        const lineColor = i === 0 ? colors.reset : colors.dim;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        process.stdout.write(`\\n    ⎿  ${lineColor}${lines[i]}${colors.reset}`);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    if (lines.length &gt; linesToShow) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      process.stdout.write(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        `\\n    ⎿  ${colors.dim}... ${lines.length - linesToShow} more lines${colors.reset}`,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      );</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  process.stdout.write(\"\\n\\n\");</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >}</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >export async function visualize(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  options: { debug?: boolean } = {},</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >): Promise&lt;void&gt; {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  const rl = createInterface({</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    input: process.stdin,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    crlfDelay: Infinity,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  });</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  const debugMode = options.debug || process.argv.includes(\"--debug\");</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  const toolCalls = new Map(); // Store tool calls by their ID</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  const pendingResults = new Map(); // Store results waiting for their tool calls</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  let lastLine: any = null; // Track the last line to detect final message</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  let isLastAssistantMessage = false;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  rl.on(\"line\", (line) =&gt; {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    if (line.trim()) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      const timestamp = debugMode</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        ? `${colors.dim}[${new Date().toISOString()}]${colors.reset} `</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        : \"\";</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      try {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        const json = JSON.parse(line);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        // Check if this is a tool call</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        if (json.type === \"assistant\" &amp;&amp; json.message?.content?.[0]?.id) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >          const toolCall = json.message.content[0];</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >          const toolId = toolCall.id;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >          // Store the tool call</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >          toolCalls.set(toolId, {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >            toolCall: json,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >            timestamp: timestamp,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >          });</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >          // Check if we have a pending result for this tool call</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >          if (pendingResults.has(toolId)) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >            const result = pendingResults.get(toolId);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >            displayToolCallWithResult(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >              toolCall,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >              json,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >              result.toolResult,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >              result.timestamp,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >              timestamp,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >            );</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >            pendingResults.delete(toolId);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >          } else {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >            // Display the tool call and mark it as pending</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >            process.stdout.write(`${timestamp + formatConcise(json)}\\n`);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >            process.stdout.write(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >              `${colors.dim}  ⎿  Waiting for result...${colors.reset}\\n\\n`,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >            );</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >          }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        // Check if this is a tool result</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        else if (</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >          json.type === \"user\" &amp;&amp;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >          json.message?.content?.[0]?.type === \"tool_result\"</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        ) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >          const toolResult = json.message.content[0];</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >          const toolId = toolResult.tool_use_id;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >          if (toolCalls.has(toolId)) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >            // We have the matching tool call, display them together</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >            const stored = toolCalls.get(toolId);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >            displayToolCallWithResult(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >              stored.toolCall.message.content[0],</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >              stored.toolCall,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >              json,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >              stored.timestamp,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >              timestamp,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >            );</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >            toolCalls.delete(toolId);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >          } else {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >            // Store the result and wait for the tool call</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >            pendingResults.set(toolId, {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >              toolResult: json,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >              timestamp: timestamp,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >            });</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >          }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        // Check if this is the result message and display full content</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        else if (json.type === \"result\" &amp;&amp; json.result) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >          process.stdout.write(`${timestamp + formatConcise(json)}\\n\\n`);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >          process.stdout.write(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >            `${colors.bright}${colors.green}=== Final Result ===${colors.reset}\\n\\n`,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >          );</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >          process.stdout.write(`${json.result}\\n`);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        // For all other message types, display normally</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        else {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >          process.stdout.write(`${timestamp + formatConcise(json)}\\n\\n`);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        // Track if this might be the last assistant message</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        lastLine = json;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        isLastAssistantMessage =</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >          json.type === \"assistant\" &amp;&amp; !json.message?.content?.[0]?.id;</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      } catch (_error) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        process.stdout.write(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >          `${timestamp}${colors.red}⏺ Parse Error${colors.reset}\\n`,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        );</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        process.stdout.write(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >          `  ⎿  ${colors.dim}${line.substring(0, 50)}...${colors.reset}\\n\\n`,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        );</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  });</span>\n<span class=\"cstat-no\" title=\"statement not covered\" ></span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  rl.on(\"close\", () =&gt; {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    // If the last message was an assistant message (not a tool call), display the full content</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    if (isLastAssistantMessage &amp;&amp; lastLine?.message?.content?.[0]?.text) {</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      process.stdout.write(</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >        `\\n${colors.bright}${colors.green}=== Final Assistant Message ===${colors.reset}\\n\\n`,</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      );</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >      process.stdout.write(`${lastLine.message.content[0].text}\\n`);</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >    }</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >  });</span>\n<span class=\"cstat-no\" title=\"statement not covered\" >}</span>\n&nbsp;</pre></td></tr></table></pre>\n\n                <div class='push'></div><!-- for sticky footer -->\n            </div><!-- /wrapper -->\n            <div class='footer quiet pad2 space-top1 center small'>\n                Code coverage generated by\n                <a href=\"https://istanbul.js.org/\" target=\"_blank\" rel=\"noopener noreferrer\">istanbul</a>\n                at 2025-08-23T07:39:41.860Z\n            </div>\n        <script src=\"../../prettify.js\"></script>\n        <script>\n            window.onload = function () {\n                prettyPrint();\n            };\n        </script>\n        <script src=\"../../sorter.js\"></script>\n        <script src=\"../../block-navigation.js\"></script>\n    </body>\n</html>\n    "
  },
  {
    "path": "coverage/src/index.html",
    "content": "\n<!doctype html>\n<html lang=\"en\">\n\n<head>\n    <title>Code coverage report for src</title>\n    <meta charset=\"utf-8\" />\n    <link rel=\"stylesheet\" href=\"../prettify.css\" />\n    <link rel=\"stylesheet\" href=\"../base.css\" />\n    <link rel=\"shortcut icon\" type=\"image/x-icon\" href=\"../favicon.png\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <style type='text/css'>\n        .coverage-summary .sorter {\n            background-image: url(../sort-arrow-sprite.png);\n        }\n    </style>\n</head>\n    \n<body>\n<div class='wrapper'>\n    <div class='pad1'>\n        <h1><a href=\"../index.html\">All files</a> src</h1>\n        <div class='clearfix'>\n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Statements</span>\n                <span class='fraction'>0/39</span>\n            </div>\n        \n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Branches</span>\n                <span class='fraction'>0/1</span>\n            </div>\n        \n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Functions</span>\n                <span class='fraction'>0/1</span>\n            </div>\n        \n            \n            <div class='fl pad1y space-right2'>\n                <span class=\"strong\">0% </span>\n                <span class=\"quiet\">Lines</span>\n                <span class='fraction'>0/39</span>\n            </div>\n        \n            \n        </div>\n        <p class=\"quiet\">\n            Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.\n        </p>\n        <template id=\"filterTemplate\">\n            <div class=\"quiet\">\n                Filter:\n                <input type=\"search\" id=\"fileSearch\">\n            </div>\n        </template>\n    </div>\n    <div class='status-line low'></div>\n    <div class=\"pad1\">\n<table class=\"coverage-summary\">\n<thead>\n<tr>\n   <th data-col=\"file\" data-fmt=\"html\" data-html=\"true\" class=\"file\">File</th>\n   <th data-col=\"pic\" data-type=\"number\" data-fmt=\"html\" data-html=\"true\" class=\"pic\"></th>\n   <th data-col=\"statements\" data-type=\"number\" data-fmt=\"pct\" class=\"pct\">Statements</th>\n   <th data-col=\"statements_raw\" data-type=\"number\" data-fmt=\"html\" class=\"abs\"></th>\n   <th data-col=\"branches\" data-type=\"number\" data-fmt=\"pct\" class=\"pct\">Branches</th>\n   <th data-col=\"branches_raw\" data-type=\"number\" data-fmt=\"html\" class=\"abs\"></th>\n   <th data-col=\"functions\" data-type=\"number\" data-fmt=\"pct\" class=\"pct\">Functions</th>\n   <th data-col=\"functions_raw\" data-type=\"number\" data-fmt=\"html\" class=\"abs\"></th>\n   <th data-col=\"lines\" data-type=\"number\" data-fmt=\"pct\" class=\"pct\">Lines</th>\n   <th data-col=\"lines_raw\" data-type=\"number\" data-fmt=\"html\" class=\"abs\"></th>\n</tr>\n</thead>\n<tbody><tr>\n\t<td class=\"file low\" data-value=\"cli.ts\"><a href=\"cli.ts.html\">cli.ts</a></td>\n\t<td data-value=\"0\" class=\"pic low\">\n\t<div class=\"chart\"><div class=\"cover-fill\" style=\"width: 0%\"></div><div class=\"cover-empty\" style=\"width: 100%\"></div></div>\n\t</td>\n\t<td data-value=\"0\" class=\"pct low\">0%</td>\n\t<td data-value=\"39\" class=\"abs low\">0/39</td>\n\t<td data-value=\"0\" class=\"pct low\">0%</td>\n\t<td data-value=\"1\" class=\"abs low\">0/1</td>\n\t<td data-value=\"0\" class=\"pct low\">0%</td>\n\t<td data-value=\"1\" class=\"abs low\">0/1</td>\n\t<td data-value=\"0\" class=\"pct low\">0%</td>\n\t<td data-value=\"39\" class=\"abs low\">0/39</td>\n\t</tr>\n\n</tbody>\n</table>\n</div>\n                <div class='push'></div><!-- for sticky footer -->\n            </div><!-- /wrapper -->\n            <div class='footer quiet pad2 space-top1 center small'>\n                Code coverage generated by\n                <a href=\"https://istanbul.js.org/\" target=\"_blank\" rel=\"noopener noreferrer\">istanbul</a>\n                at 2025-08-23T07:39:41.860Z\n            </div>\n        <script src=\"../prettify.js\"></script>\n        <script>\n            window.onload = function () {\n                prettyPrint();\n            };\n        </script>\n        <script src=\"../sorter.js\"></script>\n        <script src=\"../block-navigation.js\"></script>\n    </body>\n</html>\n    "
  },
  {
    "path": "docs/remote-repo-design.md",
    "content": "# Remote Repository Support Design\n\n## Overview\n\nThis document outlines the design for adding remote repository push/pull support to the repomirror tool. The feature will enable users to:\n\n1. Configure remote repositories for the transformed code\n2. Push transformed changes to remote repositories \n3. Pull updates from source repositories and re-sync transformations\n4. Manage multiple remote destinations for different branches/environments\n\n## Current Architecture Analysis\n\nThe repomirror tool currently has these components:\n\n- **CLI Entry Point**: `/src/cli.ts` - Commander.js based CLI with commands\n- **Commands**: `/src/commands/` directory with individual command implementations\n- **Configuration**: `repomirror.yaml` file for persistent configuration\n- **Core Components**: \n  - `init` - Interactive setup with preflight checks\n  - `sync` - Single transformation run using `.repomirror/sync.sh`\n  - `sync-forever` - Continuous sync using `.repomirror/ralph.sh`\n  - `visualize` - Stream JSON output visualization\n\n### Current Flow\n1. `init` creates `.repomirror/` directory with scripts and config\n2. `sync` runs Claude transformations via shell scripts\n3. Transformed code is written to target directory\n4. Target directory must be a git repo with remotes (preflight check)\n\n## Proposed New Commands\n\n### 1. `remote` Command\n**Purpose**: Manage remote repository configurations\n\n```typescript\n// Usage examples:\n// npx repomirror remote add origin https://github.com/user/transformed-repo.git\n// npx repomirror remote add staging https://github.com/user/staging-repo.git  \n// npx repomirror remote list\n// npx repomirror remote remove origin\n```\n\n**Implementation**: New file `/src/commands/remote.ts`\n\n### 2. `push` Command  \n**Purpose**: Push transformed changes to configured remotes\n\n```typescript\n// Usage examples:\n// npx repomirror push                    # push to default remote (origin/main)\n// npx repomirror push --remote staging   # push to specific remote\n// npx repomirror push --branch feature/new-feature # push to specific branch\n// npx repomirror push --all             # push to all configured remotes\n```\n\n**Implementation**: New file `/src/commands/push.ts`\n\n### 3. `pull` Command\n**Purpose**: Pull source changes and trigger re-sync\n\n```typescript  \n// Usage examples:\n// npx repomirror pull                    # pull source changes and re-sync\n// npx repomirror pull --source-only      # pull source without re-sync\n// npx repomirror pull --sync-after       # pull and run sync-forever after\n```\n\n**Implementation**: New file `/src/commands/pull.ts`\n\n## Configuration Changes\n\n### Enhanced `repomirror.yaml`\n\n```yaml\n# Current fields\nsourceRepo: \"./src\"\ntargetRepo: \"../myproject-ts\"\ntransformationInstructions: \"convert python to typescript\"\n\n# New remote repository configuration\nremotes:\n  origin:\n    url: \"https://github.com/user/myproject-ts.git\"\n    branch: \"main\"\n    auto_push: true          # auto-push after sync\n  staging:\n    url: \"https://github.com/user/myproject-staging.git\" \n    branch: \"develop\"\n    auto_push: false\n  \n# New options\npush:\n  default_remote: \"origin\"   # default for push command\n  default_branch: \"main\"     # default branch to push to\n  commit_prefix: \"[repomirror]\"  # prefix for commit messages\n  \npull:\n  auto_sync: true           # automatically run sync after pull\n  source_remote: \"upstream\" # remote name in source repo to pull from\n  source_branch: \"main\"     # branch to pull from in source repo\n```\n\n### Configuration Schema Updates\n\nThe `RepoMirrorConfig` interface in `/src/commands/init.ts` needs expansion:\n\n```typescript\ninterface RepoMirrorConfig {\n  // Existing fields\n  sourceRepo: string;\n  targetRepo: string;\n  transformationInstructions: string;\n  \n  // New remote configuration\n  remotes?: {\n    [remoteName: string]: {\n      url: string;\n      branch: string;\n      auto_push?: boolean;\n    };\n  };\n  \n  // New push configuration\n  push?: {\n    default_remote?: string;\n    default_branch?: string;\n    commit_prefix?: string;\n  };\n  \n  // New pull configuration  \n  pull?: {\n    auto_sync?: boolean;\n    source_remote?: string;\n    source_branch?: string;\n  };\n}\n```\n\n## Integration with Existing Commands\n\n### Enhanced `init` Command\n- Add remote configuration during setup\n- Prompt for remote repository URLs\n- Validate remote accessibility during preflight checks\n- Update existing preflight checks to verify push permissions\n\n### Enhanced `sync` Command\n- Add `--push` flag to auto-push after sync\n- Add `--remote <name>` flag to specify push destination\n- Modify generated scripts to optionally include git operations\n\n### Enhanced `sync-forever` Command  \n- Add configuration option for auto-push after each sync\n- Add failure handling for git operations\n- Continue syncing even if push fails (with warnings)\n\n## Git Operations Design\n\n### Push Workflow\n1. Check if target directory has uncommitted changes\n2. Create commit with descriptive message (include source commit hash if available)\n3. Push to specified remote/branch\n4. Handle authentication failures gracefully\n5. Support both HTTPS and SSH authentication\n\n### Pull Workflow\n1. Navigate to source repository \n2. Pull latest changes from specified remote/branch\n3. Check if changes affect files that impact transformation\n4. Optionally trigger re-sync based on configuration\n5. Handle merge conflicts in source repository\n\n### Commit Message Strategy\nFormat: `[repomirror] <transformation_summary> (source: <source_commit_hash>)`\n\nExamples:\n- `[repomirror] Update API transformations (source: abc123f)`\n- `[repomirror] Convert authentication module to TypeScript (source: def456a)`\n\n## Error Handling Considerations\n\n### Authentication Failures\n- Detect SSH key issues vs HTTPS credential issues\n- Provide helpful error messages with setup instructions\n- Support multiple authentication methods\n- Graceful fallback when push fails\n\n### Network Issues\n- Retry logic with exponential backoff\n- Offline mode detection\n- Queue operations for later when connectivity returns\n\n### Git State Issues\n- Handle dirty working directory in target repo\n- Resolve merge conflicts in source repo pulls\n- Handle detached HEAD states\n- Branch switching and creation\n\n### Sync Integration Errors\n- Continue sync-forever even if push fails\n- Log failures without stopping the sync process\n- Provide option to disable auto-push on repeated failures\n\n## Implementation Plan\n\n### Phase 1: Core Remote Management\n1. Create `remote` command for adding/listing/removing remotes\n2. Update configuration schema and init command\n3. Add configuration validation and loading functions\n\n### Phase 2: Push Functionality  \n1. Create `push` command with basic push operations\n2. Add commit message generation\n3. Integrate with sync commands (optional auto-push)\n4. Add authentication and error handling\n\n### Phase 3: Pull Functionality\n1. Create `pull` command for source repository updates\n2. Add change detection and sync triggering\n3. Integrate with sync-forever workflow\n4. Add conflict resolution guidance\n\n### Phase 4: Enhanced Integration\n1. Enhance sync scripts with git operations\n2. Add branch management features\n3. Add multi-remote push support\n4. Performance optimizations and caching\n\n## File Structure Changes\n\n```\nsrc/commands/\n├── init.ts          # Enhanced with remote config\n├── sync.ts          # Enhanced with push options  \n├── sync-forever.ts  # Enhanced with auto-push\n├── sync-one.ts      # (unchanged)\n├── visualize.ts     # (unchanged)\n├── remote.ts        # NEW: Remote management\n├── push.ts          # NEW: Push operations\n└── pull.ts          # NEW: Pull operations\n\nsrc/lib/             # NEW: Shared utilities\n├── git.ts           # Git operation helpers\n├── config.ts        # Configuration management\n└── auth.ts          # Authentication helpers\n```\n\n## Testing Strategy\n\n### Unit Tests\n- Mock git operations using `execa` mocks\n- Test configuration validation and loading\n- Test error handling scenarios\n- Test command parsing and validation\n\n### Integration Tests  \n- Test with real git repositories (using temp directories)\n- Test authentication flows\n- Test sync integration with git operations\n- Test error recovery scenarios\n\n### End-to-End Tests\n- Full workflow tests with mock remote repositories\n- Test interaction between commands\n- Test configuration persistence\n- Test sync-forever with git operations\n\n## Security Considerations\n\n### Credential Management\n- Never store credentials in configuration files\n- Use git credential helpers\n- Support SSH key authentication\n- Provide clear documentation for authentication setup\n\n### Repository Access\n- Validate remote URLs before adding\n- Check push permissions during setup\n- Handle private repository access\n- Support organization/team repository patterns\n\n## CLI Updates\n\n### New Command Structure\n```bash\n# Remote management\nrepomirror remote add <name> <url> [--branch <branch>]\nrepomirror remote list\nrepomirror remote remove <name>\nrepomirror remote set-url <name> <url>\n\n# Push operations  \nrepomirror push [--remote <name>] [--branch <branch>] [--all]\nrepomirror push --dry-run    # show what would be pushed\n\n# Pull operations\nrepomirror pull [--source-only] [--sync-after]\nrepomirror pull --check      # check for source changes without pulling\n\n# Enhanced existing commands\nrepomirror sync --push [--remote <name>]\nrepomirror sync-forever --auto-push\nrepomirror init --remote <url>  # add remote during init\n```\n\n### Help Text Updates\nUpdate CLI help text and `--help` output to document new remote repository features and workflow examples.\n\n## Migration Strategy\n\n### Backward Compatibility\n- All new features are optional\n- Existing workflows continue unchanged\n- Configuration files are upgraded automatically\n- Graceful degradation when remotes not configured\n\n### Upgrade Path\n1. Existing users can add remotes via `repomirror remote add`\n2. Configuration file is automatically migrated on first use\n3. New features are opt-in via flags or configuration\n4. Clear documentation for migrating existing setups\n\n## Success Metrics\n\n### Functionality Metrics  \n- Commands execute without errors in common scenarios\n- Git operations handle authentication correctly\n- Sync integration works smoothly with push operations\n- Error messages are helpful and actionable\n\n### Performance Metrics\n- Push operations complete in reasonable time\n- Sync-forever remains responsive with auto-push enabled\n- Pull operations detect changes efficiently\n- No significant performance regression in existing commands\n\nThis design provides a comprehensive foundation for adding remote repository support while maintaining the existing architecture and user experience. The phased implementation allows for iterative development and testing of each component."
  },
  {
    "path": "hack/ralph-validate.sh",
    "content": "#!/bin/bash\n\necho \"Testing repomirror init command validation\"\necho \"==========================================\"\n\n# Create temp directories\nSOURCE_DIR=$(mktemp -d)\nTARGET_DIR=$(mktemp -d)\n\necho \"Source dir: $SOURCE_DIR\"\necho \"Target dir: $TARGET_DIR\"\n\n# Setup source repo with hello.ts\necho 'console.log(\"Hello World\");' > \"$SOURCE_DIR/hello.ts\"\necho \"Created hello.ts in source directory\"\n\n# Setup target repo as git repo with remote\ncd \"$TARGET_DIR\"\ngit init\ngit remote add origin https://github.com/example/test.git\ncd - > /dev/null\n\necho \"\"\necho \"Running repomirror init...\"\necho \"\"\n\n# Run repomirror init from the source directory\ncd \"$SOURCE_DIR\"\nSKIP_CLAUDE_TEST=true timeout 30s node /Users/dex/go/src/github.com/dexhorthy/repomirror/dist/cli.js init \\\n  --source \"$SOURCE_DIR\" \\\n  --target \"$TARGET_DIR\" \\\n  --instructions \"translate this typescript repo to python\"\n\nEXIT_CODE=$?\n\nif [ $EXIT_CODE -eq 124 ]; then\n  echo \"\"\n  echo \"❌ Command timed out after 30 seconds\"\n  echo \"Issue: The Claude SDK call in generateTransformationPrompt is hanging\"\n  echo \"\"\n  echo \"The problem is in src/commands/init.ts lines 319-332:\"\n  echo \"- The async iterator is not properly handling all message types\"\n  echo \"- Need to add timeout or better error handling\"\n  exit 1\nelif [ $EXIT_CODE -eq 0 ]; then\n  echo \"\"\n  echo \"✅ Init command completed successfully\"\n  \n  # Check if required files were created\n  if [ -f \"$SOURCE_DIR/repomirror.yaml\" ]; then\n    echo \"✅ repomirror.yaml created\"\n  else\n    echo \"❌ repomirror.yaml not created\"\n  fi\n  \n  if [ -d \"$SOURCE_DIR/.repomirror\" ]; then\n    echo \"✅ .repomirror directory created\"\n    \n    # Check individual files\n    for file in prompt.md sync.sh ralph.sh .gitignore; do\n      if [ -f \"$SOURCE_DIR/.repomirror/$file\" ]; then\n        echo \"  ✅ $file created\"\n      else\n        echo \"  ❌ $file not created\"\n      fi\n    done\n  else\n    echo \"❌ .repomirror directory not created\"\n  fi\nelse\n  echo \"\"\n  echo \"❌ Command failed with exit code $EXIT_CODE\"\nfi\n\n# Cleanup\nrm -rf \"$SOURCE_DIR\" \"$TARGET_DIR\""
  },
  {
    "path": "hack/ralph.sh",
    "content": "while :; do\n  cat prompt.md | \\\n          claude -p --output-format=stream-json --verbose --dangerously-skip-permissions | \\\n          tee -a claude_output.jsonl | \\\n          bun hack/visualize.ts --debug;\n  echo -e \"===SLEEP===\\n===SLEEP===\\n\"; say 'looping';\nsleep 10;\ndone\n"
  },
  {
    "path": "hack/visualize.ts",
    "content": "#!/usr/bin/env bun\n\nimport { createInterface } from 'node:readline';\n\nconst colors = {\n  reset: '\\x1b[0m',\n  bright: '\\x1b[1m',\n  dim: '\\x1b[2m',\n  red: '\\x1b[31m',\n  green: '\\x1b[32m',\n  yellow: '\\x1b[33m',\n  blue: '\\x1b[34m',\n  magenta: '\\x1b[35m',\n  cyan: '\\x1b[36m',\n};\n\nfunction getTypeColor(type: string): string {\n  switch (type) {\n    case 'system':\n      return colors.magenta;\n    case 'user':\n      return colors.blue;\n    case 'assistant':\n      return colors.green;\n    case 'tool_use':\n      return colors.cyan;\n    case 'tool_result':\n      return colors.yellow;\n    case 'message':\n      return colors.dim;\n    case 'text':\n      return colors.reset;\n    default:\n      return colors.reset;\n  }\n}\n\nfunction _formatHeader(json: any, lineNumber: number): string {\n  const type = json.type || 'unknown';\n  const typeColor = getTypeColor(type);\n\n  let header = `${colors.dim}--- Line ${lineNumber} ${typeColor}[${type.toUpperCase()}]${colors.reset}`;\n\n  // Add context based on type\n  if (json.message?.role) {\n    header += ` ${colors.dim}(${json.message.role})${colors.reset}`;\n  }\n\n  if (json.message?.content?.[0]?.name) {\n    header += ` ${colors.cyan}${json.message.content[0].name}${colors.reset}`;\n  }\n\n  if (json.name) {\n    header += ` ${colors.cyan}${json.name}${colors.reset}`;\n  }\n\n  if (json.subtype) {\n    header += ` ${colors.dim}${json.subtype}${colors.reset}`;\n  }\n\n  return `${header} ${colors.dim}---${colors.reset}`;\n}\n\nfunction _colorizeJson(obj: any, indent = 0, path: string[] = []): string {\n  const spaces = '  '.repeat(indent);\n\n  if (obj === null) return `${colors.dim}null${colors.reset}`;\n  if (typeof obj === 'boolean') return `${colors.yellow}${obj}${colors.reset}`;\n  if (typeof obj === 'number') return `${colors.cyan}${obj}${colors.reset}`;\n  if (typeof obj === 'string') {\n    // Truncate very long strings\n    if (obj.length > 200) {\n      return `${colors.green}\"${obj.substring(0, 197)}...\"${colors.reset}`;\n    }\n    return `${colors.green}\"${obj}\"${colors.reset}`;\n  }\n\n  if (Array.isArray(obj)) {\n    if (obj.length === 0) return '[]';\n\n    // For content arrays, show summary\n    if (path.includes('content') && obj.length > 3) {\n      const summary = obj.slice(0, 2).map((item) => _colorizeJson(item, indent + 1, [...path]));\n      return `[\\n${summary.join(',\\n')},\\n${spaces}  ${colors.dim}... ${obj.length - 2} more items${colors.reset}\\n${spaces}]`;\n    }\n\n    const items = obj.map((item) => `${spaces}  ${_colorizeJson(item, indent + 1, [...path])}`);\n    return `[\\n${items.join(',\\n')}\\n${spaces}]`;\n  }\n\n  if (typeof obj === 'object') {\n    const keys = Object.keys(obj);\n    if (keys.length === 0) return '{}';\n\n    // Show only key fields for deeply nested objects\n    const importantKeys = [\n      'type',\n      'role',\n      'name',\n      'id',\n      'input',\n      'output',\n      'content',\n      'text',\n      'subtype',\n      'session_id',\n    ];\n    const keysToShow = indent > 2 ? keys.filter((k) => importantKeys.includes(k)) : keys;\n\n    if (keysToShow.length === 0 && keys.length > 0) {\n      return `${colors.dim}{...${keys.length} keys}${colors.reset}`;\n    }\n\n    const items = keysToShow.map((key) => {\n      let coloredKey = `${colors.blue}\"${key}\"${colors.reset}`;\n\n      // Highlight important keys\n      if (['type', 'name', 'role'].includes(key)) {\n        coloredKey = `${colors.bright}${colors.blue}\"${key}\"${colors.reset}`;\n      }\n\n      const value = _colorizeJson(obj[key], indent + 1, [...path, key]);\n      return `${spaces}  ${coloredKey}: ${value}`;\n    });\n\n    if (keysToShow.length < keys.length) {\n      items.push(\n        `${spaces}  ${colors.dim}... ${keys.length - keysToShow.length} more keys${colors.reset}`\n      );\n    }\n\n    return `{\\n${items.join(',\\n')}\\n${spaces}}`;\n  }\n\n  return String(obj);\n}\n\nfunction formatTodoList(todos: any[]): string {\n  let output = `📋 ${colors.bright}${colors.cyan}Todo List Update${colors.reset}\\n`;\n\n  const statusColors = {\n    completed: colors.dim + colors.green,\n    in_progress: colors.bright + colors.yellow,\n    pending: colors.reset,\n  };\n\n  const statusIcons = {\n    completed: '✅',\n    in_progress: '🔄',\n    pending: '⏸️',\n  };\n\n  const priorityColors = {\n    high: colors.red,\n    medium: colors.yellow,\n    low: colors.dim,\n  };\n\n  todos.forEach((todo, index) => {\n    const statusColor = statusColors[todo.status] || colors.reset;\n    const statusIcon = statusIcons[todo.status] || '❓';\n    const priorityColor = priorityColors[todo.priority] || colors.reset;\n    const checkbox = todo.status === 'completed' ? '☑️' : '☐';\n\n    output += `  ${checkbox} ${statusIcon} ${statusColor}${todo.content}${colors.reset}`;\n    output += ` ${priorityColor}[${todo.priority}]${colors.reset}`;\n\n    if (todo.status === 'in_progress') {\n      output += ` ${colors.bright}${colors.yellow}← ACTIVE${colors.reset}`;\n    }\n\n    output += '\\n';\n  });\n\n  // Add summary stats\n  const completed = todos.filter((t) => t.status === 'completed').length;\n  const inProgress = todos.filter((t) => t.status === 'in_progress').length;\n  const pending = todos.filter((t) => t.status === 'pending').length;\n\n  output += `\\n  ${colors.dim}📊 Progress: ${colors.green}${completed} completed${colors.reset}`;\n  output += `${colors.dim}, ${colors.yellow}${inProgress} active${colors.reset}`;\n  output += `${colors.dim}, ${colors.reset}${pending} pending${colors.reset}`;\n  output += `${colors.dim} (${Math.round((completed / todos.length) * 100)}% done)${colors.reset}`;\n\n  return output;\n}\n\nfunction formatConcise(json: any): string {\n  const type = json.type || 'unknown';\n  const typeColor = getTypeColor(type);\n\n  let output = `⏺ ${typeColor}${type.charAt(0).toUpperCase() + type.slice(1)}${colors.reset}`;\n\n  // Special handling for TodoWrite calls\n  if (type === 'assistant' && json.message?.content?.[0]?.name === 'TodoWrite') {\n    const toolInput = json.message.content[0].input;\n    if (toolInput?.todos && Array.isArray(toolInput.todos)) {\n      return formatTodoList(toolInput.todos);\n    }\n  }\n\n  // Add context based on type\n  if (type === 'assistant' && json.message?.content?.[0]?.name) {\n    const toolName = json.message.content[0].name;\n    const toolInput = json.message.content[0].input;\n\n    // Format tool name with key arguments\n    let toolDisplay = `${colors.cyan}${toolName}${colors.reset}`;\n\n    if (toolInput) {\n      const keyArgs = [];\n\n      // Extract the most important argument for each tool type\n      if (toolInput.file_path) keyArgs.push(toolInput.file_path);\n      else if (toolInput.path) keyArgs.push(toolInput.path);\n      else if (toolInput.pattern) keyArgs.push(`\"${toolInput.pattern}\"`);\n      else if (toolInput.command) keyArgs.push(toolInput.command);\n      else if (toolInput.cmd) keyArgs.push(toolInput.cmd);\n      else if (toolInput.query) keyArgs.push(`\"${toolInput.query}\"`);\n      else if (toolInput.description) keyArgs.push(toolInput.description);\n      else if (toolInput.prompt) keyArgs.push(`\"${toolInput.prompt.substring(0, 30)}...\"`);\n      else if (toolInput.url) keyArgs.push(toolInput.url);\n\n      if (keyArgs.length > 0) {\n        toolDisplay += `(${colors.green}${keyArgs[0]}${colors.reset})`;\n      }\n    }\n\n    output = `⏺ ${toolDisplay}`;\n\n    // Show additional arguments on next lines for complex tools\n    if (toolInput) {\n      const additionalArgs = [];\n\n      if (toolName === 'Bash' && toolInput.cwd) {\n        additionalArgs.push(`cwd: ${toolInput.cwd}`);\n      }\n      if (toolInput.limit) additionalArgs.push(`limit: ${toolInput.limit}`);\n      if (toolInput.offset) additionalArgs.push(`offset: ${toolInput.offset}`);\n      if (toolInput.include) additionalArgs.push(`include: ${toolInput.include}`);\n      if (toolInput.old_string && toolInput.new_string) {\n        additionalArgs.push(\n          `replace: \"${toolInput.old_string.substring(0, 20)}...\" → \"${toolInput.new_string.substring(0, 20)}...\"`\n        );\n      }\n      if (toolInput.timeout) additionalArgs.push(`timeout: ${toolInput.timeout}ms`);\n\n      if (additionalArgs.length > 0) {\n        output += `\\n  ⎿  ${colors.dim}${additionalArgs.join(', ')}${colors.reset}`;\n      }\n    }\n  } else if (type === 'tool_result' && json.name) {\n    output += `(${colors.cyan}${json.name}${colors.reset})`;\n  } else if (type === 'user' && json.message?.content?.[0]) {\n    const content = json.message.content[0];\n    if (content.type === 'tool_result') {\n      // Override the type display for tool results\n      output = `⏺ ${colors.yellow}Tool Result${colors.reset}`;\n\n      // Show result summary and first 2 lines\n      if (content.content) {\n        const resultText =\n          typeof content.content === 'string' ? content.content : JSON.stringify(content.content);\n        const lines = resultText.split('\\n');\n        const chars = resultText.length;\n        output += `\\n  ⎿  ${colors.dim}${lines.length} lines, ${chars} chars${colors.reset}`;\n        if (content.is_error) {\n          output += ` ${colors.red}ERROR${colors.reset}`;\n        }\n\n        // Show first 2 lines of content\n        if (lines.length > 0 && lines[0].trim()) {\n          output += `\\n  ⎿  ${colors.reset}${lines[0]}${colors.reset}`;\n        }\n        if (lines.length > 1 && lines[1].trim()) {\n          output += `\\n      ${colors.dim}${lines[1]}${colors.reset}`;\n        }\n      }\n    } else if (content.text) {\n      const text = content.text.substring(0, 50);\n      output += `: ${colors.dim}${text}${text.length === 50 ? '...' : ''}${colors.reset}`;\n    }\n  } else if (type === 'system' && json.subtype) {\n    output += `(${colors.dim}${json.subtype}${colors.reset})`;\n  }\n\n  // Show assistant message content if it exists\n  if (type === 'assistant' && json.message?.content) {\n    const textContent = json.message.content.find((c) => c.type === 'text');\n    if (textContent?.text) {\n      const lines = textContent.text.split('\\n').slice(0, 3); // Show first 3 lines\n      output += `\\n  ⎿  ${colors.reset}${lines[0]}${colors.reset}`;\n      if (lines.length > 1) {\n        output += `\\n      ${colors.dim}${lines[1]}${colors.reset}`;\n      }\n      if (lines.length > 2) {\n        output += `\\n      ${colors.dim}${lines[2]}${colors.reset}`;\n      }\n      if (textContent.text.split('\\n').length > 3) {\n        output += `\\n      ${colors.dim}...${colors.reset}`;\n      }\n    }\n  }\n\n  // Add summary line\n  let summary = '';\n  if (json.message?.usage) {\n    const usage = json.message.usage;\n    summary = `${usage.input_tokens || 0}/${usage.output_tokens || 0} tokens`;\n  } else if (json.output && typeof json.output === 'string') {\n    summary = `${json.output.length} chars output`;\n  } else if (json.message?.content?.length) {\n    summary = `${json.message.content.length} content items`;\n  } else if (json.tools?.length) {\n    summary = `${json.tools.length} tools available`;\n  }\n\n  if (summary) {\n    output += `\\n  ⎿  ${colors.dim}${summary}${colors.reset}`;\n  }\n\n  return output;\n}\n\nasync function processStream() {\n  const rl = createInterface({\n    input: process.stdin,\n    crlfDelay: Infinity,\n  });\n\n  const debugMode = process.argv.includes('--debug');\n  const toolCalls = new Map(); // Store tool calls by their ID\n  const pendingResults = new Map(); // Store results waiting for their tool calls\n  let lastLine = null; // Track the last line to detect final message\n  let isLastAssistantMessage = false;\n\n  rl.on('line', (line) => {\n    if (line.trim()) {\n      const timestamp = debugMode\n        ? `${colors.dim}[${new Date().toISOString()}]${colors.reset} `\n        : '';\n\n      try {\n        const json = JSON.parse(line);\n\n        // Check if this is a tool call\n        if (json.type === 'assistant' && json.message?.content?.[0]?.id) {\n          const toolCall = json.message.content[0];\n          const toolId = toolCall.id;\n\n          // Store the tool call\n          toolCalls.set(toolId, {\n            toolCall: json,\n            timestamp: timestamp,\n          });\n\n          // Check if we have a pending result for this tool call\n          if (pendingResults.has(toolId)) {\n            const result = pendingResults.get(toolId);\n            displayToolCallWithResult(\n              toolCall,\n              json,\n              result.toolResult,\n              result.timestamp,\n              timestamp\n            );\n            pendingResults.delete(toolId);\n          } else {\n            // Display the tool call and mark it as pending\n            process.stdout.write(`${timestamp + formatConcise(json)}\\n`);\n            process.stdout.write(`${colors.dim}  ⎿  Waiting for result...${colors.reset}\\n\\n`);\n          }\n        }\n        // Check if this is a tool result\n        else if (json.type === 'user' && json.message?.content?.[0]?.type === 'tool_result') {\n          const toolResult = json.message.content[0];\n          const toolId = toolResult.tool_use_id;\n\n          if (toolCalls.has(toolId)) {\n            // We have the matching tool call, display them together\n            const stored = toolCalls.get(toolId);\n            displayToolCallWithResult(\n              stored.toolCall.message.content[0],\n              stored.toolCall,\n              json,\n              stored.timestamp,\n              timestamp\n            );\n            toolCalls.delete(toolId);\n          } else {\n            // Store the result and wait for the tool call\n            pendingResults.set(toolId, {\n              toolResult: json,\n              timestamp: timestamp,\n            });\n          }\n        }\n        // Check if this is the result message and display full content\n        else if (json.type === 'result' && json.result) {\n          process.stdout.write(`${timestamp + formatConcise(json)}\\n\\n`);\n          process.stdout.write(`${colors.bright}${colors.green}=== Final Result ===${colors.reset}\\n\\n`);\n          process.stdout.write(`${json.result}\\n`);\n        }\n        // For all other message types, display normally\n        else {\n          process.stdout.write(`${timestamp + formatConcise(json)}\\n\\n`);\n        }\n\n        // Track if this might be the last assistant message\n        lastLine = json;\n        isLastAssistantMessage = json.type === 'assistant' && !json.message?.content?.[0]?.id;\n      } catch (_error) {\n        process.stdout.write(`${timestamp}${colors.red}⏺ Parse Error${colors.reset}\\n`);\n        process.stdout.write(`  ⎿  ${colors.dim}${line.substring(0, 50)}...${colors.reset}\\n\\n`);\n      }\n    }\n  });\n\n  rl.on('close', () => {\n    // If the last message was an assistant message (not a tool call), display the full content\n    if (isLastAssistantMessage && lastLine?.message?.content?.[0]?.text) {\n      process.stdout.write(`\\n${colors.bright}${colors.green}=== Final Assistant Message ===${colors.reset}\\n\\n`);\n      process.stdout.write(`${lastLine.message.content[0].text}\\n`);\n    }\n  });\n}\n\nfunction displayToolCallWithResult(\n  toolCall: any,\n  toolCallJson: any,\n  toolResultJson: any,\n  callTimestamp: string,\n  resultTimestamp: string\n) {\n  // Display the tool call header\n  process.stdout.write(`${callTimestamp}${formatConcise(toolCallJson)}\\n`);\n\n  // Display the result\n  const toolResult = toolResultJson.message.content[0];\n  const isError = toolResult.is_error;\n  const resultIcon = isError ? '❌' : '✅';\n  const resultColor = isError ? colors.red : colors.green;\n\n  process.stdout.write(\n    `  ${resultTimestamp}${resultIcon} ${resultColor}Tool Result${colors.reset}`\n  );\n\n  if (toolResult.content) {\n    const resultText =\n      typeof toolResult.content === 'string'\n        ? toolResult.content\n        : JSON.stringify(toolResult.content);\n    const lines = resultText.split('\\n');\n    const chars = resultText.length;\n\n    process.stdout.write(` ${colors.dim}(${lines.length} lines, ${chars} chars)${colors.reset}`);\n\n    if (isError) {\n      process.stdout.write(` ${colors.red}ERROR${colors.reset}`);\n    }\n\n    // Show first few lines of result\n    const linesToShow = Math.min(3, lines.length);\n    for (let i = 0; i < linesToShow; i++) {\n      if (lines[i].trim()) {\n        const lineColor = i === 0 ? colors.reset : colors.dim;\n        process.stdout.write(`\\n    ⎿  ${lineColor}${lines[i]}${colors.reset}`);\n      }\n    }\n\n    if (lines.length > linesToShow) {\n      process.stdout.write(\n        `\\n    ⎿  ${colors.dim}... ${lines.length - linesToShow} more lines${colors.reset}`\n      );\n    }\n  }\n\n  process.stdout.write('\\n\\n');\n}\n\nif (import.meta.main) {\n  processStream().catch(console.error);\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"repomirror\",\n  \"version\": \"0.3.0\",\n  \"description\": \"Sync and transform repositories using AI agents\",\n  \"main\": \"dist/index.js\",\n  \"bin\": {\n    \"repomirror\": \"dist/cli.js\"\n  },\n  \"files\": [\n    \"dist/**/*\",\n    \"src/templates/**/*\"\n  ],\n  \"scripts\": {\n    \"build\": \"tsc && cp -r src/templates dist/\",\n    \"dev\": \"tsx watch src/cli.ts\",\n    \"test\": \"vitest\",\n    \"lint\": \"eslint src --ext .ts\",\n    \"fix\": \"eslint src --ext .ts --fix && prettier --write 'src/**/*.ts'\",\n    \"check\": \"tsc --noEmit && npm run lint\",\n    \"publish\": \"npm run build && npm publish\"\n  },\n  \"keywords\": [\n    \"repository\",\n    \"sync\",\n    \"ai\",\n    \"migration\"\n  ],\n  \"author\": \"\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"@anthropic-ai/claude-code\": \"^1.0.89\",\n    \"@types/inquirer\": \"^9.0.9\",\n    \"chalk\": \"^5.3.0\",\n    \"commander\": \"^12.0.0\",\n    \"execa\": \"^8.0.1\",\n    \"fs-extra\": \"^11.2.0\",\n    \"glob\": \"^10.3.10\",\n    \"inquirer\": \"^12.9.3\",\n    \"ora\": \"^8.0.1\",\n    \"yaml\": \"^2.4.0\"\n  },\n  \"devDependencies\": {\n    \"@types/fs-extra\": \"^11.0.4\",\n    \"@types/node\": \"^20.11.0\",\n    \"@typescript-eslint/eslint-plugin\": \"^7.0.0\",\n    \"@typescript-eslint/parser\": \"^7.0.0\",\n    \"@vitest/coverage-v8\": \"^1.6.1\",\n    \"eslint\": \"^8.56.0\",\n    \"prettier\": \"^3.2.0\",\n    \"tsx\": \"^4.7.0\",\n    \"typescript\": \"^5.3.0\",\n    \"vitest\": \"^1.2.0\"\n  }\n}\n"
  },
  {
    "path": "prompt-validate.md",
    "content": "0a. read everything in specs/\n0b. review the files in src/\n\n1. pick the SINGLE highest priority item from VALIDATION_PLAN.md and test the functionality from the specs\n2. if things are not working as expected, refine the specs/ to highlight the example that should work, and update the @IMPLEMENTATION_PLAN.md with updated steps to address the issue\n3. update the @VALIDATION_PLAN.md with your progress and commit all changes with git add -A && git commit -m \"...\"\n"
  },
  {
    "path": "prompt.md",
    "content": "0a. read everything in specs/\n0b. review the files in src/\n\n1. pick the SINGLE highest priority item from @IMPLEMENTATION_PLAN.md and implement it using up to 50 subagents\n2. ensure the tests and checks are passing\n3. update the @IMPLEMENTATION_PLAN.md with your progress and commit all changes with git add -A && git commit -m \"...\"\n\nif there is a discrepancy in the IMPLEMENTATION_PLAN.md and the spec, always update the IMPLEMENTATION_PLAN.md to match the spec.\n"
  },
  {
    "path": "prompts/ai-sdk-python.md",
    "content": "Your job is to port ai-sdk monorepo (for typescript) to ai-sdk-python (for python) and maintain the repository.\n\nYou have access to the current ai-sdk repositorty as well as the ai-sdk-python repository.\n\nMake a commit and push your changes after every single file edit.\n\nUse the ai-sdk-python/agent/ directory as a scratchpad for your work via .MD files. Store long term plans and todo lists there. Keep track of your current status in a ai-sdk-python/agent/TODO.md file.\n\nWhen done with the initial port, feel free to occasionally check for GitHub issues and answer or resolve them. Make sure to let the user know that you are a bot if you answer an issue. Use the gh cli for this.\n"
  },
  {
    "path": "prompts/assistant-ui-vue.md",
    "content": "Your job is to port assistant-ui-react monorepo (for react) to assistant-ui-vue (for vue) and maintain the repository.\n\nYou have access to the current assistant-ui-react repository as well as the assistant-ui-vue repository.\n\nMake a commit and push your changes after every single file edit.\n\nUse the assistant-ui-vue/.agent/ directory as a scratchpad for your work. Store long term plans and todo lists there.\n\nThe original project was mostly tested by manually running the code. When porting, you will need to write end to end and unit tests for the project. But make sure to spend most of your time on the actual porting, not on the testing. A good heuristic is to spend 80% of your time on the actual porting, and 20% on the\ntesting."
  },
  {
    "path": "prompts/better-use.md",
    "content": "Your job is to port browser-use monorepo (Python) to browser-use-ts (better-use, Typescript) and maintain the repository.\n\nYou have access to the current browser-use repositorty as well as the browser-use-ts repository.\n\nMake a commit and push your changes after every single file edit.\n\nUse the browser-use-ts/agent/ directory as a scratchpad for your work. Store long term plans and todo lists there.\n\nThe original project was mostly tested by manually running the code. When porting, you will need to write end to end and unit tests for the project. But make sure to spend most of your time on the actual porting, not on the testing. A good heuristic is to spend 80% of your time on the actual porting, and 20% on the testing.\n\nWhen done with the initial port, feel free to occasionally check for GitHub issues and answer or resolve them. Make sure to let the user know that you are a bot if you answer an issue. Use the gh cli for this.\n\nKeep track of your current status in TODO.md in the browser-use-ts/agent/ directory."
  },
  {
    "path": "prompts/open-convex.md",
    "content": "read convex-11ms-full.txt IN ITS ENTIRETY - read every line, chunking if necessary\n0a. familiarize your self with the code in openconvex\n\n\n0b. Use the openconvex/agent/ directory as a scratchpad for your work. Store long term plans and todo lists there.\n\n\nImplement the SINGLE HIGHEST PRIORITY item from openconvex/agent/IMPLEMENTATION_PLAN.md with up to 50 subagents\n\nensure the tests are passing\n\nmake a commit and push your changes to the repo with git commit and git push\n\nthats it, you're done"
  },
  {
    "path": "prompts/open-dedalus.md",
    "content": "Your job is to implement open-dedalus based on the dedalus.llms.txt file.\n\nYou have access to the current dedalus.llms.txt file as well as the open-dedalus repository.\n\nMake a commit and push your changes after every single file edit.\n\nUse the open-dedalus/agent/ directory as a scratchpad for your work via .MD files. Store long term plans and todo lists there.\n\npick the single highest priority item from @IMPLEMENTATION_PLAN.md\n"
  },
  {
    "path": "prompts/repomirror.md",
    "content": "0a. read everything in specs/\n0b. review the files in src/\n\n1. pick the SINGLE highest priority item from @IMPLEMENTATION_PLAN.md and implement it using up to 50 subagents\n2. ensure the tests and checks are passing\n3. update the @IMPLEMENTATION_PLAN.md with your progress and commit all changes with git add -A && git commit -m \"...\"\n\nif there is a discrepancy in the IMPLEMENTATION_PLAN.md and the spec, always update the IMPLEMENTATION_PLAN.md to match the spec.\n"
  },
  {
    "path": "repomirror.md",
    "content": "## We Put a Coding Agent in a While Loop and It Shipped 6 Repos Overnight\n\nThis weekend at the YC Agents hackathon, we asked ourselves: *what’s the weirdest way we could use a coding agent?*  \n\nOur answer: run Claude Code headlessly in a loop forever and see what happens.\n\nTurns out, what happens is: you wake up to 1,000+ commits, six ported codebases, and a wonky little tool we’re calling [RepoMirror](https://github.com/repomirrorhq/repomirror). \n\n### How We Got Here\n\nWe recently stumbled upon a technique promoted by [Geoff Huntley](https://ghuntley.com/ralph/), to run a coding agent in a while loop:\n\n```\nwhile :; do cat prompt.md | amp; done\n```\n\nOne of our team members, Simon, is the creator of assistant-ui, a React library for building AI interfaces in React. He gets a lot of requests to add Vue.js support, and he wondered if the approach would work for porting assistant-ui to Vue.js.\n\n### How It Works\n\nBasically what we ended up doing sounds really dumb, but it works surprisingly well - we used Claude Code for the loop:\n\n```\nwhile :; do cat prompt.md | claude -p --dangerously-skip-permissions; done\n```\n\nThe prompt was simple:\n\n```\nYour job is to port assistant-ui-react monorepo (for react) to assistant-ui-vue (for vue) and maintain the repository.\n\nYou have access to the current assistant-ui-react repository as well as the assistant-ui-vue repository.\n\nMake a commit and push your changes after every single file edit.\n\nUse the assistant-ui-vue/.agent/ directory as a scratchpad for your work. Store long term plans and todo lists there.\n\nThe original project was mostly tested by manually running the code. When porting, you will need to write end to end and unit tests for the project. But make sure to spend most of your time on the actual porting, not on the testing. A good heuristic is to spend 80% of your time on the actual porting, and 20% on the\ntesting.\n```\n\n### Porting Browser-Use to TypeScript\n\nSince we were at a hackathon, we wanted to do something related to the sponsor tooling, so we decided to see if Ralph could port [Browser Use](https://github.com/browser-use/browser-use), a YC-backed web agent tool, from Python to TypeScript.\n\nWe kicked off the loop with a simple prompt:\n\n```\nYour job is to port browser-use monorepo (Python) to better-use (Typescript) and maintain the repository.\n\nMake a commit and push your changes after every single file edit.\n\nKeep track of your current status in browser-use-ts/agent/TODO.md\n```\n\nAfter a few iterations of the loop, it seemed to be on track:\n\n![First few commits](./assets/first-commits.png)\n\n### What Happened\n\nWe worked until after 2 AM, setting up a few VM instances (tmux sessions on GCP instances) to run the Claude Code loops, then headed home to get a few hours of sleep. \n\nWe came back in the morning to an [almost fully functional port](https://github.com/repomirrorhq/better-use) of Browser Use to TypeScript. \n\n![Better Use CLI](./assets/better-use.png)\n\nHere it is scraping the top 3 posts from Hacker News.\n\n[better-use.webm](https://github.com/user-attachments/assets/bdd15e9e-08e4-48a2-a6f9-05a550347c46)\n\n[View on YouTube](https://www.youtube.com/watch?v=fqp8EbYOPk8)\n\nHere's the Browser Use founder [@gregpr07](https://x.com/gregpr07), checking out the code. We think he liked it.\n\n![Gregor and Simon smiling at laptop](./assets/gregor.png)\n\n\n### We Did Some More\n\nSince we were spinning up a few loops anyways, we decided to port a few more software projects to see what came out.\n\nThe Vercel AI SDK is in TypeScript... but what if you could use it in Python? Yeah... [it kind of worked](https://github.com/repomirrorhq/ai-sdk-python). \n\n![AI SDK FastAPI Adapters](./assets/ai-sdk-fastapi.png)\n\nIf you've ever struggled with some of the deeply-nested type constructors in the AI SDK, well, now you can struggle with them in Python too.\n\nWe also tried a few specs-to-code loops - recreating [Convex](https://www.convex.dev) and [Dedalus](https://dedalus.dev) from their docs' llms-full.txt. Here's a first pass at [OpenDedalus](https://github.com/repomirrorhq/open-dedalus).\n\n![Image of open-dedalus README](./assets/open-dedalus.png)\n\n### What We Learned\n\n**Early Stopping** \n\nWhen starting the agents, we had a lot of questions. Will the agent write tests? Will it get stuck in an infinite loop and drift into random unrelated features? \n\nWe were pleasantly surprised to find that the agent wrote tests, kept to its original instructions, never got stuck, kept scope under control and mostly declared the port 'done'.\n\nAfter finishing the port, most of the agents settled for writing extra tests or continuously updating agent/TODO.md to clarify how \"done\" they were. In one instance, the agent actually used `pkill` [to terminate itself](https://www.youtube.com/watch?v=UOLBTRazZpM) after realizing it was stuck in an infinite loop. \n\n\n![Agent stopping its own process](./assets/pkill.png)\n\n**Overachieving** \n\nAnother cool emergent behavior (as is common with LLMs) - After finishing the initial port, our AI SDK Python agent started adding extra features such as an integration for Flask and FastAPI (something that has no counterpart in the AI SDK JS version), as well as support for schema validators via Pydantic, Marshmallow, JSONSchema, etc.\n\n**Keep the Prompt Simple** \n\nOverall we found that less is more - a simple prompt is better than a complex one. You want to focus on the engine, not the scaffolding. Different members of our team kicking off different projects played around with instructions and ordering. You can view the actual prompts we used in the [prompts folder](./prompts/).\n\nAt one point we tried “improving” the prompt with Claude’s help. It ballooned to 1,500 words. The agent immediately got slower and dumber. We went back to 103 words and it was back on track. \n\n**This is not perfect** \n\nFor both [better-use](https://github.com/repomirrorhq/better-use) and [ai-sdk-python](https://github.com/repomirrorhq/ai-sdk-python), the headless agent didn't always deliver perfect working code. We ended up going in and updating the prompts incrementally or working with Claude Code interactively to get things from 90% to 100%. \n\nAnd as much as Claude may [claim that things are 100% perfectly implemented](https://github.com/repomirrorhq/better-use/blob/master/agent/TODO.md), there are a few browser-use demos from the Python project that don't work yet in TypeScript.\n\n\n### Numbers\n\nWe spent a little less than $800 on inference for the project. Overall the agents made ~1100 commits across all software projects. Each Sonnet agent costs about $10.50/hour to run overnight.\n\n\n### What We Built Around It\n\nAs we went about bootstrapping so many of these, we put together a simple tool to help set up a source/target repo pair for this sync work. (and yeah, [we also built that with Ralph](https://github.com/repomirrorhq/repomirror/blob/main/prompt.md))\n\n```\nnpx repomirror init \\\n    --source-dir ./browser-use \\\n    --target-dir ./browser-use-zig \\\n    --instructions \"convert browser use to Zig\"\n```\n\nInstructions can be anything like \"convert from React to Vue\" or \"change from gRPC to REST using OpenAPI spec codegen\".\n\nIt's not perfectly architected, and it's a little hacky. But it was enough to hack things together, and it's designed similar to shadcn's \"open-box\" approach where it generates scripts/prompts that you are welcome to modify after the `init` phase. \n\nAfter the init phase, you'll have:\n\n\n```\n.repomirror/\n   - prompt.md\n   - sync.sh\n   - ralph.sh\n```\n\nWhen you've checked out the prompt and you're ready to test it, you can run `npx repomirror sync` to do a single iteration of the loop, and you can run `npx repomirror sync-forever` to kick off the Ralph infinite loop:\n\n[repomirror.webm](https://github.com/user-attachments/assets/7616825a-064d-4a5b-b1bc-08fc5f816172)\n\n[View on YouTube](https://www.youtube.com/watch?v=_GxemIzk2lo)\n\n\nIf you wanna play with some of the other repos, they're listed on the [README](https://github.com/repomirrorhq/repomirror?tab=readme-ov-file#projects). [better-use](https://github.com/repomirrorhq/better-use) is now on npm:\n\n```\nnpx better-use run\n```\n\nai-sdk-python still has [one or two issues](https://github.com/repomirrorhq/ai-sdk-python/blob/master/agent/FIX_PLAN.md) that we're working on before it makes it to PyPI.\n\n### Closing Thoughts\n\nAs you might imagine, our thoughts are all a little chaotic and conflicting, so rather than a cohesive conclusion, we'll just leave with a few of our team's personal reflections on the last ~29 hours:\n\n\n> I'm a little bit feeling the AGI and it's mostly exciting but also terrifying.\n\n> The minimalist in me is happy to have hard proof that we are probably overcomplicating things. \n\n> Clear to me that we're at the very very beginning of the exponential takeoff curve.\n\nThanks to the whole team [@yonom](https://x.com/simonfarshid) and [@AVGVSTVS96](https://x.com/AVGVSTVS96) from [assistant-ui](https://github.com/assistant-ui), [@dexhorthy](https://x.com/dexhorthy) from [HumanLayer](https://humanlayer.dev), [@Lantos1618](https://x.com/Lantos1618) from [github.gg](https://github.gg), and to [Geoff](https://x.com/GeoffreyHuntley) for the inspiration.\n\nand yeah, we forgot to get a team photo\n\n<img width=\"1166\" height=\"292\" alt=\"Screenshot 2025-08-25 at 9 03 34 AM\" src=\"https://github.com/user-attachments/assets/e19c3132-12dd-45b6-b470-f4b5281dd609\" />\n\n\n\n"
  },
  {
    "path": "repomirror.yaml",
    "content": "sourceRepo: ./\ntargetRepo: /tmp/test-target2\ntransformationInstructions: transform typescript to python\n"
  },
  {
    "path": "specs/devtooling.md",
    "content": "developer workflow:\n\npublish to npm package repomirror\n\nunit tests in github actions: just recent node, no need for matrix testing"
  },
  {
    "path": "specs/github_actions.md",
    "content": "## PR Sync\n\n```\nnpx repomirror setup-github-pr-sync\n```\n\n```\nI'll help you set up a github actions workflow that will run the sync-one command on every pr merge\n\n\nTarget repo, e.g. repomirrorhq/repomirror:\nTimes to loop (advanced, recommend 3): [3]\n```\n\nFlags `--target-repo` and `--times-to-loop`\n\ncreates .github/workflows/repomirror.yml\n\ntarget-repo and times-to-loop are persisted to repomirror.yaml similar to other flags and loaded as defaults when running the `setup-github-pr-sync` command.\n\nif already present, prompts \"want to overwrite?\" - exits if no. flag --overwrite to force overwrite.\n\nsetup-github-pr-sync will create a github actions workflow that will run the sync-one command on every pr merge\n\n\nIt will prompt the user for followup steps:\n\n- push to github\n- add secrets for ANTHROPIC_API_KEY and GITHUB_TOKEN, where GITHUB_TOKEN has read/push access to the target repo\n\n\nthe workflow will always have a workflow_dispatch trigger, and an optional push trigger.\n\nthe workflow will install repomirror and run the sync-one command in a loop N times\n\n\n### dispatch sync\n\n```\nnpx repomirror dispatch-sync\n```\n\nwill check to ensure the workflow exists and is present in the current repo.\n\nwill dispatch a workflow_dispatch event to the repomirror.yml workflow using the `gh` cli\n\n```\n\nthis will prompt the user, describing what's gonna happen and get confirmation. a `-y` `--yes` flag will skip the confirmation prompt. A `--quiet` `-q` flag will suppress output. Quiet cannot be used without --yes, but --yes can be used without --quiet.\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "specs/repomirror.md",
    "content": "run repomirror init\n\none line use prompt that asks \n\n```\nI'll help you maintain a transformed copy of this repo:\n\nSource Repo you want to tckransform: [..] # note 0\nWhere do you want to transform code to: [..] # note 1\nWhat changes do you want to make: [e.g. \"translate this python repo to typescript\"] \n```\n\nnote 0 - the default source repo is the current directory `./`\nnote 1 - the default transform directory is `../REPONAME-transformed`\n\nall prompts should be loadable from a repomirror.yaml file if present,\nor settable with a command line flag. `repomirror help` or `--help` should explain how this works and the available options.\n\n- all prompts/cli flags are stashed to a repomirror.yaml during setup, and defaults are populated from the yaml file if present (instead of core defaults)\n\n\npreflight checks\n\n- ensure the target directory exists\n- ensure the target directory is initialized as a git repo\n- ensure the target directory has at least one upstream\n- ensure the user has a configured claude code profile (e.g. `claude -p \"say hi\" and ensure the output contains \"hi\" or \"Hi\")\n\nThe preflight checks should print output about what they are doing and what they are checking.\n\n### Background: Claude sdk:\n\n```\nimport { query } from \"@anthropic-ai/claude-code\";\n\n// IMPORTANT: Handle all message types to avoid hanging\n// The loop will wait forever if you don't handle errors or check for completion\nfor await (const message of query({\n  prompt: \"...PROMPT...\",\n})) {\n  if (message.type === \"result\") {\n    if (message.is_error) {\n      // Handle error case - MUST break or throw to avoid hanging\n      throw new Error(message.result || \"Claude SDK error\");\n    }\n    console.log(message.result);\n    break; // Exit loop after getting result\n  }\n  // Consider adding timeout or other message type handlers\n}\n```\n\n### step 1\n\nuse the claude sdk to read files and generate a prompt \n\n**CRITICAL IMPLEMENTATION NOTE**: The Claude SDK async iterator can hang indefinitely if not properly handled. Ensure:\n1. Always break the loop after receiving a valid result\n2. Handle error cases explicitly (check `is_error` flag)\n3. Consider implementing a timeout mechanism\n4. Store the result and break immediately - don't continue iterating\n\nwhere PROMPT conveys:\n\n```\nyour task is to generate an optimized prompt for repo transformation. The prompt should match the format of the examples below.\n\n<example 1>\nYour job is to port [SOURCE PATH] monorepo (for react) to [TARGET PATH] (for vue) and maintain the repository.\n\nYou have access to the current [SOURCE PATH] repository as well as the [TARGET PATH] repository.\n\nMake a commit and push your changes after every single file edit.\n\nUse the [TARGET_PATH]/agent/ directory as a scratchpad for your work. Store long term plans and todo lists there.\n\nThe original project was mostly tested by manually running the code. When porting, you will need to write end to end and unit tests for the project. But make sure to spend most of your time on the actual porting, not on the testing. A good heuristic is to spend 80% of your time on the actual porting, and 20% on the testing.\n</example 1>\n\n<example 2>\nYour job is to port browser-use monorepo (Python) to browser-use-ts (Typescript) and maintain the repository.\n\nYou have access to the current [SOURCE PATH] repository as well as the target [TARGET_PATH] repository.\n\nMake a commit and push your changes after every single file edit.\n\nUse the [TARGET PATH]/agent/ directory as a scratchpad for your work. Store long term plans and todo lists there.\n\nThe original project was mostly tested by manually running the code. When porting, you will need to write end to end and unit tests for the project. But make sure to spend most of your time on the actual porting, not on the testing. A good heuristic is to spend 80% of your time on the actual porting, and 20% on the testing.\n</example 2>\n\nThe users instructions for transformation are:\n\n<user instructions from question 2 above>\n\nYour Job:\n\nWhen you are ready, respond with EXACTLY the prompt matching the example, tailored for following the users' instructions and nothing else.\n\nYou should follow the format EXACTLY, filling in information based on what you learn from a CURSORY exploration of the source repo (this directory). Ensure you ONLY use the read tools (Read, Search, Grep, LS, Glob, etc) to explore the repo. You only need enough sense to build a good prompt, so dont use subagents.\n```\n\n### step 2\n\nrun claude code with the SDK using the prompt you generated with templating.\n\nAs you are building you may need to test the phrasing to get claude to output \n\n### step 3 \n\nadd the following files to the source repo in .repomirror/\n\n- .repomirror/prompt.md # contents from the prompt\n- .repomirror/sync.sh\n- .repomirror/ralph.sh\n- .repomirror/.gitignore\n\n.repomirror/.gitignore has the exact below contents:\n```\nclaude_output.jsonl\n```\n\nsync.sh has the exact below contents:\n```\ncat .repomirror/prompt.md | \\\n        claude -p --output-format=stream-json --verbose --dangerously-skip-permissions --add-dir PATH_TO_TARGET_REPO | \\\n        tee -a .repomirror/claude_output.jsonl | \\\n        npx repomirror visualize --debug;\n```\n\nralph.sh has the exact below contents:\n\n```\nwhile :; do\n  ./sync.sh\n  echo -e \"===SLEEP===\\n===SLEEP===\\n\"; echo 'looping';\nsleep 10;\ndone\n```\n\nvisualize command is a cli command that uses the exact same logic in the hack/visualize.ts file.\n\nThe shell scripts are included in the npm dist/ bundle and baked into the package so they can be copied out of the package root by `npx repomirror init` cli command.\n\n**NOTE** - the above commands are sketches, you may find you need to adjust them to fit together well or to improve usability or reduce error-proneness.\n\n### step 4\n\nOutput instructions to the user about next steps to run the commands\n\n```\nrun `npx repomirror sync` - this will run the sync.sh script  once\n\nrun `npx repomirror sync-forever` - this will run the ralph.sh script, working forever to implement all the changes. \n\nThe following files were created and safe to commit. Edit prompt.md as you see fit, but you probably dont want to run these files directly\n\n- .repomirror/prompt.md # prompt\n- .repomirror/sync.sh \n- .repomirror/ralph.sh \n- .repomirror/.gitignore \n```\n\n### INIT CLI NOTES\n\n- if .repomirror already exists, prompt the user if they want to overwrite the existing .repomirror/ directory. Flag to `--overwrite` to force overwrite."
  },
  {
    "path": "specs_deprecated_ignore/github_actions.md",
    "content": "## PR Sync\n\n```\nnpx repomirror setup-github-pr-sync\n```\n\n```\nI'll help you set up a github actions workflow that will run the sync-one command on every pr merge\n\n\nTarget repo, e.g. repomirrorhq/repomirror:\nTimes to loop (advanced, recommend 3): [3]\n```\n\nFlags `--target-repo` and `--times-to-loop`\n\ncreates .github/workflows/repomirror.yml\n\ntarget-repo and times-to-loop are persisted to repomirror.yaml similar to other flags and loaded as defaults when running the `setup-github-pr-sync` command.\n\nif already present, prompts \"want to overwrite?\" - exits if no. flag --overwrite to force overwrite.\n\nsetup-github-pr-sync will create a github actions workflow that will run the sync-one command on every pr merge\n\n\nIt will prompt the user for followup steps:\n\n- push to github\n- add secrets for ANTHROPIC_API_KEY and GITHUB_TOKEN, where GITHUB_TOKEN has read/push access to the target repo\n\n\nthe workflow will always have a workflow_dispatch trigger, and an optional push trigger.\n\nthe workflow will install repomirror and run the sync-one command in a loop N times\n\n\n### dispatch sync\n\n```\nnpx repomirror dispatch-sync\n```\n\nwill check to ensure the workflow exists and is present in the current repo.\n\nwill dispatch a workflow_dispatch event to the repomirror.yml workflow using the `gh` cli\n\n```\n\nthis will prompt the user, describing what's gonna happen and get confirmation. a `-y` `--yes` flag will skip the confirmation prompt. A `--quiet` `-q` flag will suppress output. Quiet cannot be used without --yes, but --yes can be used without --quiet.\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "specs_deprecated_ignore/remote_sync.md",
    "content": "x"
  },
  {
    "path": "specs_deprecated_ignore/sync_check",
    "content": "sync-check serves as a terminator for the sync-one loop.\n\nfor example,\n\n```\nwhile ! sync-check TARGET; do\n    sync-one TARGET\ndone;\n```\n\nsync-check will check if the target repo is up to date with the source repo.\n\nit uses a similar prompt to sync-one, but with the final prompt to "
  },
  {
    "path": "src/cli.ts",
    "content": "#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport { init } from \"./commands/init\";\nimport { syncOne } from \"./commands/sync-one\";\nimport { sync } from \"./commands/sync\";\nimport { syncForever } from \"./commands/sync-forever\";\nimport { visualize } from \"./commands/visualize\";\nimport { remote } from \"./commands/remote\";\nimport { push } from \"./commands/push\";\nimport { pull } from \"./commands/pull\";\nimport { githubActions } from \"./commands/github-actions\";\nimport { setupGithubPrSync } from \"./commands/setup-github-pr-sync\";\nimport { dispatchSync } from \"./commands/dispatch-sync\";\n\nconst program = new Command();\n\nprogram\n  .name(\"repomirror\")\n  .description(\"Sync and transform repositories using AI agents\")\n  .version(\"0.1.0\")\n  .addHelpText(\n    \"after\",\n    `\nConfiguration:\n  repomirror uses a repomirror.yaml file to store configuration.\n  On first run, settings are saved to this file.\n  On subsequent runs, the file is used for defaults.\n\n  Command-line flags override both yaml defaults and interactive prompts.\n\nExamples:\n  $ npx repomirror init\n      Interactive mode with prompts\n\n  $ npx repomirror init --source ./ --target ../myrepo-ts --instructions \"convert to typescript\"\n      Skip prompts and use provided values\n\n  $ npx repomirror help\n      Show this help message`,\n  );\n\nprogram\n  .command(\"init\")\n  .description(\"Initialize repomirror in current directory\")\n  .option(\"-s, --source <path>\", \"Source repository path\")\n  .option(\"-t, --target <path>\", \"Target repository path\")\n  .option(\"-i, --instructions <text>\", \"Transformation instructions\")\n  .action((options) => {\n    init({\n      sourceRepo: options.source,\n      targetRepo: options.target,\n      transformationInstructions: options.instructions,\n    });\n  });\n\nprogram\n  .command(\"sync\")\n  .description(\"Run one sync iteration\")\n  .option(\"--auto-push\", \"Automatically push to all remotes after successful sync\")\n  .action((options) => sync({ autoPush: options.autoPush }));\n\nprogram\n  .command(\"sync-one\")\n  .description(\"Run one sync iteration (alias for sync)\")\n  .option(\"--auto-push\", \"Automatically push to all remotes after successful sync\")\n  .action((options) => syncOne({ autoPush: options.autoPush }));\n\nprogram\n  .command(\"sync-forever\")\n  .description(\"Run sync continuously\")\n  .option(\"--auto-push\", \"Automatically push to all remotes after each sync iteration\")\n  .action((options) => syncForever({ autoPush: options.autoPush }));\n\nprogram\n  .command(\"visualize\")\n  .description(\"Visualize Claude output stream\")\n  .option(\"--debug\", \"Show debug timestamps\")\n  .action((options) => visualize(options));\n\nprogram\n  .command(\"remote <action> [args...]\")\n  .description(\"Manage remote repositories\")\n  .addHelpText(\n    \"after\",\n    `\nActions:\n  add <name> <url> [branch]    Add a remote repository (default branch: main)\n  list                         List configured remotes\n  remove <name>                Remove a remote repository\n\nExamples:\n  $ npx repomirror remote add origin https://github.com/user/repo.git\n  $ npx repomirror remote add staging https://github.com/user/staging.git develop\n  $ npx repomirror remote list\n  $ npx repomirror remote remove origin`,\n  )\n  .action((action, args) => remote(action, ...args));\n\nprogram\n  .command(\"push\")\n  .description(\"Push transformed changes to remote repositories\")\n  .option(\"-r, --remote <name>\", \"Remote repository name\")\n  .option(\"-b, --branch <name>\", \"Branch name to push to\")\n  .option(\"--all\", \"Push to all configured remotes\")\n  .option(\"--dry-run\", \"Show what would be pushed without actually pushing\")\n  .addHelpText(\n    \"after\",\n    `\nExamples:\n  $ npx repomirror push\n      Push to default remote (origin/main)\n\n  $ npx repomirror push --remote staging\n      Push to specific remote using its configured branch\n\n  $ npx repomirror push --remote origin --branch feature-branch\n      Push to specific remote and branch\n\n  $ npx repomirror push --all\n      Push to all configured remotes\n\n  $ npx repomirror push --dry-run\n      Show what would be pushed without pushing`,\n  )\n  .action((options) => push(options));\n\nprogram\n  .command(\"pull\")\n  .description(\"Pull source changes and trigger re-sync\")\n  .option(\"--source-only\", \"Pull source without re-sync\")\n  .option(\"--sync-after\", \"Pull and run continuous sync after\")\n  .option(\"--check\", \"Check for source changes without pulling\")\n  .addHelpText(\n    \"after\",\n    `\nExamples:\n  $ npx repomirror pull\n      Pull source changes and re-sync (if auto_sync is enabled)\n\n  $ npx repomirror pull --source-only\n      Pull source changes without triggering sync\n\n  $ npx repomirror pull --sync-after\n      Pull source changes and run continuous sync\n\n  $ npx repomirror pull --check\n      Check for available changes without pulling`,\n  )\n  .action((options) => pull(options));\n\nprogram\n  .command(\"github-actions\")\n  .description(\"Generate GitHub Actions workflow for automated syncing\")\n  .option(\"-n, --name <name>\", \"Workflow file name (default: repomirror-sync.yml)\")\n  .option(\"-s, --schedule <cron>\", \"Cron schedule for automatic runs\")\n  .option(\"--no-auto-push\", \"Disable automatic pushing to target repo\")\n  .addHelpText(\n    \"after\",\n    `\nGenerates a GitHub Actions workflow file for automated repository syncing.\n\nExamples:\n  $ npx repomirror github-actions\n      Interactive setup with prompts\n\n  $ npx repomirror github-actions --schedule \"0 */12 * * *\"\n      Run every 12 hours\n\n  $ npx repomirror github-actions --no-auto-push\n      Create workflow without automatic push to target\n\nNotes:\n  - Requires repomirror.yaml to be present\n  - Creates workflow in .github/workflows/\n  - You'll need to set up CLAUDE_API_KEY secret in GitHub`,\n  )\n  .action((options) => githubActions({\n    workflowName: options.name,\n    schedule: options.schedule,\n    autoPush: options.autoPush,\n  }));\n\nprogram\n  .command(\"setup-github-pr-sync\")\n  .description(\"Setup GitHub Actions workflow for PR-triggered sync\")\n  .option(\"-t, --target-repo <repo>\", \"Target repository (owner/repo format)\")\n  .option(\"-l, --times-to-loop <number>\", \"Number of times to loop sync-one command\", \"3\")\n  .option(\"--overwrite\", \"Force overwrite existing workflow file\")\n  .addHelpText(\n    \"after\",\n    `\nSets up a GitHub Actions workflow that runs sync-one command on PR merges.\n\nExamples:\n  $ npx repomirror setup-github-pr-sync\n      Interactive setup with prompts\n\n  $ npx repomirror setup-github-pr-sync --target-repo myorg/myrepo\n      Specify target repository directly\n\n  $ npx repomirror setup-github-pr-sync --times-to-loop 5\n      Set number of sync iterations\n\n  $ npx repomirror setup-github-pr-sync --overwrite\n      Force overwrite existing workflow file\n\nNotes:\n  - Creates .github/workflows/repomirror.yml\n  - Settings are persisted to repomirror.yaml\n  - Workflow has workflow_dispatch trigger for manual runs\n  - Requires ANTHROPIC_API_KEY and GITHUB_TOKEN secrets`,\n  )\n  .action((options) => setupGithubPrSync({\n    targetRepo: options.targetRepo,\n    timesToLoop: options.timesToLoop ? parseInt(options.timesToLoop) : undefined,\n    overwrite: options.overwrite,\n  }));\n\nprogram\n  .command(\"dispatch-sync\")\n  .description(\"Dispatch GitHub Actions workflow for manual sync\")\n  .option(\"-y, --yes\", \"Skip confirmation prompt\")\n  .option(\"-q, --quiet\", \"Suppress output (requires --yes)\")\n  .addHelpText(\n    \"after\",\n    `\nDispatches a workflow_dispatch event to the repomirror.yml workflow.\n\nExamples:\n  $ npx repomirror dispatch-sync\n      Interactive mode with confirmation prompt\n\n  $ npx repomirror dispatch-sync --yes\n      Skip confirmation and dispatch immediately\n\n  $ npx repomirror dispatch-sync --yes --quiet\n      Silent dispatch without output\n\nNotes:\n  - Requires .github/workflows/repomirror.yml to exist\n  - Requires GitHub CLI (gh) to be installed and authenticated\n  - Workflow must have workflow_dispatch trigger enabled\n  - --quiet flag can only be used with --yes flag`,\n  )\n  .action((options) => dispatchSync({\n    yes: options.yes,\n    quiet: options.quiet,\n  }));\n\nprogram.parse();\n"
  },
  {
    "path": "src/commands/dispatch-sync.ts",
    "content": "import { promises as fs } from \"fs\";\nimport { join } from \"path\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { execa } from \"execa\";\nimport inquirer from \"inquirer\";\n\ninterface DispatchSyncOptions {\n  yes?: boolean;\n  quiet?: boolean;\n}\n\nasync function workflowExists(): Promise<boolean> {\n  try {\n    const workflowPath = join(process.cwd(), \".github\", \"workflows\", \"repomirror.yml\");\n    await fs.access(workflowPath);\n    return true;\n  } catch {\n    return false;\n  }\n}\n\nasync function getRepoInfo(): Promise<{ owner: string; repo: string } | null> {\n  try {\n    const { stdout } = await execa(\"git\", [\"config\", \"--get\", \"remote.origin.url\"]);\n    const url = stdout.trim();\n    \n    // Parse GitHub URL to extract owner/repo\n    let match = url.match(/github\\.com[:/]([^/]+)\\/([^/.]+)/);\n    if (!match) {\n      return null;\n    }\n    \n    return {\n      owner: match[1],\n      repo: match[2],\n    };\n  } catch {\n    return null;\n  }\n}\n\nasync function checkGhCliInstalled(): Promise<boolean> {\n  try {\n    await execa(\"gh\", [\"--version\"]);\n    return true;\n  } catch {\n    return false;\n  }\n}\n\nasync function dispatchWorkflow(owner: string, repo: string, quiet: boolean): Promise<void> {\n  const spinner = quiet ? null : ora(\"Dispatching workflow...\").start();\n\n  try {\n    const { stdout } = await execa(\"gh\", [\n      \"workflow\",\n      \"run\",\n      \"repomirror.yml\",\n      \"--repo\", \n      `${owner}/${repo}`,\n    ]);\n\n    if (spinner) {\n      spinner.succeed(\"Workflow dispatched successfully\");\n    } else if (!quiet) {\n      console.log(chalk.green(\"✅ Workflow dispatched successfully\"));\n    }\n\n    // Show workflow run URL if available\n    if (!quiet && stdout) {\n      console.log(chalk.gray(stdout));\n    }\n  } catch (error) {\n    if (spinner) {\n      spinner.fail(\"Failed to dispatch workflow\");\n    }\n\n    if (error instanceof Error) {\n      const errorMessage = error.message.toLowerCase();\n      \n      if (errorMessage.includes(\"not found\") || errorMessage.includes(\"404\")) {\n        console.error(chalk.red(\"Error: Workflow 'repomirror.yml' not found in the repository\"));\n        console.log(chalk.gray(\"Make sure the workflow file exists and you have access to the repository\"));\n      } else if (errorMessage.includes(\"authentication\") || errorMessage.includes(\"permission\")) {\n        console.error(chalk.red(\"Error: Authentication failed\"));\n        console.log(chalk.gray(\"Make sure you're authenticated with GitHub CLI:\"));\n        console.log(chalk.gray(\"  gh auth login\"));\n      } else if (errorMessage.includes(\"workflow_dispatch\")) {\n        console.error(chalk.red(\"Error: Workflow does not support manual dispatch\"));\n        console.log(chalk.gray(\"Make sure the workflow has 'workflow_dispatch:' trigger\"));\n      } else {\n        console.error(chalk.red(`Error dispatching workflow: ${error.message}`));\n      }\n    } else {\n      console.error(chalk.red(`Error dispatching workflow: ${String(error)}`));\n    }\n\n    throw error;\n  }\n}\n\nexport async function dispatchSync(options: DispatchSyncOptions = {}): Promise<void> {\n  // Validate flag combination\n  if (options.quiet && !options.yes) {\n    console.error(chalk.red(\"Error: --quiet cannot be used without --yes\"));\n    console.log(chalk.gray(\"Use --quiet and --yes together, or use --yes alone\"));\n    process.exit(1);\n  }\n\n  // Check if repomirror.yml workflow exists\n  const exists = await workflowExists();\n  if (!exists) {\n    console.error(chalk.red(\"Error: .github/workflows/repomirror.yml not found\"));\n    console.log(chalk.gray(\"Run 'npx repomirror setup-github-pr-sync' to create the workflow first\"));\n    process.exit(1);\n  }\n\n  // Check if gh CLI is installed\n  const ghInstalled = await checkGhCliInstalled();\n  if (!ghInstalled) {\n    console.error(chalk.red(\"Error: GitHub CLI (gh) is not installed\"));\n    console.log(chalk.gray(\"Install it from: https://cli.github.com/\"));\n    process.exit(1);\n  }\n\n  // Get repository information\n  const repoInfo = await getRepoInfo();\n  if (!repoInfo) {\n    console.error(chalk.red(\"Error: Could not determine GitHub repository\"));\n    console.log(chalk.gray(\"Make sure you're in a git repository with a GitHub origin remote\"));\n    process.exit(1);\n  }\n\n  const { owner, repo } = repoInfo;\n\n  // Show what's going to happen (unless quiet)\n  if (!options.quiet) {\n    console.log(chalk.cyan(\"This will dispatch the repomirror.yml workflow to run sync-one command\"));\n    console.log(chalk.gray(`Repository: ${owner}/${repo}`));\n    console.log(chalk.gray(\"Workflow: .github/workflows/repomirror.yml\"));\n    console.log();\n  }\n\n  // Get confirmation (unless --yes flag is used)\n  if (!options.yes) {\n    const { shouldProceed } = await inquirer.prompt([\n      {\n        type: \"confirm\",\n        name: \"shouldProceed\",\n        message: \"Do you want to dispatch the workflow?\",\n        default: true,\n      },\n    ]);\n\n    if (!shouldProceed) {\n      console.log(chalk.yellow(\"Operation cancelled\"));\n      process.exit(0);\n    }\n  }\n\n  try {\n    await dispatchWorkflow(owner, repo, options.quiet || false);\n    \n    if (!options.quiet) {\n      console.log(chalk.green(\"\\n✅ Workflow dispatch completed\"));\n      console.log(chalk.gray(\"You can monitor the workflow run at:\"));\n      console.log(chalk.gray(`https://github.com/${owner}/${repo}/actions`));\n    }\n  } catch (error) {\n    console.error(\n      chalk.red(\n        `Dispatch failed: ${error instanceof Error ? error.message : String(error)}`\n      )\n    );\n    process.exit(1);\n  }\n}"
  },
  {
    "path": "src/commands/github-actions.ts",
    "content": "import { promises as fs } from \"fs\";\nimport { join } from \"path\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport inquirer from \"inquirer\";\n\ninterface GitHubActionsOptions {\n  workflowName?: string;\n  schedule?: string;\n  autoPush?: boolean;\n}\n\nconst DEFAULT_WORKFLOW = `name: RepoMirror Sync\n\non:\n  schedule:\n    # Run every 6 hours\n    - cron: '{SCHEDULE}'\n  workflow_dispatch: # Allow manual trigger\n  push:\n    branches: [ main ]\n    paths:\n      - '.repomirror/**'\n      - 'repomirror.yaml'\n\njobs:\n  sync:\n    runs-on: ubuntu-latest\n    \n    steps:\n    - name: Checkout source repository\n      uses: actions/checkout@v3\n      with:\n        path: source\n    \n    - name: Checkout target repository\n      uses: actions/checkout@v3\n      with:\n        repository: {TARGET_REPO}\n        token: \\${{ secrets.GITHUB_TOKEN }}\n        path: target\n    \n    - name: Setup Node.js\n      uses: actions/setup-node@v3\n      with:\n        node-version: '20'\n    \n    - name: Install repomirror\n      run: npm install -g repomirror\n    \n    - name: Setup Claude Code\n      env:\n        CLAUDE_API_KEY: \\${{ secrets.CLAUDE_API_KEY }}\n      run: |\n        # Setup Claude Code with API key\n        echo \"Setting up Claude Code...\"\n        # Note: You'll need to configure Claude Code authentication\n        # according to your setup. This might involve setting up\n        # a service account or using API keys.\n    \n    - name: Run RepoMirror sync\n      working-directory: source\n      env:\n        SKIP_CLAUDE_TEST: true # Skip interactive Claude test in CI\n      run: |\n        # Run the sync once\n        npx repomirror sync\n    \n    - name: Push changes to target\n      if: {AUTO_PUSH}\n      working-directory: target\n      run: |\n        git config user.name \"GitHub Actions\"\n        git config user.email \"actions@github.com\"\n        \n        if [ -n \"$(git status --porcelain)\" ]; then\n          git add -A\n          git commit -m \"Automated sync from source repository\"\n          git push\n        else\n          echo \"No changes to push\"\n        fi\n`;\n\nexport async function githubActions(options?: GitHubActionsOptions): Promise<void> {\n  console.log(chalk.cyan(\"Setting up GitHub Actions workflow for RepoMirror\\n\"));\n\n  // Check if repomirror.yaml exists\n  const configPath = join(process.cwd(), \"repomirror.yaml\");\n  try {\n    await fs.access(configPath);\n  } catch {\n    console.error(chalk.red(\"Error: repomirror.yaml not found\"));\n    console.log(chalk.yellow(\"Please run 'npx repomirror init' first\"));\n    process.exit(1);\n  }\n\n  // Load config to get target repo\n  const yaml = await import(\"yaml\");\n  const configContent = await fs.readFile(configPath, \"utf-8\");\n  const config = yaml.parse(configContent);\n\n  // Prompt for workflow configuration\n  const answers = await inquirer.prompt([\n    {\n      type: \"input\",\n      name: \"workflowName\",\n      message: \"Workflow file name:\",\n      default: options?.workflowName || \"repomirror-sync.yml\",\n      validate: (input) => {\n        if (!input.endsWith(\".yml\") && !input.endsWith(\".yaml\")) {\n          return \"Workflow file must end with .yml or .yaml\";\n        }\n        return true;\n      },\n    },\n    {\n      type: \"input\",\n      name: \"schedule\",\n      message: \"Cron schedule (or press enter for every 6 hours):\",\n      default: options?.schedule || \"0 */6 * * *\",\n      when: !options?.schedule,\n    },\n    {\n      type: \"confirm\",\n      name: \"autoPush\",\n      message: \"Automatically push changes to target repository?\",\n      default: options?.autoPush !== undefined ? options.autoPush : true,\n      when: options?.autoPush === undefined,\n    },\n    {\n      type: \"input\",\n      name: \"targetRepo\",\n      message: \"Target repository (owner/repo format for GitHub):\",\n      default: config.targetRepo?.replace(/^\\.\\.\\//, \"\").replace(/-transformed$/, \"\"),\n      validate: (input) => {\n        if (!input || input === config.targetRepo) {\n          return \"Please provide the GitHub repository in owner/repo format\";\n        }\n        return true;\n      },\n    },\n  ]);\n\n  const finalOptions = {\n    workflowName: options?.workflowName || answers.workflowName,\n    schedule: options?.schedule || answers.schedule,\n    autoPush: options?.autoPush !== undefined ? options.autoPush : answers.autoPush,\n    targetRepo: answers.targetRepo,\n  };\n\n  // Create .github/workflows directory if it doesn't exist\n  const workflowDir = join(process.cwd(), \".github\", \"workflows\");\n  await fs.mkdir(workflowDir, { recursive: true });\n\n  // Generate workflow content\n  const workflowContent = DEFAULT_WORKFLOW\n    .replace(\"{SCHEDULE}\", finalOptions.schedule)\n    .replace(\"{TARGET_REPO}\", finalOptions.targetRepo)\n    .replace(\"{AUTO_PUSH}\", finalOptions.autoPush ? \"true\" : \"false\");\n\n  // Write workflow file\n  const workflowPath = join(workflowDir, finalOptions.workflowName);\n  const spinner = ora(\"Creating GitHub Actions workflow...\").start();\n  \n  try {\n    await fs.writeFile(workflowPath, workflowContent);\n    spinner.succeed(\"GitHub Actions workflow created\");\n    \n    console.log(chalk.green(`\\n✅ Workflow created at ${workflowPath}`));\n    console.log(chalk.cyan(\"\\nNext steps:\"));\n    console.log(chalk.white(\"1. Review and customize the workflow file as needed\"));\n    console.log(chalk.white(\"2. Set up the following GitHub secrets:\"));\n    console.log(chalk.gray(\"   - CLAUDE_API_KEY: Your Claude API key\"));\n    console.log(chalk.gray(\"   - GITHUB_TOKEN: Already provided by GitHub Actions\"));\n    console.log(chalk.white(\"3. Commit and push the workflow file to your repository\"));\n    console.log(chalk.white(\"4. The workflow will run on the schedule you specified\"));\n    \n    console.log(chalk.yellow(\"\\n⚠️  Important:\"));\n    console.log(chalk.yellow(\"Make sure to configure Claude Code authentication in the workflow\"));\n    console.log(chalk.yellow(\"This typically requires setting up API keys or service accounts\"));\n  } catch (error) {\n    spinner.fail(\"Failed to create workflow\");\n    console.error(chalk.red(`Error: ${error instanceof Error ? error.message : String(error)}`));\n    process.exit(1);\n  }\n}"
  },
  {
    "path": "src/commands/init.ts",
    "content": "import { promises as fs } from \"fs\";\nimport path, { join, basename, resolve } from \"path\";\nimport inquirer from \"inquirer\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { query } from \"@anthropic-ai/claude-code\";\nimport { execa } from \"execa\";\nimport yaml from \"yaml\";\n\ninterface InitOptions {\n  sourceRepo: string;\n  targetRepo: string;\n  transformationInstructions: string;\n}\n\ninterface RemoteConfig {\n  url: string;\n  branch: string;\n  auto_push?: boolean;\n}\n\ninterface RepoMirrorConfig {\n  sourceRepo: string;\n  targetRepo: string;\n  transformationInstructions: string;\n  remotes?: {\n    [remoteName: string]: RemoteConfig;\n  };\n  push?: {\n    default_remote?: string;\n    default_branch?: string;\n    commit_prefix?: string;\n  };\n  pull?: {\n    auto_sync?: boolean;\n    source_remote?: string;\n    source_branch?: string;\n  };\n}\n\nasync function loadExistingConfig(sourceRepo?: string): Promise<Partial<RepoMirrorConfig> | null> {\n  try {\n    // Use process.cwd() as base for relative paths to ensure proper mocking in tests\n    const baseDir = sourceRepo && sourceRepo !== \"./\" \n      ? resolve(process.cwd(), sourceRepo) \n      : process.cwd();\n    const configPath = join(baseDir, \"repomirror.yaml\");\n    const configContent = await fs.readFile(configPath, \"utf-8\");\n    return yaml.parse(configContent) as RepoMirrorConfig;\n  } catch {\n    return null;\n  }\n}\n\nasync function saveConfig(config: RepoMirrorConfig, sourceRepo?: string): Promise<void> {\n  // Use process.cwd() as base for relative paths to ensure proper mocking in tests\n  const baseDir = sourceRepo && sourceRepo !== \"./\" \n    ? resolve(process.cwd(), sourceRepo) \n    : process.cwd();\n  // Ensure the directory exists before writing the config\n  await fs.mkdir(baseDir, { recursive: true });\n  const configPath = join(baseDir, \"repomirror.yaml\");\n  const configContent = yaml.stringify(config);\n  await fs.writeFile(configPath, configContent, \"utf-8\");\n}\n\nexport async function init(cliOptions?: Partial<InitOptions>): Promise<void> {\n  console.log(\n    chalk.cyan(\"I'll help you maintain a transformed copy of this repo:\\n\"),\n  );\n\n  // Load existing config if present from source repo\n  const sourceRepoPath = cliOptions?.sourceRepo || \"./\";\n  const existingConfig = await loadExistingConfig(sourceRepoPath);\n  if (existingConfig) {\n    console.log(\n      chalk.yellow(\"Found existing repomirror.yaml, using as defaults\\n\"),\n    );\n  }\n\n  // Get current directory name for default target\n  const currentDir = process.cwd();\n  const repoName = basename(currentDir);\n  const defaultTarget =\n    existingConfig?.targetRepo || `../${repoName}-transformed`;\n\n  // Merge CLI options, existing config, and defaults\n  const defaults = {\n    sourceRepo: cliOptions?.sourceRepo || existingConfig?.sourceRepo || \"./\",\n    targetRepo:\n      cliOptions?.targetRepo || existingConfig?.targetRepo || defaultTarget,\n    transformationInstructions:\n      cliOptions?.transformationInstructions ||\n      existingConfig?.transformationInstructions ||\n      \"translate this python repo to typescript\",\n  };\n\n  const answers = await inquirer.prompt<InitOptions>([\n    {\n      type: \"input\",\n      name: \"sourceRepo\",\n      message: \"Source Repo you want to transform:\",\n      default: defaults.sourceRepo,\n      when: !cliOptions?.sourceRepo,\n    },\n    {\n      type: \"input\",\n      name: \"targetRepo\",\n      message: \"Where do you want to transform code to:\",\n      default: defaults.targetRepo,\n      when: !cliOptions?.targetRepo,\n    },\n    {\n      type: \"input\",\n      name: \"transformationInstructions\",\n      message: \"What changes do you want to make:\",\n      default: defaults.transformationInstructions,\n      when: !cliOptions?.transformationInstructions,\n    },\n  ]);\n\n  // Merge CLI options with answers\n  const finalConfig: InitOptions = {\n    sourceRepo: cliOptions?.sourceRepo || answers.sourceRepo,\n    targetRepo: cliOptions?.targetRepo || answers.targetRepo,\n    transformationInstructions:\n      cliOptions?.transformationInstructions ||\n      answers.transformationInstructions,\n  };\n\n  // Save configuration to repomirror.yaml in source directory\n  await saveConfig(finalConfig, finalConfig.sourceRepo);\n  console.log(chalk.green(\"\\n✅ Saved configuration to repomirror.yaml\"));\n\n  // Perform preflight checks\n  await performPreflightChecks(finalConfig.targetRepo);\n\n  // Generate transformation prompt using Claude SDK\n  console.log(chalk.cyan(\"\\nGenerating transformation prompt...\"));\n\n  try {\n    const optimizedPrompt = await generateTransformationPrompt(\n      finalConfig.sourceRepo,\n      finalConfig.targetRepo,\n      finalConfig.transformationInstructions,\n    );\n\n    console.log(chalk.green(\"✔ Generated transformation prompt\"));\n\n    // Create .repomirror directory and files\n    await createRepoMirrorFiles(\n      finalConfig.sourceRepo,\n      finalConfig.targetRepo,\n      optimizedPrompt,\n    );\n\n    console.log(chalk.green(\"\\n✅ repomirror initialized successfully!\"));\n    console.log(chalk.cyan(\"\\nNext steps:\"));\n    console.log(\n      chalk.white(\n        \"run `npx repomirror sync` - this will run the sync.sh script once\",\n      ),\n    );\n    console.log(\"\");\n    console.log(\n      chalk.white(\n        \"run `npx repomirror sync-forever` - this will run the ralph.sh script, working forever to implement all the changes\",\n      ),\n    );\n    console.log(\"\");\n    console.log(\n      chalk.white(\n        \"The following files were created and safe to commit. Edit prompt.md as you see fit, but you probably dont want to run these files directly\",\n      ),\n    );\n    console.log(\"\");\n    console.log(chalk.white(\"- .repomirror/prompt.md # prompt\"));\n    console.log(chalk.white(\"- .repomirror/sync.sh\"));\n    console.log(chalk.white(\"- .repomirror/ralph.sh\"));\n    console.log(chalk.white(\"- .repomirror/.gitignore\"));\n  } catch (error) {\n    console.log(chalk.red(\"✖ Failed to generate transformation prompt\"));\n    console.error(\n      chalk.red(\n        `Error: ${error instanceof Error ? error.message : String(error)}`,\n      ),\n    );\n    process.exit(1);\n  }\n}\n\nasync function performPreflightChecks(targetRepo: string): Promise<void> {\n  console.log(chalk.cyan(\"\\n🔍 Performing preflight checks...\\n\"));\n\n  // Check if target directory exists\n  console.log(chalk.white(\"1. Checking if target directory exists...\"));\n  const dirSpinner = ora(`   Accessing ${targetRepo}`).start();\n  try {\n    await fs.access(targetRepo);\n    dirSpinner.succeed(`   Target directory ${chalk.green(targetRepo)} exists`);\n  } catch {\n    dirSpinner.fail(\n      `   Target directory ${chalk.red(targetRepo)} does not exist`,\n    );\n    process.exit(1);\n  }\n\n  // Check if target directory is a git repo\n  console.log(\n    chalk.white(\"2. Checking if target directory is a git repository...\"),\n  );\n  const gitSpinner = ora(\n    `   Verifying git repository in ${targetRepo}`,\n  ).start();\n  try {\n    const { stdout } = await execa(\"git\", [\"rev-parse\", \"--git-dir\"], {\n      cwd: targetRepo,\n    });\n    const gitDir = stdout.trim();\n    gitSpinner.succeed(\n      `   Git repository found (git dir: ${chalk.green(gitDir)})`,\n    );\n  } catch {\n    gitSpinner.fail(\n      `   Target directory ${chalk.red(targetRepo)} is not a git repository`,\n    );\n    process.exit(1);\n  }\n\n  // Check if target directory has at least one upstream\n  console.log(chalk.white(\"3. Checking git remotes configuration...\"));\n  const remoteSpinner = ora(`   Listing git remotes in ${targetRepo}`).start();\n  try {\n    const { stdout } = await execa(\"git\", [\"remote\", \"-v\"], {\n      cwd: targetRepo,\n    });\n    if (!stdout.trim()) {\n      remoteSpinner.fail(\n        `   Target directory ${chalk.red(targetRepo)} has no git remotes configured`,\n      );\n      process.exit(1);\n    }\n\n    const remotes = stdout.trim().split(\"\\n\");\n    const remoteNames = [\n      ...new Set(remotes.map((line) => line.split(\"\\t\")[0])),\n    ];\n    remoteSpinner.succeed(\n      `   Found ${chalk.green(remoteNames.length)} git remote(s): ${chalk.green(remoteNames.join(\", \"))}`,\n    );\n\n    // Show the actual remotes for user reference\n    console.log(chalk.gray(\"   Remotes:\"));\n    remotes.forEach((remote) => {\n      console.log(chalk.gray(`     ${remote}`));\n    });\n  } catch {\n    remoteSpinner.fail(\n      `   Failed to check git remotes in ${chalk.red(targetRepo)}`,\n    );\n    process.exit(1);\n  }\n\n  // Check if Claude Code is configured (skip in test mode)\n  if (process.env.SKIP_CLAUDE_TEST === \"true\") {\n    console.log(chalk.yellow(\"4. Skipping Claude Code test (test mode)\"));\n  } else {\n    console.log(chalk.white(\"4. Testing Claude Code configuration...\"));\n    const claudeSpinner = ora(\"   Running Claude Code test command\").start();\n    try {\n      const { stdout } = await execa(\"claude\", [\"-p\", \"say hi\"], {\n        timeout: 30000, // 30 second timeout\n        input: \"\", // Provide empty stdin to prevent claude from waiting\n      });\n      // Check if Claude responded with something reasonable (not empty and more than 10 chars)\n      if (!stdout || stdout.trim().length < 10) {\n        claudeSpinner.fail(\n          \"   Claude Code test failed - response was empty or too short\",\n        );\n        console.log(\n          chalk.gray(\n            `   Actual response: ${stdout.slice(0, 100)}${stdout.length > 100 ? \"...\" : \"\"}`,\n          ),\n        );\n        process.exit(1);\n      }\n      claudeSpinner.succeed(\"   Claude Code is working correctly\");\n      console.log(\n        chalk.gray(\n          `   Claude response: ${stdout.slice(0, 100)}${stdout.length > 100 ? \"...\" : \"\"}`,\n        ),\n      );\n    } catch (error) {\n      if (error instanceof Error && error.message.includes(\"timed out\")) {\n        claudeSpinner.fail(\"   Claude Code test timed out after 30 seconds\");\n        console.log(chalk.red(\"   The 'claude -p \\\"say hi\\\"' command is not responding\"));\n        console.log(chalk.yellow(\"   This might indicate an issue with your Claude Code setup\"));\n      } else {\n        claudeSpinner.fail(\"   Claude Code is not properly configured\");\n        console.log(chalk.red(\"   Please run `claude` to set up your profile\"));\n      }\n      if (error instanceof Error) {\n        console.log(chalk.gray(`   Error: ${error.message}`));\n      }\n      process.exit(1);\n    }\n  }\n\n  console.log(chalk.green(\"\\n✅ All preflight checks passed!\\n\"));\n}\n\nasync function generateTransformationPrompt(\n  sourceRepo: string,\n  targetRepo: string,\n  transformationInstructions: string,\n): Promise<string> {\n  // In test mode, return a simple template without calling Claude\n  if (process.env.SKIP_CLAUDE_TEST === \"true\") {\n    const testPrompt = `Your job is to port ${sourceRepo} to ${targetRepo} and maintain the repository.\n\nYou have access to the current ${sourceRepo} repository as well as the ${targetRepo} repository.\n\nMake a commit and push your changes after every single file edit.\n\nUse the ${targetRepo}/agent/ directory as a scratchpad for your work. Store long term plans and todo lists there.\n\n${transformationInstructions}`;\n    return testPrompt;\n  }\n\n  const metaPrompt = `your task is to generate an optimized prompt for repo transformation. The prompt should match the format of the examples below.\n\n<example 1>\nYour job is to port [SOURCE PATH] monorepo (for react) to [TARGET PATH] (for vue) and maintain the repository.\n\nYou have access to the current [SOURCE PATH] repository as well as the [TARGET PATH] repository.\n\nMake a commit and push your changes after every single file edit.\n\nUse the [TARGET_PATH]/agent/ directory as a scratchpad for your work. Store long term plans and todo lists there.\n\nThe original project was mostly tested by manually running the code. When porting, you will need to write end to end and unit tests for the project. But make sure to spend most of your time on the actual porting, not on the testing. A good heuristic is to spend 80% of your time on the actual porting, and 20% on the testing.\n</example 1>\n\n<example 2>\nYour job is to port browser-use monorepo (Python) to browser-use-ts (Typescript) and maintain the repository.\n\nYou have access to the current [SOURCE PATH] repository as well as the target [TARGET_PATH] repository.\n\nMake a commit and push your changes after every single file edit.\n\nUse the [TARGET PATH]/agent/ directory as a scratchpad for your work. Store long term plans and todo lists there.\n\nThe original project was mostly tested by manually running the code. When porting, you will need to write end to end and unit tests for the project. But make sure to spend most of your time on the actual porting, not on the testing. A good heuristic is to spend 80% of your time on the actual porting, and 20% on the testing.\n</example 2>\n\nThe users instructions for transformation are:\n\n<user instructions>\n${transformationInstructions}\n</user instructions>\n\nYour Job:\n\nWhen you are ready, respond with EXACTLY the prompt matching the example, tailored for following the users' instructions and nothing else.\n\nYou should follow the format EXACTLY, filling in information based on what you learn from a CURSORY exploration of the source repo (this directory). Ensure you ONLY use the read tools (Read, Search, Grep, LS, Glob, etc) to explore the repo. You only need enough sense to build a good prompt, so dont use subagents.`;\n\n  let result = \"\";\n  let toolCallCount = 0;\n  let queryAborted = false;\n  \n  // Handle graceful shutdown during Claude SDK query\n  const signalHandler = () => {\n    console.log(chalk.yellow(\"\\n\\nStopping prompt generation...\"));\n    queryAborted = true;\n    process.exit(0);\n  };\n\n  process.on('SIGINT', signalHandler);\n  process.on('SIGTERM', signalHandler);\n  \n  try {\n    for await (const message of query({\n      prompt: metaPrompt,\n    })) {\n      if (queryAborted) break;\n    // Stream tool calls to user in a compact format\n    if (message.type === \"assistant\" && (message as any).message?.content?.[0]?.name) {\n      const toolName = (message as any).message.content[0].name;\n      const toolInput = (message as any).message.content[0].input;\n      toolCallCount++;\n      \n      // Build compact tool display\n      let toolDisplay = `  ${chalk.cyan(toolName)}`;\n      \n      // Add key argument for the tool\n      if (toolInput) {\n        if (toolInput.file_path) {\n          toolDisplay += `(${chalk.green(toolInput.file_path)})`;\n        } else if (toolInput.path) {\n          toolDisplay += `(${chalk.green(toolInput.path)})`;\n        } else if (toolInput.pattern) {\n          toolDisplay += `(${chalk.green(`\"${toolInput.pattern}\"`)})`;\n        } else if (toolInput.command) {\n          const cmd = toolInput.command.length > 50 \n            ? toolInput.command.substring(0, 50) + \"...\"\n            : toolInput.command;\n          toolDisplay += `(${chalk.green(cmd)})`;\n        } else if (toolInput.query) {\n          const q = toolInput.query.length > 30\n            ? toolInput.query.substring(0, 30) + \"...\"\n            : toolInput.query;\n          toolDisplay += `(${chalk.green(`\"${q}\"`)})`;\n        }\n      }\n      \n      console.log(toolDisplay);\n    }\n    \n    if (message.type === \"result\") {\n      if (message.is_error) {\n        throw new Error(\n          (message as any).result ||\n            \"Claude SDK error during prompt generation\",\n        );\n      }\n      result = (message as any).result || \"\";\n      break;\n    }\n  }\n  } finally {\n    // Clean up signal handlers\n    process.off('SIGINT', signalHandler);\n    process.off('SIGTERM', signalHandler);\n  }\n\n  if (toolCallCount > 0) {\n    console.log(chalk.gray(`  Analyzed codebase with ${toolCallCount} tool calls`));\n  }\n\n  if (!result) {\n    throw new Error(\n      \"Failed to generate transformation prompt - no result received\",\n    );\n  }\n\n  // Replace placeholders with actual paths\n  return result\n    .replace(/\\[SOURCE PATH\\]/g, sourceRepo)\n    .replace(/\\[TARGET PATH\\]/g, targetRepo)\n    .replace(/\\[TARGET_PATH\\]/g, targetRepo);\n}\n\nasync function createRepoMirrorFiles(\n  sourceRepo: string,\n  targetRepo: string,\n  optimizedPrompt: string,\n): Promise<void> {\n  // Use process.cwd() as base for relative paths to ensure proper mocking in tests\n  const sourceDir = sourceRepo && sourceRepo !== \"./\" \n    ? resolve(process.cwd(), sourceRepo) \n    : process.cwd();\n  const repoMirrorDir = join(sourceDir, \".repomirror\");\n\n  // Create .repomirror directory\n  await fs.mkdir(repoMirrorDir, { recursive: true });\n\n  // Create prompt.md\n  await fs.writeFile(join(repoMirrorDir, \"prompt.md\"), optimizedPrompt);\n\n  // Get template directory - look for templates in the package\n  const templateDir = await getTemplateDir();\n\n  // Create sync.sh from template\n  const syncTemplate = await fs.readFile(join(templateDir, \"sync.sh.template\"), \"utf8\");\n  const syncScript = syncTemplate.replace(/\\${targetRepo}/g, targetRepo);\n  await fs.writeFile(join(repoMirrorDir, \"sync.sh\"), syncScript, {\n    mode: 0o755,\n  });\n\n  // Create ralph.sh from template\n  const ralphTemplate = await fs.readFile(join(templateDir, \"ralph.sh.template\"), \"utf8\");\n  await fs.writeFile(join(repoMirrorDir, \"ralph.sh\"), ralphTemplate, {\n    mode: 0o755,\n  });\n\n  // Create .gitignore from template\n  const gitignoreTemplate = await fs.readFile(join(templateDir, \"gitignore.template\"), \"utf8\");\n  await fs.writeFile(\n    join(repoMirrorDir, \".gitignore\"),\n    gitignoreTemplate + \"\\n\",\n  );\n}\n\nasync function getTemplateDir(): Promise<string> {\n  // First try to find templates in the package dist (for published package)\n  try {\n    const packageRoot = path.dirname(path.dirname(__dirname)); // From dist/commands to project root\n    const distTemplateDir = join(packageRoot, \"dist\", \"templates\");\n    await fs.access(distTemplateDir);\n    return distTemplateDir;\n  } catch {\n    // Fallback to src templates (for development and tests)\n    const packageRoot = path.dirname(path.dirname(__dirname)); // From src/commands to project root  \n    const srcTemplateDir = join(packageRoot, \"src\", \"templates\");\n    try {\n      await fs.access(srcTemplateDir);\n      return srcTemplateDir;\n    } catch {\n      // If neither works, throw a more helpful error\n      throw new Error(`Could not find templates in either dist/templates or src/templates. Package root: ${packageRoot}`);\n    }\n  }\n}\n"
  },
  {
    "path": "src/commands/pull.ts",
    "content": "import { promises as fs } from \"fs\";\nimport { join } from \"path\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { execa } from \"execa\";\nimport yaml from \"yaml\";\n\ninterface RemoteConfig {\n  url: string;\n  branch: string;\n  auto_push?: boolean;\n}\n\ninterface RepoMirrorConfig {\n  sourceRepo: string;\n  targetRepo: string;\n  transformationInstructions: string;\n  remotes?: {\n    [remoteName: string]: RemoteConfig;\n  };\n  push?: {\n    default_remote?: string;\n    default_branch?: string;\n    commit_prefix?: string;\n  };\n  pull?: {\n    auto_sync?: boolean;\n    source_remote?: string;\n    source_branch?: string;\n  };\n}\n\ninterface PullOptions {\n  sourceOnly?: boolean;\n  syncAfter?: boolean;\n  check?: boolean;\n}\n\nasync function loadConfig(): Promise<RepoMirrorConfig | null> {\n  try {\n    const configPath = join(process.cwd(), \"repomirror.yaml\");\n    const configContent = await fs.readFile(configPath, \"utf-8\");\n    return yaml.parse(configContent) as RepoMirrorConfig;\n  } catch {\n    return null;\n  }\n}\n\nasync function checkSourceRepoStatus(sourceRepo: string): Promise<{\n  isGitRepo: boolean;\n  hasRemotes: boolean;\n  currentBranch: string | null;\n  hasUncommittedChanges: boolean;\n}> {\n  try {\n    // Check if it's a git repository\n    await execa(\"git\", [\"rev-parse\", \"--git-dir\"], { cwd: sourceRepo });\n\n    // Get current branch\n    const { stdout: branchOutput } = await execa(\n      \"git\",\n      [\"branch\", \"--show-current\"],\n      { cwd: sourceRepo },\n    );\n    const currentBranch = branchOutput.trim();\n\n    // Check for remotes\n    const { stdout: remotesOutput } = await execa(\"git\", [\"remote\"], {\n      cwd: sourceRepo,\n    });\n    const hasRemotes = remotesOutput.trim().length > 0;\n\n    // Check for uncommitted changes\n    const { stdout: statusOutput } = await execa(\n      \"git\",\n      [\"status\", \"--porcelain\"],\n      { cwd: sourceRepo },\n    );\n    const hasUncommittedChanges = statusOutput.trim().length > 0;\n\n    return {\n      isGitRepo: true,\n      hasRemotes,\n      currentBranch,\n      hasUncommittedChanges,\n    };\n  } catch {\n    return {\n      isGitRepo: false,\n      hasRemotes: false,\n      currentBranch: null,\n      hasUncommittedChanges: false,\n    };\n  }\n}\n\nasync function getRemoteChangesSummary(\n  sourceRepo: string,\n  remoteName: string,\n  remoteBranch: string,\n): Promise<{\n  hasNewCommits: boolean;\n  commitCount: number;\n  commitMessages: string[];\n}> {\n  try {\n    // Fetch latest changes from remote\n    await execa(\"git\", [\"fetch\", remoteName], { cwd: sourceRepo });\n\n    // Check for new commits\n    const { stdout: countOutput } = await execa(\n      \"git\",\n      [\"rev-list\", \"--count\", `HEAD..${remoteName}/${remoteBranch}`],\n      { cwd: sourceRepo },\n    );\n    const commitCount = parseInt(countOutput.trim(), 10) || 0;\n\n    if (commitCount === 0 || isNaN(commitCount)) {\n      return {\n        hasNewCommits: false,\n        commitCount: 0,\n        commitMessages: [],\n      };\n    }\n\n    // Get commit messages for preview\n    const { stdout: logOutput } = await execa(\n      \"git\",\n      [\n        \"log\",\n        \"--oneline\",\n        \"--no-merges\",\n        `-${Math.min(commitCount, 5)}`, // Show up to 5 commits\n        `HEAD..${remoteName}/${remoteBranch}`,\n      ],\n      { cwd: sourceRepo },\n    );\n\n    const commitMessages = logOutput.trim().split(\"\\n\").filter(Boolean);\n\n    return {\n      hasNewCommits: true,\n      commitCount,\n      commitMessages,\n    };\n  } catch (error) {\n    throw new Error(`Failed to check for remote changes: ${error}`);\n  }\n}\n\nasync function pullSourceChanges(\n  sourceRepo: string,\n  remoteName: string,\n  remoteBranch: string,\n): Promise<{ success: boolean; conflictsDetected: boolean }> {\n  const spinner = ora(\n    `Pulling changes from ${remoteName}/${remoteBranch}...`,\n  ).start();\n\n  try {\n    // Attempt to pull changes\n    const { stdout, stderr } = await execa(\n      \"git\",\n      [\"pull\", remoteName, remoteBranch],\n      { cwd: sourceRepo },\n    );\n\n    // Check for merge conflicts\n    const conflictsDetected =\n      stderr.includes(\"CONFLICT\") ||\n      stdout.includes(\"CONFLICT\") ||\n      stderr.includes(\"Automatic merge failed\");\n\n    if (conflictsDetected) {\n      spinner.fail(\"Pull completed with merge conflicts\");\n      return { success: false, conflictsDetected: true };\n    }\n\n    spinner.succeed(`Successfully pulled from ${remoteName}/${remoteBranch}`);\n\n    // Show pull summary if there's useful information\n    if (stdout && !stdout.includes(\"Already up to date\")) {\n      console.log(chalk.gray(\"Pull summary:\"));\n      console.log(chalk.gray(stdout.split(\"\\n\").slice(0, 3).join(\"\\n\")));\n    }\n\n    return { success: true, conflictsDetected: false };\n  } catch (error) {\n    spinner.fail(`Failed to pull from ${remoteName}/${remoteBranch}`);\n\n    if (error instanceof Error) {\n      const errorMessage = error.message.toLowerCase();\n      if (\n        errorMessage.includes(\"authentication failed\") ||\n        errorMessage.includes(\"permission denied\")\n      ) {\n        console.log(chalk.yellow(\"\\n🔐 Authentication Issue Detected:\"));\n        console.log(\n          chalk.gray(\"• For HTTPS: Check your GitHub token or credentials\"),\n        );\n        console.log(\n          chalk.gray(\n            \"• For SSH: Ensure your SSH key is added to your GitHub account\",\n          ),\n        );\n      } else if (errorMessage.includes(\"couldn't find remote ref\")) {\n        console.log(\n          chalk.yellow(\n            `\\n🌿 Branch '${remoteBranch}' not found on remote '${remoteName}'`,\n          ),\n        );\n        console.log(\n          chalk.gray(\"• Check the branch name in your repomirror.yaml\"),\n        );\n        console.log(\n          chalk.gray(\"• List available branches with: git ls-remote --heads\"),\n        );\n      }\n    }\n\n    throw error;\n  }\n}\n\nasync function triggerSync(syncAfter: boolean): Promise<void> {\n  const syncScript = join(process.cwd(), \".repomirror\", \"sync.sh\");\n  const ralphScript = join(process.cwd(), \".repomirror\", \"ralph.sh\");\n\n  try {\n    if (syncAfter) {\n      // Check if ralph.sh exists for continuous sync\n      await fs.access(ralphScript);\n      console.log(chalk.cyan(\"\\n🔄 Starting continuous sync (ralph.sh)...\"));\n      console.log(chalk.yellow(\"Press Ctrl+C to stop\"));\n\n      const subprocess = execa(\"bash\", [ralphScript], {\n        stdio: \"inherit\",\n        cwd: process.cwd(),\n      });\n\n      // Handle graceful shutdown for subprocess\n      const signalHandler = () => {\n        console.log(chalk.yellow(\"\\nStopping continuous sync...\"));\n        subprocess.kill(\"SIGINT\");\n      };\n\n      process.on('SIGINT', signalHandler);\n      process.on('SIGTERM', signalHandler);\n\n      try {\n        await subprocess;\n      } finally {\n        // Clean up signal handlers\n        process.off('SIGINT', signalHandler);\n        process.off('SIGTERM', signalHandler);\n      }\n    } else {\n      // Check if sync.sh exists for single sync\n      await fs.access(syncScript);\n      console.log(chalk.cyan(\"\\n🔄 Running single sync (sync.sh)...\"));\n\n      const subprocess = execa(\"bash\", [syncScript], {\n        stdio: \"inherit\",\n        cwd: process.cwd(),\n      });\n\n      // Handle graceful shutdown for subprocess\n      const signalHandler = () => {\n        console.log(chalk.yellow(\"\\nStopping sync...\"));\n        subprocess.kill(\"SIGINT\");\n      };\n\n      process.on('SIGINT', signalHandler);\n      process.on('SIGTERM', signalHandler);\n\n      try {\n        await subprocess;\n        console.log(chalk.green(\"✅ Sync completed\"));\n      } finally {\n        // Clean up signal handlers\n        process.off('SIGINT', signalHandler);\n        process.off('SIGTERM', signalHandler);\n      }\n    }\n  } catch (error) {\n    if (error instanceof Error && (error as any).signal === \"SIGINT\") {\n      console.log(chalk.yellow(\"\\nStopped by user\"));\n    } else {\n      throw new Error(`Sync failed: ${error}`);\n    }\n  }\n}\n\nasync function performPull(\n  config: RepoMirrorConfig,\n  options: PullOptions,\n): Promise<void> {\n  const { sourceRepo } = config;\n\n  // Verify source directory exists\n  try {\n    await fs.access(sourceRepo);\n  } catch {\n    console.error(\n      chalk.red(`Error: Source directory ${sourceRepo} does not exist`),\n    );\n    process.exit(1);\n  }\n\n  // Check source repository status\n  const repoStatus = await checkSourceRepoStatus(sourceRepo);\n\n  if (!repoStatus.isGitRepo) {\n    console.error(\n      chalk.red(\n        `Error: Source directory ${sourceRepo} is not a git repository`,\n      ),\n    );\n    process.exit(1);\n  }\n\n  if (!repoStatus.hasRemotes) {\n    console.error(\n      chalk.red(\"Error: Source repository has no configured remotes\"),\n    );\n    console.log(chalk.gray(\"Add a remote with: git remote add <name> <url>\"));\n    process.exit(1);\n  }\n\n  if (repoStatus.hasUncommittedChanges) {\n    console.log(chalk.yellow(\"⚠️  Source repository has uncommitted changes\"));\n    console.log(\n      chalk.gray(\"Consider committing or stashing changes before pulling\"),\n    );\n    console.log();\n  }\n\n  // Determine remote and branch to pull from\n  const remoteName = config.pull?.source_remote || \"upstream\";\n  const remoteBranch = config.pull?.source_branch || \"main\";\n\n  console.log(\n    chalk.cyan(`📡 Checking for changes from ${remoteName}/${remoteBranch}...`),\n  );\n\n  try {\n    // Get summary of remote changes\n    const changesSummary = await getRemoteChangesSummary(\n      sourceRepo,\n      remoteName,\n      remoteBranch,\n    );\n\n    if (!changesSummary.hasNewCommits) {\n      console.log(chalk.green(\"✅ Source repository is already up to date\"));\n      return;\n    }\n\n    // Show preview of incoming changes\n    console.log(\n      chalk.cyan(`\\n📥 ${changesSummary.commitCount} new commit(s) available:`),\n    );\n    changesSummary.commitMessages.forEach((message) => {\n      console.log(chalk.gray(`  • ${message}`));\n    });\n\n    if (changesSummary.commitCount > changesSummary.commitMessages.length) {\n      const remaining =\n        changesSummary.commitCount - changesSummary.commitMessages.length;\n      console.log(chalk.gray(`  ... and ${remaining} more commit(s)`));\n    }\n    console.log();\n\n    // If this is just a check, return here\n    if (options.check) {\n      console.log(\n        chalk.blue(\n          \"🔍 Check complete - use 'npx repomirror pull' to apply changes\",\n        ),\n      );\n      return;\n    }\n\n    // Pull the changes\n    const pullResult = await pullSourceChanges(\n      sourceRepo,\n      remoteName,\n      remoteBranch,\n    );\n\n    if (!pullResult.success) {\n      if (pullResult.conflictsDetected) {\n        console.log(chalk.red(\"\\n❌ Merge conflicts detected\"));\n        console.log(chalk.yellow(\"Please resolve conflicts manually:\"));\n        console.log(chalk.gray(\"1. Navigate to source repository\"));\n        console.log(chalk.gray(\"2. Resolve conflicts in affected files\"));\n        console.log(chalk.gray(\"3. Run: git add . && git commit\"));\n        console.log(chalk.gray(\"4. Re-run: npx repomirror pull\"));\n        process.exit(1);\n      }\n      return;\n    }\n\n    // Trigger sync if requested or configured\n    const shouldSync =\n      options.syncAfter || (config.pull?.auto_sync && !options.sourceOnly);\n\n    if (shouldSync && !options.sourceOnly) {\n      await triggerSync(!!options.syncAfter);\n    } else if (!options.sourceOnly) {\n      console.log(chalk.blue(\"\\n💡 Source changes pulled successfully\"));\n      console.log(\n        chalk.gray(\"Run 'npx repomirror sync' to apply transformations\"),\n      );\n    }\n  } catch (error) {\n    throw new Error(`Pull operation failed: ${error}`);\n  }\n}\n\nexport async function pull(options: PullOptions = {}): Promise<void> {\n  const config = await loadConfig();\n  if (!config) {\n    console.error(\n      chalk.red(\n        \"Error: repomirror.yaml not found. Run 'npx repomirror init' first.\",\n      ),\n    );\n    process.exit(1);\n  }\n\n  try {\n    await performPull(config, options);\n  } catch (error) {\n    console.error(\n      chalk.red(\n        `Pull failed: ${error instanceof Error ? error.message : String(error)}`,\n      ),\n    );\n    process.exit(1);\n  }\n}\n"
  },
  {
    "path": "src/commands/push.ts",
    "content": "import { promises as fs } from \"fs\";\nimport { join } from \"path\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { execa } from \"execa\";\nimport yaml from \"yaml\";\n\ninterface RemoteConfig {\n  url: string;\n  branch: string;\n  auto_push?: boolean;\n}\n\ninterface RepoMirrorConfig {\n  sourceRepo: string;\n  targetRepo: string;\n  transformationInstructions: string;\n  remotes?: {\n    [remoteName: string]: RemoteConfig;\n  };\n  push?: {\n    default_remote?: string;\n    default_branch?: string;\n    commit_prefix?: string;\n  };\n  pull?: {\n    auto_sync?: boolean;\n    source_remote?: string;\n    source_branch?: string;\n  };\n}\n\ninterface PushOptions {\n  remote?: string;\n  branch?: string;\n  all?: boolean;\n  dryRun?: boolean;\n}\n\nasync function loadConfig(): Promise<RepoMirrorConfig | null> {\n  try {\n    const configPath = join(process.cwd(), \"repomirror.yaml\");\n    const configContent = await fs.readFile(configPath, \"utf-8\");\n    return yaml.parse(configContent) as RepoMirrorConfig;\n  } catch {\n    return null;\n  }\n}\n\nasync function getGitStatus(targetRepo: string): Promise<{\n  hasChanges: boolean;\n  stagedFiles: string[];\n  unstagedFiles: string[];\n}> {\n  try {\n    // Check for staged changes\n    const { stdout: stagedOutput } = await execa(\n      \"git\",\n      [\"diff\", \"--cached\", \"--name-only\"],\n      { cwd: targetRepo },\n    );\n    const stagedFiles = stagedOutput.trim()\n      ? stagedOutput.trim().split(\"\\n\")\n      : [];\n\n    // Check for unstaged changes\n    const { stdout: unstagedOutput } = await execa(\n      \"git\",\n      [\"diff\", \"--name-only\"],\n      { cwd: targetRepo },\n    );\n    const unstagedFiles = unstagedOutput.trim()\n      ? unstagedOutput.trim().split(\"\\n\")\n      : [];\n\n    // Check for untracked files\n    const { stdout: untrackedOutput } = await execa(\n      \"git\",\n      [\"ls-files\", \"--others\", \"--exclude-standard\"],\n      { cwd: targetRepo },\n    );\n    const untrackedFiles = untrackedOutput.trim()\n      ? untrackedOutput.trim().split(\"\\n\")\n      : [];\n\n    const hasChanges =\n      stagedFiles.length > 0 ||\n      unstagedFiles.length > 0 ||\n      untrackedFiles.length > 0;\n\n    return {\n      hasChanges,\n      stagedFiles,\n      unstagedFiles: [...unstagedFiles, ...untrackedFiles],\n    };\n  } catch (error) {\n    throw new Error(`Failed to check git status: ${error}`);\n  }\n}\n\nasync function getSourceCommitHash(sourceRepo: string): Promise<string | null> {\n  try {\n    const { stdout } = await execa(\"git\", [\"rev-parse\", \"HEAD\"], {\n      cwd: sourceRepo,\n    });\n    return stdout.trim().substring(0, 7); // Short hash\n  } catch {\n    return null; // Source repo might not be a git repository\n  }\n}\n\nasync function generateCommitMessage(\n  config: RepoMirrorConfig,\n  sourceCommitHash: string | null,\n): Promise<string> {\n  const prefix = config.push?.commit_prefix || \"[repomirror]\";\n  const instructions = config.transformationInstructions;\n\n  // Create a concise summary of transformation\n  let summary = \"Apply transformations\";\n  if (instructions.length < 80) {\n    summary = instructions;\n  } else {\n    // Extract key transformation type from instructions\n    const lowerInstructions = instructions.toLowerCase();\n    if (\n      lowerInstructions.includes(\"typescript\") ||\n      lowerInstructions.includes(\"ts\")\n    ) {\n      summary = \"Convert to TypeScript\";\n    } else if (lowerInstructions.includes(\"python\")) {\n      summary = \"Convert to Python\";\n    } else if (lowerInstructions.includes(\"react\")) {\n      summary = \"Convert to React\";\n    } else if (lowerInstructions.includes(\"vue\")) {\n      summary = \"Convert to Vue\";\n    } else {\n      summary = \"Apply code transformations\";\n    }\n  }\n\n  let commitMessage = `${prefix} ${summary}`;\n\n  if (sourceCommitHash) {\n    commitMessage += ` (source: ${sourceCommitHash})`;\n  }\n\n  return commitMessage;\n}\n\nasync function stageAndCommitChanges(\n  targetRepo: string,\n  commitMessage: string,\n): Promise<void> {\n  const spinner = ora(\"Staging changes...\").start();\n\n  try {\n    // Add all changes to staging\n    await execa(\"git\", [\"add\", \".\"], { cwd: targetRepo });\n    spinner.succeed(\"Staged all changes\");\n\n    // Create commit\n    const commitSpinner = ora(\"Creating commit...\").start();\n    await execa(\"git\", [\"commit\", \"-m\", commitMessage], { cwd: targetRepo });\n    commitSpinner.succeed(\"Created commit successfully\");\n  } catch (error) {\n    spinner.fail(\"Failed to stage and commit changes\");\n    throw new Error(`Git commit failed: ${error}`);\n  }\n}\n\nasync function pushToRemote(\n  targetRepo: string,\n  remoteName: string,\n  remoteBranch: string,\n  dryRun: boolean = false,\n): Promise<void> {\n  const action = dryRun ? \"dry-run push to\" : \"push to\";\n  const spinner = ora(\n    `Starting ${action} ${remoteName}/${remoteBranch}...`,\n  ).start();\n\n  try {\n    const args = [\"push\"];\n    if (dryRun) {\n      args.push(\"--dry-run\");\n    }\n    args.push(remoteName, remoteBranch);\n\n    const { stdout, stderr } = await execa(\"git\", args, {\n      cwd: targetRepo,\n      timeout: 60000, // 60 second timeout\n    });\n\n    if (dryRun) {\n      spinner.succeed(`Dry run completed for ${remoteName}/${remoteBranch}`);\n      if (stdout) {\n        console.log(chalk.gray(\"Dry run output:\"));\n        console.log(chalk.gray(stdout));\n      }\n    } else {\n      spinner.succeed(`Successfully pushed to ${remoteName}/${remoteBranch}`);\n    }\n\n    // Show any informational output from git push\n    if (stderr && !stderr.includes(\"error\") && !stderr.includes(\"fatal\")) {\n      console.log(chalk.gray(stderr));\n    }\n  } catch (error) {\n    const actionText = dryRun ? \"Dry run failed\" : \"Push failed\";\n    spinner.fail(`${actionText} for ${remoteName}/${remoteBranch}`);\n\n    if (error instanceof Error) {\n      // Check for common authentication issues\n      const errorMessage = error.message.toLowerCase();\n      if (\n        errorMessage.includes(\"authentication failed\") ||\n        errorMessage.includes(\"permission denied\")\n      ) {\n        console.log(chalk.yellow(\"\\n🔐 Authentication Issue Detected:\"));\n        console.log(\n          chalk.gray(\"• For HTTPS: Check your GitHub token or credentials\"),\n        );\n        console.log(\n          chalk.gray(\n            \"• For SSH: Ensure your SSH key is added to your GitHub account\",\n          ),\n        );\n        console.log(\n          chalk.gray(\"• Test with: git push from the target directory\"),\n        );\n      } else if (errorMessage.includes(\"timeout\")) {\n        console.log(\n          chalk.yellow(\"\\n⏰ Push timed out - check your network connection\"),\n        );\n      } else if (errorMessage.includes(\"rejected\")) {\n        console.log(\n          chalk.yellow(\"\\n🚫 Push rejected - you may need to pull first\"),\n        );\n        console.log(chalk.gray(\"• Try: git pull from the target directory\"));\n      }\n    }\n\n    throw error;\n  }\n}\n\nasync function performPush(\n  config: RepoMirrorConfig,\n  options: PushOptions,\n): Promise<void> {\n  const { targetRepo } = config;\n\n  // Verify target directory exists and is a git repository\n  try {\n    await fs.access(targetRepo);\n    await execa(\"git\", [\"rev-parse\", \"--git-dir\"], { cwd: targetRepo });\n  } catch {\n    console.error(\n      chalk.red(\n        `Error: Target directory ${targetRepo} is not a valid git repository`,\n      ),\n    );\n    process.exit(1);\n  }\n\n  // Check git status\n  const gitStatus = await getGitStatus(targetRepo);\n\n  if (!gitStatus.hasChanges) {\n    console.log(chalk.yellow(\"No changes to commit\"));\n    return;\n  }\n\n  // Show what changes will be committed\n  console.log(chalk.cyan(\"Changes to be pushed:\"));\n  if (gitStatus.stagedFiles.length > 0) {\n    console.log(chalk.green(\"  Staged files:\"));\n    gitStatus.stagedFiles.forEach((file) =>\n      console.log(chalk.green(`    + ${file}`)),\n    );\n  }\n  if (gitStatus.unstagedFiles.length > 0) {\n    console.log(chalk.yellow(\"  Modified/untracked files:\"));\n    gitStatus.unstagedFiles.forEach((file) =>\n      console.log(chalk.yellow(`    M ${file}`)),\n    );\n  }\n  console.log();\n\n  // Get source commit hash for commit message\n  const sourceCommitHash = await getSourceCommitHash(config.sourceRepo);\n\n  // Generate commit message\n  const commitMessage = await generateCommitMessage(config, sourceCommitHash);\n  console.log(chalk.gray(`Commit message: ${commitMessage}\\n`));\n\n  // If dry run, skip committing\n  if (!options.dryRun) {\n    // Stage and commit changes\n    await stageAndCommitChanges(targetRepo, commitMessage);\n  }\n\n  // Determine which remotes to push to\n  const remotesToPush: Array<{ name: string; branch: string }> = [];\n\n  if (options.all) {\n    // Push to all configured remotes\n    if (config.remotes) {\n      Object.entries(config.remotes).forEach(([name, remote]) => {\n        remotesToPush.push({ name, branch: remote.branch });\n      });\n    }\n  } else {\n    // Push to specific or default remote\n    const remoteName = options.remote || config.push?.default_remote;\n    const remoteBranch = options.branch || config.push?.default_branch;\n\n    if (!remoteName) {\n      console.error(\n        chalk.red(\n          \"Error: No remote specified and no default remote configured\",\n        ),\n      );\n      console.log(\n        chalk.gray(\n          \"Use --remote <name> or add a default remote with: npx repomirror remote add\",\n        ),\n      );\n      process.exit(1);\n    }\n\n    if (!config.remotes?.[remoteName]) {\n      console.error(chalk.red(`Error: Remote '${remoteName}' not found`));\n      console.log(\n        chalk.gray(\"List configured remotes with: npx repomirror remote list\"),\n      );\n      process.exit(1);\n    }\n\n    const branch = remoteBranch || config.remotes[remoteName].branch;\n    remotesToPush.push({ name: remoteName, branch });\n  }\n\n  if (remotesToPush.length === 0) {\n    console.log(chalk.yellow(\"No remotes configured for push\"));\n    console.log(\n      chalk.gray(\"Add a remote with: npx repomirror remote add <name> <url>\"),\n    );\n    return;\n  }\n\n  // Push to each remote\n  const errors: string[] = [];\n  for (const remote of remotesToPush) {\n    try {\n      await pushToRemote(\n        targetRepo,\n        remote.name,\n        remote.branch,\n        options.dryRun,\n      );\n    } catch (error) {\n      errors.push(`${remote.name}/${remote.branch}: ${error}`);\n    }\n  }\n\n  // Report results\n  if (errors.length === 0) {\n    const action = options.dryRun\n      ? \"Dry run completed\"\n      : \"All pushes completed successfully\";\n    console.log(chalk.green(`\\n✅ ${action}`));\n  } else {\n    console.log(chalk.red(\"\\n❌ Some pushes failed:\"));\n    errors.forEach((error) => console.log(chalk.red(`  • ${error}`)));\n    process.exit(1);\n  }\n}\n\nexport async function push(options: PushOptions = {}): Promise<void> {\n  const config = await loadConfig();\n  if (!config) {\n    console.error(\n      chalk.red(\n        \"Error: repomirror.yaml not found. Run 'npx repomirror init' first.\",\n      ),\n    );\n    process.exit(1);\n  }\n\n  if (!config.remotes || Object.keys(config.remotes).length === 0) {\n    console.error(chalk.red(\"Error: No remotes configured\"));\n    console.log(\n      chalk.gray(\"Add a remote with: npx repomirror remote add <name> <url>\"),\n    );\n    process.exit(1);\n  }\n\n  try {\n    await performPush(config, options);\n  } catch (error) {\n    console.error(\n      chalk.red(\n        `Push failed: ${error instanceof Error ? error.message : String(error)}`,\n      ),\n    );\n    process.exit(1);\n  }\n}\n"
  },
  {
    "path": "src/commands/remote.ts",
    "content": "import { promises as fs } from \"fs\";\nimport { join } from \"path\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { execa } from \"execa\";\nimport yaml from \"yaml\";\n\ninterface RemoteConfig {\n  url: string;\n  branch: string;\n  auto_push?: boolean;\n}\n\ninterface RepoMirrorConfig {\n  sourceRepo: string;\n  targetRepo: string;\n  transformationInstructions: string;\n  remotes?: {\n    [remoteName: string]: RemoteConfig;\n  };\n  push?: {\n    default_remote?: string;\n    default_branch?: string;\n    commit_prefix?: string;\n  };\n  pull?: {\n    auto_sync?: boolean;\n    source_remote?: string;\n    source_branch?: string;\n  };\n}\n\nasync function loadConfig(): Promise<RepoMirrorConfig | null> {\n  try {\n    const configPath = join(process.cwd(), \"repomirror.yaml\");\n    const configContent = await fs.readFile(configPath, \"utf-8\");\n    return yaml.parse(configContent) as RepoMirrorConfig;\n  } catch {\n    return null;\n  }\n}\n\nasync function saveConfig(config: RepoMirrorConfig): Promise<void> {\n  const configPath = join(process.cwd(), \"repomirror.yaml\");\n  const configContent = yaml.stringify(config);\n  await fs.writeFile(configPath, configContent, \"utf-8\");\n}\n\nasync function validateRemoteUrl(url: string): Promise<boolean> {\n  try {\n    // Basic URL validation\n    new URL(url);\n\n    // Check if it's a valid git URL (basic patterns)\n    const gitUrlPattern = /^(https?:\\/\\/|git@).+\\.git$/i;\n    const githubPattern = /^https?:\\/\\/github\\.com\\/.+\\/.+/i;\n\n    return gitUrlPattern.test(url) || githubPattern.test(url);\n  } catch {\n    return false;\n  }\n}\n\nexport async function remoteAdd(\n  name: string,\n  url: string,\n  branch = \"main\",\n): Promise<void> {\n  if (!name || !url) {\n    console.error(chalk.red(\"Error: Remote name and URL are required\"));\n    process.exit(1);\n  }\n\n  // Validate remote URL\n  if (!(await validateRemoteUrl(url))) {\n    console.error(chalk.red(`Error: Invalid git URL: ${url}`));\n    console.log(\n      chalk.gray(\n        \"Expected format: https://github.com/user/repo.git or git@github.com:user/repo.git\",\n      ),\n    );\n    process.exit(1);\n  }\n\n  const config = await loadConfig();\n  if (!config) {\n    console.error(\n      chalk.red(\n        \"Error: repomirror.yaml not found. Run 'npx repomirror init' first.\",\n      ),\n    );\n    process.exit(1);\n  }\n\n  // Initialize remotes object if it doesn't exist\n  if (!config.remotes) {\n    config.remotes = {};\n  }\n\n  // Check if remote already exists\n  if (config.remotes[name]) {\n    console.error(chalk.red(`Error: Remote '${name}' already exists`));\n    console.log(chalk.gray(`Current URL: ${config.remotes[name].url}`));\n    console.log(\n      chalk.gray(\n        \"Use 'npx repomirror remote remove <name>' to remove it first\",\n      ),\n    );\n    process.exit(1);\n  }\n\n  // Test remote accessibility (optional, non-blocking)\n  const spinner = ora(`Testing remote accessibility for ${name}...`).start();\n  try {\n    // Try to ls-remote to verify the URL is accessible\n    await execa(\"git\", [\"ls-remote\", \"--heads\", url], { timeout: 10000 });\n    spinner.succeed(`Remote ${name} is accessible`);\n  } catch (error) {\n    spinner.warn(`Warning: Could not verify remote accessibility`);\n    console.log(\n      chalk.gray(`  This might be due to authentication or network issues`),\n    );\n    console.log(\n      chalk.gray(\n        `  Remote will be added anyway - ensure you have proper access`,\n      ),\n    );\n  }\n\n  // Add remote to configuration\n  config.remotes[name] = {\n    url,\n    branch,\n    auto_push: false,\n  };\n\n  // Set as default remote if it's the first one\n  if (!config.push) {\n    config.push = {};\n  }\n  if (!config.push.default_remote) {\n    config.push.default_remote = name;\n  }\n  if (!config.push.default_branch) {\n    config.push.default_branch = branch;\n  }\n  if (!config.push.commit_prefix) {\n    config.push.commit_prefix = \"[repomirror]\";\n  }\n\n  await saveConfig(config);\n\n  console.log(chalk.green(`✅ Added remote '${name}'`));\n  console.log(chalk.gray(`   URL: ${url}`));\n  console.log(chalk.gray(`   Branch: ${branch}`));\n\n  if (config.push.default_remote === name) {\n    console.log(chalk.gray(`   Set as default remote for push operations`));\n  }\n}\n\nexport async function remoteList(): Promise<void> {\n  const config = await loadConfig();\n  if (!config) {\n    console.error(\n      chalk.red(\n        \"Error: repomirror.yaml not found. Run 'npx repomirror init' first.\",\n      ),\n    );\n    process.exit(1);\n  }\n\n  if (!config.remotes || Object.keys(config.remotes).length === 0) {\n    console.log(chalk.yellow(\"No remotes configured\"));\n    console.log(\n      chalk.gray(\"Add a remote with: npx repomirror remote add <name> <url>\"),\n    );\n    return;\n  }\n\n  console.log(chalk.cyan(\"Configured remotes:\"));\n  console.log();\n\n  Object.entries(config.remotes).forEach(([name, remote]) => {\n    const isDefault = config.push?.default_remote === name;\n    const prefix = isDefault ? chalk.green(\"* \") : \"  \";\n\n    console.log(`${prefix}${chalk.bold(name)}`);\n    console.log(`    URL: ${remote.url}`);\n    console.log(`    Branch: ${remote.branch}`);\n    console.log(`    Auto-push: ${remote.auto_push ? \"enabled\" : \"disabled\"}`);\n\n    if (isDefault) {\n      console.log(chalk.gray(\"    (default remote)\"));\n    }\n    console.log();\n  });\n\n  if (config.push) {\n    console.log(chalk.gray(\"Push settings:\"));\n    console.log(\n      chalk.gray(`  Default remote: ${config.push.default_remote || \"none\"}`),\n    );\n    console.log(\n      chalk.gray(`  Default branch: ${config.push.default_branch || \"none\"}`),\n    );\n    console.log(\n      chalk.gray(\n        `  Commit prefix: ${config.push.commit_prefix || \"[repomirror]\"}`,\n      ),\n    );\n  }\n}\n\nexport async function remoteRemove(name: string): Promise<void> {\n  if (!name) {\n    console.error(chalk.red(\"Error: Remote name is required\"));\n    process.exit(1);\n  }\n\n  const config = await loadConfig();\n  if (!config) {\n    console.error(\n      chalk.red(\n        \"Error: repomirror.yaml not found. Run 'npx repomirror init' first.\",\n      ),\n    );\n    process.exit(1);\n  }\n\n  if (!config.remotes || !config.remotes[name]) {\n    console.error(chalk.red(`Error: Remote '${name}' not found`));\n    console.log(chalk.gray(\"List remotes with: npx repomirror remote list\"));\n    process.exit(1);\n  }\n\n  const remote = config.remotes[name];\n  delete config.remotes[name];\n\n  // Update default remote if we're removing it\n  if (config.push?.default_remote === name) {\n    const remainingRemotes = Object.keys(config.remotes);\n    if (remainingRemotes.length > 0) {\n      config.push.default_remote = remainingRemotes[0];\n      console.log(\n        chalk.yellow(`Updated default remote to '${remainingRemotes[0]}'`),\n      );\n    } else {\n      delete config.push.default_remote;\n      console.log(chalk.yellow(\"No default remote (no remotes remaining)\"));\n    }\n  }\n\n  await saveConfig(config);\n\n  console.log(chalk.green(`✅ Removed remote '${name}'`));\n  console.log(chalk.gray(`   URL: ${remote.url}`));\n}\n\nexport async function remote(action: string, ...args: string[]): Promise<void> {\n  switch (action) {\n    case \"add\":\n      if (args.length < 2) {\n        console.error(\n          chalk.red(\"Usage: npx repomirror remote add <name> <url> [branch]\"),\n        );\n        process.exit(1);\n      }\n      await remoteAdd(args[0], args[1], args[2]);\n      break;\n\n    case \"list\":\n      await remoteList();\n      break;\n\n    case \"remove\":\n    case \"rm\":\n      if (args.length < 1) {\n        console.error(chalk.red(\"Usage: npx repomirror remote remove <name>\"));\n        process.exit(1);\n      }\n      await remoteRemove(args[0]);\n      break;\n\n    default:\n      console.error(chalk.red(`Unknown remote action: ${action}`));\n      console.log(chalk.gray(\"Available actions:\"));\n      console.log(\n        chalk.gray(\"  add <name> <url> [branch] - Add a remote repository\"),\n      );\n      console.log(\n        chalk.gray(\"  list                     - List configured remotes\"),\n      );\n      console.log(chalk.gray(\"  remove <name>            - Remove a remote\"));\n      process.exit(1);\n  }\n}\n"
  },
  {
    "path": "src/commands/setup-github-pr-sync.ts",
    "content": "import { promises as fs } from \"fs\";\nimport { join, resolve } from \"path\";\nimport inquirer from \"inquirer\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport yaml from \"yaml\";\n\ninterface SetupGithubPrSyncOptions {\n  targetRepo?: string;\n  timesToLoop?: number;\n  overwrite?: boolean;\n}\n\ninterface RemoteConfig {\n  url: string;\n  branch: string;\n  auto_push?: boolean;\n}\n\ninterface RepoMirrorConfig {\n  sourceRepo: string;\n  targetRepo: string;\n  transformationInstructions: string;\n  remotes?: {\n    [remoteName: string]: RemoteConfig;\n  };\n  push?: {\n    default_remote?: string;\n    default_branch?: string;\n    commit_prefix?: string;\n  };\n  pull?: {\n    auto_sync?: boolean;\n    source_remote?: string;\n    source_branch?: string;\n  };\n  \"github-pr-sync\"?: {\n    targetRepo?: string;\n    timesToLoop?: number;\n  };\n}\n\nconst DEFAULT_WORKFLOW = `name: RepoMirror PR Sync\n\non:\n  workflow_dispatch: # Allow manual trigger\n  push:\n    branches: [ main ]\n    paths:\n      - '.repomirror/**'\n      - 'repomirror.yaml'\n\njobs:\n  sync:\n    runs-on: ubuntu-latest\n    \n    steps:\n    - name: Checkout source repository\n      uses: actions/checkout@v4\n      with:\n        path: source\n    \n    - name: Checkout target repository  \n      uses: actions/checkout@v4\n      with:\n        repository: {TARGET_REPO}\n        token: $\\{{ secrets.GITHUB_TOKEN }}\n        path: target\n    \n    - name: Setup Node.js\n      uses: actions/setup-node@v4\n      with:\n        node-version: '20'\n    \n    - name: Install repomirror\n      run: npm install -g repomirror\n    \n    - name: Setup Claude Code\n      env:\n        ANTHROPIC_API_KEY: $\\{{ secrets.ANTHROPIC_API_KEY }}\n      run: |\n        # Setup Claude Code with API key\n        echo \"Setting up Claude Code...\"\n        # Configure Claude Code authentication for CI\n        mkdir -p ~/.config/claude\n        echo \"api_key = \\\\\"$ANTHROPIC_API_KEY\\\\\"\" > ~/.config/claude/config\n    \n    - name: Run RepoMirror sync loop\n      working-directory: source\n      env:\n        SKIP_CLAUDE_TEST: true # Skip interactive Claude test in CI\n      run: |\n        # Run the sync-one command in a loop {TIMES_TO_LOOP} times\n        for i in $(seq 1 {TIMES_TO_LOOP}); do\n          echo \"=== Sync iteration $i of {TIMES_TO_LOOP} ===\"\n          npx repomirror sync-one --auto-push || echo \"Sync iteration $i failed, continuing...\"\n          if [ $i -lt {TIMES_TO_LOOP} ]; then\n            echo \"Sleeping 30 seconds before next iteration...\"\n            sleep 30\n          fi\n        done\n    \n    - name: Push final changes to target\n      working-directory: target\n      run: |\n        git config user.name \"GitHub Actions\"\n        git config user.email \"actions@github.com\"\n        \n        if [ -n \"$(git status --porcelain)\" ]; then\n          git add -A\n          git commit -m \"Automated PR sync from source repository [$(date)]\"\n          git push\n        else\n          echo \"No changes to push\"\n        fi\n`;\n\nasync function loadExistingConfig(): Promise<Partial<RepoMirrorConfig> | null> {\n  try {\n    const configPath = join(process.cwd(), \"repomirror.yaml\");\n    const configContent = await fs.readFile(configPath, \"utf-8\");\n    return yaml.parse(configContent) as RepoMirrorConfig;\n  } catch {\n    return null;\n  }\n}\n\nasync function saveConfig(config: RepoMirrorConfig): Promise<void> {\n  const configPath = join(process.cwd(), \"repomirror.yaml\");\n  const configContent = yaml.stringify(config);\n  await fs.writeFile(configPath, configContent, \"utf-8\");\n}\n\nasync function workflowExists(): Promise<boolean> {\n  try {\n    const workflowPath = join(process.cwd(), \".github\", \"workflows\", \"repomirror.yml\");\n    await fs.access(workflowPath);\n    return true;\n  } catch {\n    return false;\n  }\n}\n\nexport async function setupGithubPrSync(options?: SetupGithubPrSyncOptions): Promise<void> {\n  console.log(\n    chalk.cyan(\"I'll help you set up a github actions workflow that will run the sync-one command on every pr merge\\n\")\n  );\n\n  // Check if repomirror.yaml exists\n  const existingConfig = await loadExistingConfig();\n  if (!existingConfig) {\n    console.error(chalk.red(\"Error: repomirror.yaml not found\"));\n    console.log(chalk.yellow(\"Please run 'npx repomirror init' first\"));\n    process.exit(1);\n  }\n\n  // Load existing GitHub PR sync defaults from config\n  const existingGithubPrSyncConfig = existingConfig[\"github-pr-sync\"] || {};\n  \n  // Merge CLI options, existing config, and defaults\n  const defaults = {\n    targetRepo: options?.targetRepo || existingGithubPrSyncConfig.targetRepo || \"\",\n    timesToLoop: options?.timesToLoop || existingGithubPrSyncConfig.timesToLoop || 3,\n  };\n\n  // Check if workflow already exists\n  const exists = await workflowExists();\n  if (exists && !options?.overwrite) {\n    const { shouldOverwrite } = await inquirer.prompt([\n      {\n        type: \"confirm\",\n        name: \"shouldOverwrite\",\n        message: \"GitHub Actions workflow already exists. Do you want to overwrite it?\",\n        default: false,\n      },\n    ]);\n\n    if (!shouldOverwrite) {\n      console.log(chalk.yellow(\"Exiting without making changes.\"));\n      process.exit(0);\n    }\n  }\n\n  // Prompt for configuration\n  const answers = await inquirer.prompt([\n    {\n      type: \"input\",\n      name: \"targetRepo\",\n      message: \"Target repo, e.g. repomirrorhq/repomirror:\",\n      default: defaults.targetRepo,\n      when: !options?.targetRepo,\n      validate: (input) => {\n        if (!input || !input.includes(\"/\")) {\n          return \"Please provide the GitHub repository in owner/repo format\";\n        }\n        return true;\n      },\n    },\n    {\n      type: \"input\",\n      name: \"timesToLoop\",\n      message: \"Times to loop (advanced, recommend 3):\",\n      default: defaults.timesToLoop.toString(),\n      when: !options?.timesToLoop,\n      validate: (input) => {\n        const num = parseInt(input);\n        if (isNaN(num) || num < 1 || num > 10) {\n          return \"Please enter a number between 1 and 10\";\n        }\n        return true;\n      },\n      filter: (input) => parseInt(input),\n    },\n  ]);\n\n  // Merge final configuration\n  const finalConfig = {\n    targetRepo: options?.targetRepo || answers.targetRepo,\n    timesToLoop: options?.timesToLoop || answers.timesToLoop,\n  };\n\n  // Update and save repomirror.yaml with GitHub PR sync settings\n  const updatedConfig: RepoMirrorConfig = {\n    sourceRepo: existingConfig.sourceRepo || \"./\",\n    targetRepo: existingConfig.targetRepo || \"../transformed\",\n    transformationInstructions: existingConfig.transformationInstructions || \"transform the repository\",\n    ...existingConfig,\n    \"github-pr-sync\": {\n      targetRepo: finalConfig.targetRepo,\n      timesToLoop: finalConfig.timesToLoop,\n    },\n  };\n\n  await saveConfig(updatedConfig);\n  console.log(chalk.green(\"✅ Updated repomirror.yaml with GitHub PR sync settings\"));\n\n  // Create .github/workflows directory if it doesn't exist\n  const workflowDir = join(process.cwd(), \".github\", \"workflows\");\n  await fs.mkdir(workflowDir, { recursive: true });\n\n  // Generate workflow content\n  const workflowContent = DEFAULT_WORKFLOW\n    .replace(/{TARGET_REPO}/g, finalConfig.targetRepo)\n    .replace(/{TIMES_TO_LOOP}/g, finalConfig.timesToLoop.toString());\n\n  // Write workflow file\n  const workflowPath = join(workflowDir, \"repomirror.yml\");\n  const spinner = ora(\"Creating GitHub Actions workflow...\").start();\n\n  try {\n    await fs.writeFile(workflowPath, workflowContent);\n    spinner.succeed(\"GitHub Actions workflow created\");\n\n    console.log(chalk.green(`\\n✅ Workflow created at ${workflowPath}`));\n    console.log(chalk.cyan(\"\\nNext steps:\"));\n    console.log(chalk.white(\"• push to github\"));\n    console.log(chalk.white(\"• add secrets for ANTHROPIC_API_KEY and GITHUB_TOKEN, where GITHUB_TOKEN has read/push access to the target repo\"));\n    \n    console.log(chalk.yellow(\"\\n⚠️  Important:\"));\n    console.log(chalk.yellow(\"Make sure to set up the required GitHub secrets:\"));\n    console.log(chalk.gray(\"  - ANTHROPIC_API_KEY: Your Anthropic API key for Claude\"));\n    console.log(chalk.gray(\"  - GITHUB_TOKEN: Already provided by GitHub Actions (ensure repo permissions)\"));\n    \n  } catch (error) {\n    spinner.fail(\"Failed to create workflow\");\n    console.error(chalk.red(`Error: ${error instanceof Error ? error.message : String(error)}`));\n    process.exit(1);\n  }\n}"
  },
  {
    "path": "src/commands/sync-forever.ts",
    "content": "import { execa } from \"execa\";\nimport chalk from \"chalk\";\nimport { join } from \"path\";\nimport { promises as fs } from \"fs\";\nimport { sync } from \"./sync\";\n\nexport async function syncForever(options?: { autoPush?: boolean }): Promise<void> {\n  const ralphScript = join(process.cwd(), \".repomirror\", \"ralph.sh\");\n  const syncScript = join(process.cwd(), \".repomirror\", \"sync.sh\");\n\n  // Check if scripts exist\n  let ralphExists = false;\n  let syncExists = false;\n  \n  try {\n    await fs.access(ralphScript);\n    ralphExists = true;\n  } catch {\n    // ralph.sh doesn't exist\n  }\n\n  try {\n    await fs.access(syncScript);\n    syncExists = true;\n  } catch {\n    // sync.sh doesn't exist\n  }\n\n  // For strict backward compatibility: without --auto-push, always require ralph.sh\n  if (!options?.autoPush && !ralphExists) {\n    console.error(\n      chalk.red(\n        \"Error: .repomirror/ralph.sh not found. Run 'npx repomirror init' first.\",\n      ),\n    );\n    process.exit(1);\n  }\n\n  // If auto-push is requested, we need to use the new sync() approach\n  // If ralph.sh exists and no auto-push, use legacy approach\n  if (options?.autoPush || !ralphExists) {\n    if (!syncExists) {\n      console.error(\n        chalk.red(\n          \"Error: .repomirror/sync.sh not found. Run 'npx repomirror init' first.\",\n        ),\n      );\n      process.exit(1);\n    }\n\n    console.log(chalk.cyan(\"Running continuous sync...\"));\n    if (options?.autoPush) {\n      console.log(chalk.cyan(\"Auto-push is enabled\"));\n    }\n    console.log(chalk.yellow(\"Press Ctrl+C to stop\"));\n\n    let isRunning = true;\n\n    // Handle graceful shutdown\n    const signalHandler = () => {\n      console.log(chalk.yellow(\"\\nStopping continuous sync...\"));\n      isRunning = false;\n    };\n\n    process.on('SIGINT', signalHandler);\n    process.on('SIGTERM', signalHandler);\n\n    try {\n      while (isRunning) {\n        try {\n          await sync(options?.autoPush ? { autoPush: options.autoPush } : undefined);\n          \n          if (isRunning) {\n            console.log(chalk.gray(\"===SLEEP===\"));\n            console.log(chalk.gray(\"looping\"));\n            \n            // Sleep for 10 seconds, but check for stop condition every second\n            for (let i = 0; i < 10 && isRunning; i++) {\n              await new Promise(resolve => setTimeout(resolve, 1000));\n            }\n          }\n        } catch (error) {\n          console.error(\n            chalk.red(\n              `Sync iteration failed: ${error instanceof Error ? error.message : String(error)}`,\n            ),\n          );\n          console.log(chalk.gray(\"Continuing with next iteration...\"));\n          \n          // Sleep for 10 seconds before retrying\n          for (let i = 0; i < 10 && isRunning; i++) {\n            await new Promise(resolve => setTimeout(resolve, 1000));\n          }\n        }\n      }\n    } finally {\n      // Clean up signal handlers\n      process.off('SIGINT', signalHandler);\n      process.off('SIGTERM', signalHandler);\n      console.log(chalk.yellow(\"Stopped by user\"));\n    }\n  } else {\n    // Use legacy ralph.sh approach\n    if (!ralphExists) {\n      console.error(\n        chalk.red(\n          \"Error: .repomirror/ralph.sh not found. Run 'npx repomirror init' first.\",\n        ),\n      );\n      process.exit(1);\n    }\n\n    console.log(chalk.cyan(\"Running ralph.sh (continuous sync)...\"));\n    console.log(chalk.yellow(\"Press Ctrl+C to stop\"));\n\n    const subprocess = execa(\"bash\", [ralphScript], {\n      stdio: \"inherit\",\n      cwd: process.cwd(),\n    });\n\n    // Handle graceful shutdown for subprocess\n    const signalHandler = () => {\n      console.log(chalk.yellow(\"\\nStopping continuous sync...\"));\n      subprocess.kill(\"SIGINT\");\n    };\n\n    process.on('SIGINT', signalHandler);\n    process.on('SIGTERM', signalHandler);\n\n    try {\n      await subprocess;\n    } catch (error) {\n      // Clean up signal handlers\n      process.off('SIGINT', signalHandler);\n      process.off('SIGTERM', signalHandler);\n      \n      if (error instanceof Error && (error as any).signal === \"SIGINT\") {\n        console.log(chalk.yellow(\"Stopped by user\"));\n      } else {\n        console.error(\n          chalk.red(\n            `Sync forever failed: ${error instanceof Error ? error.message : String(error)}`,\n          ),\n        );\n        process.exit(1);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/commands/sync-one.ts",
    "content": "import { sync } from \"./sync\";\n\n// sync-one is just an alias for sync\nexport async function syncOne(options?: { autoPush?: boolean }): Promise<void> {\n  if (options) {\n    await sync(options);\n  } else {\n    await sync();\n  }\n}\n"
  },
  {
    "path": "src/commands/sync.ts",
    "content": "import { execa } from \"execa\";\nimport chalk from \"chalk\";\nimport { join } from \"path\";\nimport { promises as fs } from \"fs\";\nimport yaml from \"yaml\";\nimport { push } from \"./push\";\n\ninterface RemoteConfig {\n  url: string;\n  branch: string;\n  auto_push?: boolean;\n}\n\ninterface RepoMirrorConfig {\n  sourceRepo: string;\n  targetRepo: string;\n  transformationInstructions: string;\n  remotes?: {\n    [remoteName: string]: RemoteConfig;\n  };\n  push?: {\n    default_remote?: string;\n    default_branch?: string;\n    commit_prefix?: string;\n  };\n  pull?: {\n    auto_sync?: boolean;\n    source_remote?: string;\n    source_branch?: string;\n  };\n}\n\nasync function loadConfig(): Promise<RepoMirrorConfig | null> {\n  try {\n    const configPath = join(process.cwd(), \"repomirror.yaml\");\n    const configContent = await fs.readFile(configPath, \"utf-8\");\n    return yaml.parse(configContent) as RepoMirrorConfig;\n  } catch {\n    return null;\n  }\n}\n\nasync function performAutoPush(config: RepoMirrorConfig, cliAutoPush: boolean): Promise<void> {\n  if (!cliAutoPush && !config.remotes) {\n    return;\n  }\n\n  // Find remotes with auto_push enabled\n  const autoPushRemotes: string[] = [];\n  if (config.remotes) {\n    for (const [remoteName, remoteConfig] of Object.entries(config.remotes)) {\n      if (cliAutoPush || remoteConfig.auto_push) {\n        autoPushRemotes.push(remoteName);\n      }\n    }\n  }\n\n  if (autoPushRemotes.length === 0) {\n    return;\n  }\n\n  console.log(chalk.cyan(\"\\n🚀 Auto-push enabled - pushing to configured remotes...\"));\n\n  try {\n    if (cliAutoPush) {\n      // Push to all remotes when --auto-push is used\n      await push({ all: true });\n    } else {\n      // Push only to remotes with auto_push enabled\n      for (const remoteName of autoPushRemotes) {\n        await push({ remote: remoteName });\n      }\n    }\n    console.log(chalk.green(\"✅ Auto-push completed successfully\"));\n  } catch (error) {\n    // Log the error but don't break the sync workflow\n    console.log(chalk.yellow(\"⚠️  Auto-push failed, but sync completed successfully:\"));\n    console.log(chalk.red(`   ${error instanceof Error ? error.message : String(error)}`));\n    console.log(chalk.gray(\"   You can manually push using: npx repomirror push\"));\n  }\n}\n\nexport async function sync(options?: { autoPush?: boolean }): Promise<void> {\n  const syncScript = join(process.cwd(), \".repomirror\", \"sync.sh\");\n\n  try {\n    // Check if sync.sh exists\n    await fs.access(syncScript);\n  } catch {\n    console.error(\n      chalk.red(\n        \"Error: .repomirror/sync.sh not found. Run 'npx repomirror init' first.\",\n      ),\n    );\n    process.exit(1);\n  }\n\n  console.log(chalk.cyan(\"Running sync.sh...\"));\n\n  const subprocess = execa(\"bash\", [syncScript], {\n    stdio: \"inherit\",\n    cwd: process.cwd(),\n  });\n\n  // Handle graceful shutdown for subprocess\n  const signalHandler = () => {\n    console.log(chalk.yellow(\"\\nStopping sync...\"));\n    subprocess.kill(\"SIGINT\");\n  };\n\n  process.on('SIGINT', signalHandler);\n  process.on('SIGTERM', signalHandler);\n\n  try {\n    await subprocess;\n    console.log(chalk.green(\"Sync completed successfully\"));\n\n    // Check for auto-push after successful sync\n    const config = await loadConfig();\n    if (config && (options?.autoPush || config.remotes)) {\n      await performAutoPush(config, options?.autoPush || false);\n    }\n  } catch (error) {\n    // Clean up signal handlers\n    process.off('SIGINT', signalHandler);\n    process.off('SIGTERM', signalHandler);\n    \n    if (error instanceof Error && (error as any).signal === \"SIGINT\") {\n      console.log(chalk.yellow(\"\\nSync stopped by user\"));\n      process.exit(0);\n    }\n    \n    console.error(\n      chalk.red(\n        `Sync failed: ${error instanceof Error ? error.message : String(error)}`,\n      ),\n    );\n    process.exit(1);\n  } finally {\n    // Clean up signal handlers\n    process.off('SIGINT', signalHandler);\n    process.off('SIGTERM', signalHandler);\n  }\n}\n"
  },
  {
    "path": "src/commands/visualize.ts",
    "content": "import { createInterface } from \"node:readline\";\n\nconst colors = {\n  reset: \"\\x1b[0m\",\n  bright: \"\\x1b[1m\",\n  dim: \"\\x1b[2m\",\n  red: \"\\x1b[31m\",\n  green: \"\\x1b[32m\",\n  yellow: \"\\x1b[33m\",\n  blue: \"\\x1b[34m\",\n  magenta: \"\\x1b[35m\",\n  cyan: \"\\x1b[36m\",\n};\n\nfunction getTypeColor(type: string): string {\n  switch (type) {\n    case \"system\":\n      return colors.magenta;\n    case \"user\":\n      return colors.blue;\n    case \"assistant\":\n      return colors.green;\n    case \"tool_use\":\n      return colors.cyan;\n    case \"tool_result\":\n      return colors.yellow;\n    case \"message\":\n      return colors.dim;\n    case \"text\":\n      return colors.reset;\n    default:\n      return colors.reset;\n  }\n}\n\ninterface Todo {\n  status: string;\n  content: string;\n  priority?: string;\n}\n\nfunction formatTodoList(todos: Todo[]): string {\n  let output = `📋 ${colors.bright}${colors.cyan}Todo List Update${colors.reset}\\n`;\n\n  const statusColors = {\n    completed: colors.dim + colors.green,\n    in_progress: colors.bright + colors.yellow,\n    pending: colors.reset,\n  };\n\n  const statusIcons = {\n    completed: \"✅\",\n    in_progress: \"🔄\",\n    pending: \"⏸️\",\n  };\n\n  const priorityColors = {\n    high: colors.red,\n    medium: colors.yellow,\n    low: colors.dim,\n  };\n\n  todos.forEach((todo, index) => {\n    const statusColor =\n      statusColors[todo.status as keyof typeof statusColors] || colors.reset;\n    const statusIcon =\n      statusIcons[todo.status as keyof typeof statusIcons] || \"❓\";\n    const priorityColor =\n      priorityColors[todo.priority as keyof typeof priorityColors] ||\n      colors.reset;\n    const checkbox = todo.status === \"completed\" ? \"☑️\" : \"☐\";\n\n    output += `  ${checkbox} ${statusIcon} ${statusColor}${todo.content}${colors.reset}`;\n    output += ` ${priorityColor}[${todo.priority}]${colors.reset}`;\n\n    if (todo.status === \"in_progress\") {\n      output += ` ${colors.bright}${colors.yellow}← ACTIVE${colors.reset}`;\n    }\n\n    output += \"\\n\";\n  });\n\n  // Add summary stats\n  const completed = todos.filter((t) => t.status === \"completed\").length;\n  const inProgress = todos.filter((t) => t.status === \"in_progress\").length;\n  const pending = todos.filter((t) => t.status === \"pending\").length;\n\n  output += `\\n  ${colors.dim}📊 Progress: ${colors.green}${completed} completed${colors.reset}`;\n  output += `${colors.dim}, ${colors.yellow}${inProgress} active${colors.reset}`;\n  output += `${colors.dim}, ${colors.reset}${pending} pending${colors.reset}`;\n  output += `${colors.dim} (${Math.round((completed / todos.length) * 100)}% done)${colors.reset}`;\n\n  return output;\n}\n\nfunction formatConcise(json: any): string {\n  const type = json.type || \"unknown\";\n  const typeColor = getTypeColor(type);\n\n  let output = `⏺ ${typeColor}${type.charAt(0).toUpperCase() + type.slice(1)}${colors.reset}`;\n\n  // Special handling for TodoWrite calls\n  if (\n    type === \"assistant\" &&\n    json.message?.content?.[0]?.name === \"TodoWrite\"\n  ) {\n    const toolInput = json.message.content[0].input;\n    if (toolInput?.todos && Array.isArray(toolInput.todos)) {\n      return formatTodoList(toolInput.todos);\n    }\n  }\n\n  // Add context based on type\n  if (type === \"assistant\" && json.message?.content?.[0]?.name) {\n    const toolName = json.message.content[0].name;\n    const toolInput = json.message.content[0].input;\n\n    // Format tool name with key arguments\n    let toolDisplay = `${colors.cyan}${toolName}${colors.reset}`;\n\n    if (toolInput) {\n      const keyArgs = [];\n\n      // Extract the most important argument for each tool type\n      if (toolInput.file_path) keyArgs.push(toolInput.file_path);\n      else if (toolInput.path) keyArgs.push(toolInput.path);\n      else if (toolInput.pattern) keyArgs.push(`\"${toolInput.pattern}\"`);\n      else if (toolInput.command) keyArgs.push(toolInput.command);\n      else if (toolInput.cmd) keyArgs.push(toolInput.cmd);\n      else if (toolInput.query) keyArgs.push(`\"${toolInput.query}\"`);\n      else if (toolInput.description) keyArgs.push(toolInput.description);\n      else if (toolInput.prompt)\n        keyArgs.push(`\"${toolInput.prompt.substring(0, 30)}...\"`);\n      else if (toolInput.url) keyArgs.push(toolInput.url);\n\n      if (keyArgs.length > 0) {\n        toolDisplay += `(${colors.green}${keyArgs[0]}${colors.reset})`;\n      }\n    }\n\n    output = `⏺ ${toolDisplay}`;\n\n    // Show additional arguments on next lines for complex tools\n    if (toolInput) {\n      const additionalArgs = [];\n\n      if (toolName === \"Bash\" && toolInput.cwd) {\n        additionalArgs.push(`cwd: ${toolInput.cwd}`);\n      }\n      if (toolInput.limit) additionalArgs.push(`limit: ${toolInput.limit}`);\n      if (toolInput.offset) additionalArgs.push(`offset: ${toolInput.offset}`);\n      if (toolInput.include)\n        additionalArgs.push(`include: ${toolInput.include}`);\n      if (toolInput.old_string && toolInput.new_string) {\n        additionalArgs.push(\n          `replace: \"${toolInput.old_string.substring(0, 20)}...\" → \"${toolInput.new_string.substring(0, 20)}...\"`,\n        );\n      }\n      if (toolInput.timeout)\n        additionalArgs.push(`timeout: ${toolInput.timeout}ms`);\n\n      if (additionalArgs.length > 0) {\n        output += `\\n  ⎿  ${colors.dim}${additionalArgs.join(\", \")}${colors.reset}`;\n      }\n    }\n  } else if (type === \"tool_result\" && json.name) {\n    output += `(${colors.cyan}${json.name}${colors.reset})`;\n  } else if (type === \"user\" && json.message?.content?.[0]) {\n    const content = json.message.content[0];\n    if (content.type === \"tool_result\") {\n      // Override the type display for tool results\n      output = `⏺ ${colors.yellow}Tool Result${colors.reset}`;\n\n      // Show result summary and first 2 lines\n      if (content.content) {\n        const resultText =\n          typeof content.content === \"string\"\n            ? content.content\n            : JSON.stringify(content.content);\n        const lines = resultText.split(\"\\n\");\n        const chars = resultText.length;\n        output += `\\n  ⎿  ${colors.dim}${lines.length} lines, ${chars} chars${colors.reset}`;\n        if (content.is_error) {\n          output += ` ${colors.red}ERROR${colors.reset}`;\n        }\n\n        // Show first 2 lines of content\n        if (lines.length > 0 && lines[0].trim()) {\n          output += `\\n  ⎿  ${colors.reset}${lines[0]}${colors.reset}`;\n        }\n        if (lines.length > 1 && lines[1].trim()) {\n          output += `\\n      ${colors.dim}${lines[1]}${colors.reset}`;\n        }\n      }\n    } else if (content.text) {\n      const text = content.text.substring(0, 50);\n      output += `: ${colors.dim}${text}${text.length === 50 ? \"...\" : \"\"}${colors.reset}`;\n    }\n  } else if (type === \"system\" && json.subtype) {\n    output += `(${colors.dim}${json.subtype}${colors.reset})`;\n  }\n\n  // Show assistant message content if it exists\n  if (type === \"assistant\" && json.message?.content) {\n    const textContent = json.message.content.find(\n      (c: any) => c.type === \"text\",\n    );\n    if (textContent?.text) {\n      const lines = textContent.text.split(\"\\n\").slice(0, 3); // Show first 3 lines\n      output += `\\n  ⎿  ${colors.reset}${lines[0]}${colors.reset}`;\n      if (lines.length > 1) {\n        output += `\\n      ${colors.dim}${lines[1]}${colors.reset}`;\n      }\n      if (lines.length > 2) {\n        output += `\\n      ${colors.dim}${lines[2]}${colors.reset}`;\n      }\n      if (textContent.text.split(\"\\n\").length > 3) {\n        output += `\\n      ${colors.dim}...${colors.reset}`;\n      }\n    }\n  }\n\n  // Add summary line\n  let summary = \"\";\n  if (json.message?.usage) {\n    const usage = json.message.usage;\n    summary = `${usage.input_tokens || 0}/${usage.output_tokens || 0} tokens`;\n  } else if (json.output && typeof json.output === \"string\") {\n    summary = `${json.output.length} chars output`;\n  } else if (json.message?.content?.length) {\n    summary = `${json.message.content.length} content items`;\n  } else if (json.tools?.length) {\n    summary = `${json.tools.length} tools available`;\n  }\n\n  if (summary) {\n    output += `\\n  ⎿  ${colors.dim}${summary}${colors.reset}`;\n  }\n\n  return output;\n}\n\nfunction displayToolCallWithResult(\n  toolCall: any,\n  toolCallJson: any,\n  toolResultJson: any,\n  callTimestamp: string,\n  resultTimestamp: string,\n) {\n  // Display the tool call header\n  process.stdout.write(`${callTimestamp}${formatConcise(toolCallJson)}\\n`);\n\n  // Display the result\n  const toolResult = toolResultJson.message.content[0];\n  const isError = toolResult.is_error;\n  const resultIcon = isError ? \"❌\" : \"✅\";\n  const resultColor = isError ? colors.red : colors.green;\n\n  process.stdout.write(\n    `  ${resultTimestamp}${resultIcon} ${resultColor}Tool Result${colors.reset}`,\n  );\n\n  if (toolResult.content) {\n    const resultText =\n      typeof toolResult.content === \"string\"\n        ? toolResult.content\n        : JSON.stringify(toolResult.content);\n    const lines = resultText.split(\"\\n\");\n    const chars = resultText.length;\n\n    process.stdout.write(\n      ` ${colors.dim}(${lines.length} lines, ${chars} chars)${colors.reset}`,\n    );\n\n    if (isError) {\n      process.stdout.write(` ${colors.red}ERROR${colors.reset}`);\n    }\n\n    // Show first few lines of result\n    const linesToShow = Math.min(3, lines.length);\n    for (let i = 0; i < linesToShow; i++) {\n      if (lines[i].trim()) {\n        const lineColor = i === 0 ? colors.reset : colors.dim;\n        process.stdout.write(`\\n    ⎿  ${lineColor}${lines[i]}${colors.reset}`);\n      }\n    }\n\n    if (lines.length > linesToShow) {\n      process.stdout.write(\n        `\\n    ⎿  ${colors.dim}... ${lines.length - linesToShow} more lines${colors.reset}`,\n      );\n    }\n  }\n\n  process.stdout.write(\"\\n\\n\");\n}\n\nexport async function visualize(\n  options: { debug?: boolean } = {},\n): Promise<void> {\n  const rl = createInterface({\n    input: process.stdin,\n    crlfDelay: Infinity,\n  });\n\n  const debugMode = options.debug || process.argv.includes(\"--debug\");\n  const toolCalls = new Map(); // Store tool calls by their ID\n  const pendingResults = new Map(); // Store results waiting for their tool calls\n  let lastLine: any = null; // Track the last line to detect final message\n  let isLastAssistantMessage = false;\n\n  // Handle graceful shutdown\n  const signalHandler = () => {\n    console.log(\"\\n\" + colors.yellow + \"Stopping visualizer...\" + colors.reset);\n    rl.close();\n    process.exit(0);\n  };\n\n  process.on('SIGINT', signalHandler);\n  process.on('SIGTERM', signalHandler);\n\n  rl.on(\"line\", (line) => {\n    if (line.trim()) {\n      const timestamp = debugMode\n        ? `${colors.dim}[${new Date().toISOString()}]${colors.reset} `\n        : \"\";\n\n      try {\n        const json = JSON.parse(line);\n\n        // Check if this is a tool call\n        if (json.type === \"assistant\" && json.message?.content?.[0]?.id) {\n          const toolCall = json.message.content[0];\n          const toolId = toolCall.id;\n\n          // Store the tool call\n          toolCalls.set(toolId, {\n            toolCall: json,\n            timestamp: timestamp,\n          });\n\n          // Check if we have a pending result for this tool call\n          if (pendingResults.has(toolId)) {\n            const result = pendingResults.get(toolId);\n            displayToolCallWithResult(\n              toolCall,\n              json,\n              result.toolResult,\n              result.timestamp,\n              timestamp,\n            );\n            pendingResults.delete(toolId);\n          } else {\n            // Display the tool call and mark it as pending\n            process.stdout.write(`${timestamp + formatConcise(json)}\\n`);\n            process.stdout.write(\n              `${colors.dim}  ⎿  Waiting for result...${colors.reset}\\n\\n`,\n            );\n          }\n        }\n        // Check if this is a tool result\n        else if (\n          json.type === \"user\" &&\n          json.message?.content?.[0]?.type === \"tool_result\"\n        ) {\n          const toolResult = json.message.content[0];\n          const toolId = toolResult.tool_use_id;\n\n          if (toolCalls.has(toolId)) {\n            // We have the matching tool call, display them together\n            const stored = toolCalls.get(toolId);\n            displayToolCallWithResult(\n              stored.toolCall.message.content[0],\n              stored.toolCall,\n              json,\n              stored.timestamp,\n              timestamp,\n            );\n            toolCalls.delete(toolId);\n          } else {\n            // Store the result and wait for the tool call\n            pendingResults.set(toolId, {\n              toolResult: json,\n              timestamp: timestamp,\n            });\n          }\n        }\n        // Check if this is the result message and display full content\n        else if (json.type === \"result\" && json.result) {\n          process.stdout.write(`${timestamp + formatConcise(json)}\\n\\n`);\n          process.stdout.write(\n            `${colors.bright}${colors.green}=== Final Result ===${colors.reset}\\n\\n`,\n          );\n          process.stdout.write(`${json.result}\\n`);\n        }\n        // For all other message types, display normally\n        else {\n          process.stdout.write(`${timestamp + formatConcise(json)}\\n\\n`);\n        }\n\n        // Track if this might be the last assistant message\n        lastLine = json;\n        isLastAssistantMessage =\n          json.type === \"assistant\" && !json.message?.content?.[0]?.id;\n      } catch (_error) {\n        process.stdout.write(\n          `${timestamp}${colors.red}⏺ Parse Error${colors.reset}\\n`,\n        );\n        process.stdout.write(\n          `  ⎿  ${colors.dim}${line.substring(0, 50)}...${colors.reset}\\n\\n`,\n        );\n      }\n    }\n  });\n\n  rl.on(\"close\", () => {\n    // If the last message was an assistant message (not a tool call), display the full content\n    if (isLastAssistantMessage && lastLine?.message?.content?.[0]?.text) {\n      process.stdout.write(\n        `\\n${colors.bright}${colors.green}=== Final Assistant Message ===${colors.reset}\\n\\n`,\n      );\n      process.stdout.write(`${lastLine.message.content[0].text}\\n`);\n    }\n  });\n}\n"
  },
  {
    "path": "src/templates/gitignore.template",
    "content": "claude_output.jsonl"
  },
  {
    "path": "src/templates/ralph.sh.template",
    "content": "#!/bin/bash\nwhile :; do\n  ./.repomirror/sync.sh\n  echo -e \"===SLEEP===\\n===SLEEP===\\n\"; echo 'looping';\n  sleep 10;\ndone"
  },
  {
    "path": "src/templates/sync.sh.template",
    "content": "#!/bin/bash\ncat .repomirror/prompt.md | \\\n        claude -p --output-format=stream-json --verbose --dangerously-skip-permissions --add-dir ${targetRepo} | \\\n        tee -a .repomirror/claude_output.jsonl | \\\n        npx repomirror visualize --debug;"
  },
  {
    "path": "test-resolve.js",
    "content": "const path = require('path');\n\n// Mock process.cwd()\nconst mockCwd = '/tmp/test-dir';\nprocess.cwd = () => mockCwd;\n\nconst sourceRepo = './source';\n\n// Our current logic\nconst baseDir1 = sourceRepo && sourceRepo !== './' \n  ? path.resolve(process.cwd(), sourceRepo) \n  : process.cwd();\n\nconsole.log('sourceRepo:', sourceRepo);\nconsole.log('process.cwd():', process.cwd());\nconsole.log('baseDir:', baseDir1);\nconsole.log('Expected files at:', path.join(baseDir1, '.repomirror'));\n"
  },
  {
    "path": "tests/README.md",
    "content": "# Test Suite for repomirror\n\nThis directory contains the test suite for the repomirror project using Vitest.\n\n## Structure\n\n```\ntests/\n├── README.md           # This file\n├── setup.ts            # Global test setup\n├── basic.test.ts       # Basic functionality tests\n├── commands/           # Command-specific tests\n│   └── simple.test.ts  # Example command tests\n└── helpers/            # Test utilities and helpers\n    ├── index.ts        # Helper exports\n    ├── test-utils.ts   # Test utility functions\n    └── fixtures.ts     # Mock data and fixtures\n```\n\n## Configuration\n\nThe test configuration is defined in `vitest.config.ts` in the project root:\n\n- **Environment**: Node.js\n- **Coverage**: V8 provider with HTML and JSON reports\n- **TypeScript**: Full TypeScript support with path aliases\n- **Coverage Thresholds**: 80% for branches, functions, lines, and statements\n\n## Running Tests\n\n```bash\n# Run all tests\nnpm test\n\n# Run tests with coverage\nnpm test -- --coverage\n\n# Run specific test file\nnpx vitest run tests/basic.test.ts\n\n# Run tests in watch mode\nnpx vitest\n```\n\n## Test Utilities\n\nThe `helpers/` directory provides utilities for testing:\n\n### `test-utils.ts`\n- `createTempDir()` - Create temporary directories for testing\n- `cleanupTempDir()` - Clean up temporary directories\n- `createMockGitRepo()` - Create mock git repositories\n- `mockConsole()` - Mock console methods\n- `mockProcess()` - Mock process methods\n- `mockInquirer()` - Mock inquirer prompts\n- `mockOra()` - Mock ora spinners\n- `mockExeca()` - Mock command execution\n\n### `fixtures.ts`\n- Pre-defined mock data for consistent testing\n- Sample repository configurations\n- Mock command responses\n- File structure templates\n\n## Writing Tests\n\nExample test structure:\n\n```typescript\nimport { describe, it, expect, beforeEach, afterEach, vi } from \"vitest\";\nimport { createTempDir, cleanupTempDir, mockConsole } from \"../helpers\";\n\ndescribe(\"your feature\", () => {\n  let tempDir: string;\n\n  beforeEach(async () => {\n    tempDir = await createTempDir();\n    // Setup test environment\n  });\n\n  afterEach(async () => {\n    await cleanupTempDir(tempDir);\n    vi.restoreAllMocks();\n  });\n\n  it(\"should do something\", async () => {\n    // Your test code here\n    expect(true).toBe(true);\n  });\n});\n```\n\n## Coverage\n\nCoverage reports are generated in the `coverage/` directory:\n- HTML report: `coverage/index.html`\n- JSON report: `coverage/coverage.json`\n\nThe project maintains coverage thresholds of 80% across all metrics.\n\n## TypeScript Support\n\nTests have full TypeScript support with:\n- Path aliases: `@/` for `src/`, `@tests/` for `tests/`\n- Type checking during test runs\n- Import resolution for both source and test files\n\n## Integration with CI/CD\n\nThe test suite is designed to work with continuous integration:\n- Exit codes properly indicate success/failure\n- Coverage reports can be uploaded to coverage services\n- Tests run in isolated environments"
  },
  {
    "path": "tests/basic.test.ts",
    "content": "import { describe, it, expect } from \"vitest\";\n\ndescribe(\"basic test\", () => {\n  it(\"should pass a simple test\", () => {\n    expect(1 + 1).toBe(2);\n  });\n  \n  it(\"should work with async functions\", async () => {\n    const result = await Promise.resolve(\"hello\");\n    expect(result).toBe(\"hello\");\n  });\n  \n  it(\"should work with TypeScript types\", () => {\n    const obj: { name: string; age: number } = {\n      name: \"test\",\n      age: 42\n    };\n    \n    expect(obj.name).toBe(\"test\");\n    expect(obj.age).toBe(42);\n  });\n});"
  },
  {
    "path": "tests/commands/dispatch-sync.test.ts",
    "content": "import { describe, it, expect, vi, beforeEach, afterEach } from \"vitest\";\nimport { promises as fs } from \"fs\";\nimport { execa } from \"execa\";\nimport inquirer from \"inquirer\";\nimport chalk from \"chalk\";\nimport { dispatchSync } from \"../../src/commands/dispatch-sync\";\n\n// Mock dependencies\nvi.mock(\"fs\", () => ({\n  promises: {\n    access: vi.fn(),\n  },\n}));\n\nvi.mock(\"execa\");\nvi.mock(\"inquirer\");\nvi.mock(\"chalk\", () => ({\n  default: {\n    red: vi.fn((text) => text),\n    green: vi.fn((text) => text),\n    yellow: vi.fn((text) => text),\n    cyan: vi.fn((text) => text),\n    gray: vi.fn((text) => text),\n  },\n}));\n\nvi.mock(\"ora\", () => ({\n  default: vi.fn(() => ({\n    start: vi.fn().mockReturnThis(),\n    succeed: vi.fn().mockReturnThis(),\n    fail: vi.fn().mockReturnThis(),\n    warn: vi.fn().mockReturnThis(),\n  })),\n}));\n\n// Mock console methods\nconst mockConsoleLog = vi.spyOn(console, \"log\").mockImplementation(() => {});\nconst mockConsoleError = vi.spyOn(console, \"error\").mockImplementation(() => {});\nconst mockProcessExit = vi.spyOn(process, \"exit\").mockImplementation((code?: number) => {\n  throw new Error(`process.exit unexpectedly called with \"${code}\"`);\n  return undefined as never;\n});\n\ndescribe(\"dispatch-sync command\", () => {\n  beforeEach(() => {\n    vi.clearAllMocks();\n  });\n\n  afterEach(() => {\n    vi.restoreAllMocks();\n  });\n\n  describe(\"flag validation\", () => {\n    it(\"should exit with error when --quiet is used without --yes\", async () => {\n      await expect(() => dispatchSync({ quiet: true, yes: false })).rejects.toThrow(\"process.exit unexpectedly called with \\\"1\\\"\");\n    });\n\n    it(\"should allow --yes without --quiet\", async () => {\n      // Mock workflow exists\n      vi.mocked(fs.access).mockResolvedValue(undefined);\n      \n      // Mock gh CLI installed\n      vi.mocked(execa).mockResolvedValueOnce({ stdout: \"gh version 2.0.0\", stderr: \"\" } as any);\n      \n      // Mock git remote origin URL\n      vi.mocked(execa).mockResolvedValueOnce({ \n        stdout: \"https://github.com/testowner/testrepo.git\", \n        stderr: \"\" \n      } as any);\n      \n      // Mock workflow dispatch\n      vi.mocked(execa).mockResolvedValueOnce({ stdout: \"workflow dispatched\", stderr: \"\" } as any);\n\n      await expect(dispatchSync({ yes: true, quiet: false })).resolves.toBeUndefined();\n    });\n\n    it(\"should allow --yes and --quiet together\", async () => {\n      // Mock workflow exists\n      vi.mocked(fs.access).mockResolvedValue(undefined);\n      \n      // Mock gh CLI installed\n      vi.mocked(execa).mockResolvedValueOnce({ stdout: \"gh version 2.0.0\", stderr: \"\" } as any);\n      \n      // Mock git remote origin URL\n      vi.mocked(execa).mockResolvedValueOnce({ \n        stdout: \"https://github.com/testowner/testrepo.git\", \n        stderr: \"\" \n      } as any);\n      \n      // Mock workflow dispatch\n      vi.mocked(execa).mockResolvedValueOnce({ stdout: \"workflow dispatched\", stderr: \"\" } as any);\n\n      await expect(dispatchSync({ yes: true, quiet: true })).resolves.toBeUndefined();\n    });\n  });\n\n  describe(\"prerequisite checks\", () => {\n    it(\"should exit when workflow file doesn't exist\", async () => {\n      vi.mocked(fs.access).mockRejectedValue(new Error(\"ENOENT\"));\n\n      await expect(() => dispatchSync()).rejects.toThrow(\"process.exit unexpectedly called with \\\"1\\\"\");\n    });\n\n    it(\"should exit when gh CLI is not installed\", async () => {\n      // Mock workflow exists\n      vi.mocked(fs.access).mockResolvedValue(undefined);\n      \n      // Mock gh CLI not installed\n      vi.mocked(execa).mockRejectedValueOnce(new Error(\"command not found\"));\n\n      await expect(() => dispatchSync()).rejects.toThrow(\"process.exit unexpectedly called with \\\"1\\\"\");\n    });\n\n    it(\"should exit when git repository info cannot be determined\", async () => {\n      // Mock workflow exists\n      vi.mocked(fs.access).mockResolvedValue(undefined);\n      \n      // Mock gh CLI installed\n      vi.mocked(execa).mockResolvedValueOnce({ stdout: \"gh version 2.0.0\", stderr: \"\" } as any);\n      \n      // Mock git remote origin URL failure\n      vi.mocked(execa).mockRejectedValueOnce(new Error(\"no remote origin\"));\n\n      await expect(() => dispatchSync()).rejects.toThrow(\"process.exit unexpectedly called with \\\"1\\\"\");\n    });\n  });\n\n  describe(\"user confirmation\", () => {\n    beforeEach(() => {\n      // Mock all prerequisite checks pass\n      vi.mocked(fs.access).mockResolvedValue(undefined);\n      vi.mocked(execa)\n        .mockResolvedValueOnce({ stdout: \"gh version 2.0.0\", stderr: \"\" } as any) // gh --version\n        .mockResolvedValueOnce({ \n          stdout: \"https://github.com/testowner/testrepo.git\", \n          stderr: \"\" \n        } as any); // git config --get remote.origin.url\n    });\n\n    it(\"should prompt for confirmation when --yes is not provided\", async () => {\n      // Mock user confirms\n      vi.mocked(inquirer.prompt).mockResolvedValue({ shouldProceed: true });\n      \n      // Mock workflow dispatch\n      vi.mocked(execa).mockResolvedValueOnce({ stdout: \"workflow dispatched\", stderr: \"\" } as any);\n\n      await dispatchSync();\n\n      expect(inquirer.prompt).toHaveBeenCalledWith([\n        {\n          type: \"confirm\",\n          name: \"shouldProceed\",\n          message: \"Do you want to dispatch the workflow?\",\n          default: true,\n        },\n      ]);\n    });\n\n    it(\"should exit when user declines confirmation\", async () => {\n      // Mock user declines\n      vi.mocked(inquirer.prompt).mockResolvedValue({ shouldProceed: false });\n\n      await expect(() => dispatchSync()).rejects.toThrow(\"process.exit unexpectedly called with \\\"0\\\"\");\n    });\n\n    it(\"should skip confirmation when --yes is provided\", async () => {\n      // Mock workflow dispatch\n      vi.mocked(execa).mockResolvedValueOnce({ stdout: \"workflow dispatched\", stderr: \"\" } as any);\n\n      await dispatchSync({ yes: true });\n\n      expect(inquirer.prompt).not.toHaveBeenCalled();\n    });\n  });\n\n  describe(\"workflow dispatch\", () => {\n    beforeEach(() => {\n      // Mock all prerequisite checks pass\n      vi.mocked(fs.access).mockResolvedValue(undefined);\n      vi.mocked(execa)\n        .mockResolvedValueOnce({ stdout: \"gh version 2.0.0\", stderr: \"\" } as any) // gh --version\n        .mockResolvedValueOnce({ \n          stdout: \"https://github.com/testowner/testrepo.git\", \n          stderr: \"\" \n        } as any); // git config --get remote.origin.url\n    });\n\n    it(\"should successfully dispatch workflow\", async () => {\n      // Mock workflow dispatch\n      vi.mocked(execa).mockResolvedValueOnce({ stdout: \"workflow dispatched\", stderr: \"\" } as any);\n\n      await dispatchSync({ yes: true });\n\n      expect(vi.mocked(execa)).toHaveBeenCalledWith(\"gh\", [\n        \"workflow\",\n        \"run\",\n        \"repomirror.yml\",\n        \"--repo\",\n        \"testowner/testrepo\",\n      ]);\n    });\n\n    it(\"should handle workflow dispatch failure\", async () => {\n      // Mock workflow dispatch failure\n      vi.mocked(execa).mockRejectedValueOnce(new Error(\"workflow not found\"));\n\n      await expect(() => dispatchSync({ yes: true })).rejects.toThrow(\"process.exit unexpectedly called with \\\"1\\\"\");\n    });\n\n    it(\"should handle authentication errors\", async () => {\n      // Mock authentication error\n      vi.mocked(execa).mockRejectedValueOnce(new Error(\"authentication failed\"));\n\n      await expect(() => dispatchSync({ yes: true })).rejects.toThrow(\"process.exit unexpectedly called with \\\"1\\\"\");\n    });\n\n    it(\"should handle workflow not found errors\", async () => {\n      // Mock workflow not found error\n      vi.mocked(execa).mockRejectedValueOnce(new Error(\"not found\"));\n\n      await expect(() => dispatchSync({ yes: true })).rejects.toThrow(\"process.exit unexpectedly called with \\\"1\\\"\");\n    });\n  });\n\n  describe(\"repository URL parsing\", () => {\n    beforeEach(() => {\n      vi.mocked(fs.access).mockResolvedValue(undefined);\n      vi.mocked(execa).mockResolvedValueOnce({ stdout: \"gh version 2.0.0\", stderr: \"\" } as any);\n    });\n\n    it(\"should parse HTTPS GitHub URL correctly\", async () => {\n      vi.mocked(execa).mockResolvedValueOnce({ \n        stdout: \"https://github.com/owner/repo.git\", \n        stderr: \"\" \n      } as any);\n      \n      vi.mocked(execa).mockResolvedValueOnce({ stdout: \"dispatched\", stderr: \"\" } as any);\n\n      await dispatchSync({ yes: true });\n\n      expect(vi.mocked(execa)).toHaveBeenCalledWith(\"gh\", [\n        \"workflow\",\n        \"run\",\n        \"repomirror.yml\",\n        \"--repo\",\n        \"owner/repo\",\n      ]);\n    });\n\n    it(\"should parse SSH GitHub URL correctly\", async () => {\n      vi.mocked(execa).mockResolvedValueOnce({ \n        stdout: \"git@github.com:owner/repo.git\", \n        stderr: \"\" \n      } as any);\n      \n      vi.mocked(execa).mockResolvedValueOnce({ stdout: \"dispatched\", stderr: \"\" } as any);\n\n      await dispatchSync({ yes: true });\n\n      expect(vi.mocked(execa)).toHaveBeenCalledWith(\"gh\", [\n        \"workflow\",\n        \"run\",\n        \"repomirror.yml\",\n        \"--repo\",\n        \"owner/repo\",\n      ]);\n    });\n  });\n\n  describe(\"output modes\", () => {\n    beforeEach(() => {\n      // Mock all prerequisite checks pass\n      vi.mocked(fs.access).mockResolvedValue(undefined);\n      vi.mocked(execa)\n        .mockResolvedValueOnce({ stdout: \"gh version 2.0.0\", stderr: \"\" } as any)\n        .mockResolvedValueOnce({ \n          stdout: \"https://github.com/testowner/testrepo.git\", \n          stderr: \"\" \n        } as any)\n        .mockResolvedValueOnce({ stdout: \"workflow dispatched\", stderr: \"\" } as any);\n    });\n\n    it(\"should complete successfully in normal mode\", async () => {\n      await expect(dispatchSync({ yes: true })).resolves.toBeUndefined();\n    });\n\n    it(\"should complete successfully in quiet mode\", async () => {\n      await expect(dispatchSync({ yes: true, quiet: true })).resolves.toBeUndefined();\n    });\n  });\n});"
  },
  {
    "path": "tests/commands/github-actions.test.ts",
    "content": "import { describe, it, expect, vi, beforeEach, afterEach } from \"vitest\";\nimport { promises as fs } from \"fs\";\nimport { join } from \"path\";\nimport {\n  createTempDir,\n  cleanupTempDir,\n  mockConsole,\n  mockProcess,\n} from \"../helpers/test-utils\";\n\n// Mock external dependencies\nconst mockInquirerPrompt = vi.fn();\nconst mockOra = vi.fn();\n\nvi.mock(\"inquirer\", () => ({\n  default: {\n    prompt: mockInquirerPrompt,\n  },\n}));\n\nvi.mock(\"ora\", () => ({\n  default: mockOra,\n}));\n\n// Import after mocking\nconst { githubActions } = await import(\"../../src/commands/github-actions\");\n\ndescribe(\"github-actions command\", () => {\n  let tempDir: string;\n  let consoleMock: ReturnType<typeof mockConsole>;\n  let processMock: ReturnType<typeof mockProcess>;\n  let spinnerMock: any;\n\n  beforeEach(async () => {\n    tempDir = await createTempDir(\"repomirror-github-actions-\");\n    consoleMock = mockConsole();\n    processMock = mockProcess(true);\n    processMock.cwd.mockReturnValue(tempDir);\n\n    spinnerMock = {\n      start: vi.fn().mockReturnThis(),\n      succeed: vi.fn().mockReturnThis(),\n      fail: vi.fn().mockReturnThis(),\n    };\n    mockOra.mockReturnValue(spinnerMock);\n\n    vi.clearAllMocks();\n  });\n\n  afterEach(async () => {\n    await cleanupTempDir(tempDir);\n    vi.restoreAllMocks();\n  });\n\n  describe(\"successful workflow generation\", () => {\n    beforeEach(async () => {\n      // Create a mock repomirror.yaml\n      const config = {\n        sourceRepo: \"./\",\n        targetRepo: \"../myrepo-transformed\",\n        transformationInstructions: \"transform to typescript\",\n      };\n      const yaml = await import(\"yaml\");\n      await fs.writeFile(\n        join(tempDir, \"repomirror.yaml\"),\n        yaml.stringify(config),\n      );\n    });\n\n    it(\"should create GitHub Actions workflow with defaults\", async () => {\n      mockInquirerPrompt.mockResolvedValue({\n        workflowName: \"repomirror-sync.yml\",\n        schedule: \"0 */6 * * *\",\n        autoPush: true,\n        targetRepo: \"user/myrepo-transformed\",\n      });\n\n      await githubActions();\n\n      // Check workflow directory was created\n      const workflowDir = join(tempDir, \".github\", \"workflows\");\n      const dirStats = await fs.stat(workflowDir);\n      expect(dirStats.isDirectory()).toBe(true);\n\n      // Check workflow file was created\n      const workflowPath = join(workflowDir, \"repomirror-sync.yml\");\n      const workflowContent = await fs.readFile(workflowPath, \"utf-8\");\n\n      // Verify content includes expected elements\n      expect(workflowContent).toContain(\"name: RepoMirror Sync\");\n      expect(workflowContent).toContain(\"cron: '0 */6 * * *'\");\n      expect(workflowContent).toContain(\"repository: user/myrepo-transformed\");\n      expect(workflowContent).toContain(\"if: true\");\n\n      // Verify success messages\n      expect(spinnerMock.succeed).toHaveBeenCalledWith(\n        \"GitHub Actions workflow created\",\n      );\n      expect(consoleMock.log).toHaveBeenCalledWith(\n        expect.stringContaining(\"✅ Workflow created\"),\n      );\n    });\n\n    it(\"should handle CLI options correctly\", async () => {\n      mockInquirerPrompt.mockResolvedValue({\n        targetRepo: \"org/repo\",\n      });\n\n      await githubActions({\n        workflowName: \"custom.yml\",\n        schedule: \"0 0 * * *\",\n        autoPush: false,\n      });\n\n      const workflowPath = join(tempDir, \".github\", \"workflows\", \"custom.yml\");\n      const workflowContent = await fs.readFile(workflowPath, \"utf-8\");\n\n      expect(workflowContent).toContain(\"cron: '0 0 * * *'\");\n      expect(workflowContent).toContain(\"if: false\");\n      expect(mockInquirerPrompt).toHaveBeenCalledWith(\n        expect.arrayContaining([\n          expect.objectContaining({\n            name: \"targetRepo\",\n          }),\n        ]),\n      );\n    });\n\n    it(\"should validate workflow file extension\", async () => {\n      mockInquirerPrompt.mockImplementation((questions) => {\n        const workflowQuestion = questions.find(\n          (q: any) => q.name === \"workflowName\",\n        );\n        if (workflowQuestion && workflowQuestion.validate) {\n          expect(workflowQuestion.validate(\"test.yml\")).toBe(true);\n          expect(workflowQuestion.validate(\"test.yaml\")).toBe(true);\n          expect(workflowQuestion.validate(\"test.txt\")).toBe(\n            \"Workflow file must end with .yml or .yaml\",\n          );\n        }\n        return Promise.resolve({\n          workflowName: \"test.yml\",\n          schedule: \"0 */6 * * *\",\n          autoPush: true,\n          targetRepo: \"user/repo\",\n        });\n      });\n\n      await githubActions();\n    });\n  });\n\n  describe(\"error handling\", () => {\n    it(\"should exit when repomirror.yaml not found\", async () => {\n      await expect(githubActions()).rejects.toThrow(\n        \"Process exit called with code 1\",\n      );\n\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"repomirror.yaml not found\"),\n      );\n      expect(consoleMock.log).toHaveBeenCalledWith(\n        expect.stringContaining(\"npx repomirror init\"),\n      );\n    });\n\n    it(\"should handle file write errors\", async () => {\n      // Create config\n      const yaml = await import(\"yaml\");\n      await fs.writeFile(\n        join(tempDir, \"repomirror.yaml\"),\n        yaml.stringify({ sourceRepo: \"./\", targetRepo: \"../target\" }),\n      );\n\n      mockInquirerPrompt.mockResolvedValue({\n        workflowName: \"test.yml\",\n        schedule: \"0 */6 * * *\",\n        autoPush: true,\n        targetRepo: \"user/repo\",\n      });\n\n      // Mock writeFile to fail\n      const originalWriteFile = fs.writeFile;\n      vi.spyOn(fs, \"writeFile\").mockImplementation((path, content, options) => {\n        if (typeof path === \"string\" && path.endsWith(\".yml\")) {\n          throw new Error(\"Permission denied\");\n        }\n        return originalWriteFile(path as any, content, options);\n      });\n\n      await expect(githubActions()).rejects.toThrow(\n        \"Process exit called with code 1\",\n      );\n\n      expect(spinnerMock.fail).toHaveBeenCalledWith(\n        \"Failed to create workflow\",\n      );\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Permission denied\"),\n      );\n    });\n  });\n\n  describe(\"workflow content generation\", () => {\n    beforeEach(async () => {\n      const yaml = await import(\"yaml\");\n      await fs.writeFile(\n        join(tempDir, \"repomirror.yaml\"),\n        yaml.stringify({\n          sourceRepo: \"./\",\n          targetRepo: \"../target\",\n          transformationInstructions: \"test\",\n        }),\n      );\n    });\n\n    it(\"should include all required workflow steps\", async () => {\n      mockInquirerPrompt.mockResolvedValue({\n        workflowName: \"test.yml\",\n        schedule: \"0 */6 * * *\",\n        autoPush: true,\n        targetRepo: \"user/repo\",\n      });\n\n      await githubActions();\n\n      const workflowPath = join(tempDir, \".github\", \"workflows\", \"test.yml\");\n      const content = await fs.readFile(workflowPath, \"utf-8\");\n\n      // Check for essential workflow components\n      expect(content).toContain(\"on:\");\n      expect(content).toContain(\"schedule:\");\n      expect(content).toContain(\"workflow_dispatch:\");\n      expect(content).toContain(\"uses: actions/checkout@v3\");\n      expect(content).toContain(\"uses: actions/setup-node@v3\");\n      expect(content).toContain(\"npm install -g repomirror\");\n      expect(content).toContain(\"npx repomirror sync\");\n      expect(content).toContain(\"CLAUDE_API_KEY\");\n      expect(content).toContain(\"SKIP_CLAUDE_TEST: true\");\n    });\n\n    it(\"should handle custom schedule correctly\", async () => {\n      mockInquirerPrompt.mockResolvedValue({\n        workflowName: \"test.yml\",\n        schedule: \"*/15 * * * *\",\n        autoPush: true,\n        targetRepo: \"user/repo\",\n      });\n\n      await githubActions();\n\n      const workflowPath = join(tempDir, \".github\", \"workflows\", \"test.yml\");\n      const content = await fs.readFile(workflowPath, \"utf-8\");\n\n      expect(content).toContain(\"cron: '*/15 * * * *'\");\n    });\n\n    it(\"should disable auto-push when requested\", async () => {\n      mockInquirerPrompt.mockResolvedValue({\n        workflowName: \"test.yml\",\n        schedule: \"0 */6 * * *\",\n        autoPush: false,\n        targetRepo: \"user/repo\",\n      });\n\n      await githubActions();\n\n      const workflowPath = join(tempDir, \".github\", \"workflows\", \"test.yml\");\n      const content = await fs.readFile(workflowPath, \"utf-8\");\n\n      expect(content).toContain(\"if: false\");\n    });\n  });\n\n  describe(\"user prompts and validation\", () => {\n    beforeEach(async () => {\n      const yaml = await import(\"yaml\");\n      await fs.writeFile(\n        join(tempDir, \"repomirror.yaml\"),\n        yaml.stringify({\n          sourceRepo: \"./\",\n          targetRepo: \"../myrepo-transformed\",\n          transformationInstructions: \"test\",\n        }),\n      );\n    });\n\n    it(\"should validate target repo format\", async () => {\n      mockInquirerPrompt.mockImplementation((questions) => {\n        const targetQuestion = questions.find(\n          (q: any) => q.name === \"targetRepo\",\n        );\n        if (targetQuestion && targetQuestion.validate) {\n          expect(targetQuestion.validate(\"\")).toBe(\n            \"Please provide the GitHub repository in owner/repo format\",\n          );\n          expect(targetQuestion.validate(\"../myrepo-transformed\")).toBe(\n            \"Please provide the GitHub repository in owner/repo format\",\n          );\n          expect(targetQuestion.validate(\"user/repo\")).toBe(true);\n        }\n        return Promise.resolve({\n          workflowName: \"test.yml\",\n          schedule: \"0 */6 * * *\",\n          autoPush: true,\n          targetRepo: \"user/repo\",\n        });\n      });\n\n      await githubActions();\n    });\n\n    it(\"should provide sensible defaults\", async () => {\n      mockInquirerPrompt.mockImplementation((questions) => {\n        const nameQuestion = questions.find(\n          (q: any) => q.name === \"workflowName\",\n        );\n        const scheduleQuestion = questions.find(\n          (q: any) => q.name === \"schedule\",\n        );\n        const pushQuestion = questions.find(\n          (q: any) => q.name === \"autoPush\",\n        );\n\n        expect(nameQuestion?.default).toBe(\"repomirror-sync.yml\");\n        expect(scheduleQuestion?.default).toBe(\"0 */6 * * *\");\n        expect(pushQuestion?.default).toBe(true);\n\n        return Promise.resolve({\n          workflowName: \"test.yml\",\n          schedule: \"0 */6 * * *\",\n          autoPush: true,\n          targetRepo: \"user/repo\",\n        });\n      });\n\n      await githubActions();\n    });\n  });\n});"
  },
  {
    "path": "tests/commands/init.test.ts",
    "content": "import { describe, it, expect, vi, beforeEach, afterEach } from \"vitest\";\nimport { promises as fs } from \"fs\";\nimport { join } from \"path\";\nimport {\n  createTempDir,\n  cleanupTempDir,\n  mockConsole,\n  mockProcess,\n  createMockGitRepo,\n} from \"../helpers/test-utils\";\nimport {\n  mockInquirerResponses,\n  mockTransformationPrompt,\n} from \"../helpers/fixtures\";\n\n// Mock external dependencies at module level\nconst mockInquirerPrompt = vi.fn();\nconst mockOra = vi.fn();\nconst mockExeca = vi.fn();\nconst mockClaudeQuery = vi.fn();\n\nvi.mock(\"inquirer\", () => ({\n  default: {\n    prompt: mockInquirerPrompt,\n  },\n}));\n\nvi.mock(\"ora\", () => ({\n  default: mockOra,\n}));\n\nvi.mock(\"execa\", () => ({\n  execa: mockExeca,\n}));\n\nvi.mock(\"@anthropic-ai/claude-code\", () => ({\n  query: mockClaudeQuery,\n}));\n\n// Import the module after mocking\nconst { init } = await import(\"../../src/commands/init\");\n\ndescribe(\"init command\", () => {\n  let tempSourceDir: string;\n  let tempTargetDir: string;\n  let consoleMock: ReturnType<typeof mockConsole>;\n  let processMock: ReturnType<typeof mockProcess>;\n  let spinnerMock: any;\n\n  beforeEach(async () => {\n    // Create temporary directories\n    tempSourceDir = await createTempDir(\"repomirror-source-\");\n    tempTargetDir = await createTempDir(\"repomirror-target-\");\n\n    // Setup mocks\n    consoleMock = mockConsole();\n    processMock = mockProcess(true); // Throw on process.exit by default\n\n    // Mock process.cwd to return our temp source directory\n    processMock.cwd.mockReturnValue(tempSourceDir);\n\n    // Setup spinner mock\n    spinnerMock = {\n      start: vi.fn().mockReturnThis(),\n      succeed: vi.fn().mockReturnThis(),\n      fail: vi.fn().mockReturnThis(),\n      stop: vi.fn().mockReturnThis(),\n    };\n    mockOra.mockReturnValue(spinnerMock);\n    \n    // Clear all mocks\n    vi.clearAllMocks();\n  });\n\n  afterEach(async () => {\n    // Cleanup temp directories\n    await cleanupTempDir(tempSourceDir);\n    await cleanupTempDir(tempTargetDir);\n\n    // Restore all mocks\n    vi.restoreAllMocks();\n  });\n\n  describe(\"successful initialization flow\", () => {\n    it(\"should complete full initialization with all checks passing\", async () => {\n      // Setup target directory as git repo with remotes\n      await createMockGitRepo(tempTargetDir, true);\n\n      // Mock inquirer responses\n      mockInquirerPrompt.mockResolvedValue({\n        ...mockInquirerResponses,\n        targetRepo: tempTargetDir,\n      });\n\n      // Mock execa with successful responses\n      mockExeca\n        .mockResolvedValueOnce({ stdout: \".git\", exitCode: 0 }) // git rev-parse\n        .mockResolvedValueOnce({ \n          stdout: \"origin\\thttps://github.com/test/repo.git (fetch)\\norigin\\thttps://github.com/test/repo.git (push)\", \n          exitCode: 0 \n        }) // git remote -v\n        .mockResolvedValueOnce({ stdout: \"Hi there! How can I help you today?\", exitCode: 0 }); // claude test\n\n      // Mock Claude SDK query\n      mockClaudeQuery.mockImplementation(async function* () {\n        yield {\n          type: \"result\",\n          is_error: false,\n          result: mockTransformationPrompt,\n        };\n      });\n\n      // Run init\n      await init();\n\n      // Verify inquirer was called\n      expect(mockInquirerPrompt).toHaveBeenCalledWith([\n        expect.objectContaining({\n          type: \"input\",\n          name: \"sourceRepo\",\n          message: \"Source Repo you want to transform:\",\n          default: \"./\"\n        }),\n        expect.objectContaining({\n          type: \"input\", \n          name: \"targetRepo\",\n          message: \"Where do you want to transform code to:\",\n          default: expect.stringMatching(/-transformed$/)\n        }),\n        expect.objectContaining({\n          type: \"input\",\n          name: \"transformationInstructions\",\n          message: \"What changes do you want to make:\",\n          default: \"translate this python repo to typescript\"\n        })\n      ]);\n\n      // Verify preflight checks were called\n      expect(mockExeca).toHaveBeenCalledWith(\"git\", [\"rev-parse\", \"--git-dir\"], { cwd: tempTargetDir });\n      expect(mockExeca).toHaveBeenCalledWith(\"git\", [\"remote\", \"-v\"], { cwd: tempTargetDir });\n      expect(mockExeca).toHaveBeenCalledWith(\"claude\", [\"-p\", \"say hi\"], { timeout: 30000, input: \"\" });\n\n      // Verify Claude query was called\n      expect(mockClaudeQuery).toHaveBeenCalledWith({\n        prompt: expect.stringContaining(\"your task is to generate an optimized prompt\"),\n      });\n\n      // Verify .repomirror directory and files were created\n      const repoMirrorDir = join(tempSourceDir, \".repomirror\");\n      const stats = await fs.stat(repoMirrorDir);\n      expect(stats.isDirectory()).toBe(true);\n\n      // Check prompt.md\n      const promptContent = await fs.readFile(join(repoMirrorDir, \"prompt.md\"), \"utf8\");\n      expect(promptContent).toBe(mockTransformationPrompt);\n\n      // Check sync.sh\n      const syncContent = await fs.readFile(join(repoMirrorDir, \"sync.sh\"), \"utf8\");\n      expect(syncContent).toContain(\"claude -p --output-format=stream-json\");\n      expect(syncContent).toContain(tempTargetDir);\n\n      // Check ralph.sh\n      const ralphContent = await fs.readFile(join(repoMirrorDir, \"ralph.sh\"), \"utf8\");\n      expect(ralphContent).toContain(\"while :\");\n      expect(ralphContent).toContain(\"./.repomirror/sync.sh\");\n\n      // Check .gitignore\n      const gitignoreContent = await fs.readFile(join(repoMirrorDir, \".gitignore\"), \"utf8\");\n      expect(gitignoreContent).toBe(\"claude_output.jsonl\\n\");\n\n      // Check file permissions on scripts\n      const syncStats = await fs.stat(join(repoMirrorDir, \"sync.sh\"));\n      const ralphStats = await fs.stat(join(repoMirrorDir, \"ralph.sh\"));\n      expect(syncStats.mode & 0o111).toBeTruthy(); // Executable\n      expect(ralphStats.mode & 0o111).toBeTruthy(); // Executable\n\n      // Verify success messages\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"✅ repomirror initialized successfully!\"));\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"Next steps:\"));\n    });\n  });\n\n  describe(\"preflight check failures\", () => {\n    beforeEach(() => {\n      // Mock inquirer responses for all failure tests\n      mockInquirerPrompt.mockResolvedValue({\n        ...mockInquirerResponses,\n        targetRepo: tempTargetDir,\n      });\n    });\n\n    it(\"should fail when target directory does not exist\", async () => {\n      const nonExistentDir = \"/path/that/does/not/exist\";\n      \n      mockInquirerPrompt.mockResolvedValue({\n        ...mockInquirerResponses,\n        targetRepo: nonExistentDir,\n      });\n\n      await expect(init()).rejects.toThrow(\"Process exit called with code 1\");\n\n      expect(spinnerMock.fail).toHaveBeenCalledWith(expect.stringContaining(\"does not exist\"));\n    });\n\n    it(\"should fail when target directory is not a git repository\", async () => {\n      // Create directory but don't make it a git repo\n      await fs.mkdir(tempTargetDir, { recursive: true });\n\n      mockExeca.mockRejectedValueOnce(new Error(\"Not a git repository\"));\n\n      await expect(init()).rejects.toThrow(\"Process exit called with code 1\");\n\n      expect(mockExeca).toHaveBeenCalledWith(\"git\", [\"rev-parse\", \"--git-dir\"], { cwd: tempTargetDir });\n      expect(spinnerMock.fail).toHaveBeenCalledWith(expect.stringContaining(\"is not a git repository\"));\n    });\n\n    it(\"should fail when target directory has no git remotes\", async () => {\n      await createMockGitRepo(tempTargetDir, false);\n\n      mockExeca\n        .mockResolvedValueOnce({ stdout: \".git\", exitCode: 0 }) // git rev-parse success\n        .mockResolvedValueOnce({ stdout: \"\", exitCode: 0 }); // git remote -v empty\n\n      await expect(init()).rejects.toThrow(\"Process exit called with code 1\");\n\n      expect(mockExeca).toHaveBeenCalledWith(\"git\", [\"remote\", \"-v\"], { cwd: tempTargetDir });\n      expect(spinnerMock.fail).toHaveBeenCalledWith(expect.stringContaining(\"has no git remotes configured\"));\n    });\n\n    it(\"should fail when Claude Code is not configured\", async () => {\n      await createMockGitRepo(tempTargetDir, true);\n\n      mockExeca\n        .mockResolvedValueOnce({ stdout: \".git\", exitCode: 0 }) // git rev-parse success\n        .mockResolvedValueOnce({ \n          stdout: \"origin\\thttps://github.com/test/repo.git (fetch)\", \n          exitCode: 0 \n        }) // git remote -v success\n        .mockRejectedValueOnce(new Error(\"claude command not found\")); // claude test failure\n\n      await expect(init()).rejects.toThrow(\"Process exit called with code 1\");\n\n      expect(mockExeca).toHaveBeenCalledWith(\"claude\", [\"-p\", \"say hi\"], { timeout: 30000, input: \"\" });\n      expect(spinnerMock.fail).toHaveBeenCalledWith(expect.stringContaining(\"Claude Code is not properly configured\"));\n    });\n\n    it(\"should fail when Claude Code response is too short\", async () => {\n      await createMockGitRepo(tempTargetDir, true);\n\n      mockExeca\n        .mockResolvedValueOnce({ stdout: \".git\", exitCode: 0 }) // git rev-parse success\n        .mockResolvedValueOnce({ \n          stdout: \"origin\\thttps://github.com/test/repo.git (fetch)\", \n          exitCode: 0 \n        }) // git remote -v success\n        .mockResolvedValueOnce({ stdout: \"Hi\", exitCode: 0 }); // claude test with response too short (< 10 chars)\n\n      await expect(init()).rejects.toThrow(\"Process exit called with code 1\");\n\n      expect(spinnerMock.fail).toHaveBeenCalledWith(expect.stringContaining(\"response was empty or too short\"));\n    });\n  });\n\n  describe(\"Claude SDK integration\", () => {\n    beforeEach(async () => {\n      await createMockGitRepo(tempTargetDir, true);\n\n      mockInquirerPrompt.mockResolvedValue({\n        ...mockInquirerResponses,\n        targetRepo: tempTargetDir,\n      });\n\n      mockExeca\n        .mockResolvedValueOnce({ stdout: \".git\", exitCode: 0 }) // git rev-parse\n        .mockResolvedValueOnce({ \n          stdout: \"origin\\thttps://github.com/test/repo.git (fetch)\\norigin\\thttps://github.com/test/repo.git (push)\", \n          exitCode: 0 \n        }) // git remote -v\n        .mockResolvedValueOnce({ stdout: \"Hi there! How can I help you today?\", exitCode: 0 }); // claude test\n    });\n\n    it(\"should call Claude SDK with correct metaprompt\", async () => {\n      mockClaudeQuery.mockImplementation(async function* () {\n        yield {\n          type: \"result\",\n          is_error: false,\n          result: mockTransformationPrompt,\n        };\n      });\n\n      await init();\n\n      expect(mockClaudeQuery).toHaveBeenCalledWith({\n        prompt: expect.stringContaining(\"your task is to generate an optimized prompt for repo transformation\"),\n      });\n\n      const call = mockClaudeQuery.mock.calls[0][0];\n      expect(call.prompt).toContain(\"transform python to typescript\");\n      expect(call.prompt).toContain(\"<example 1>\");\n      expect(call.prompt).toContain(\"<example 2>\");\n    });\n\n    it(\"should handle Claude SDK errors gracefully\", async () => {\n      mockClaudeQuery.mockImplementation(async function* () {\n        throw new Error(\"Claude API error\");\n      });\n\n      await expect(init()).rejects.toThrow(\"Process exit called with code 1\");\n\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"✖ Failed to generate transformation prompt\"));\n      expect(consoleMock.error).toHaveBeenCalledWith(expect.stringContaining(\"Claude API error\"));\n    });\n\n    it(\"should handle empty Claude SDK response\", async () => {\n      mockClaudeQuery.mockImplementation(async function* () {\n        yield {\n          type: \"result\",\n          is_error: false,\n          result: \"\",\n        };\n      });\n\n      await expect(init()).rejects.toThrow(\"Process exit called with code 1\");\n\n      expect(consoleMock.error).toHaveBeenCalledWith(expect.stringContaining(\"Failed to generate transformation prompt\"));\n    });\n\n    it(\"should replace placeholders in generated prompt\", async () => {\n      const promptWithPlaceholders = `Your job is to port [SOURCE PATH] to [TARGET PATH] and maintain the repository.\nUse the [TARGET_PATH]/agent/ directory as a scratchpad.`;\n\n      mockClaudeQuery.mockImplementation(async function* () {\n        yield {\n          type: \"result\",\n          is_error: false,\n          result: promptWithPlaceholders,\n        };\n      });\n\n      await init();\n\n      // Check that placeholders were replaced in the prompt.md file\n      const repoMirrorDir = join(tempSourceDir, \".repomirror\");\n      const promptContent = await fs.readFile(join(repoMirrorDir, \"prompt.md\"), \"utf8\");\n      \n      expect(promptContent).not.toContain(\"[SOURCE PATH]\");\n      expect(promptContent).not.toContain(\"[TARGET PATH]\");\n      expect(promptContent).not.toContain(\"[TARGET_PATH]\");\n      expect(promptContent).toContain(mockInquirerResponses.sourceRepo);\n      expect(promptContent).toContain(tempTargetDir);\n    });\n  });\n\n  describe(\"file creation\", () => {\n    beforeEach(async () => {\n      await createMockGitRepo(tempTargetDir, true);\n\n      mockInquirerPrompt.mockResolvedValue({\n        ...mockInquirerResponses,\n        targetRepo: tempTargetDir,\n      });\n\n      mockExeca\n        .mockResolvedValueOnce({ stdout: \".git\", exitCode: 0 }) // git rev-parse\n        .mockResolvedValueOnce({ \n          stdout: \"origin\\thttps://github.com/test/repo.git (fetch)\\norigin\\thttps://github.com/test/repo.git (push)\", \n          exitCode: 0 \n        }) // git remote -v\n        .mockResolvedValueOnce({ stdout: \"Hi there! How can I help you today?\", exitCode: 0 }); // claude test\n\n      mockClaudeQuery.mockImplementation(async function* () {\n        yield {\n          type: \"result\",\n          is_error: false,\n          result: mockTransformationPrompt,\n        };\n      });\n    });\n\n    it(\"should create .repomirror directory\", async () => {\n      await init();\n\n      const repoMirrorDir = join(tempSourceDir, \".repomirror\");\n      const stats = await fs.stat(repoMirrorDir);\n      expect(stats.isDirectory()).toBe(true);\n    });\n\n    it(\"should create prompt.md with correct content\", async () => {\n      await init();\n\n      const promptPath = join(tempSourceDir, \".repomirror\", \"prompt.md\");\n      const content = await fs.readFile(promptPath, \"utf8\");\n      expect(content).toBe(mockTransformationPrompt);\n    });\n\n    it(\"should create executable sync.sh script\", async () => {\n      await init();\n\n      const syncPath = join(tempSourceDir, \".repomirror\", \"sync.sh\");\n      const content = await fs.readFile(syncPath, \"utf8\");\n      const stats = await fs.stat(syncPath);\n\n      expect(content).toContain(\"#!/bin/bash\");\n      expect(content).toContain(\"cat .repomirror/prompt.md\");\n      expect(content).toContain(`--add-dir ${tempTargetDir}`);\n      expect(content).toContain(\"npx repomirror visualize --debug\");\n      expect(stats.mode & 0o111).toBeTruthy(); // Check executable bit\n    });\n\n    it(\"should create executable ralph.sh script\", async () => {\n      await init();\n\n      const ralphPath = join(tempSourceDir, \".repomirror\", \"ralph.sh\");\n      const content = await fs.readFile(ralphPath, \"utf8\");\n      const stats = await fs.stat(ralphPath);\n\n      expect(content).toContain(\"#!/bin/bash\");\n      expect(content).toContain(\"while :\");\n      expect(content).toContain(\"./.repomirror/sync.sh\");\n      expect(content).toContain(\"sleep 10\");\n      expect(stats.mode & 0o111).toBeTruthy(); // Check executable bit\n    });\n\n    it(\"should create .gitignore file\", async () => {\n      await init();\n\n      const gitignorePath = join(tempSourceDir, \".repomirror\", \".gitignore\");\n      const content = await fs.readFile(gitignorePath, \"utf8\");\n\n      expect(content).toBe(\"claude_output.jsonl\\n\");\n    });\n\n    it(\"should handle file creation errors gracefully\", async () => {\n      // Mock fs.mkdir to fail on the .repomirror directory creation\n      // This happens in createRepoMirrorFiles which is inside the try-catch\n      const mkdirSpy = vi.spyOn(fs, \"mkdir\");\n      let callCount = 0;\n      mkdirSpy.mockImplementation(async (path, options) => {\n        callCount++;\n        // Let the first call (for config directory) succeed\n        if (callCount === 1) {\n          return Promise.resolve(undefined);\n        }\n        // Fail on the second call (for .repomirror directory)\n        throw new Error(\"Permission denied\");\n      });\n\n      await expect(init()).rejects.toThrow(\"Process exit called with code 1\");\n      \n      // Verify error message was logged\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"✖ Failed to generate transformation prompt\"));\n      expect(consoleMock.error).toHaveBeenCalledWith(expect.stringContaining(\"Permission denied\"));\n    });\n  });\n\n  describe(\"user interaction\", () => {\n    it(\"should use default values for prompts\", async () => {\n      await createMockGitRepo(tempTargetDir, true);\n\n      const basename = require(\"path\").basename;\n      \n      // Setup mocks to use defaults\n      mockInquirerPrompt.mockResolvedValue({\n        sourceRepo: \"./\",\n        targetRepo: `../${basename(tempSourceDir)}-transformed`,\n        transformationInstructions: \"translate this python repo to typescript\",\n      });\n\n      mockExeca\n        .mockResolvedValueOnce({ stdout: \".git\", exitCode: 0 }) // git rev-parse\n        .mockResolvedValueOnce({ \n          stdout: \"origin\\thttps://github.com/test/repo.git (fetch)\\norigin\\thttps://github.com/test/repo.git (push)\", \n          exitCode: 0 \n        }) // git remote -v\n        .mockResolvedValueOnce({ stdout: \"Hi there! How can I help you today?\", exitCode: 0 }); // claude test\n\n      mockClaudeQuery.mockImplementation(async function* () {\n        yield {\n          type: \"result\",\n          is_error: false,\n          result: mockTransformationPrompt,\n        };\n      });\n\n      // Mock directory access for the default target\n      const defaultTarget = `../${basename(tempSourceDir)}-transformed`;\n      vi.spyOn(fs, \"access\").mockImplementation(async (path) => {\n        if (path === defaultTarget) {\n          return Promise.resolve();\n        }\n        // Allow access to template files\n        if (typeof path === 'string' && path.includes('templates')) {\n          return Promise.resolve();\n        }\n        return Promise.reject(new Error(\"Not found\"));\n      });\n\n      // Mock readFile for templates\n      vi.spyOn(fs, \"readFile\").mockImplementation(async (path, encoding) => {\n        if (typeof path === 'string') {\n          if (path.includes('sync.sh.template')) {\n            return `#!/bin/bash\ncat .repomirror/prompt.md | \\\\\n        claude -p --output-format=stream-json --verbose --dangerously-skip-permissions --add-dir \\${targetRepo} | \\\\\n        tee -a .repomirror/claude_output.jsonl | \\\\\n        npx repomirror visualize --debug;`;\n          }\n          if (path.includes('ralph.sh.template')) {\n            return `#!/bin/bash\nwhile :; do\n  ./.repomirror/sync.sh\n  echo -e \"===SLEEP===\\\\n===SLEEP===\\\\n\"; echo 'looping';\n  sleep 10;\ndone`;\n          }\n          if (path.includes('gitignore.template')) {\n            return 'claude_output.jsonl';\n          }\n        }\n        return Promise.reject(new Error(\"File not found\"));\n      });\n\n      await init();\n\n      expect(mockInquirerPrompt).toHaveBeenCalledWith(expect.arrayContaining([\n        expect.objectContaining({\n          default: \"./\"\n        }),\n        expect.objectContaining({\n          default: expect.stringMatching(/-transformed$/)\n        }),\n        expect.objectContaining({\n          default: \"translate this python repo to typescript\"\n        })\n      ]));\n    });\n\n    it(\"should handle custom user responses\", async () => {\n      await createMockGitRepo(tempTargetDir, true);\n\n      // Create a custom source directory for testing\n      const customSourceDir = await createTempDir(\"custom-source-\");\n      \n      const customResponses = {\n        sourceRepo: customSourceDir,\n        targetRepo: tempTargetDir,\n        transformationInstructions: \"convert java to golang\",\n      };\n\n      mockInquirerPrompt.mockResolvedValue(customResponses);\n\n      mockExeca\n        .mockResolvedValueOnce({ stdout: \".git\", exitCode: 0 }) // git rev-parse\n        .mockResolvedValueOnce({ \n          stdout: \"origin\\thttps://github.com/test/repo.git (fetch)\\norigin\\thttps://github.com/test/repo.git (push)\", \n          exitCode: 0 \n        }) // git remote -v\n        .mockResolvedValueOnce({ stdout: \"Hi there! How can I help you today?\", exitCode: 0 }); // claude test\n\n      mockClaudeQuery.mockImplementation(async function* () {\n        yield {\n          type: \"result\",\n          is_error: false,\n          result: \"Custom prompt with java to golang conversion\",\n        };\n      });\n\n      await init();\n\n      // Verify custom values were used in the Claude query\n      expect(mockClaudeQuery).toHaveBeenCalledWith({\n        prompt: expect.stringContaining(\"convert java to golang\"),\n      });\n\n      // Check generated files contain custom values\n      const syncPath = join(customSourceDir, \".repomirror\", \"sync.sh\");\n      const syncContent = await fs.readFile(syncPath, \"utf8\");\n      expect(syncContent).toContain(tempTargetDir);\n      \n      // Clean up custom source dir\n      await cleanupTempDir(customSourceDir);\n    });\n  });\n\n  describe(\"error handling and exit codes\", () => {\n    it(\"should exit with code 1 on preflight check failure\", async () => {\n      mockInquirerPrompt.mockResolvedValue({\n        ...mockInquirerResponses,\n        targetRepo: \"/nonexistent\",\n      });\n\n      await expect(init()).rejects.toThrow(\"Process exit called with code 1\");\n    });\n\n    it(\"should exit with code 1 on Claude SDK failure\", async () => {\n      await createMockGitRepo(tempTargetDir, true);\n\n      mockInquirerPrompt.mockResolvedValue({\n        ...mockInquirerResponses,\n        targetRepo: tempTargetDir,\n      });\n\n      mockExeca\n        .mockResolvedValueOnce({ stdout: \".git\", exitCode: 0 }) // git rev-parse\n        .mockResolvedValueOnce({ \n          stdout: \"origin\\thttps://github.com/test/repo.git (fetch)\\norigin\\thttps://github.com/test/repo.git (push)\", \n          exitCode: 0 \n        }) // git remote -v\n        .mockResolvedValueOnce({ stdout: \"Hi there! How can I help you today?\", exitCode: 0 }); // claude test\n\n      mockClaudeQuery.mockImplementation(async function* () {\n        throw new Error(\"Claude API error\");\n      });\n\n      await expect(init()).rejects.toThrow(\"Process exit called with code 1\");\n\n      expect(consoleMock.error).toHaveBeenCalledWith(expect.stringContaining(\"Claude API error\"));\n    });\n\n    it(\"should handle unexpected errors gracefully\", async () => {\n      // Mock inquirer to throw an unexpected error\n      mockInquirerPrompt.mockRejectedValue(new Error(\"Unexpected error\"));\n\n      await expect(init()).rejects.toThrow(\"Unexpected error\");\n    });\n  });\n\n  describe(\"spinner and console output\", () => {\n    beforeEach(async () => {\n      await createMockGitRepo(tempTargetDir, true);\n\n      mockInquirerPrompt.mockResolvedValue({\n        ...mockInquirerResponses,\n        targetRepo: tempTargetDir,\n      });\n\n      mockExeca\n        .mockResolvedValueOnce({ stdout: \".git\", exitCode: 0 }) // git rev-parse\n        .mockResolvedValueOnce({ \n          stdout: \"origin\\thttps://github.com/test/repo.git (fetch)\\norigin\\thttps://github.com/test/repo.git (push)\", \n          exitCode: 0 \n        }) // git remote -v\n        .mockResolvedValueOnce({ stdout: \"Hi there! How can I help you today?\", exitCode: 0 }); // claude test\n\n      mockClaudeQuery.mockImplementation(async function* () {\n        yield {\n          type: \"result\",\n          is_error: false,\n          result: mockTransformationPrompt,\n        };\n      });\n    });\n\n    it(\"should show appropriate spinner messages\", async () => {\n      await init();\n\n      // Check that individual spinners were created for each preflight check\n      expect(mockOra).toHaveBeenCalledWith(expect.stringContaining(\"Accessing\"));\n      expect(mockOra).toHaveBeenCalledWith(expect.stringContaining(\"Verifying git repository\"));\n      expect(mockOra).toHaveBeenCalledWith(expect.stringContaining(\"Listing git remotes\"));\n      expect(mockOra).toHaveBeenCalledWith(\"   Running Claude Code test command\");\n\n      // Check that spinners were started and succeeded (4 preflight checks only)\n      expect(spinnerMock.start).toHaveBeenCalledTimes(4);\n      \n      // Check that console.log was called for generating prompt message\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"Generating transformation prompt...\"));\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"✔ Generated transformation prompt\"));\n    });\n\n    it(\"should show correct console output\", async () => {\n      await init();\n\n      expect(consoleMock.log).toHaveBeenCalledWith(\n        expect.stringContaining(\"I'll help you maintain a transformed copy of this repo:\")\n      );\n      expect(consoleMock.log).toHaveBeenCalledWith(\n        expect.stringContaining(\"✅ repomirror initialized successfully!\")\n      );\n      expect(consoleMock.log).toHaveBeenCalledWith(\n        expect.stringContaining(\"Next steps:\")\n      );\n      expect(consoleMock.log).toHaveBeenCalledWith(\n        expect.stringContaining(\"npx repomirror sync\")\n      );\n      expect(consoleMock.log).toHaveBeenCalledWith(\n        expect.stringContaining(\"npx repomirror sync-forever\")\n      );\n    });\n  });\n\n  describe(\"configuration file handling\", () => {\n    beforeEach(async () => {\n      await createMockGitRepo(tempTargetDir, true);\n\n      // Reset mocks between tests in this describe block\n      vi.clearAllMocks();\n\n      mockClaudeQuery.mockImplementation(async function* () {\n        yield {\n          type: \"result\",\n          is_error: false,\n          result: mockTransformationPrompt,\n        };\n      });\n    });\n\n    it(\"should create repomirror.yaml config file\", async () => {\n      mockExeca\n        .mockResolvedValueOnce({ stdout: \".git\", exitCode: 0 }) // git rev-parse\n        .mockResolvedValueOnce({ \n          stdout: \"origin\\thttps://github.com/test/repo.git (fetch)\\norigin\\thttps://github.com/test/repo.git (push)\", \n          exitCode: 0 \n        }) // git remote -v\n        .mockResolvedValueOnce({ stdout: \"Hi there! How can I help you today?\", exitCode: 0 }); // claude test\n\n      mockInquirerPrompt.mockResolvedValue({\n        ...mockInquirerResponses,\n        targetRepo: tempTargetDir,\n      });\n\n      await init();\n\n      const configPath = join(tempSourceDir, \"repomirror.yaml\");\n      const configExists = await fs.stat(configPath).then(() => true).catch(() => false);\n      expect(configExists).toBe(true);\n\n      const configContent = await fs.readFile(configPath, \"utf-8\");\n      const yaml = await import(\"yaml\");\n      const config = yaml.parse(configContent);\n      \n      expect(config).toEqual({\n        sourceRepo: mockInquirerResponses.sourceRepo,\n        targetRepo: tempTargetDir,\n        transformationInstructions: mockInquirerResponses.transformationInstructions,\n      });\n\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"✅ Saved configuration to repomirror.yaml\"));\n    });\n\n    it(\"should load existing repomirror.yaml as defaults\", async () => {\n      mockExeca\n        .mockResolvedValueOnce({ stdout: \".git\", exitCode: 0 }) // git rev-parse\n        .mockResolvedValueOnce({ \n          stdout: \"origin\\thttps://github.com/test/repo.git (fetch)\\norigin\\thttps://github.com/test/repo.git (push)\", \n          exitCode: 0 \n        }) // git remote -v\n        .mockResolvedValueOnce({ stdout: \"Hi there! How can I help you today?\", exitCode: 0 }); // claude test\n\n      // Create existing config file with the tempTargetDir that's already set up\n      const existingConfig = {\n        sourceRepo: \"./existing-source\", \n        targetRepo: tempTargetDir, // Use the temp target dir from the test setup\n        transformationInstructions: \"existing transformation instructions\",\n      };\n      \n      const yaml = await import(\"yaml\");\n      const configContent = yaml.stringify(existingConfig);\n      await fs.writeFile(join(tempSourceDir, \"repomirror.yaml\"), configContent, \"utf-8\");\n\n      // Mock inquirer to use defaults - return the existing config values\n      // Since the existing config is loaded, inquirer should get these as defaults\n      mockInquirerPrompt.mockResolvedValue({\n        sourceRepo: \"./existing-source\",\n        targetRepo: tempTargetDir,\n        transformationInstructions: \"existing transformation instructions\",\n      });\n\n      await init();\n\n      // Verify the existing config message was shown\n      expect(consoleMock.log).toHaveBeenCalledWith(\n        expect.stringContaining(\"Found existing repomirror.yaml, using as defaults\")\n      );\n\n      // Verify the existing config was used\n      expect(mockClaudeQuery).toHaveBeenCalledWith({\n        prompt: expect.stringContaining(\"existing transformation instructions\"),\n      });\n\n      // Verify the config file was updated with the same values\n      const finalConfigContent = await fs.readFile(join(tempSourceDir, \"repomirror.yaml\"), \"utf-8\");\n      const finalConfig = yaml.parse(finalConfigContent);\n      expect(finalConfig).toEqual(existingConfig);\n    });\n\n    it(\"should handle corrupted repomirror.yaml gracefully\", async () => {\n      mockExeca\n        .mockResolvedValueOnce({ stdout: \".git\", exitCode: 0 }) // git rev-parse\n        .mockResolvedValueOnce({ \n          stdout: \"origin\\thttps://github.com/test/repo.git (fetch)\\norigin\\thttps://github.com/test/repo.git (push)\", \n          exitCode: 0 \n        }) // git remote -v\n        .mockResolvedValueOnce({ stdout: \"Hi there! How can I help you today?\", exitCode: 0 }); // claude test\n\n      // Create corrupted YAML file\n      await fs.writeFile(join(tempSourceDir, \"repomirror.yaml\"), \"invalid: yaml: content: [\", \"utf-8\");\n\n      mockInquirerPrompt.mockResolvedValue({\n        ...mockInquirerResponses,\n        targetRepo: tempTargetDir,\n      });\n\n      await init();\n\n      // Should not show the existing config message\n      expect(consoleMock.log).not.toHaveBeenCalledWith(\n        expect.stringContaining(\"Found existing repomirror.yaml, using as defaults\")\n      );\n\n      // Should create new valid config\n      const configPath = join(tempSourceDir, \"repomirror.yaml\");\n      const configContent = await fs.readFile(configPath, \"utf-8\");\n      const yaml = await import(\"yaml\");\n      const config = yaml.parse(configContent);\n      expect(config.sourceRepo).toBeDefined();\n    });\n\n    it(\"should save config with normalized paths\", async () => {\n      mockExeca\n        .mockResolvedValueOnce({ stdout: \".git\", exitCode: 0 }) // git rev-parse\n        .mockResolvedValueOnce({ \n          stdout: \"origin\\thttps://github.com/test/repo.git (fetch)\\norigin\\thttps://github.com/test/repo.git (push)\", \n          exitCode: 0 \n        }) // git remote -v\n        .mockResolvedValueOnce({ stdout: \"Hi there! How can I help you today?\", exitCode: 0 }); // claude test\n\n      const inputPaths = {\n        sourceRepo: \"./source/../source/./\",\n        targetRepo: \"../target/./nested/../\",\n        transformationInstructions: \"test transformation\",\n      };\n\n      mockInquirerPrompt.mockResolvedValue({\n        ...inputPaths,\n        targetRepo: tempTargetDir, // Use valid temp dir for preflight checks\n      });\n\n      await init();\n\n      // Config should be saved in the source subdirectory when sourceRepo is relative\n      const configPath = join(tempSourceDir, \"source/../source/./\", \"repomirror.yaml\");\n      const configContent = await fs.readFile(configPath, \"utf-8\");\n      const yaml = await import(\"yaml\");\n      const config = yaml.parse(configContent);\n      \n      // Paths should be saved as entered (init doesn't normalize, that's the user's choice)\n      expect(config.sourceRepo).toBe(inputPaths.sourceRepo);\n      expect(config.transformationInstructions).toBe(inputPaths.transformationInstructions);\n    });\n  });\n\n  describe(\"CLI flag overrides\", () => {\n    beforeEach(async () => {\n      await createMockGitRepo(tempTargetDir, true);\n\n      mockExeca\n        .mockResolvedValueOnce({ stdout: \".git\", exitCode: 0 }) // git rev-parse\n        .mockResolvedValueOnce({ \n          stdout: \"origin\\thttps://github.com/test/repo.git (fetch)\\norigin\\thttps://github.com/test/repo.git (push)\", \n          exitCode: 0 \n        }) // git remote -v\n        .mockResolvedValueOnce({ stdout: \"Hi there! How can I help you today?\", exitCode: 0 }); // claude test\n\n      mockClaudeQuery.mockImplementation(async function* () {\n        yield {\n          type: \"result\",\n          is_error: false,\n          result: mockTransformationPrompt,\n        };\n      });\n    });\n\n    it(\"should skip prompts when all CLI options provided\", async () => {\n      const cliOptions = {\n        sourceRepo: \"./cli-source\",\n        targetRepo: tempTargetDir,\n        transformationInstructions: \"CLI transformation instructions\",\n      };\n\n      // Mock inquirer to return empty object since all prompts have when: false\n      mockInquirerPrompt.mockResolvedValue({});\n\n      await init(cliOptions);\n\n      // Inquirer should be called but with all when: false conditions\n      expect(mockInquirerPrompt).toHaveBeenCalled();\n      const promptCall = mockInquirerPrompt.mock.calls[0][0];\n      expect(promptCall.every((p: any) => p.when === false)).toBe(true);\n\n      // Verify CLI options were used in Claude query\n      expect(mockClaudeQuery).toHaveBeenCalledWith({\n        prompt: expect.stringContaining(\"CLI transformation instructions\"),\n      });\n\n      // Verify config was saved with CLI values\n      // Config should be saved in the cli-source subdirectory\n      const configPath = join(tempSourceDir, \"cli-source\", \"repomirror.yaml\");\n      const configContent = await fs.readFile(configPath, \"utf-8\");\n      const yaml = await import(\"yaml\");\n      const config = yaml.parse(configContent);\n      expect(config).toEqual(cliOptions);\n    });\n\n    it(\"should partially override with CLI flags\", async () => {\n      const cliOptions = {\n        sourceRepo: \"./cli-source\",\n        // targetRepo and transformationInstructions will come from prompt\n      };\n\n      mockInquirerPrompt.mockResolvedValue({\n        targetRepo: tempTargetDir,\n        transformationInstructions: \"prompted instructions\",\n      });\n\n      await init(cliOptions);\n\n      // Should only prompt for missing options\n      const promptCall = mockInquirerPrompt.mock.calls[0][0];\n      const sourceRepoPrompt = promptCall.find((p: any) => p.name === \"sourceRepo\");\n      const targetRepoPrompt = promptCall.find((p: any) => p.name === \"targetRepo\");\n      const instructionsPrompt = promptCall.find((p: any) => p.name === \"transformationInstructions\");\n      \n      expect(sourceRepoPrompt.when).toBe(false); // Should skip source repo prompt\n      expect(targetRepoPrompt.when).toBe(true);  // Should show target repo prompt\n      expect(instructionsPrompt.when).toBe(true); // Should show instructions prompt\n\n      // Verify final config contains CLI override\n      // Config should be saved in the cli-source subdirectory\n      const configPath = join(tempSourceDir, \"cli-source\", \"repomirror.yaml\");\n      const configContent = await fs.readFile(configPath, \"utf-8\");\n      const yaml = await import(\"yaml\");\n      const config = yaml.parse(configContent);\n      \n      expect(config.sourceRepo).toBe(\"./cli-source\");\n      expect(config.targetRepo).toBe(tempTargetDir);\n      expect(config.transformationInstructions).toBe(\"prompted instructions\");\n    });\n\n    it(\"should prioritize CLI flags over existing config\", async () => {\n      // Create existing config\n      const existingConfig = {\n        sourceRepo: \"./existing-source\",\n        targetRepo: tempTargetDir, // Use the temp dir for preflight checks\n        transformationInstructions: \"existing instructions\",\n      };\n      \n      const yaml = await import(\"yaml\");\n      const configContent = yaml.stringify(existingConfig);\n      await fs.writeFile(join(tempSourceDir, \"repomirror.yaml\"), configContent, \"utf-8\");\n\n      // CLI overrides part of the config\n      const cliOptions = {\n        targetRepo: tempTargetDir,\n        transformationInstructions: \"CLI override instructions\",\n      };\n\n      // Mock inquirer to return the sourceRepo value since it's the only one not overridden by CLI\n      mockInquirerPrompt.mockResolvedValue({\n        sourceRepo: \"./existing-source\", // Only sourceRepo should be prompted since it's not in CLI options\n      });\n\n      await init(cliOptions);\n\n      // Verify CLI overrides were used\n      expect(mockClaudeQuery).toHaveBeenCalledWith({\n        prompt: expect.stringContaining(\"CLI override instructions\"),\n      });\n\n      // Verify final config has CLI overrides\n      // Config gets saved to the final sourceRepo location\n      const finalConfigContent = await fs.readFile(join(tempSourceDir, \"existing-source\", \"repomirror.yaml\"), \"utf-8\");\n      const finalConfig = yaml.parse(finalConfigContent);\n      \n      expect(finalConfig.sourceRepo).toBe(\"./existing-source\"); // From existing config (no CLI override)\n      expect(finalConfig.targetRepo).toBe(tempTargetDir); // CLI override\n      expect(finalConfig.transformationInstructions).toBe(\"CLI override instructions\"); // CLI override\n    });\n\n    it(\"should handle CLI flags with empty values\", async () => {\n      const cliOptions = {\n        sourceRepo: \"\",\n        targetRepo: tempTargetDir,\n        transformationInstructions: undefined,\n      };\n\n      mockInquirerPrompt.mockResolvedValue({\n        sourceRepo: \"./prompted-source\",\n        transformationInstructions: \"prompted instructions\",\n      });\n\n      await init(cliOptions);\n\n      // Empty CLI values should not prevent prompting\n      const promptCall = mockInquirerPrompt.mock.calls[0][0];\n      const sourceRepoPrompt = promptCall.find((p: any) => p.name === \"sourceRepo\");\n      const instructionsPrompt = promptCall.find((p: any) => p.name === \"transformationInstructions\");\n      \n      expect(sourceRepoPrompt.when).toBe(true); // Empty string should allow prompting\n      expect(instructionsPrompt.when).toBe(true); // undefined should allow prompting\n    });\n  });\n\n  describe(\"Claude SDK async iterator edge cases\", () => {\n    beforeEach(async () => {\n      await createMockGitRepo(tempTargetDir, true);\n\n      mockInquirerPrompt.mockResolvedValue({\n        ...mockInquirerResponses,\n        targetRepo: tempTargetDir,\n      });\n\n      mockExeca\n        .mockResolvedValueOnce({ stdout: \".git\", exitCode: 0 }) // git rev-parse\n        .mockResolvedValueOnce({ \n          stdout: \"origin\\thttps://github.com/test/repo.git (fetch)\\norigin\\thttps://github.com/test/repo.git (push)\", \n          exitCode: 0 \n        }) // git remote -v\n        .mockResolvedValueOnce({ stdout: \"Hi there! How can I help you today?\", exitCode: 0 }); // claude test\n    });\n\n    it(\"should handle Claude SDK yielding multiple messages before result\", async () => {\n      mockClaudeQuery.mockImplementation(async function* () {\n        yield { type: \"other\", data: \"some data\" };\n        yield { type: \"progress\", percentage: 50 };\n        yield {\n          type: \"result\",\n          is_error: false,\n          result: mockTransformationPrompt,\n        };\n        yield { type: \"after_result\", data: \"ignored\" }; // Should be ignored after break\n      });\n\n      await init();\n\n      // Should successfully create files with the result\n      const promptContent = await fs.readFile(join(tempSourceDir, \".repomirror\", \"prompt.md\"), \"utf8\");\n      expect(promptContent).toBe(mockTransformationPrompt);\n    });\n\n    it(\"should handle Claude SDK yielding error result\", async () => {\n      mockClaudeQuery.mockImplementation(async function* () {\n        yield {\n          type: \"result\",\n          is_error: true,\n          result: \"Claude API returned an error\",\n        };\n      });\n\n      await expect(init()).rejects.toThrow(\"Process exit called with code 1\");\n\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"✖ Failed to generate transformation prompt\"));\n      expect(consoleMock.error).toHaveBeenCalledWith(expect.stringContaining(\"Claude API returned an error\"));\n    });\n\n    it(\"should handle Claude SDK yielding result with missing result field\", async () => {\n      mockClaudeQuery.mockImplementation(async function* () {\n        yield {\n          type: \"result\",\n          is_error: false,\n          // Missing result field - result property is undefined\n        } as any;\n      });\n\n      await expect(init()).rejects.toThrow(\"Process exit called with code 1\");\n\n      expect(consoleMock.error).toHaveBeenCalledWith(expect.stringContaining(\"Failed to generate transformation prompt\"));\n    });\n\n    it(\"should handle Claude SDK iterator that never yields a result\", async () => {\n      mockClaudeQuery.mockImplementation(async function* () {\n        yield { type: \"other\", data: \"some data\" };\n        yield { type: \"progress\", percentage: 100 };\n        // Never yields a result type\n      });\n\n      await expect(init()).rejects.toThrow(\"Process exit called with code 1\");\n\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Failed to generate transformation prompt - no result received\")\n      );\n    });\n\n    it(\"should handle Claude SDK network timeout gracefully\", async () => {\n      mockClaudeQuery.mockImplementation(async function* () {\n        await new Promise(resolve => setTimeout(resolve, 10)); // Small delay\n        throw new Error(\"Network timeout\");\n      });\n\n      await expect(init()).rejects.toThrow(\"Process exit called with code 1\");\n\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"✖ Failed to generate transformation prompt\"));\n      expect(consoleMock.error).toHaveBeenCalledWith(expect.stringContaining(\"Network timeout\"));\n    });\n\n    it(\"should handle Claude SDK iterator throwing on first yield\", async () => {\n      let hasYielded = false;\n      mockClaudeQuery.mockImplementation(async function* () {\n        if (!hasYielded) {\n          hasYielded = true;\n          throw new Error(\"Iterator initialization failed\");\n        }\n      });\n\n      await expect(init()).rejects.toThrow(\"Process exit called with code 1\");\n\n      expect(consoleMock.error).toHaveBeenCalledWith(expect.stringContaining(\"Iterator initialization failed\"));\n    });\n\n    it(\"should handle partial Claude SDK response objects\", async () => {\n      mockClaudeQuery.mockImplementation(async function* () {\n        yield { type: \"progress\" }; // Missing other fields\n        yield { type: \"other\", data: \"some data\" }; // Different type\n        yield { type: \"status\", status: \"processing\" }; // Different type\n        yield {\n          type: \"result\",\n          is_error: false,\n          result: mockTransformationPrompt,\n        };\n      });\n\n      await init();\n\n      // Should handle partial responses gracefully and use the valid result\n      const promptContent = await fs.readFile(join(tempSourceDir, \".repomirror\", \"prompt.md\"), \"utf8\");\n      expect(promptContent).toBe(mockTransformationPrompt);\n    });\n  });\n\n  describe(\"path resolution and normalization edge cases\", () => {\n    beforeEach(async () => {\n      mockExeca\n        .mockResolvedValueOnce({ stdout: \".git\", exitCode: 0 }) // git rev-parse\n        .mockResolvedValueOnce({ \n          stdout: \"origin\\thttps://github.com/test/repo.git (fetch)\\norigin\\thttps://github.com/test/repo.git (push)\", \n          exitCode: 0 \n        }) // git remote -v\n        .mockResolvedValueOnce({ stdout: \"Hi there! How can I help you today?\", exitCode: 0 }); // claude test\n\n      mockClaudeQuery.mockImplementation(async function* () {\n        yield {\n          type: \"result\",\n          is_error: false,\n          result: mockTransformationPrompt,\n        };\n      });\n    });\n\n    it(\"should handle absolute paths correctly\", async () => {\n      await createMockGitRepo(tempTargetDir, true);\n\n      mockInquirerPrompt.mockResolvedValue({\n        sourceRepo: tempSourceDir, // absolute path\n        targetRepo: tempTargetDir, // absolute path\n        transformationInstructions: \"test transformation\",\n      });\n\n      await init();\n\n      // Check sync.sh contains the absolute path\n      const syncContent = await fs.readFile(join(tempSourceDir, \".repomirror\", \"sync.sh\"), \"utf8\");\n      expect(syncContent).toContain(`--add-dir ${tempTargetDir}`);\n    });\n\n    it(\"should handle relative paths with dots and slashes\", async () => {\n      await createMockGitRepo(tempTargetDir, true);\n\n      const relativePaths = {\n        sourceRepo: \"./src/../src/./\",\n        targetRepo: tempTargetDir, // Use real path for preflight checks\n        transformationInstructions: \"test transformation\",\n      };\n\n      mockInquirerPrompt.mockResolvedValue(relativePaths);\n\n      await init();\n\n      // Check that paths are preserved as entered in the config\n      // Config should be saved in the src/../src/./ subdirectory\n      const configPath = join(tempSourceDir, \"src/../src/./\", \"repomirror.yaml\");\n      const configContent = await fs.readFile(configPath, \"utf-8\");\n      const yaml = await import(\"yaml\");\n      const config = yaml.parse(configContent);\n      expect(config.sourceRepo).toBe(\"./src/../src/./\");\n    });\n\n    it(\"should handle paths with spaces\", async () => {\n      // Create temp dir with spaces\n      const tempDirWithSpaces = await createTempDir(\"repo mirror test \");\n      await createMockGitRepo(tempDirWithSpaces, true);\n\n      try {\n        mockInquirerPrompt.mockResolvedValue({\n          sourceRepo: \"./source with spaces\",\n          targetRepo: tempDirWithSpaces,\n          transformationInstructions: \"test transformation\",\n        });\n\n        await init();\n\n        // Check sync.sh properly handles the path with spaces\n        // Files should be in the \"source with spaces\" subdirectory\n        const syncContent = await fs.readFile(join(tempSourceDir, \"source with spaces\", \".repomirror\", \"sync.sh\"), \"utf8\");\n        expect(syncContent).toContain(`--add-dir ${tempDirWithSpaces}`);\n      } finally {\n        await cleanupTempDir(tempDirWithSpaces);\n      }\n    });\n\n    it(\"should handle empty and invalid path values\", async () => {\n      mockInquirerPrompt.mockResolvedValue({\n        sourceRepo: \"\",\n        targetRepo: \"/invalid/nonexistent/path\",\n        transformationInstructions: \"test transformation\",\n      });\n\n      await expect(init()).rejects.toThrow(\"Process exit called with code 1\");\n\n      expect(spinnerMock.fail).toHaveBeenCalledWith(\n        expect.stringContaining(\"does not exist\")\n      );\n    });\n\n    it(\"should handle very long paths\", async () => {\n      const longDirName = \"a\".repeat(100);\n      const tempLongDir = await createTempDir(`repomirror-long-${longDirName}-`);\n      await createMockGitRepo(tempLongDir, true);\n\n      try {\n        mockInquirerPrompt.mockResolvedValue({\n          sourceRepo: \"./\" + \"nested/\".repeat(20),\n          targetRepo: tempLongDir,\n          transformationInstructions: \"test transformation\",\n        });\n\n        await init();\n\n        // Should handle long paths without issue\n        // Config should be saved in the nested subdirectory\n        const configPath = join(tempSourceDir, \".\" + \"/nested\".repeat(20), \"repomirror.yaml\");\n        const configContent = await fs.readFile(configPath, \"utf-8\");\n        const yaml = await import(\"yaml\");\n        const config = yaml.parse(configContent);\n        expect(config.targetRepo).toBe(tempLongDir);\n      } finally {\n        await cleanupTempDir(tempLongDir);\n      }\n    });\n  });\n\n  describe(\"script generation edge cases\", () => {\n    beforeEach(async () => {\n      await createMockGitRepo(tempTargetDir, true);\n\n      mockInquirerPrompt.mockResolvedValue({\n        ...mockInquirerResponses,\n        targetRepo: tempTargetDir,\n      });\n\n      mockExeca\n        .mockResolvedValueOnce({ stdout: \".git\", exitCode: 0 }) // git rev-parse\n        .mockResolvedValueOnce({ \n          stdout: \"origin\\thttps://github.com/test/repo.git (fetch)\\norigin\\thttps://github.com/test/repo.git (push)\", \n          exitCode: 0 \n        }) // git remote -v\n        .mockResolvedValueOnce({ stdout: \"Hi there! How can I help you today?\", exitCode: 0 }); // claude test\n\n      mockClaudeQuery.mockImplementation(async function* () {\n        yield {\n          type: \"result\",\n          is_error: false,\n          result: mockTransformationPrompt,\n        };\n      });\n    });\n\n    it(\"should create scripts with exact file permissions\", async () => {\n      await init();\n\n      const syncPath = join(tempSourceDir, \".repomirror\", \"sync.sh\");\n      const ralphPath = join(tempSourceDir, \".repomirror\", \"ralph.sh\");\n      \n      const syncStats = await fs.stat(syncPath);\n      const ralphStats = await fs.stat(ralphPath);\n\n      // Check exact mode (should be 0o755)\n      expect(syncStats.mode & 0o777).toBe(0o755);\n      expect(ralphStats.mode & 0o777).toBe(0o755);\n      \n      // Check owner permissions\n      expect(syncStats.mode & 0o700).toBe(0o700); // rwx for owner\n      expect(ralphStats.mode & 0o700).toBe(0o700); // rwx for owner\n    });\n\n    it(\"should generate sync.sh with proper bash escaping\", async () => {\n      await init();\n\n      const syncContent = await fs.readFile(join(tempSourceDir, \".repomirror\", \"sync.sh\"), \"utf8\");\n      \n      // Check shebang\n      expect(syncContent.startsWith(\"#!/bin/bash\")).toBe(true);\n      \n      // Check line continuation\n      expect(syncContent).toContain(\" | \\\\\");\n      \n      // Check command structure\n      expect(syncContent).toContain(\"cat .repomirror/prompt.md\");\n      expect(syncContent).toContain(\"claude -p --output-format=stream-json\");\n      expect(syncContent).toContain(\"--verbose --dangerously-skip-permissions\");\n      expect(syncContent).toContain(\"tee -a .repomirror/claude_output.jsonl\");\n      expect(syncContent).toContain(\"npx repomirror visualize --debug\");\n    });\n\n    it(\"should generate ralph.sh with proper loop structure\", async () => {\n      await init();\n\n      const ralphContent = await fs.readFile(join(tempSourceDir, \".repomirror\", \"ralph.sh\"), \"utf8\");\n      \n      // Check shebang\n      expect(ralphContent.startsWith(\"#!/bin/bash\")).toBe(true);\n      \n      // Check loop structure\n      expect(ralphContent).toContain(\"while :; do\");\n      expect(ralphContent).toContain(\"./.repomirror/sync.sh\");\n      expect(ralphContent).toContain(\"echo -e \\\"===SLEEP===\\\\n===SLEEP===\\\\n\\\"; echo 'looping';\");\n      expect(ralphContent).toContain(\"sleep 10;\");\n      expect(ralphContent).toContain(\"done\");\n    });\n\n    it(\"should handle file creation permission errors\", async () => {\n      // Mock writeFile to fail on script creation\n      const originalWriteFile = fs.writeFile;\n      const writeFileSpy = vi.spyOn(fs, \"writeFile\").mockImplementation(async (path, content, options) => {\n        if (typeof path === 'string' && (path.endsWith('sync.sh') || path.endsWith('ralph.sh'))) {\n          throw new Error(\"Permission denied: Cannot create executable file\");\n        }\n        return originalWriteFile(path as any, content, options);\n      });\n\n      await expect(init()).rejects.toThrow(\"Process exit called with code 1\");\n\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Permission denied: Cannot create executable file\")\n      );\n\n      writeFileSpy.mockRestore();\n    });\n\n    it(\"should create gitignore with correct content and no extra whitespace\", async () => {\n      await init();\n\n      const gitignoreContent = await fs.readFile(join(tempSourceDir, \".repomirror\", \".gitignore\"), \"utf8\");\n      \n      // Check exact content\n      expect(gitignoreContent).toBe(\"claude_output.jsonl\\n\");\n      \n      // Verify no extra whitespace\n      expect(gitignoreContent.trim()).toBe(\"claude_output.jsonl\");\n      expect(gitignoreContent.split('\\n')).toHaveLength(2); // content + empty line\n    });\n  });\n});"
  },
  {
    "path": "tests/commands/pull.test.ts",
    "content": "import { describe, it, expect, vi, beforeEach, afterEach } from \"vitest\";\nimport { promises as fs } from \"fs\";\nimport { execa } from \"execa\";\nimport { pull } from \"../../src/commands/pull\";\n\n// Mock dependencies\nvi.mock(\"fs\", () => ({\n  promises: {\n    readFile: vi.fn(),\n    access: vi.fn(),\n  },\n}));\n\nvi.mock(\"execa\");\nvi.mock(\"chalk\", () => ({\n  default: {\n    red: vi.fn((text) => text),\n    green: vi.fn((text) => text),\n    yellow: vi.fn((text) => text),\n    cyan: vi.fn((text) => text),\n    gray: vi.fn((text) => text),\n    blue: vi.fn((text) => text),\n  },\n}));\n\nvi.mock(\"ora\", () => ({\n  default: vi.fn(() => ({\n    start: vi.fn().mockReturnThis(),\n    succeed: vi.fn().mockReturnThis(),\n    fail: vi.fn().mockReturnThis(),\n    warn: vi.fn().mockReturnThis(),\n  })),\n}));\n\n// Mock console methods\nconst mockConsoleError = vi.spyOn(console, \"error\").mockImplementation(() => {});\nconst mockProcessExit = vi.spyOn(process, \"exit\").mockImplementation((code?: number) => {\n  throw new Error(`process.exit unexpectedly called with \"${code}\"`);\n  return undefined as never;\n});\n\ndescribe(\"pull command\", () => {\n  beforeEach(() => {\n    vi.clearAllMocks();\n  });\n\n  afterEach(() => {\n    vi.restoreAllMocks();\n  });\n\n  describe(\"basic functionality\", () => {\n    it(\"should exit with error when repomirror.yaml not found\", async () => {\n      vi.mocked(fs.readFile).mockRejectedValue(new Error(\"ENOENT\"));\n\n      await expect(() => pull()).rejects.toThrow(\"process.exit unexpectedly called with \\\"1\\\"\");\n    });\n\n    it(\"should exit with error when source directory does not exist\", async () => {\n      vi.mocked(fs.readFile).mockResolvedValue(`\nsourceRepo: ./nonexistent\ntargetRepo: ../target\ntransformationInstructions: test transformation\npull:\n  source_remote: upstream\n  source_branch: main`);\n      \n      vi.mocked(fs.access).mockRejectedValue(new Error(\"ENOENT\"));\n\n      await expect(() => pull()).rejects.toThrow(\"process.exit unexpectedly called with \\\"1\\\"\");\n    });\n\n    it(\"should trigger sync when auto_sync is enabled and changes are pulled\", async () => {\n      const config = `\nsourceRepo: ./src\ntargetRepo: ../target\ntransformationInstructions: test transformation\npull:\n  source_remote: upstream\n  source_branch: main\n  auto_sync: true`;\n      \n      vi.mocked(fs.readFile).mockResolvedValue(config);\n      vi.mocked(fs.access).mockResolvedValue(undefined);\n      \n      vi.mocked(execa)\n        .mockResolvedValueOnce({ stdout: \".git\", stderr: \"\" } as any) // git rev-parse --git-dir\n        .mockResolvedValueOnce({ stdout: \"main\", stderr: \"\" } as any) // git branch --show-current\n        .mockResolvedValueOnce({ stdout: \"origin\\nupstream\", stderr: \"\" } as any) // git remote\n        .mockResolvedValueOnce({ stdout: \"\", stderr: \"\" } as any) // git status --porcelain\n        .mockResolvedValueOnce({ stdout: \"\", stderr: \"\" } as any) // git fetch upstream\n        .mockResolvedValueOnce({ stdout: \"1\", stderr: \"\" } as any) // git rev-list --count HEAD..upstream/main\n        .mockResolvedValueOnce({ \n          stdout: \"abc123f New feature\", \n          stderr: \"\" \n        } as any) // git log --oneline\n        .mockResolvedValueOnce({ \n          stdout: \"Updated 1 file\", \n          stderr: \"\" \n        } as any) // git pull upstream main\n        .mockResolvedValueOnce({ stdout: \"Sync completed\", stderr: \"\" } as any); // bash sync.sh\n\n      await expect(pull()).resolves.toBeUndefined();\n      expect(vi.mocked(execa)).toHaveBeenCalledWith(\n        \"bash\",\n        expect.arrayContaining([expect.stringContaining(\"sync.sh\")]),\n        expect.objectContaining({ stdio: \"inherit\" })\n      );\n    });\n\n    it(\"should handle --check option without pulling\", async () => {\n      const config = `\nsourceRepo: ./src\ntargetRepo: ../target\ntransformationInstructions: test transformation\npull:\n  source_remote: upstream\n  source_branch: main`;\n      \n      vi.mocked(fs.readFile).mockResolvedValue(config);\n      vi.mocked(fs.access).mockResolvedValue(undefined);\n      \n      vi.mocked(execa)\n        .mockResolvedValueOnce({ stdout: \".git\", stderr: \"\" } as any) // git rev-parse --git-dir\n        .mockResolvedValueOnce({ stdout: \"main\", stderr: \"\" } as any) // git branch --show-current\n        .mockResolvedValueOnce({ stdout: \"origin\\nupstream\", stderr: \"\" } as any) // git remote\n        .mockResolvedValueOnce({ stdout: \"\", stderr: \"\" } as any) // git status --porcelain\n        .mockResolvedValueOnce({ stdout: \"\", stderr: \"\" } as any) // git fetch upstream\n        .mockResolvedValueOnce({ stdout: \"2\", stderr: \"\" } as any) // git rev-list --count HEAD..upstream/main\n        .mockResolvedValueOnce({ \n          stdout: \"abc123f Fix bug\\ndef456a Update docs\", \n          stderr: \"\" \n        } as any); // git log --oneline\n\n      await expect(pull({ check: true })).resolves.toBeUndefined();\n      \n      // Should not attempt to pull\n      expect(vi.mocked(execa)).not.toHaveBeenCalledWith(\n        expect.anything(), \n        expect.arrayContaining([\"pull\"]), \n        expect.anything()\n      );\n    });\n\n    it(\"should skip sync with --source-only option\", async () => {\n      const config = `\nsourceRepo: ./src\ntargetRepo: ../target\ntransformationInstructions: test transformation\npull:\n  source_remote: upstream\n  source_branch: main`;\n      \n      vi.mocked(fs.readFile).mockResolvedValue(config);\n      vi.mocked(fs.access).mockResolvedValue(undefined);\n      \n      vi.mocked(execa)\n        .mockResolvedValueOnce({ stdout: \".git\", stderr: \"\" } as any) // git rev-parse --git-dir\n        .mockResolvedValueOnce({ stdout: \"main\", stderr: \"\" } as any) // git branch --show-current\n        .mockResolvedValueOnce({ stdout: \"origin\\nupstream\", stderr: \"\" } as any) // git remote\n        .mockResolvedValueOnce({ stdout: \"\", stderr: \"\" } as any) // git status --porcelain\n        .mockResolvedValueOnce({ stdout: \"\", stderr: \"\" } as any) // git fetch upstream\n        .mockResolvedValueOnce({ stdout: \"1\", stderr: \"\" } as any) // git rev-list --count HEAD..upstream/main\n        .mockResolvedValueOnce({ \n          stdout: \"abc123f New feature\", \n          stderr: \"\" \n        } as any) // git log --oneline\n        .mockResolvedValueOnce({ \n          stdout: \"Updated 1 file\", \n          stderr: \"\" \n        } as any); // git pull upstream main\n\n      await expect(pull({ sourceOnly: true })).resolves.toBeUndefined();\n      \n      // Should not attempt to run sync scripts\n      expect(vi.mocked(execa)).not.toHaveBeenCalledWith(\n        \"bash\",\n        expect.arrayContaining([expect.stringContaining(\"sync.sh\")]),\n        expect.anything()\n      );\n    });\n  });\n});"
  },
  {
    "path": "tests/commands/push.test.ts",
    "content": "import { describe, it, expect, vi, beforeEach, afterEach } from \"vitest\";\nimport { promises as fs } from \"fs\";\nimport { execa } from \"execa\";\nimport chalk from \"chalk\";\nimport { push } from \"../../src/commands/push\";\n\n// Mock dependencies\nvi.mock(\"fs\", () => ({\n  promises: {\n    readFile: vi.fn(),\n    access: vi.fn(),\n  },\n}));\n\nvi.mock(\"execa\");\nvi.mock(\"chalk\", () => ({\n  default: {\n    red: vi.fn((text) => text),\n    green: vi.fn((text) => text),\n    yellow: vi.fn((text) => text),\n    cyan: vi.fn((text) => text),\n    gray: vi.fn((text) => text),\n  },\n}));\n\nvi.mock(\"ora\", () => ({\n  default: vi.fn(() => ({\n    start: vi.fn().mockReturnThis(),\n    succeed: vi.fn().mockReturnThis(),\n    fail: vi.fn().mockReturnThis(),\n    warn: vi.fn().mockReturnThis(),\n  })),\n}));\n\n// Mock console methods\nconst mockConsoleLog = vi.spyOn(console, \"log\").mockImplementation(() => {});\nconst mockConsoleError = vi.spyOn(console, \"error\").mockImplementation(() => {});\nconst mockProcessExit = vi.spyOn(process, \"exit\").mockImplementation((code?: number) => {\n  throw new Error(`process.exit unexpectedly called with \"${code}\"`);\n  return undefined as never;\n});\n\ndescribe(\"push command\", () => {\n  beforeEach(() => {\n    vi.clearAllMocks();\n  });\n\n  afterEach(() => {\n    vi.restoreAllMocks();\n  });\n\n  describe(\"configuration loading\", () => {\n    it(\"should exit with error when repomirror.yaml not found\", async () => {\n      // Mock file not found\n      vi.mocked(fs.readFile).mockRejectedValue(new Error(\"ENOENT\"));\n\n      await expect(() => push()).rejects.toThrow(\"process.exit unexpectedly called with \\\"1\\\"\");\n    });\n\n    it(\"should exit with error when no remotes configured\", async () => {\n      vi.mocked(fs.readFile).mockResolvedValue(\"sourceRepo: ./src\\ntargetRepo: ../target\\ntransformationInstructions: test transformation\\nremotes: {}\");\n      \n      await expect(() => push()).rejects.toThrow(\"process.exit unexpectedly called with \\\"1\\\"\");\n    });\n\n    it(\"should handle no changes to commit gracefully\", async () => {\n      vi.mocked(fs.readFile).mockResolvedValue(\n        `sourceRepo: ./src\ntargetRepo: ../target\ntransformationInstructions: test transformation\nremotes:\n  origin:\n    url: https://github.com/test/repo.git\n    branch: main\n    auto_push: false\npush:\n  default_remote: origin\n  default_branch: main\n  commit_prefix: \"[repomirror]\"`\n      );\n\n      // Mock target directory exists and is a git repo\n      vi.mocked(fs.access).mockResolvedValue(undefined);\n      vi.mocked(execa)\n        .mockResolvedValueOnce({ stdout: \".git\", stderr: \"\" } as any) // git rev-parse --git-dir\n        .mockResolvedValueOnce({ stdout: \"\", stderr: \"\" } as any) // git diff --cached --name-only  \n        .mockResolvedValueOnce({ stdout: \"\", stderr: \"\" } as any) // git diff --name-only\n        .mockResolvedValueOnce({ stdout: \"\", stderr: \"\" } as any); // git ls-files --others --exclude-standard\n\n      // Should complete successfully without errors\n      await expect(push()).resolves.toBeUndefined();\n    });\n  });\n\n  describe(\"git operations\", () => {\n    beforeEach(() => {\n      const config = `sourceRepo: ./src\ntargetRepo: ../target\ntransformationInstructions: test transformation\nremotes:\n  origin:\n    url: https://github.com/test/repo.git\n    branch: main\n    auto_push: false\npush:\n  default_remote: origin\n  default_branch: main\n  commit_prefix: \"[repomirror]\"`;\n\n      vi.mocked(fs.readFile).mockResolvedValue(config);\n      vi.mocked(fs.access).mockResolvedValue(undefined);\n      vi.mocked(execa)\n        .mockResolvedValueOnce({ stdout: \".git\", stderr: \"\" } as any); // git rev-parse --git-dir\n    });\n\n    it(\"should detect changes and create commit\", async () => {\n      // Mock git status showing changes\n      vi.mocked(execa)\n        .mockResolvedValueOnce({ stdout: \"file1.txt\", stderr: \"\" } as any) // git diff --cached --name-only\n        .mockResolvedValueOnce({ stdout: \"file2.txt\", stderr: \"\" } as any) // git diff --name-only  \n        .mockResolvedValueOnce({ stdout: \"file3.txt\", stderr: \"\" } as any) // git ls-files --others --exclude-standard\n        .mockResolvedValueOnce({ stdout: \"abc123f\", stderr: \"\" } as any) // git rev-parse HEAD (source)\n        .mockResolvedValueOnce({ stdout: \"\", stderr: \"\" } as any) // git add .\n        .mockResolvedValueOnce({ stdout: \"\", stderr: \"\" } as any) // git commit -m\n        .mockResolvedValueOnce({ stdout: \"success\", stderr: \"\" } as any); // git push\n\n      await push();\n\n      expect(vi.mocked(execa)).toHaveBeenCalledWith(\n        \"git\", \n        [\"commit\", \"-m\", expect.stringContaining(\"[repomirror]\")], \n        { cwd: \"../target\" }\n      );\n      expect(vi.mocked(execa)).toHaveBeenCalledWith(\n        \"git\", \n        [\"push\", \"origin\", \"main\"], \n        { cwd: \"../target\", timeout: 60000 }\n      );\n    });\n\n    it(\"should handle push to all remotes\", async () => {\n      const config = `sourceRepo: ./src\ntargetRepo: ../target\ntransformationInstructions: test transformation\nremotes:\n  origin:\n    url: https://github.com/test/repo.git\n    branch: main\n  staging:\n    url: https://github.com/test/staging.git\n    branch: develop\npush:\n  default_remote: origin`;\n\n      vi.mocked(fs.readFile).mockResolvedValue(config);\n\n      // Mock git status showing changes\n      vi.mocked(execa)\n        .mockResolvedValueOnce({ stdout: \"file1.txt\", stderr: \"\" } as any) // git diff --cached --name-only\n        .mockResolvedValueOnce({ stdout: \"\", stderr: \"\" } as any) // git diff --name-only\n        .mockResolvedValueOnce({ stdout: \"\", stderr: \"\" } as any) // git ls-files --others --exclude-standard\n        .mockResolvedValueOnce({ stdout: \"abc123f\", stderr: \"\" } as any) // git rev-parse HEAD (source)\n        .mockResolvedValueOnce({ stdout: \"\", stderr: \"\" } as any) // git add .\n        .mockResolvedValueOnce({ stdout: \"\", stderr: \"\" } as any) // git commit -m\n        .mockResolvedValueOnce({ stdout: \"success\", stderr: \"\" } as any) // git push origin main\n        .mockResolvedValueOnce({ stdout: \"success\", stderr: \"\" } as any); // git push staging develop\n\n      await push({ all: true });\n\n      expect(vi.mocked(execa)).toHaveBeenCalledWith(\n        \"git\", \n        [\"push\", \"origin\", \"main\"], \n        { cwd: \"../target\", timeout: 60000 }\n      );\n      expect(vi.mocked(execa)).toHaveBeenCalledWith(\n        \"git\", \n        [\"push\", \"staging\", \"develop\"], \n        { cwd: \"../target\", timeout: 60000 }\n      );\n    });\n\n    it(\"should perform dry run without committing\", async () => {\n      // Mock git status showing changes\n      vi.mocked(execa)\n        .mockResolvedValueOnce({ stdout: \"file1.txt\", stderr: \"\" } as any) // git diff --cached --name-only\n        .mockResolvedValueOnce({ stdout: \"\", stderr: \"\" } as any) // git diff --name-only\n        .mockResolvedValueOnce({ stdout: \"\", stderr: \"\" } as any) // git ls-files --others --exclude-standard  \n        .mockResolvedValueOnce({ stdout: \"abc123f\", stderr: \"\" } as any) // git rev-parse HEAD (source)\n        .mockResolvedValueOnce({ stdout: \"dry-run output\", stderr: \"\" } as any); // git push --dry-run\n\n      await push({ dryRun: true });\n\n      // Should not call git add or git commit\n      expect(vi.mocked(execa)).not.toHaveBeenCalledWith(\n        \"git\", \n        [\"add\", \".\"], \n        expect.any(Object)\n      );\n      expect(vi.mocked(execa)).not.toHaveBeenCalledWith(\n        \"git\", \n        expect.arrayContaining([\"commit\"]), \n        expect.any(Object)\n      );\n\n      // Should call git push --dry-run\n      expect(vi.mocked(execa)).toHaveBeenCalledWith(\n        \"git\", \n        [\"push\", \"--dry-run\", \"origin\", \"main\"], \n        { cwd: \"../target\", timeout: 60000 }\n      );\n    });\n  });\n\n  describe(\"error handling\", () => {\n    it(\"should handle authentication errors gracefully\", async () => {\n      const config = `sourceRepo: ./src\ntargetRepo: ../target\ntransformationInstructions: test transformation\nremotes:\n  origin:\n    url: https://github.com/test/repo.git\n    branch: main\npush:\n  default_remote: origin`;\n\n      vi.mocked(fs.readFile).mockResolvedValue(config);\n      vi.mocked(fs.access).mockResolvedValue(undefined);\n      \n      // Mock successful initial checks but failed push with auth error\n      vi.mocked(execa)\n        .mockResolvedValueOnce({ stdout: \".git\", stderr: \"\" } as any) // git rev-parse --git-dir\n        .mockResolvedValueOnce({ stdout: \"file1.txt\", stderr: \"\" } as any) // git diff --cached --name-only\n        .mockResolvedValueOnce({ stdout: \"\", stderr: \"\" } as any) // git diff --name-only\n        .mockResolvedValueOnce({ stdout: \"\", stderr: \"\" } as any) // git ls-files --others --exclude-standard\n        .mockResolvedValueOnce({ stdout: \"abc123f\", stderr: \"\" } as any) // git rev-parse HEAD (source)\n        .mockResolvedValueOnce({ stdout: \"\", stderr: \"\" } as any) // git add .\n        .mockResolvedValueOnce({ stdout: \"\", stderr: \"\" } as any) // git commit -m\n        .mockRejectedValueOnce(new Error(\"authentication failed\")); // git push fails\n\n      await expect(() => push()).rejects.toThrow(\"process.exit unexpectedly called with \\\"1\\\"\");\n    });\n  });\n});"
  },
  {
    "path": "tests/commands/setup-github-pr-sync.test.ts",
    "content": "import { describe, it, expect, vi, beforeEach, afterEach } from \"vitest\";\nimport { promises as fs } from \"fs\";\nimport { join } from \"path\";\nimport yaml from \"yaml\";\nimport { createTempDir, cleanupTempDir, mockConsole, mockProcess } from \"../helpers\";\n\n// Mock external dependencies\nconst mockInquirerPrompt = vi.fn();\nconst mockOra = vi.fn();\n\nvi.mock(\"inquirer\", () => ({\n  default: {\n    prompt: mockInquirerPrompt,\n  },\n}));\n\nvi.mock(\"ora\", () => ({\n  default: mockOra,\n}));\n\n// Import after mocking\nconst { setupGithubPrSync } = await import(\"../../src/commands/setup-github-pr-sync\");\n\ndescribe(\"setup-github-pr-sync command\", () => {\n  let tempDir: string;\n  let consoleMock: ReturnType<typeof mockConsole>;\n  let processMock: ReturnType<typeof mockProcess>;\n  let spinnerMock: any;\n\n  beforeEach(async () => {\n    tempDir = await createTempDir(\"repomirror-setup-pr-sync-\");\n    consoleMock = mockConsole();\n    processMock = mockProcess(true);\n    processMock.cwd.mockReturnValue(tempDir);\n\n    spinnerMock = {\n      start: vi.fn().mockReturnThis(),\n      succeed: vi.fn().mockReturnThis(),\n      fail: vi.fn().mockReturnThis(),\n    };\n    mockOra.mockReturnValue(spinnerMock);\n\n    vi.clearAllMocks();\n  });\n\n  afterEach(async () => {\n    await cleanupTempDir(tempDir);\n    vi.restoreAllMocks();\n  });\n\n  describe(\"configuration validation\", () => {\n    it(\"should error if repomirror.yaml doesn't exist\", async () => {\n      await expect(setupGithubPrSync()).rejects.toThrow(\"Process exit called with code 1\");\n      expect(processMock.exit).toHaveBeenCalledWith(1);\n    });\n\n    it(\"should read existing configuration successfully\", async () => {\n      // Create a valid repomirror.yaml\n      const config = {\n        sourceRepo: \"./\",\n        targetRepo: \"../test-transformed\",\n        transformationInstructions: \"convert to typescript\",\n      };\n      await fs.writeFile(\n        join(tempDir, \"repomirror.yaml\"),\n        yaml.stringify(config)\n      );\n\n      mockInquirerPrompt.mockResolvedValue({\n        targetRepo: \"myorg/myrepo\",\n        timesToLoop: 3,\n      });\n\n      await setupGithubPrSync();\n\n      // Verify workflow was created\n      const workflowPath = join(tempDir, \".github\", \"workflows\", \"repomirror.yml\");\n      const workflowExists = await fs.access(workflowPath).then(() => true).catch(() => false);\n      expect(workflowExists).toBe(true);\n    });\n  });\n\n  describe(\"workflow generation\", () => {\n    beforeEach(async () => {\n      // Create a valid repomirror.yaml\n      const config = {\n        sourceRepo: \"./\",\n        targetRepo: \"../test-transformed\", \n        transformationInstructions: \"convert to typescript\",\n      };\n      await fs.writeFile(\n        join(tempDir, \"repomirror.yaml\"),\n        yaml.stringify(config)\n      );\n    });\n\n    it(\"should create workflow with correct content\", async () => {\n      mockInquirerPrompt.mockResolvedValue({\n        targetRepo: \"myorg/myrepo\",\n        timesToLoop: 5,\n      });\n\n      await setupGithubPrSync();\n\n      const workflowPath = join(tempDir, \".github\", \"workflows\", \"repomirror.yml\");\n      const workflowContent = await fs.readFile(workflowPath, \"utf-8\");\n\n      expect(workflowContent).toContain(\"repository: myorg/myrepo\");\n      expect(workflowContent).toContain(\"for i in $(seq 1 5);\");\n      expect(workflowContent).toContain(\"workflow_dispatch:\");\n      expect(workflowContent).toContain(\"ANTHROPIC_API_KEY\");\n      expect(workflowContent).toContain(\"npx repomirror sync-one --auto-push\");\n    });\n\n    it(\"should handle CLI options\", async () => {\n      await setupGithubPrSync({\n        targetRepo: \"testorg/testrepo\",\n        timesToLoop: 2,\n      });\n\n      const workflowPath = join(tempDir, \".github\", \"workflows\", \"repomirror.yml\");\n      const workflowContent = await fs.readFile(workflowPath, \"utf-8\");\n\n      expect(workflowContent).toContain(\"repository: testorg/testrepo\");\n      expect(workflowContent).toContain(\"for i in $(seq 1 2);\");\n    });\n\n    it(\"should update repomirror.yaml with github-pr-sync settings\", async () => {\n      mockInquirerPrompt.mockResolvedValue({\n        targetRepo: \"myorg/myrepo\",\n        timesToLoop: 3,\n      });\n\n      await setupGithubPrSync();\n\n      const configPath = join(tempDir, \"repomirror.yaml\");\n      const configContent = await fs.readFile(configPath, \"utf-8\");\n      const config = yaml.parse(configContent);\n\n      expect(config[\"github-pr-sync\"]).toEqual({\n        targetRepo: \"myorg/myrepo\",\n        timesToLoop: 3,\n      });\n    });\n  });\n\n  describe(\"overwrite protection\", () => {\n    beforeEach(async () => {\n      // Create a valid repomirror.yaml\n      const config = {\n        sourceRepo: \"./\",\n        targetRepo: \"../test-transformed\",\n        transformationInstructions: \"convert to typescript\",\n      };\n      await fs.writeFile(\n        join(tempDir, \"repomirror.yaml\"),\n        yaml.stringify(config)\n      );\n\n      // Create existing workflow\n      const workflowDir = join(tempDir, \".github\", \"workflows\");\n      await fs.mkdir(workflowDir, { recursive: true });\n      await fs.writeFile(join(workflowDir, \"repomirror.yml\"), \"existing content\");\n    });\n\n    it(\"should prompt before overwriting existing workflow\", async () => {\n      mockInquirerPrompt\n        .mockResolvedValueOnce({ shouldOverwrite: false })\n        .mockResolvedValueOnce({\n          targetRepo: \"myorg/myrepo\",\n          timesToLoop: 3,\n        });\n\n      await expect(setupGithubPrSync()).rejects.toThrow(\"Process exit called with code 0\");\n      expect(processMock.exit).toHaveBeenCalledWith(0);\n    });\n\n    it(\"should overwrite when --overwrite flag is used\", async () => {\n      mockInquirerPrompt.mockResolvedValue({\n        targetRepo: \"myorg/myrepo\", \n        timesToLoop: 3,\n      });\n\n      await setupGithubPrSync({ overwrite: true });\n\n      const workflowPath = join(tempDir, \".github\", \"workflows\", \"repomirror.yml\");\n      const workflowContent = await fs.readFile(workflowPath, \"utf-8\");\n      expect(workflowContent).toContain(\"repository: myorg/myrepo\");\n      expect(workflowContent).not.toBe(\"existing content\");\n    });\n  });\n\n  describe(\"defaults from existing config\", () => {\n    it(\"should use existing github-pr-sync settings as defaults\", async () => {\n      const config = {\n        sourceRepo: \"./\",\n        targetRepo: \"../test-transformed\",\n        transformationInstructions: \"convert to typescript\",\n        \"github-pr-sync\": {\n          targetRepo: \"existing/repo\",\n          timesToLoop: 4,\n        },\n      };\n      await fs.writeFile(\n        join(tempDir, \"repomirror.yaml\"),\n        yaml.stringify(config)\n      );\n\n      // Mock prompts to use defaults\n      mockInquirerPrompt.mockResolvedValue({\n        targetRepo: \"existing/repo\", // Should use existing default\n        timesToLoop: 4, // Should use existing default\n      });\n\n      await setupGithubPrSync();\n\n      const workflowPath = join(tempDir, \".github\", \"workflows\", \"repomirror.yml\");\n      const workflowContent = await fs.readFile(workflowPath, \"utf-8\");\n\n      expect(workflowContent).toContain(\"repository: existing/repo\");\n      expect(workflowContent).toContain(\"for i in $(seq 1 4);\");\n    });\n  });\n});"
  },
  {
    "path": "tests/commands/simple.test.ts",
    "content": "import { describe, it, expect, vi } from \"vitest\";\nimport { createTempDir, cleanupTempDir, mockConsole } from \"../helpers/test-utils\";\n\ndescribe(\"command utilities\", () => {\n  it(\"should create and cleanup temporary directories\", async () => {\n    const tempDir = await createTempDir(\"test-\");\n    expect(tempDir).toMatch(/test-/);\n    \n    // Directory should exist\n    const fs = await import(\"fs\");\n    const stats = await fs.promises.stat(tempDir);\n    expect(stats.isDirectory()).toBe(true);\n    \n    // Cleanup should work\n    await cleanupTempDir(tempDir);\n  });\n\n  it(\"should mock console methods\", () => {\n    const consoleMock = mockConsole();\n    \n    console.log(\"test message\");\n    console.error(\"error message\");\n    \n    expect(consoleMock.log).toHaveBeenCalledWith(\"test message\");\n    expect(consoleMock.error).toHaveBeenCalledWith(\"error message\");\n    \n    vi.restoreAllMocks();\n  });\n\n  it(\"should work with TypeScript imports\", async () => {\n    // Test that we can import from source with TypeScript\n    const { basename } = await import(\"path\");\n    const result = basename(\"/some/path/file.txt\");\n    expect(result).toBe(\"file.txt\");\n  });\n});"
  },
  {
    "path": "tests/commands/sync-forever.test.ts",
    "content": "import { describe, it, expect, vi, beforeEach, afterEach } from \"vitest\";\nimport { promises as fs } from \"fs\";\nimport { join } from \"path\";\nimport {\n  createTempDir,\n  cleanupTempDir,\n  mockConsole,\n  mockProcess,\n  createMockFileStructure,\n} from \"../helpers/test-utils\";\n\n// Mock external dependencies at module level\nconst mockExeca = vi.fn();\n\nvi.mock(\"execa\", () => ({\n  execa: mockExeca,\n}));\n\n// Import the module after mocking\nconst { syncForever } = await import(\"../../src/commands/sync-forever\");\n\ndescribe(\"sync-forever command\", () => {\n  let tempDir: string;\n  let consoleMock: ReturnType<typeof mockConsole>;\n  let processMock: ReturnType<typeof mockProcess>;\n\n  beforeEach(async () => {\n    // Create temporary directory\n    tempDir = await createTempDir(\"repomirror-sync-forever-\");\n\n    // Setup mocks\n    consoleMock = mockConsole();\n    processMock = mockProcess(true); // Throw on process.exit by default\n\n    // Mock process.cwd to return our temp directory\n    processMock.cwd.mockReturnValue(tempDir);\n\n    // Clear all mocks\n    vi.clearAllMocks();\n  });\n\n  afterEach(async () => {\n    // Cleanup temp directory\n    await cleanupTempDir(tempDir);\n\n    // Restore all mocks\n    vi.restoreAllMocks();\n  });\n\n  describe(\"successful execution\", () => {\n    it(\"should execute ralph.sh successfully when script exists\", async () => {\n      // Create .repomirror directory and ralph.sh script\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"ralph.sh\": `#!/bin/bash\nwhile :; do\n  ./.repomirror/sync.sh\n  echo -e \"===SLEEP===\\\\n===SLEEP===\\\\n\"; echo 'looping';\n  sleep 10;\ndone`,\n        },\n      });\n\n      // Mock successful execa execution\n      mockExeca.mockResolvedValue({\n        stdout: \"Continuous sync running\",\n        stderr: \"\",\n        exitCode: 0,\n      });\n\n      // Run syncForever\n      await syncForever();\n\n      // Verify execa was called with correct parameters\n      expect(mockExeca).toHaveBeenCalledWith(\"bash\", [join(tempDir, \".repomirror\", \"ralph.sh\")], {\n        stdio: \"inherit\",\n        cwd: tempDir,\n      });\n\n      // Verify console output\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"Running ralph.sh (continuous sync)...\"));\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"Press Ctrl+C to stop\"));\n\n      // Verify process.exit was not called (successful execution)\n      expect(processMock.exit).not.toHaveBeenCalled();\n    });\n\n    it(\"should use correct working directory and script path\", async () => {\n      // Create .repomirror directory and ralph.sh script\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"ralph.sh\": \"#!/bin/bash\\necho 'continuous sync'\",\n        },\n      });\n\n      // Mock successful execa execution\n      mockExeca.mockResolvedValue({\n        stdout: \"continuous sync\",\n        stderr: \"\",\n        exitCode: 0,\n      });\n\n      await syncForever();\n\n      const expectedScriptPath = join(tempDir, \".repomirror\", \"ralph.sh\");\n\n      // Verify execa was called with absolute path to ralph.sh\n      expect(mockExeca).toHaveBeenCalledWith(\"bash\", [expectedScriptPath], {\n        stdio: \"inherit\",\n        cwd: tempDir,\n      });\n    });\n\n    it(\"should inherit stdio for continuous output monitoring\", async () => {\n      // Create .repomirror directory and ralph.sh script\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"ralph.sh\": \"#!/bin/bash\\nwhile true; do echo 'continuous'; sleep 1; done\",\n        },\n      });\n\n      mockExeca.mockResolvedValue({\n        stdout: \"continuous\",\n        stderr: \"\",\n        exitCode: 0,\n      });\n\n      await syncForever();\n\n      // Verify stdio: \"inherit\" was passed to execa for real-time output\n      expect(mockExeca).toHaveBeenCalledWith(\n        \"bash\",\n        [join(tempDir, \".repomirror\", \"ralph.sh\")],\n        expect.objectContaining({\n          stdio: \"inherit\",\n        })\n      );\n    });\n  });\n\n  describe(\"error cases\", () => {\n    it(\"should exit with error when .repomirror/ralph.sh does not exist\", async () => {\n      // Don't create the ralph.sh script\n\n      await expect(syncForever()).rejects.toThrow(\"Process exit called with code 1\");\n\n      // Verify error message\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Error: .repomirror/ralph.sh not found. Run 'npx repomirror init' first.\")\n      );\n\n      // Verify process.exit was called with code 1\n      expect(processMock.exit).toHaveBeenCalledWith(1);\n\n      // Verify execa was not called\n      expect(mockExeca).not.toHaveBeenCalled();\n    });\n\n    it(\"should exit with error when .repomirror directory does not exist\", async () => {\n      // Don't create the .repomirror directory at all\n\n      await expect(syncForever()).rejects.toThrow(\"Process exit called with code 1\");\n\n      // Verify error message\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Error: .repomirror/ralph.sh not found. Run 'npx repomirror init' first.\")\n      );\n\n      // Verify process.exit was called with code 1\n      expect(processMock.exit).toHaveBeenCalledWith(1);\n\n      // Verify execa was not called\n      expect(mockExeca).not.toHaveBeenCalled();\n    });\n\n    it(\"should handle script execution errors gracefully\", async () => {\n      // Create .repomirror directory and ralph.sh script\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"ralph.sh\": \"#!/bin/bash\\nexit 1\",\n        },\n      });\n\n      // Mock failed execa execution\n      const scriptError = new Error(\"Ralph script execution failed\");\n      (scriptError as any).exitCode = 1;\n      mockExeca.mockRejectedValue(scriptError);\n\n      await expect(syncForever()).rejects.toThrow(\"Process exit called with code 1\");\n\n      // Verify initial success messages\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"Running ralph.sh (continuous sync)...\"));\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"Press Ctrl+C to stop\"));\n\n      // Verify error message\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Sync forever failed: Ralph script execution failed\")\n      );\n\n      // Verify process.exit was called with code 1\n      expect(processMock.exit).toHaveBeenCalledWith(1);\n    });\n\n    it(\"should handle non-Error exceptions in script execution\", async () => {\n      // Create .repomirror directory and ralph.sh script\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"ralph.sh\": \"#!/bin/bash\\necho 'failing'\",\n        },\n      });\n\n      // Mock execa to throw a non-Error object\n      mockExeca.mockRejectedValue(\"String error in ralph\");\n\n      await expect(syncForever()).rejects.toThrow(\"Process exit called with code 1\");\n\n      // Verify error message handles non-Error exceptions\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Sync forever failed: String error in ralph\")\n      );\n\n      // Verify process.exit was called with code 1\n      expect(processMock.exit).toHaveBeenCalledWith(1);\n    });\n  });\n\n  describe(\"SIGINT signal handling\", () => {\n    it(\"should handle SIGINT gracefully with user-friendly message\", async () => {\n      // Create .repomirror directory and ralph.sh script\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"ralph.sh\": `#!/bin/bash\ntrap 'exit 0' SIGINT\nwhile true; do\n  echo \"Running...\"\n  sleep 1\ndone`,\n        },\n      });\n\n      // Mock execa to simulate SIGINT (Ctrl+C)\n      const sigintError = new Error(\"Process interrupted\");\n      (sigintError as any).signal = \"SIGINT\";\n      mockExeca.mockRejectedValue(sigintError);\n\n      // Should not throw or exit - SIGINT is handled gracefully\n      await syncForever();\n\n      // Verify user-friendly stop message\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"Stopped by user\"));\n\n      // Verify process.exit was NOT called (graceful shutdown)\n      expect(processMock.exit).not.toHaveBeenCalled();\n    });\n\n    it(\"should distinguish SIGINT from other errors\", async () => {\n      // Create .repomirror directory and ralph.sh script\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"ralph.sh\": \"#!/bin/bash\\necho 'running'\",\n        },\n      });\n\n      // Mock execa to simulate a non-SIGINT error\n      const otherError = new Error(\"Network error\");\n      (otherError as any).signal = \"SIGTERM\";\n      mockExeca.mockRejectedValue(otherError);\n\n      await expect(syncForever()).rejects.toThrow(\"Process exit called with code 1\");\n\n      // Should show error message, not the user-friendly stop message\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Sync forever failed: Network error\")\n      );\n      expect(consoleMock.log).not.toHaveBeenCalledWith(expect.stringContaining(\"Stopped by user\"));\n\n      // Verify process.exit was called with code 1\n      expect(processMock.exit).toHaveBeenCalledWith(1);\n    });\n\n    it(\"should handle Error objects with SIGINT signal correctly\", async () => {\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"ralph.sh\": \"#!/bin/bash\\ntrap 'exit 0' SIGINT; sleep 1000\",\n        },\n      });\n\n      // Create proper Error object with SIGINT signal\n      const sigintError = new Error(\"Command was killed with SIGINT\");\n      (sigintError as any).signal = \"SIGINT\";\n      mockExeca.mockRejectedValue(sigintError);\n\n      // Should complete without throwing\n      await syncForever();\n\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"Stopped by user\"));\n      expect(processMock.exit).not.toHaveBeenCalled();\n    });\n\n    it(\"should handle non-Error objects with SIGINT signal\", async () => {\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"ralph.sh\": \"#!/bin/bash\\necho 'test'\",\n        },\n      });\n\n      // Mock with non-Error object that has signal property\n      const sigintObj = { signal: \"SIGINT\", message: \"Interrupted\" };\n      mockExeca.mockRejectedValue(sigintObj);\n\n      await expect(syncForever()).rejects.toThrow(\"Process exit called with code 1\");\n\n      // Non-Error objects should be treated as regular errors\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Sync forever failed:\")\n      );\n      expect(consoleMock.log).not.toHaveBeenCalledWith(expect.stringContaining(\"Stopped by user\"));\n    });\n  });\n\n  describe(\"console output verification\", () => {\n    beforeEach(async () => {\n      // Create .repomirror directory and ralph.sh script for all output tests\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"ralph.sh\": `#!/bin/bash\necho \"Continuous sync\"\nwhile true; do sleep 1; done`,\n        },\n      });\n    });\n\n    it(\"should show cyan colored startup messages\", async () => {\n      mockExeca.mockResolvedValue({ stdout: \"\", stderr: \"\", exitCode: 0 });\n\n      await syncForever();\n\n      // Check that the startup messages were logged\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"Running ralph.sh (continuous sync)...\"));\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"Press Ctrl+C to stop\"));\n    });\n\n    it(\"should show yellow colored stop message on SIGINT\", async () => {\n      const sigintError = new Error(\"Interrupted\");\n      (sigintError as any).signal = \"SIGINT\";\n      mockExeca.mockRejectedValue(sigintError);\n\n      await syncForever();\n\n      // Check that the stop message was logged in yellow\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"Stopped by user\"));\n    });\n\n    it(\"should show red colored error message on failure\", async () => {\n      const error = new Error(\"Command failed\");\n      mockExeca.mockRejectedValue(error);\n\n      await expect(syncForever()).rejects.toThrow(\"Process exit called with code 1\");\n\n      // Check that the error message was logged\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Sync forever failed: Command failed\")\n      );\n    });\n\n    it(\"should show red colored error message when ralph.sh is missing\", async () => {\n      // Remove the ralph.sh script\n      await fs.rm(join(tempDir, \".repomirror\", \"ralph.sh\"));\n\n      await expect(syncForever()).rejects.toThrow(\"Process exit called with code 1\");\n\n      // Check that the missing file error message was logged\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Error: .repomirror/ralph.sh not found. Run 'npx repomirror init' first.\")\n      );\n    });\n  });\n\n  describe(\"file system access patterns\", () => {\n    it(\"should use fs.access to check ralph.sh existence\", async () => {\n      const fsAccessSpy = vi.spyOn(fs, \"access\");\n\n      // Create the script so access check passes\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"ralph.sh\": \"#!/bin/bash\\necho 'continuous'\",\n        },\n      });\n\n      mockExeca.mockResolvedValue({ stdout: \"\", stderr: \"\", exitCode: 0 });\n\n      await syncForever();\n\n      // Verify fs.access was called with the correct path\n      expect(fsAccessSpy).toHaveBeenCalledWith(join(tempDir, \".repomirror\", \"ralph.sh\"));\n\n      fsAccessSpy.mockRestore();\n    });\n\n    it(\"should handle permission denied errors on file access\", async () => {\n      const fsAccessSpy = vi.spyOn(fs, \"access\");\n      fsAccessSpy.mockRejectedValue(new Error(\"EACCES: permission denied\"));\n\n      await expect(syncForever()).rejects.toThrow(\"Process exit called with code 1\");\n\n      // Verify the error message still shows file not found (since we catch all access errors)\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Error: .repomirror/ralph.sh not found. Run 'npx repomirror init' first.\")\n      );\n\n      fsAccessSpy.mockRestore();\n    });\n  });\n\n  describe(\"continuous execution scenarios\", () => {\n    it(\"should handle long-running scripts appropriately\", async () => {\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"ralph.sh\": `#!/bin/bash\nwhile true; do\n  echo \"Syncing...\"\n  sleep 5\ndone`,\n        },\n      });\n\n      // Mock long-running process that eventually completes\n      mockExeca.mockResolvedValue({ stdout: \"\", stderr: \"\", exitCode: 0 });\n\n      await syncForever();\n\n      expect(mockExeca).toHaveBeenCalledWith(\"bash\", [join(tempDir, \".repomirror\", \"ralph.sh\")], {\n        stdio: \"inherit\",\n        cwd: tempDir,\n      });\n    });\n\n    it(\"should handle scripts with complex loop logic\", async () => {\n      const complexScript = `#!/bin/bash\nset -euo pipefail\n\ncleanup() {\n    echo \"Cleaning up...\"\n    exit 0\n}\n\ntrap cleanup SIGINT SIGTERM\n\nwhile :; do\n    echo \"Starting sync cycle...\"\n    \n    # Run sync\n    if ./.repomirror/sync.sh; then\n        echo \"Sync successful\"\n    else\n        echo \"Sync failed, continuing anyway...\"\n    fi\n    \n    echo -e \"===SLEEP===\\\\n===SLEEP===\\\\n\"\n    echo 'Waiting before next cycle...'\n    sleep 10\ndone`;\n\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"ralph.sh\": complexScript,\n        },\n      });\n\n      mockExeca.mockResolvedValue({ stdout: \"Complex script output\", stderr: \"\", exitCode: 0 });\n\n      await syncForever();\n\n      // Should execute the complex script successfully\n      expect(mockExeca).toHaveBeenCalledWith(\"bash\", [join(tempDir, \".repomirror\", \"ralph.sh\")], {\n        stdio: \"inherit\",\n        cwd: tempDir,\n      });\n    });\n\n    it(\"should handle empty script content\", async () => {\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"ralph.sh\": \"\",\n        },\n      });\n\n      mockExeca.mockResolvedValue({ stdout: \"\", stderr: \"\", exitCode: 0 });\n\n      await syncForever();\n\n      // Should still execute successfully\n      expect(mockExeca).toHaveBeenCalled();\n    });\n  });\n\n  describe(\"process and signal handling edge cases\", () => {\n    it(\"should preserve working directory context for continuous execution\", async () => {\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"ralph.sh\": \"#!/bin/bash\\npwd; while true; do sleep 1; done\",\n        },\n      });\n\n      mockExeca.mockResolvedValue({ stdout: tempDir, stderr: \"\", exitCode: 0 });\n\n      await syncForever();\n\n      // Verify that execa is called with the correct working directory\n      expect(mockExeca).toHaveBeenCalledWith(\n        \"bash\",\n        [join(tempDir, \".repomirror\", \"ralph.sh\")],\n        expect.objectContaining({\n          cwd: tempDir,\n        })\n      );\n    });\n\n    it(\"should handle bash command execution with proper shell for continuous processes\", async () => {\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"ralph.sh\": \"#!/bin/bash\\necho 'continuous execution'; while true; do sleep 1; done\",\n        },\n      });\n\n      mockExeca.mockResolvedValue({ stdout: \"continuous execution\", stderr: \"\", exitCode: 0 });\n\n      await syncForever();\n\n      // Verify that bash is used as the shell command\n      expect(mockExeca).toHaveBeenCalledWith(\n        \"bash\",\n        expect.any(Array),\n        expect.any(Object)\n      );\n    });\n\n    it(\"should handle multiple different signal types correctly\", async () => {\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"ralph.sh\": \"#!/bin/bash\\nwhile true; do sleep 1; done\",\n        },\n      });\n\n      // Test different signals\n      const signals = [\"SIGTERM\", \"SIGKILL\", \"SIGHUP\", \"SIGQUIT\"];\n      \n      for (const signal of signals) {\n        mockExeca.mockClear();\n        const error = new Error(`Process killed with ${signal}`);\n        (error as any).signal = signal;\n        mockExeca.mockRejectedValue(error);\n\n        await expect(syncForever()).rejects.toThrow(\"Process exit called with code 1\");\n        \n        // Only SIGINT should show the user-friendly message\n        expect(consoleMock.error).toHaveBeenCalledWith(\n          expect.stringContaining(`Sync forever failed: Process killed with ${signal}`)\n        );\n      }\n    });\n  });\n\n  describe(\"ralph.sh script verification and execution\", () => {\n    it(\"should verify ralph.sh exists before attempting execution\", async () => {\n      // Don't create any files\n      const fsAccessSpy = vi.spyOn(fs, \"access\");\n      \n      await expect(syncForever()).rejects.toThrow(\"Process exit called with code 1\");\n      \n      // Verify fs.access was called to check script existence\n      expect(fsAccessSpy).toHaveBeenCalledWith(join(tempDir, \".repomirror\", \"ralph.sh\"));\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Error: .repomirror/ralph.sh not found.\")\n      );\n      \n      fsAccessSpy.mockRestore();\n    });\n\n    it(\"should handle gracefully when .repomirror directory is missing\", async () => {\n      const fsAccessSpy = vi.spyOn(fs, \"access\");\n      \n      await expect(syncForever()).rejects.toThrow(\"Process exit called with code 1\");\n      \n      // Verify the error is caught and handled gracefully\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Error: .repomirror/ralph.sh not found. Run 'npx repomirror init' first.\")\n      );\n      expect(processMock.exit).toHaveBeenCalledWith(1);\n      expect(mockExeca).not.toHaveBeenCalled();\n      \n      fsAccessSpy.mockRestore();\n    });\n\n    it(\"should handle file system permission errors during script verification\", async () => {\n      const fsAccessSpy = vi.spyOn(fs, \"access\");\n      const permissionError = new Error(\"EACCES: permission denied\");\n      (permissionError as any).code = \"EACCES\";\n      fsAccessSpy.mockRejectedValue(permissionError);\n      \n      await expect(syncForever()).rejects.toThrow(\"Process exit called with code 1\");\n      \n      // Should treat permission errors as \"file not found\" for user-friendly message\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Error: .repomirror/ralph.sh not found. Run 'npx repomirror init' first.\")\n      );\n      \n      fsAccessSpy.mockRestore();\n    });\n\n    it(\"should only execute ralph.sh after successful verification\", async () => {\n      const fsAccessSpy = vi.spyOn(fs, \"access\");\n      \n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"ralph.sh\": \"#!/bin/bash\\necho 'verified and running'\",\n        },\n      });\n      \n      mockExeca.mockResolvedValue({ stdout: \"verified and running\", stderr: \"\", exitCode: 0 });\n      \n      await syncForever();\n      \n      // Verify fs.access was called first\n      expect(fsAccessSpy).toHaveBeenCalledWith(join(tempDir, \".repomirror\", \"ralph.sh\"));\n      \n      // Then execa was called\n      expect(mockExeca).toHaveBeenCalledWith(\"bash\", [join(tempDir, \".repomirror\", \"ralph.sh\")], {\n        stdio: \"inherit\",\n        cwd: tempDir,\n      });\n      \n      fsAccessSpy.mockRestore();\n    });\n\n    it(\"should specifically look for ralph.sh not sync.sh\", async () => {\n      // Create only sync.sh, not ralph.sh\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"sync.sh\": \"#!/bin/bash\\necho 'sync'\",\n        },\n      });\n\n      await expect(syncForever()).rejects.toThrow(\"Process exit called with code 1\");\n\n      // Should specifically complain about ralph.sh being missing\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Error: .repomirror/ralph.sh not found.\")\n      );\n    });\n\n    it(\"should execute ralph.sh with appropriate permissions expectations\", async () => {\n      // Create ralph.sh with executable permissions\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"ralph.sh\": \"#!/bin/bash\\necho 'ralph running'\",\n        },\n      });\n\n      // Make script executable (simulate proper init)\n      const scriptPath = join(tempDir, \".repomirror\", \"ralph.sh\");\n      const stats = await fs.stat(scriptPath);\n      await fs.chmod(scriptPath, stats.mode | 0o111);\n\n      mockExeca.mockResolvedValue({ stdout: \"ralph running\", stderr: \"\", exitCode: 0 });\n\n      await syncForever();\n\n      expect(mockExeca).toHaveBeenCalledWith(\"bash\", [scriptPath], {\n        stdio: \"inherit\",\n        cwd: tempDir,\n      });\n    });\n\n    it(\"should work with ralph.sh that contains typical continuous sync logic\", async () => {\n      const typicalRalphScript = `#!/bin/bash\nwhile :; do\n  ./.repomirror/sync.sh\n  echo -e \"===SLEEP===\\\\n===SLEEP===\\\\n\"; echo 'looping';\n  sleep 10;\ndone`;\n\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"ralph.sh\": typicalRalphScript,\n        },\n      });\n\n      mockExeca.mockResolvedValue({ stdout: \"Typical ralph execution\", stderr: \"\", exitCode: 0 });\n\n      await syncForever();\n\n      expect(mockExeca).toHaveBeenCalledWith(\n        \"bash\",\n        [join(tempDir, \".repomirror\", \"ralph.sh\")],\n        {\n          stdio: \"inherit\",\n          cwd: tempDir,\n        }\n      );\n      \n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"Running ralph.sh (continuous sync)...\"));\n    });\n\n    it(\"should demonstrate continuous loop verification through script content\", async () => {\n      const continuousLoopScript = `#!/bin/bash\nset -euo pipefail\n\necho \"Starting continuous sync loop...\"\n\nwhile true; do\n  echo \"[$(date)] Running sync cycle\"\n  \n  if ./.repomirror/sync.sh; then\n    echo \"[$(date)] Sync completed successfully\"\n  else\n    echo \"[$(date)] Sync failed, will retry in 10 seconds\"\n  fi\n  \n  echo -e \"===SLEEP===\\\\n===SLEEP===\\\\n\"\n  echo \"Waiting 10 seconds before next cycle...\"\n  sleep 10\ndone`;\n\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"ralph.sh\": continuousLoopScript,\n        },\n      });\n\n      // Mock long-running process that demonstrates continuous behavior\n      mockExeca.mockResolvedValue({ \n        stdout: \"Starting continuous sync loop...\\n[date] Running sync cycle\", \n        stderr: \"\", \n        exitCode: 0 \n      });\n\n      await syncForever();\n\n      // Verify the continuous script is executed properly\n      expect(mockExeca).toHaveBeenCalledWith(\n        \"bash\",\n        [join(tempDir, \".repomirror\", \"ralph.sh\")],\n        expect.objectContaining({\n          stdio: \"inherit\",  // This allows the continuous output to be seen\n        })\n      );\n    });\n\n    it(\"should verify ralph.sh runs forever through proper execution setup\", async () => {\n      const foreverScript = `#!/bin/bash\nset -euo pipefail\n\n# Setup trap for graceful shutdown\ntrap 'echo \"Shutting down gracefully...\"; exit 0' SIGINT SIGTERM\n\necho \"Starting forever loop...\"\ncounter=0\n\nwhile true; do\n  ((counter++))\n  echo \"[Cycle $counter] Running sync...\"\n  \n  # Simulate sync work\n  if ./.repomirror/sync.sh; then\n    echo \"[Cycle $counter] Sync completed\"\n  else\n    echo \"[Cycle $counter] Sync failed, continuing...\"\n  fi\n  \n  echo -e \"===SLEEP===\\\\n===SLEEP===\\\\n\"\n  echo \"Sleeping 10 seconds before next cycle...\"\n  sleep 10\ndone`;\n\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"ralph.sh\": foreverScript,\n        },\n      });\n\n      // Mock a long-running process that eventually gets interrupted\n      const longRunningOutput = \"Starting forever loop...\\n[Cycle 1] Running sync...\\n[Cycle 1] Sync completed\\n===SLEEP===\\n===SLEEP===\\nSleeping 10 seconds before next cycle...\";\n      mockExeca.mockResolvedValue({ \n        stdout: longRunningOutput, \n        stderr: \"\", \n        exitCode: 0 \n      });\n\n      await syncForever();\n\n      // Verify the forever script is properly executed\n      expect(mockExeca).toHaveBeenCalledWith(\n        \"bash\",\n        [join(tempDir, \".repomirror\", \"ralph.sh\")],\n        expect.objectContaining({\n          stdio: \"inherit\", // Essential for monitoring continuous output\n          cwd: tempDir,\n        })\n      );\n      \n      expect(consoleMock.log).toHaveBeenCalledWith(\n        expect.stringContaining(\"Running ralph.sh (continuous sync)...\")\n      );\n      expect(consoleMock.log).toHaveBeenCalledWith(\n        expect.stringContaining(\"Press Ctrl+C to stop\")\n      );\n    });\n  });\n\n  describe(\"enhanced signal handling and process management\", () => {\n    it(\"should handle SIGTERM gracefully as a shutdown signal\", async () => {\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"ralph.sh\": \"#!/bin/bash\\ntrap 'exit 0' SIGTERM; while true; do sleep 1; done\",\n        },\n      });\n\n      const sigtermError = new Error(\"Process terminated\");\n      (sigtermError as any).signal = \"SIGTERM\";\n      mockExeca.mockRejectedValue(sigtermError);\n\n      await expect(syncForever()).rejects.toThrow(\"Process exit called with code 1\");\n\n      // SIGTERM should be treated as an error, not graceful user stop\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Sync forever failed: Process terminated\")\n      );\n      expect(consoleMock.log).not.toHaveBeenCalledWith(expect.stringContaining(\"Stopped by user\"));\n      expect(processMock.exit).toHaveBeenCalledWith(1);\n    });\n\n    it(\"should properly distinguish SIGINT from other termination signals\", async () => {\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"ralph.sh\": \"#!/bin/bash\\nwhile true; do sleep 1; done\",\n        },\n      });\n\n      // Test SIGINT (user interrupt) - should be handled gracefully\n      const sigintError = new Error(\"User interrupted\");\n      (sigintError as any).signal = \"SIGINT\";\n      mockExeca.mockRejectedValue(sigintError);\n\n      await syncForever();\n\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"Stopped by user\"));\n      expect(processMock.exit).not.toHaveBeenCalled();\n      \n      // Reset mocks and test non-SIGINT signal\n      mockExeca.mockClear();\n      consoleMock.log.mockClear();\n      consoleMock.error.mockClear();\n      \n      const sigkillError = new Error(\"Process killed\");\n      (sigkillError as any).signal = \"SIGKILL\";\n      mockExeca.mockRejectedValue(sigkillError);\n\n      await expect(syncForever()).rejects.toThrow(\"Process exit called with code 1\");\n      \n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Sync forever failed: Process killed\")\n      );\n      expect(consoleMock.log).not.toHaveBeenCalledWith(expect.stringContaining(\"Stopped by user\"));\n    });\n  });\n\n  describe(\"shell script execution and process management\", () => {\n    it(\"should execute ralph.sh with bash shell for proper script interpretation\", async () => {\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"ralph.sh\": \"#!/bin/bash\\nset -euo pipefail\\necho 'bash specific features'\",\n        },\n      });\n\n      mockExeca.mockResolvedValue({ stdout: \"bash specific features\", stderr: \"\", exitCode: 0 });\n\n      await syncForever();\n\n      // Verify bash is specifically used as the shell\n      expect(mockExeca).toHaveBeenCalledWith(\n        \"bash\", \n        expect.arrayContaining([join(tempDir, \".repomirror\", \"ralph.sh\")]), \n        expect.any(Object)\n      );\n    });\n\n    it(\"should pass correct execution context to subprocess\", async () => {\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"ralph.sh\": \"#!/bin/bash\\necho \\\"CWD: $PWD\\\"\",\n        },\n      });\n\n      mockExeca.mockResolvedValue({ stdout: `CWD: ${tempDir}`, stderr: \"\", exitCode: 0 });\n\n      await syncForever();\n\n      expect(mockExeca).toHaveBeenCalledWith(\n        \"bash\",\n        [join(tempDir, \".repomirror\", \"ralph.sh\")],\n        expect.objectContaining({\n          stdio: \"inherit\",    // For real-time output\n          cwd: tempDir,        // Correct working directory\n        })\n      );\n    });\n\n    it(\"should maintain process inheritance for continuous execution monitoring\", async () => {\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"ralph.sh\": `#!/bin/bash\nwhile true; do\n  echo \"[$(date)] Continuous execution...\"\n  sleep 1\ndone`,\n        },\n      });\n\n      // Mock a process that produces continuous output\n      mockExeca.mockResolvedValue({ \n        stdout: \"[date] Continuous execution...\\n[date] Continuous execution...\", \n        stderr: \"\", \n        exitCode: 0 \n      });\n\n      await syncForever();\n\n      // Verify stdio inheritance allows real-time monitoring\n      expect(mockExeca).toHaveBeenCalledWith(\n        expect.any(String),\n        expect.any(Array),\n        expect.objectContaining({\n          stdio: \"inherit\", // Critical for continuous process monitoring\n        })\n      );\n    });\n\n    it(\"should handle script execution failures with proper error context\", async () => {\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"ralph.sh\": \"#!/bin/bash\\nexit 42\",\n        },\n      });\n\n      const executionError = new Error(\"Script execution failed with exit code 42\");\n      (executionError as any).exitCode = 42;\n      (executionError as any).stderr = \"Script error output\";\n      mockExeca.mockRejectedValue(executionError);\n\n      await expect(syncForever()).rejects.toThrow(\"Process exit called with code 1\");\n\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Sync forever failed: Script execution failed with exit code 42\")\n      );\n      expect(processMock.exit).toHaveBeenCalledWith(1);\n    });\n\n    it(\"should handle permission denied during script execution\", async () => {\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"ralph.sh\": \"#!/bin/bash\\necho 'should not run'\",\n        },\n      });\n\n      const permissionError = new Error(\"Permission denied\");\n      (permissionError as any).code = \"EACCES\";\n      (permissionError as any).errno = -13;\n      mockExeca.mockRejectedValue(permissionError);\n\n      await expect(syncForever()).rejects.toThrow(\"Process exit called with code 1\");\n\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Sync forever failed: Permission denied\")\n      );\n    });\n  });\n});"
  },
  {
    "path": "tests/commands/sync-one.test.ts",
    "content": "import { describe, it, expect, vi, beforeEach, afterEach } from \"vitest\";\nimport { promises as fs } from \"fs\";\nimport { join } from \"path\";\nimport {\n  createTempDir,\n  cleanupTempDir,\n  mockConsole,\n  mockProcess,\n  createMockFileStructure,\n} from \"../helpers/test-utils\";\n\n// Mock the sync function since sync-one is just an alias\nconst mockSync = vi.fn();\n\nvi.mock(\"../../src/commands/sync\", () => ({\n  sync: mockSync,\n}));\n\n// Import the module after mocking\nconst { syncOne } = await import(\"../../src/commands/sync-one\");\n\ndescribe(\"sync-one command\", () => {\n  let tempDir: string;\n  let consoleMock: ReturnType<typeof mockConsole>;\n  let processMock: ReturnType<typeof mockProcess>;\n\n  beforeEach(async () => {\n    // Create temporary directory\n    tempDir = await createTempDir(\"repomirror-sync-one-\");\n\n    // Setup mocks\n    consoleMock = mockConsole();\n    processMock = mockProcess(true);\n\n    // Mock process.cwd to return our temp directory\n    processMock.cwd.mockReturnValue(tempDir);\n\n    // Clear all mocks\n    vi.clearAllMocks();\n  });\n\n  afterEach(async () => {\n    // Cleanup temp directory\n    await cleanupTempDir(tempDir);\n\n    // Restore all mocks\n    vi.restoreAllMocks();\n  });\n\n  describe(\"alias functionality\", () => {\n    it(\"should call the sync function when executed\", async () => {\n      // Mock sync to resolve successfully\n      mockSync.mockResolvedValue(undefined);\n\n      await syncOne();\n\n      // Verify that the sync function was called exactly once\n      expect(mockSync).toHaveBeenCalledTimes(1);\n      expect(mockSync).toHaveBeenCalledWith();\n    });\n\n    it(\"should pass through successful execution from sync\", async () => {\n      // Mock sync to resolve successfully\n      mockSync.mockResolvedValue(undefined);\n\n      const result = await syncOne();\n\n      // Verify successful completion (no return value)\n      expect(result).toBeUndefined();\n      expect(mockSync).toHaveBeenCalledTimes(1);\n    });\n\n    it(\"should pass through errors from sync function\", async () => {\n      // Mock sync to reject with an error\n      const syncError = new Error(\"Sync failed\");\n      mockSync.mockRejectedValue(syncError);\n\n      // Verify that syncOne propagates the error\n      await expect(syncOne()).rejects.toThrow(\"Sync failed\");\n      expect(mockSync).toHaveBeenCalledTimes(1);\n    });\n\n    it(\"should pass through process exit calls from sync\", async () => {\n      // Mock sync to throw a process exit error (as our test mock does)\n      mockSync.mockRejectedValue(new Error(\"Process exit called with code 1\"));\n\n      await expect(syncOne()).rejects.toThrow(\"Process exit called with code 1\");\n      expect(mockSync).toHaveBeenCalledTimes(1);\n    });\n  });\n\n  describe(\"integration behavior\", () => {\n    it(\"should maintain the same interface as sync command\", async () => {\n      mockSync.mockResolvedValue(undefined);\n\n      // syncOne should be a function that returns a Promise<void>\n      const result = syncOne();\n      expect(result).toBeInstanceOf(Promise);\n\n      await result;\n      expect(mockSync).toHaveBeenCalled();\n    });\n\n    it(\"should handle multiple consecutive calls\", async () => {\n      mockSync.mockResolvedValue(undefined);\n\n      // Call syncOne multiple times\n      await syncOne();\n      await syncOne();\n      await syncOne();\n\n      // Each call should result in a call to sync\n      expect(mockSync).toHaveBeenCalledTimes(3);\n    });\n\n    it(\"should handle async errors properly\", async () => {\n      // Test different types of errors that sync might throw\n      const errors = [\n        new Error(\"File not found\"),\n        new Error(\"Permission denied\"),\n        new Error(\"Script execution failed\"),\n      ];\n\n      for (const error of errors) {\n        mockSync.mockClear();\n        mockSync.mockRejectedValue(error);\n\n        await expect(syncOne()).rejects.toThrow(error.message);\n        expect(mockSync).toHaveBeenCalledTimes(1);\n      }\n    });\n  });\n\n  describe(\"command consistency\", () => {\n    it(\"should behave identically to sync command for successful execution\", async () => {\n      // Create a real sync environment to verify behavior is consistent\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"sync.sh\": `#!/bin/bash\necho \"Test sync execution\"`,\n        },\n      });\n\n      mockSync.mockResolvedValue(undefined);\n\n      await syncOne();\n\n      // Verify the same sync function is called with the same parameters\n      expect(mockSync).toHaveBeenCalledWith();\n    });\n\n    it(\"should maintain error handling consistency with sync\", async () => {\n      // Test that sync-one doesn't add any additional error handling\n      const syncError = new Error(\"Custom sync error with specific message\");\n      mockSync.mockRejectedValue(syncError);\n\n      try {\n        await syncOne();\n        // Should not reach here\n        expect(true).toBe(false);\n      } catch (error) {\n        // Error should be exactly the same as what sync threw\n        expect(error).toBe(syncError);\n      }\n\n      expect(mockSync).toHaveBeenCalledTimes(1);\n    });\n  });\n\n  describe(\"documentation and clarity\", () => {\n    it(\"should be clear that sync-one is an alias for sync\", async () => {\n      mockSync.mockResolvedValue(undefined);\n\n      // The function name and behavior should make it clear this is an alias\n      await syncOne();\n\n      // Should delegate entirely to sync function\n      expect(mockSync).toHaveBeenCalledTimes(1);\n      expect(mockSync).toHaveBeenCalledWith();\n    });\n\n    it(\"should be a true alias with zero functional differences from sync\", async () => {\n      mockSync.mockResolvedValue(undefined);\n\n      // Verify that syncOne is literally just a wrapper around sync\n      // with no additional logic, parameters, or side effects\n      await syncOne();\n\n      // Should call sync exactly once with no arguments\n      expect(mockSync).toHaveBeenCalledTimes(1);\n      expect(mockSync).toHaveBeenCalledWith();\n\n      // Should not have any other function calls or side effects\n      expect(consoleMock.log).not.toHaveBeenCalled();\n      expect(consoleMock.error).not.toHaveBeenCalled();\n      expect(consoleMock.warn).not.toHaveBeenCalled();\n    });\n\n    it(\"should not add any additional functionality beyond sync\", async () => {\n      mockSync.mockResolvedValue(undefined);\n\n      const startTime = Date.now();\n      await syncOne();\n      const endTime = Date.now();\n\n      // Should complete quickly since it's just a function call\n      expect(endTime - startTime).toBeLessThan(100);\n      expect(mockSync).toHaveBeenCalledTimes(1);\n    });\n  });\n\n  describe(\"error propagation\", () => {\n    it(\"should propagate file not found errors\", async () => {\n      const fileError = new Error(\"Process exit called with code 1\");\n      mockSync.mockRejectedValue(fileError);\n\n      await expect(syncOne()).rejects.toThrow(\"Process exit called with code 1\");\n    });\n\n    it(\"should propagate script execution errors\", async () => {\n      const execError = new Error(\"Script execution failed\");\n      mockSync.mockRejectedValue(execError);\n\n      await expect(syncOne()).rejects.toThrow(\"Script execution failed\");\n    });\n\n    it(\"should propagate permission errors\", async () => {\n      const permError = new Error(\"Permission denied\");\n      mockSync.mockRejectedValue(permError);\n\n      await expect(syncOne()).rejects.toThrow(\"Permission denied\");\n    });\n\n    it(\"should handle sync function returning promises correctly\", async () => {\n      // Test that syncOne properly awaits the sync promise\n      let syncResolved = false;\n      \n      mockSync.mockImplementation(async () => {\n        await new Promise(resolve => setTimeout(resolve, 10));\n        syncResolved = true;\n      });\n\n      await syncOne();\n\n      expect(syncResolved).toBe(true);\n      expect(mockSync).toHaveBeenCalledTimes(1);\n    });\n  });\n\n  describe(\"type safety and interface\", () => {\n    it(\"should have the same return type as sync\", async () => {\n      mockSync.mockResolvedValue(undefined);\n\n      const result = await syncOne();\n      \n      // Both sync and syncOne should return Promise<void>\n      expect(result).toBeUndefined();\n      expect(mockSync).toHaveBeenCalledTimes(1);\n    });\n\n    it(\"should accept no parameters like sync\", async () => {\n      mockSync.mockResolvedValue(undefined);\n\n      // syncOne should not accept any parameters\n      await syncOne();\n\n      // Verify sync was called with no parameters\n      expect(mockSync).toHaveBeenCalledWith();\n    });\n\n    it(\"should be usable in the same contexts as sync\", async () => {\n      mockSync.mockResolvedValue(undefined);\n\n      // Should be able to use syncOne anywhere sync can be used\n      const commands = [syncOne];\n      \n      for (const command of commands) {\n        await command();\n      }\n\n      expect(mockSync).toHaveBeenCalledTimes(1);\n    });\n  });\n\n  describe(\"performance and efficiency\", () => {\n    it(\"should add minimal overhead over sync\", async () => {\n      mockSync.mockResolvedValue(undefined);\n\n      const startTime = process.hrtime.bigint();\n      await syncOne();\n      const endTime = process.hrtime.bigint();\n\n      // Should complete very quickly as it's just a function call\n      const durationMs = Number(endTime - startTime) / 1_000_000;\n      expect(durationMs).toBeLessThan(50); // Less than 50ms overhead\n\n      expect(mockSync).toHaveBeenCalledTimes(1);\n    });\n\n    it(\"should not create unnecessary promises or async overhead\", async () => {\n      let syncCallCount = 0;\n      \n      mockSync.mockImplementation(async () => {\n        syncCallCount++;\n        return undefined;\n      });\n\n      await syncOne();\n\n      // Should result in exactly one call to sync\n      expect(syncCallCount).toBe(1);\n      expect(mockSync).toHaveBeenCalledTimes(1);\n    });\n  });\n\n  describe(\"console output verification\", () => {\n    it(\"should not produce any console output directly (all output from sync)\", async () => {\n      mockSync.mockResolvedValue(undefined);\n\n      await syncOne();\n\n      // syncOne should not produce any console output itself\n      // All output should come from the sync function it calls\n      expect(consoleMock.log).not.toHaveBeenCalled();\n      expect(consoleMock.error).not.toHaveBeenCalled();\n      expect(consoleMock.warn).not.toHaveBeenCalled();\n      expect(consoleMock.info).not.toHaveBeenCalled();\n      expect(mockSync).toHaveBeenCalledTimes(1);\n    });\n\n    it(\"should let sync handle all console output on success\", async () => {\n      // Mock sync to produce some console output\n      mockSync.mockImplementation(async () => {\n        console.log(\"Running sync.sh...\");\n        console.log(\"Sync completed successfully\");\n        return undefined;\n      });\n\n      await syncOne();\n\n      // Verify sync was called and produced output\n      expect(mockSync).toHaveBeenCalledTimes(1);\n      expect(consoleMock.log).toHaveBeenCalledWith(\"Running sync.sh...\");\n      expect(consoleMock.log).toHaveBeenCalledWith(\"Sync completed successfully\");\n    });\n\n    it(\"should let sync handle all console output on error\", async () => {\n      // Mock sync to produce error output before throwing\n      mockSync.mockImplementation(async () => {\n        console.error(\"Error: .repomirror/sync.sh not found. Run 'npx repomirror init' first.\");\n        throw new Error(\"Process exit called with code 1\");\n      });\n\n      await expect(syncOne()).rejects.toThrow(\"Process exit called with code 1\");\n\n      // Verify sync was called and produced error output\n      expect(mockSync).toHaveBeenCalledTimes(1);\n      expect(consoleMock.error).toHaveBeenCalledWith(\"Error: .repomirror/sync.sh not found. Run 'npx repomirror init' first.\");\n    });\n\n    it(\"should preserve the exact console output from sync\", async () => {\n      const testMessages = [\n        \"Running sync.sh...\",\n        \"Processing files...\",\n        \"Sync completed successfully\"\n      ];\n\n      mockSync.mockImplementation(async () => {\n        testMessages.forEach(msg => console.log(msg));\n        return undefined;\n      });\n\n      await syncOne();\n\n      // Verify all messages were logged in the correct order\n      expect(mockSync).toHaveBeenCalledTimes(1);\n      testMessages.forEach(msg => {\n        expect(consoleMock.log).toHaveBeenCalledWith(msg);\n      });\n      expect(consoleMock.log).toHaveBeenCalledTimes(testMessages.length);\n    });\n  });\n\n  describe(\"argument passing verification\", () => {\n    it(\"should pass no arguments to sync (both take zero parameters)\", async () => {\n      mockSync.mockResolvedValue(undefined);\n\n      // Call syncOne with no arguments (as it should be called)\n      await syncOne();\n\n      // Verify sync was called with exactly zero arguments\n      expect(mockSync).toHaveBeenCalledTimes(1);\n      expect(mockSync).toHaveBeenCalledWith();\n      expect(mockSync).toHaveBeenCalledWith(...[]); // Explicitly verify no args\n    });\n\n    it(\"should handle the fact that neither command accepts parameters\", async () => {\n      mockSync.mockResolvedValue(undefined);\n\n      // syncOne doesn't accept parameters, just like sync\n      const result = await syncOne();\n\n      // Verify the call signature matches sync exactly\n      expect(result).toBeUndefined();\n      expect(mockSync).toHaveBeenCalledTimes(1);\n      expect(mockSync).toHaveBeenCalledWith();\n    });\n\n    it(\"should maintain parameter consistency with sync command\", async () => {\n      mockSync.mockResolvedValue(undefined);\n\n      // Both commands should have identical function signatures\n      // syncOne: () => Promise<void>\n      // sync: () => Promise<void>\n      \n      // Test that syncOne behaves exactly like sync would\n      await syncOne();\n\n      expect(mockSync).toHaveBeenCalledTimes(1);\n      expect(mockSync).toHaveBeenCalledWith();\n\n      // Clear mocks and test direct sync call for comparison\n      mockSync.mockClear();\n      await mockSync();\n\n      // Both calls should be identical\n      expect(mockSync).toHaveBeenCalledTimes(1);\n      expect(mockSync).toHaveBeenCalledWith();\n    });\n  });\n\n  describe(\"delegation verification\", () => {\n    it(\"should delegate 100% of functionality to sync\", async () => {\n      mockSync.mockResolvedValue(undefined);\n\n      await syncOne();\n\n      // syncOne should do nothing except call sync\n      expect(mockSync).toHaveBeenCalledTimes(1);\n      expect(mockSync).toHaveBeenCalledWith();\n\n      // No other system calls should be made by syncOne itself\n      expect(processMock.exit).not.toHaveBeenCalled();\n      expect(consoleMock.log).not.toHaveBeenCalled();\n      expect(consoleMock.error).not.toHaveBeenCalled();\n    });\n\n    it(\"should delegate error handling entirely to sync\", async () => {\n      const customError = new Error(\"Custom delegation test error\");\n      mockSync.mockRejectedValue(customError);\n\n      try {\n        await syncOne();\n        expect(true).toBe(false); // Should not reach here\n      } catch (error) {\n        // Error should be the exact same object from sync\n        expect(error).toBe(customError);\n        expect(error.message).toBe(\"Custom delegation test error\");\n      }\n\n      expect(mockSync).toHaveBeenCalledTimes(1);\n    });\n\n    it(\"should delegate success handling entirely to sync\", async () => {\n      const customReturnValue = undefined; // sync returns Promise<void>\n      mockSync.mockResolvedValue(customReturnValue);\n\n      const result = await syncOne();\n\n      // Result should be exactly what sync returned\n      expect(result).toBe(customReturnValue);\n      expect(mockSync).toHaveBeenCalledTimes(1);\n    });\n\n    it(\"should delegate all async behavior to sync\", async () => {\n      let syncStarted = false;\n      let syncCompleted = false;\n\n      mockSync.mockImplementation(async () => {\n        syncStarted = true;\n        await new Promise(resolve => setTimeout(resolve, 50));\n        syncCompleted = true;\n        return undefined;\n      });\n\n      // Before calling syncOne, sync should not have started\n      expect(syncStarted).toBe(false);\n      expect(syncCompleted).toBe(false);\n\n      const promise = syncOne();\n      \n      // After calling syncOne but before awaiting, sync should have started\n      expect(syncStarted).toBe(true);\n      expect(syncCompleted).toBe(false);\n\n      await promise;\n\n      // After awaiting, sync should be completed\n      expect(syncCompleted).toBe(true);\n      expect(mockSync).toHaveBeenCalledTimes(1);\n    });\n  });\n\n  describe(\"comprehensive error propagation\", () => {\n    it(\"should propagate all error types without modification\", async () => {\n      const errorTypes = [\n        new Error(\"Standard error\"),\n        new TypeError(\"Type error\"),\n        new ReferenceError(\"Reference error\"),\n        new SyntaxError(\"Syntax error\"),\n        { name: \"CustomError\", message: \"Custom error object\" },\n        \"String error\",\n        42, // Number error\n        null,\n        undefined\n      ];\n\n      for (const error of errorTypes) {\n        mockSync.mockClear();\n        mockSync.mockRejectedValue(error);\n\n        try {\n          await syncOne();\n          expect(true).toBe(false); // Should not reach here\n        } catch (caughtError) {\n          // Error should be exactly the same object/value\n          expect(caughtError).toBe(error);\n        }\n\n        expect(mockSync).toHaveBeenCalledTimes(1);\n      }\n    });\n\n    it(\"should preserve error stack traces\", async () => {\n      const errorWithStack = new Error(\"Error with stack trace\");\n      const originalStack = errorWithStack.stack;\n      mockSync.mockRejectedValue(errorWithStack);\n\n      try {\n        await syncOne();\n        expect(true).toBe(false); // Should not reach here\n      } catch (error) {\n        // Stack trace should be preserved\n        expect(error.stack).toBe(originalStack);\n      }\n\n      expect(mockSync).toHaveBeenCalledTimes(1);\n    });\n\n    it(\"should handle promise rejection timing correctly\", async () => {\n      let rejectionHandled = false;\n      \n      mockSync.mockImplementation(async () => {\n        await new Promise(resolve => setTimeout(resolve, 10));\n        throw new Error(\"Delayed rejection\");\n      });\n\n      try {\n        await syncOne();\n        expect(true).toBe(false); // Should not reach here\n      } catch (error) {\n        rejectionHandled = true;\n        expect(error.message).toBe(\"Delayed rejection\");\n      }\n\n      expect(rejectionHandled).toBe(true);\n      expect(mockSync).toHaveBeenCalledTimes(1);\n    });\n  });\n\n  describe(\"edge cases and robustness\", () => {\n    it(\"should handle sync returning resolved promises correctly\", async () => {\n      // Test with already resolved promise\n      mockSync.mockReturnValue(Promise.resolve(undefined));\n\n      const result = await syncOne();\n\n      expect(result).toBeUndefined();\n      expect(mockSync).toHaveBeenCalledTimes(1);\n    });\n\n    it(\"should handle sync returning rejected promises correctly\", async () => {\n      // Test with already rejected promise\n      const error = new Error(\"Pre-rejected promise\");\n      mockSync.mockReturnValue(Promise.reject(error));\n\n      await expect(syncOne()).rejects.toThrow(\"Pre-rejected promise\");\n      expect(mockSync).toHaveBeenCalledTimes(1);\n    });\n\n    it(\"should work correctly when called in quick succession\", async () => {\n      mockSync.mockResolvedValue(undefined);\n\n      // Fire off multiple calls simultaneously\n      const promises = [\n        syncOne(),\n        syncOne(),\n        syncOne()\n      ];\n\n      await Promise.all(promises);\n\n      // Each call should result in a separate call to sync\n      expect(mockSync).toHaveBeenCalledTimes(3);\n    });\n\n    it(\"should handle mixed success and failure scenarios\", async () => {\n      // First call succeeds\n      mockSync.mockResolvedValue(undefined);\n      await syncOne();\n      expect(mockSync).toHaveBeenCalledTimes(1);\n\n      // Second call fails\n      mockSync.mockClear();\n      mockSync.mockRejectedValue(new Error(\"Second call failed\"));\n      await expect(syncOne()).rejects.toThrow(\"Second call failed\");\n      expect(mockSync).toHaveBeenCalledTimes(1);\n\n      // Third call succeeds again\n      mockSync.mockClear();\n      mockSync.mockResolvedValue(undefined);\n      await syncOne();\n      expect(mockSync).toHaveBeenCalledTimes(1);\n    });\n  });\n});"
  },
  {
    "path": "tests/commands/sync.test.ts",
    "content": "import { describe, it, expect, vi, beforeEach, afterEach } from \"vitest\";\nimport { promises as fs } from \"fs\";\nimport { join } from \"path\";\nimport {\n  createTempDir,\n  cleanupTempDir,\n  mockConsole,\n  mockProcess,\n  createMockFileStructure,\n} from \"../helpers/test-utils\";\n\n// Mock external dependencies at module level\nconst mockExeca = vi.fn();\n\nvi.mock(\"execa\", () => ({\n  execa: mockExeca,\n}));\n\n// Import the module after mocking\nconst { sync } = await import(\"../../src/commands/sync\");\n\ndescribe(\"sync command\", () => {\n  let tempDir: string;\n  let consoleMock: ReturnType<typeof mockConsole>;\n  let processMock: ReturnType<typeof mockProcess>;\n\n  beforeEach(async () => {\n    // Create temporary directory\n    tempDir = await createTempDir(\"repomirror-sync-\");\n\n    // Setup mocks\n    consoleMock = mockConsole();\n    processMock = mockProcess(true); // Throw on process.exit by default\n\n    // Mock process.cwd to return our temp directory\n    processMock.cwd.mockReturnValue(tempDir);\n\n    // Clear all mocks\n    vi.clearAllMocks();\n  });\n\n  afterEach(async () => {\n    // Cleanup temp directory\n    await cleanupTempDir(tempDir);\n\n    // Restore all mocks\n    vi.restoreAllMocks();\n  });\n\n  describe(\"successful execution\", () => {\n    it(\"should execute sync.sh successfully when script exists\", async () => {\n      // Create .repomirror directory and sync.sh script\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"sync.sh\": `#!/bin/bash\ncat .repomirror/prompt.md | \\\\\n        claude -p --output-format=stream-json --verbose --dangerously-skip-permissions --add-dir ../target | \\\\\n        tee -a .repomirror/claude_output.jsonl | \\\\\n        npx repomirror visualize --debug;`,\n        },\n      });\n\n      // Mock successful execa execution\n      mockExeca.mockResolvedValue({\n        stdout: \"Sync completed\",\n        stderr: \"\",\n        exitCode: 0,\n      });\n\n      // Run sync\n      await sync();\n\n      // Verify execa was called with correct parameters\n      expect(mockExeca).toHaveBeenCalledWith(\"bash\", [join(tempDir, \".repomirror\", \"sync.sh\")], {\n        stdio: \"inherit\",\n        cwd: tempDir,\n      });\n\n      // Verify console output\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"Running sync.sh...\"));\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"Sync completed successfully\"));\n\n      // Verify process.exit was not called\n      expect(processMock.exit).not.toHaveBeenCalled();\n    });\n\n    it(\"should use correct working directory and script path\", async () => {\n      // Create .repomirror directory and sync.sh script\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"sync.sh\": \"#!/bin/bash\\necho 'test sync'\",\n        },\n      });\n\n      // Mock successful execa execution\n      mockExeca.mockResolvedValue({\n        stdout: \"test sync\",\n        stderr: \"\",\n        exitCode: 0,\n      });\n\n      await sync();\n\n      const expectedScriptPath = join(tempDir, \".repomirror\", \"sync.sh\");\n\n      // Verify execa was called with absolute path to sync.sh\n      expect(mockExeca).toHaveBeenCalledWith(\"bash\", [expectedScriptPath], {\n        stdio: \"inherit\",\n        cwd: tempDir,\n      });\n    });\n\n    it(\"should inherit stdio for interactive output\", async () => {\n      // Create .repomirror directory and sync.sh script\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"sync.sh\": \"#!/bin/bash\\necho 'interactive output'\",\n        },\n      });\n\n      mockExeca.mockResolvedValue({\n        stdout: \"interactive output\",\n        stderr: \"\",\n        exitCode: 0,\n      });\n\n      await sync();\n\n      // Verify stdio: \"inherit\" was passed to execa\n      expect(mockExeca).toHaveBeenCalledWith(\n        \"bash\",\n        [join(tempDir, \".repomirror\", \"sync.sh\")],\n        expect.objectContaining({\n          stdio: \"inherit\",\n        })\n      );\n    });\n  });\n\n  describe(\"error cases\", () => {\n    it(\"should exit with error when .repomirror/sync.sh does not exist\", async () => {\n      // Don't create the sync.sh script\n\n      await expect(sync()).rejects.toThrow(\"Process exit called with code 1\");\n\n      // Verify error message\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Error: .repomirror/sync.sh not found. Run 'npx repomirror init' first.\")\n      );\n\n      // Verify process.exit was called with code 1\n      expect(processMock.exit).toHaveBeenCalledWith(1);\n\n      // Verify execa was not called\n      expect(mockExeca).not.toHaveBeenCalled();\n    });\n\n    it(\"should exit with error when .repomirror directory does not exist\", async () => {\n      // Don't create the .repomirror directory at all\n\n      await expect(sync()).rejects.toThrow(\"Process exit called with code 1\");\n\n      // Verify error message\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Error: .repomirror/sync.sh not found. Run 'npx repomirror init' first.\")\n      );\n\n      // Verify process.exit was called with code 1\n      expect(processMock.exit).toHaveBeenCalledWith(1);\n\n      // Verify execa was not called\n      expect(mockExeca).not.toHaveBeenCalled();\n    });\n\n    it(\"should handle script execution errors gracefully\", async () => {\n      // Create .repomirror directory and sync.sh script\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"sync.sh\": \"#!/bin/bash\\nexit 1\",\n        },\n      });\n\n      // Mock failed execa execution\n      const scriptError = new Error(\"Script execution failed\");\n      (scriptError as any).exitCode = 1;\n      mockExeca.mockRejectedValue(scriptError);\n\n      await expect(sync()).rejects.toThrow(\"Process exit called with code 1\");\n\n      // Verify initial success message\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"Running sync.sh...\"));\n\n      // Verify error message\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Sync failed: Script execution failed\")\n      );\n\n      // Verify process.exit was called with code 1\n      expect(processMock.exit).toHaveBeenCalledWith(1);\n    });\n\n    it(\"should handle non-Error exceptions in script execution\", async () => {\n      // Create .repomirror directory and sync.sh script\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"sync.sh\": \"#!/bin/bash\\necho 'failing'\",\n        },\n      });\n\n      // Mock execa to throw a non-Error object\n      mockExeca.mockRejectedValue(\"String error\");\n\n      await expect(sync()).rejects.toThrow(\"Process exit called with code 1\");\n\n      // Verify error message handles non-Error exceptions\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Sync failed: String error\")\n      );\n\n      // Verify process.exit was called with code 1\n      expect(processMock.exit).toHaveBeenCalledWith(1);\n    });\n  });\n\n  describe(\"console output verification\", () => {\n    beforeEach(async () => {\n      // Create .repomirror directory and sync.sh script for all output tests\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"sync.sh\": \"#!/bin/bash\\necho 'sync output'\",\n        },\n      });\n    });\n\n    it(\"should show cyan colored 'Running sync.sh...' message\", async () => {\n      mockExeca.mockResolvedValue({ stdout: \"\", stderr: \"\", exitCode: 0 });\n\n      await sync();\n\n      // Check that the running message was logged\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"Running sync.sh...\"));\n    });\n\n    it(\"should show green colored success message on completion\", async () => {\n      mockExeca.mockResolvedValue({ stdout: \"\", stderr: \"\", exitCode: 0 });\n\n      await sync();\n\n      // Check that the success message was logged\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"Sync completed successfully\"));\n    });\n\n    it(\"should show red colored error message on failure\", async () => {\n      const error = new Error(\"Command failed\");\n      mockExeca.mockRejectedValue(error);\n\n      await expect(sync()).rejects.toThrow(\"Process exit called with code 1\");\n\n      // Check that the error message was logged\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Sync failed: Command failed\")\n      );\n    });\n\n    it(\"should show red colored error message when sync.sh is missing\", async () => {\n      // Remove the sync.sh script\n      await fs.rm(join(tempDir, \".repomirror\", \"sync.sh\"));\n\n      await expect(sync()).rejects.toThrow(\"Process exit called with code 1\");\n\n      // Check that the missing file error message was logged\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Error: .repomirror/sync.sh not found. Run 'npx repomirror init' first.\")\n      );\n    });\n  });\n\n  describe(\"file system access patterns\", () => {\n    it(\"should use fs.access to check file existence\", async () => {\n      const fsAccessSpy = vi.spyOn(fs, \"access\");\n\n      // Create the script so access check passes\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"sync.sh\": \"#!/bin/bash\\necho 'test'\",\n        },\n      });\n\n      mockExeca.mockResolvedValue({ stdout: \"\", stderr: \"\", exitCode: 0 });\n\n      await sync();\n\n      // Verify fs.access was called with the correct path\n      expect(fsAccessSpy).toHaveBeenCalledWith(join(tempDir, \".repomirror\", \"sync.sh\"));\n\n      fsAccessSpy.mockRestore();\n    });\n\n    it(\"should handle permission denied errors on file access\", async () => {\n      const fsAccessSpy = vi.spyOn(fs, \"access\");\n      fsAccessSpy.mockRejectedValue(new Error(\"EACCES: permission denied\"));\n\n      await expect(sync()).rejects.toThrow(\"Process exit called with code 1\");\n\n      // Verify the error message still shows file not found (since we catch all access errors)\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Error: .repomirror/sync.sh not found. Run 'npx repomirror init' first.\")\n      );\n\n      fsAccessSpy.mockRestore();\n    });\n  });\n\n  describe(\"shell script execution with different exit codes\", () => {\n    it(\"should handle script that exits with code 2\", async () => {\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"sync.sh\": \"#!/bin/bash\\nexit 2\",\n        },\n      });\n\n      // Mock execa to reject with exit code 2\n      const scriptError = new Error(\"Command failed with exit code 2\");\n      (scriptError as any).exitCode = 2;\n      (scriptError as any).stderr = \"Error: Invalid argument\";\n      mockExeca.mockRejectedValue(scriptError);\n\n      await expect(sync()).rejects.toThrow(\"Process exit called with code 1\");\n\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"Running sync.sh...\"));\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Sync failed: Command failed with exit code 2\")\n      );\n      expect(processMock.exit).toHaveBeenCalledWith(1);\n    });\n\n    it(\"should handle script that exits with code 127 (command not found)\", async () => {\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"sync.sh\": \"#!/bin/bash\\nnonexistent_command\",\n        },\n      });\n\n      const scriptError = new Error(\"Command failed: nonexistent_command: command not found\");\n      (scriptError as any).exitCode = 127;\n      mockExeca.mockRejectedValue(scriptError);\n\n      await expect(sync()).rejects.toThrow(\"Process exit called with code 1\");\n\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Sync failed: Command failed: nonexistent_command: command not found\")\n      );\n      expect(processMock.exit).toHaveBeenCalledWith(1);\n    });\n\n    it(\"should handle script that succeeds with non-zero but success exit code\", async () => {\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"sync.sh\": \"#!/bin/bash\\necho 'success with warnings'\\nexit 0\",\n        },\n      });\n\n      mockExeca.mockResolvedValue({ \n        stdout: \"success with warnings\", \n        stderr: \"warning: deprecated option used\", \n        exitCode: 0 \n      });\n\n      await sync();\n\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"Running sync.sh...\"));\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"Sync completed successfully\"));\n      expect(processMock.exit).not.toHaveBeenCalled();\n    });\n  });\n\n  describe(\"stdout and stderr capture handling\", () => {\n    it(\"should handle scripts that output to stdout\", async () => {\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"sync.sh\": \"#!/bin/bash\\necho 'Processing files...'\\necho 'Sync complete'\",\n        },\n      });\n\n      // Note: With stdio: 'inherit', stdout/stderr go directly to console, not captured\n      mockExeca.mockResolvedValue({ \n        stdout: \"Processing files...\\nSync complete\", \n        stderr: \"\", \n        exitCode: 0 \n      });\n\n      await sync();\n\n      // Verify execa was called with stdio: 'inherit' which means output goes directly to console\n      expect(mockExeca).toHaveBeenCalledWith(\"bash\", [join(tempDir, \".repomirror\", \"sync.sh\")], {\n        stdio: \"inherit\",\n        cwd: tempDir,\n      });\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"Sync completed successfully\"));\n    });\n\n    it(\"should handle scripts that output to stderr\", async () => {\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"sync.sh\": \"#!/bin/bash\\necho 'warning message' >&2\\nexit 0\",\n        },\n      });\n\n      mockExeca.mockResolvedValue({ \n        stdout: \"\", \n        stderr: \"warning message\", \n        exitCode: 0 \n      });\n\n      await sync();\n\n      expect(mockExeca).toHaveBeenCalledWith(\"bash\", [join(tempDir, \".repomirror\", \"sync.sh\")], {\n        stdio: \"inherit\",\n        cwd: tempDir,\n      });\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"Sync completed successfully\"));\n    });\n\n    it(\"should handle scripts with mixed stdout and stderr output\", async () => {\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"sync.sh\": \"#!/bin/bash\\necho 'Starting sync'\\necho 'warning' >&2\\necho 'Finished'\",\n        },\n      });\n\n      mockExeca.mockResolvedValue({ \n        stdout: \"Starting sync\\nFinished\", \n        stderr: \"warning\", \n        exitCode: 0 \n      });\n\n      await sync();\n\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"Sync completed successfully\"));\n      expect(processMock.exit).not.toHaveBeenCalled();\n    });\n  });\n\n  describe(\"permission and execution error handling\", () => {\n    it(\"should handle permission denied when executing script\", async () => {\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"sync.sh\": \"#!/bin/bash\\necho 'test'\",\n        },\n      });\n\n      // Mock execa to reject with permission denied error\n      const permissionError = new Error(\"Permission denied\");\n      (permissionError as any).code = \"EACCES\";\n      mockExeca.mockRejectedValue(permissionError);\n\n      await expect(sync()).rejects.toThrow(\"Process exit called with code 1\");\n\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Sync failed: Permission denied\")\n      );\n      expect(processMock.exit).toHaveBeenCalledWith(1);\n    });\n\n    it(\"should handle bash command not found error\", async () => {\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"sync.sh\": \"#!/bin/bash\\necho 'test'\",\n        },\n      });\n\n      const commandError = new Error(\"bash: command not found\");\n      (commandError as any).code = \"ENOENT\";\n      mockExeca.mockRejectedValue(commandError);\n\n      await expect(sync()).rejects.toThrow(\"Process exit called with code 1\");\n\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Sync failed: bash: command not found\")\n      );\n      expect(processMock.exit).toHaveBeenCalledWith(1);\n    });\n\n    it(\"should handle file system errors during script execution\", async () => {\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"sync.sh\": \"#!/bin/bash\\necho 'test'\",\n        },\n      });\n\n      const fsError = new Error(\"EIO: i/o error, read\");\n      (fsError as any).code = \"EIO\";\n      mockExeca.mockRejectedValue(fsError);\n\n      await expect(sync()).rejects.toThrow(\"Process exit called with code 1\");\n\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Sync failed: EIO: i/o error, read\")\n      );\n      expect(processMock.exit).toHaveBeenCalledWith(1);\n    });\n  });\n\n  describe(\"working directory context preservation\", () => {\n    it(\"should maintain current working directory after sync execution\", async () => {\n      const originalCwd = tempDir;\n      processMock.cwd.mockReturnValue(originalCwd);\n\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"sync.sh\": \"#!/bin/bash\\ncd / && echo 'changed directory'\",\n        },\n      });\n\n      mockExeca.mockResolvedValue({ stdout: \"changed directory\", stderr: \"\", exitCode: 0 });\n\n      await sync();\n\n      // Verify that the working directory is preserved in the execa call\n      expect(mockExeca).toHaveBeenCalledWith(\"bash\", [join(tempDir, \".repomirror\", \"sync.sh\")], {\n        stdio: \"inherit\",\n        cwd: originalCwd,\n      });\n\n      // The process.cwd() should still return the original directory\n      expect(processMock.cwd).toHaveBeenCalled();\n    });\n\n    it(\"should handle scripts that change directory internally\", async () => {\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"sync.sh\": `#!/bin/bash\ncd subdir\necho \"Working in $(pwd)\"\ncd ..\necho \"Back to $(pwd)\"`,\n        },\n        \"subdir\": {},\n      });\n\n      mockExeca.mockResolvedValue({ \n        stdout: `Working in ${tempDir}/subdir\\nBack to ${tempDir}`, \n        stderr: \"\", \n        exitCode: 0 \n      });\n\n      await sync();\n\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"Sync completed successfully\"));\n      expect(mockExeca).toHaveBeenCalledWith(\"bash\", [join(tempDir, \".repomirror\", \"sync.sh\")], {\n        stdio: \"inherit\",\n        cwd: tempDir,\n      });\n    });\n\n    it(\"should work correctly when invoked from different working directories\", async () => {\n      // Create nested directory structure\n      const nestedDir = join(tempDir, \"nested\", \"deep\");\n      await fs.mkdir(nestedDir, { recursive: true });\n      \n      // Create script in the nested directory\n      await createMockFileStructure(nestedDir, {\n        \".repomirror\": {\n          \"sync.sh\": \"#!/bin/bash\\necho 'nested sync'\",\n        },\n      });\n\n      // Mock cwd to return the nested directory\n      processMock.cwd.mockReturnValue(nestedDir);\n\n      mockExeca.mockResolvedValue({ stdout: \"nested sync\", stderr: \"\", exitCode: 0 });\n\n      await sync();\n\n      // Should use the nested directory path\n      expect(mockExeca).toHaveBeenCalledWith(\n        \"bash\", \n        [join(nestedDir, \".repomirror\", \"sync.sh\")], \n        {\n          stdio: \"inherit\",\n          cwd: nestedDir,\n        }\n      );\n    });\n  });\n\n  describe(\"script verification and existence checks\", () => {\n    it(\"should verify script exists before execution using fs.access\", async () => {\n      const fsAccessSpy = vi.spyOn(fs, \"access\");\n      \n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"sync.sh\": \"#!/bin/bash\\necho 'test'\",\n        },\n      });\n\n      mockExeca.mockResolvedValue({ stdout: \"test\", stderr: \"\", exitCode: 0 });\n\n      await sync();\n\n      // Verify fs.access was called to check file existence\n      expect(fsAccessSpy).toHaveBeenCalledWith(join(tempDir, \".repomirror\", \"sync.sh\"));\n      \n      fsAccessSpy.mockRestore();\n    });\n\n    it(\"should handle fs.access throwing ENOENT error\", async () => {\n      const fsAccessSpy = vi.spyOn(fs, \"access\");\n      const enoentError = new Error(\"ENOENT: no such file or directory\");\n      (enoentError as any).code = \"ENOENT\";\n      fsAccessSpy.mockRejectedValue(enoentError);\n\n      await expect(sync()).rejects.toThrow(\"Process exit called with code 1\");\n\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Error: .repomirror/sync.sh not found. Run 'npx repomirror init' first.\")\n      );\n      expect(mockExeca).not.toHaveBeenCalled();\n      \n      fsAccessSpy.mockRestore();\n    });\n\n    it(\"should handle fs.access throwing ENOTDIR error\", async () => {\n      // Create a file where the .repomirror directory should be\n      await fs.writeFile(join(tempDir, \".repomirror\"), \"not a directory\");\n\n      await expect(sync()).rejects.toThrow(\"Process exit called with code 1\");\n\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Error: .repomirror/sync.sh not found. Run 'npx repomirror init' first.\")\n      );\n      expect(mockExeca).not.toHaveBeenCalled();\n    });\n\n    it(\"should handle script that exists but is not readable\", async () => {\n      const fsAccessSpy = vi.spyOn(fs, \"access\");\n      const permissionError = new Error(\"EACCES: permission denied\");\n      (permissionError as any).code = \"EACCES\";\n      fsAccessSpy.mockRejectedValue(permissionError);\n\n      await expect(sync()).rejects.toThrow(\"Process exit called with code 1\");\n\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Error: .repomirror/sync.sh not found. Run 'npx repomirror init' first.\")\n      );\n      expect(mockExeca).not.toHaveBeenCalled();\n      \n      fsAccessSpy.mockRestore();\n    });\n  });\n\n  describe(\"edge cases\", () => {\n    it(\"should handle empty script content\", async () => {\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"sync.sh\": \"\",\n        },\n      });\n\n      mockExeca.mockResolvedValue({ stdout: \"\", stderr: \"\", exitCode: 0 });\n\n      await sync();\n\n      // Should still execute successfully\n      expect(mockExeca).toHaveBeenCalled();\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"Sync completed successfully\"));\n    });\n\n    it(\"should handle script with complex bash syntax\", async () => {\n      const complexScript = `#!/bin/bash\nset -euo pipefail\n\n# Complex sync script with pipes and redirects\ncat .repomirror/prompt.md | \\\\\n  claude -p --output-format=stream-json --verbose --dangerously-skip-permissions --add-dir ../target | \\\\\n  tee -a .repomirror/claude_output.jsonl | \\\\\n  npx repomirror visualize --debug\n\nif [ $? -eq 0 ]; then\n  echo \"Sync successful\"\nelse\n  echo \"Sync failed\" >&2\n  exit 1\nfi`;\n\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"sync.sh\": complexScript,\n        },\n      });\n\n      mockExeca.mockResolvedValue({ stdout: \"Sync successful\", stderr: \"\", exitCode: 0 });\n\n      await sync();\n\n      // Should execute the complex script successfully\n      expect(mockExeca).toHaveBeenCalledWith(\"bash\", [join(tempDir, \".repomirror\", \"sync.sh\")], {\n        stdio: \"inherit\",\n        cwd: tempDir,\n      });\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"Sync completed successfully\"));\n    });\n\n    it(\"should handle scripts in different working directories\", async () => {\n      const subdirPath = join(tempDir, \"subdir\");\n      await fs.mkdir(subdirPath, { recursive: true });\n\n      // Mock cwd to return subdirectory\n      processMock.cwd.mockReturnValue(subdirPath);\n\n      // Create script in subdirectory's .repomirror folder\n      await createMockFileStructure(subdirPath, {\n        \".repomirror\": {\n          \"sync.sh\": \"#!/bin/bash\\necho 'subdir sync'\",\n        },\n      });\n\n      mockExeca.mockResolvedValue({ stdout: \"subdir sync\", stderr: \"\", exitCode: 0 });\n\n      await sync();\n\n      // Should use the subdirectory as working directory\n      expect(mockExeca).toHaveBeenCalledWith(\n        \"bash\",\n        [join(subdirPath, \".repomirror\", \"sync.sh\")],\n        expect.objectContaining({\n          cwd: subdirPath,\n        })\n      );\n    });\n\n    it(\"should handle very large script files\", async () => {\n      // Create a large script with many lines\n      const largeScript = [\n        \"#!/bin/bash\",\n        \"set -e\",\n        ...Array(1000).fill(0).map((_, i) => `echo \"Line ${i}\"`),\n        \"echo 'Large script completed'\"\n      ].join(\"\\n\");\n\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"sync.sh\": largeScript,\n        },\n      });\n\n      mockExeca.mockResolvedValue({ stdout: \"Large script completed\", stderr: \"\", exitCode: 0 });\n\n      await sync();\n\n      expect(mockExeca).toHaveBeenCalledWith(\"bash\", [join(tempDir, \".repomirror\", \"sync.sh\")], {\n        stdio: \"inherit\",\n        cwd: tempDir,\n      });\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"Sync completed successfully\"));\n    });\n\n    it(\"should handle scripts with special characters in path\", async () => {\n      // Test script execution when the path contains special characters\n      const specialDir = join(tempDir, \"dir with spaces\");\n      await fs.mkdir(specialDir, { recursive: true });\n      \n      processMock.cwd.mockReturnValue(specialDir);\n\n      await createMockFileStructure(specialDir, {\n        \".repomirror\": {\n          \"sync.sh\": \"#!/bin/bash\\necho 'special path sync'\",\n        },\n      });\n\n      mockExeca.mockResolvedValue({ stdout: \"special path sync\", stderr: \"\", exitCode: 0 });\n\n      await sync();\n\n      expect(mockExeca).toHaveBeenCalledWith(\n        \"bash\", \n        [join(specialDir, \".repomirror\", \"sync.sh\")], \n        {\n          stdio: \"inherit\",\n          cwd: specialDir,\n        }\n      );\n    });\n\n    it(\"should handle script execution timeout scenarios\", async () => {\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"sync.sh\": \"#!/bin/bash\\nsleep 300\",\n        },\n      });\n\n      // Mock a timeout error\n      const timeoutError = new Error(\"Command timed out after 30000 milliseconds\");\n      (timeoutError as any).timedOut = true;\n      mockExeca.mockRejectedValue(timeoutError);\n\n      await expect(sync()).rejects.toThrow(\"Process exit called with code 1\");\n\n      expect(consoleMock.error).toHaveBeenCalledWith(\n        expect.stringContaining(\"Sync failed: Command timed out after 30000 milliseconds\")\n      );\n      expect(processMock.exit).toHaveBeenCalledWith(1);\n    });\n\n    it(\"should handle scripts with binary output\", async () => {\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"sync.sh\": \"#!/bin/bash\\nprintf '\\\\x00\\\\x01\\\\x02\\\\x03'\",\n        },\n      });\n\n      // Mock binary output\n      mockExeca.mockResolvedValue({ \n        stdout: Buffer.from([0, 1, 2, 3]).toString(), \n        stderr: \"\", \n        exitCode: 0 \n      });\n\n      await sync();\n\n      expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining(\"Sync completed successfully\"));\n      expect(processMock.exit).not.toHaveBeenCalled();\n    });\n  });\n\n  describe(\"process and signal handling\", () => {\n    it(\"should preserve working directory context\", async () => {\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"sync.sh\": \"#!/bin/bash\\npwd\",\n        },\n      });\n\n      mockExeca.mockResolvedValue({ stdout: tempDir, stderr: \"\", exitCode: 0 });\n\n      await sync();\n\n      // Verify that execa is called with the correct working directory\n      expect(mockExeca).toHaveBeenCalledWith(\n        \"bash\",\n        [join(tempDir, \".repomirror\", \"sync.sh\")],\n        expect.objectContaining({\n          cwd: tempDir,\n        })\n      );\n    });\n\n    it(\"should handle bash command execution with proper shell\", async () => {\n      await createMockFileStructure(tempDir, {\n        \".repomirror\": {\n          \"sync.sh\": \"#!/bin/bash\\necho 'bash execution'\",\n        },\n      });\n\n      mockExeca.mockResolvedValue({ stdout: \"bash execution\", stderr: \"\", exitCode: 0 });\n\n      await sync();\n\n      // Verify that bash is used as the shell command\n      expect(mockExeca).toHaveBeenCalledWith(\n        \"bash\",\n        expect.any(Array),\n        expect.any(Object)\n      );\n    });\n  });\n});"
  },
  {
    "path": "tests/commands/visualize.test.ts",
    "content": "import { describe, it, expect, vi, beforeEach, afterEach } from \"vitest\";\nimport { createInterface } from \"node:readline\";\n\n// Mock process.stdout.write\nconst mockStdoutWrite = vi.fn();\n\nvi.mock(\"node:readline\", () => ({\n  createInterface: vi.fn(),\n}));\n\n// Import the module after mocking\nconst { visualize } = await import(\"../../src/commands/visualize\");\n\ndescribe(\"visualize command\", () => {\n  let mockReadlineInterface: any;\n  let originalProcessArgv: string[];\n  let stdoutWriteSpy: any;\n\n  beforeEach(() => {\n    // Save original values\n    originalProcessArgv = [...process.argv];\n\n    // Mock readline interface\n    mockReadlineInterface = {\n      on: vi.fn(),\n    };\n\n    vi.mocked(createInterface).mockReturnValue(mockReadlineInterface);\n\n    // Mock process.stdout.write\n    stdoutWriteSpy = vi.spyOn(process.stdout, 'write').mockImplementation(mockStdoutWrite);\n\n    // Clear all mocks\n    vi.clearAllMocks();\n  });\n\n  afterEach(() => {\n    // Restore original values\n    process.argv = originalProcessArgv;\n    \n    vi.restoreAllMocks();\n  });\n\n  describe(\"initialization\", () => {\n    it(\"should create readline interface correctly\", () => {\n      visualize();\n\n      expect(createInterface).toHaveBeenCalledWith({\n        input: process.stdin,\n        crlfDelay: Infinity,\n      });\n    });\n\n    it(\"should setup event listeners on readline interface\", () => {\n      visualize();\n\n      expect(mockReadlineInterface.on).toHaveBeenCalledWith(\"line\", expect.any(Function));\n      expect(mockReadlineInterface.on).toHaveBeenCalledWith(\"close\", expect.any(Function));\n    });\n  });\n\n  describe(\"debug mode detection\", () => {\n    it(\"should enable debug mode when --debug is in process.argv\", () => {\n      process.argv = [\"node\", \"script.js\", \"--debug\"];\n      \n      visualize();\n\n      // Get the line handler from the mock call\n      const lineHandler = mockReadlineInterface.on.mock.calls.find(\n        (call) => call[0] === \"line\"\n      )[1];\n\n      // Test with a valid JSON line\n      const testJson = { type: \"system\", message: \"test\" };\n      lineHandler(JSON.stringify(testJson));\n\n      // Should include timestamp when debug mode is enabled\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"[\")\n      );\n    });\n\n    it(\"should enable debug mode when options.debug is true\", () => {\n      visualize({ debug: true });\n\n      const lineHandler = mockReadlineInterface.on.mock.calls.find(\n        (call) => call[0] === \"line\"\n      )[1];\n\n      const testJson = { type: \"system\", message: \"test\" };\n      lineHandler(JSON.stringify(testJson));\n\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"[\")\n      );\n    });\n\n    it(\"should not include timestamp in normal mode\", () => {\n      process.argv = [\"node\", \"script.js\"];\n      \n      visualize();\n\n      const lineHandler = mockReadlineInterface.on.mock.calls.find(\n        (call) => call[0] === \"line\"\n      )[1];\n\n      const testJson = { type: \"system\", message: \"test\" };\n      lineHandler(JSON.stringify(testJson));\n\n      // Should not include timestamp bracket\n      const calls = mockStdoutWrite.mock.calls;\n      const hasTimestamp = calls.some(call => \n        typeof call[0] === 'string' && call[0].includes('[2') // ISO timestamp starts with year\n      );\n      expect(hasTimestamp).toBe(false);\n    });\n  });\n\n  describe(\"parsing different message types\", () => {\n    let lineHandler: Function;\n\n    beforeEach(() => {\n      visualize();\n      lineHandler = mockReadlineInterface.on.mock.calls.find(\n        (call) => call[0] === \"line\"\n      )[1];\n    });\n\n    it(\"should handle system messages\", () => {\n      const systemMessage = {\n        type: \"system\",\n        subtype: \"init\",\n        message: \"System initialization\"\n      };\n\n      lineHandler(JSON.stringify(systemMessage));\n\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"System\")\n      );\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"init\")\n      );\n    });\n\n    it(\"should handle user messages with text content\", () => {\n      const userMessage = {\n        type: \"user\",\n        message: {\n          content: [{\n            text: \"This is a user message with some content that should be truncated if too long\"\n          }]\n        }\n      };\n\n      lineHandler(JSON.stringify(userMessage));\n\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"User\")\n      );\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"This is a user message\")\n      );\n    });\n\n    it(\"should handle assistant messages\", () => {\n      const assistantMessage = {\n        type: \"assistant\",\n        message: {\n          content: [{\n            type: \"text\",\n            text: \"This is an assistant response\\nwith multiple lines\\nof content\\nthat should be truncated\"\n          }]\n        }\n      };\n\n      lineHandler(JSON.stringify(assistantMessage));\n\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"Assistant\")\n      );\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"This is an assistant response\")\n      );\n    });\n\n    it(\"should handle result messages\", () => {\n      const resultMessage = {\n        type: \"result\",\n        result: \"Final result content goes here\"\n      };\n\n      lineHandler(JSON.stringify(resultMessage));\n\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"=== Final Result ===\")\n      );\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"Final result content goes here\")\n      );\n    });\n  });\n\n  describe(\"tool call handling\", () => {\n    let lineHandler: Function;\n\n    beforeEach(() => {\n      visualize();\n      lineHandler = mockReadlineInterface.on.mock.calls.find(\n        (call) => call[0] === \"line\"\n      )[1];\n    });\n\n    it(\"should handle tool calls with Read tool\", () => {\n      const toolCall = {\n        type: \"assistant\",\n        message: {\n          content: [{\n            id: \"tool_123\",\n            name: \"Read\",\n            input: {\n              file_path: \"/path/to/file.ts\"\n            }\n          }]\n        }\n      };\n\n      lineHandler(JSON.stringify(toolCall));\n\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"Read\")\n      );\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"/path/to/file.ts\")\n      );\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"Waiting for result...\")\n      );\n    });\n\n    it(\"should handle tool calls with Bash tool\", () => {\n      const toolCall = {\n        type: \"assistant\",\n        message: {\n          content: [{\n            id: \"tool_456\",\n            name: \"Bash\",\n            input: {\n              command: \"ls -la\",\n              cwd: \"/home/user\",\n              timeout: 5000\n            }\n          }]\n        }\n      };\n\n      lineHandler(JSON.stringify(toolCall));\n\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"Bash\")\n      );\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"ls -la\")\n      );\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"cwd: /home/user\")\n      );\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"timeout: 5000ms\")\n      );\n    });\n\n    it(\"should handle tool calls with Edit tool\", () => {\n      const toolCall = {\n        type: \"assistant\",\n        message: {\n          content: [{\n            id: \"tool_789\",\n            name: \"Edit\",\n            input: {\n              file_path: \"/path/to/file.ts\",\n              old_string: \"function oldImplementation() {\",\n              new_string: \"function newImplementation() {\",\n              limit: 100,\n              offset: 50\n            }\n          }]\n        }\n      };\n\n      lineHandler(JSON.stringify(toolCall));\n\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"Edit\")\n      );\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"/path/to/file.ts\")\n      );\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"replace:\")\n      );\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"limit: 100\")\n      );\n    });\n\n    it(\"should handle tool results\", () => {\n      const toolResult = {\n        type: \"user\",\n        message: {\n          content: [{\n            type: \"tool_result\",\n            tool_use_id: \"tool_123\",\n            content: \"File content line 1\\nFile content line 2\\nFile content line 3\\nMore content...\",\n            is_error: false\n          }]\n        }\n      };\n\n      lineHandler(JSON.stringify(toolResult));\n\n      // When a tool result comes in without a matching tool call, \n      // it gets stored in pendingResults and doesn't output anything immediately\n      // This is correct behavior - it's waiting for the tool call\n      expect(mockStdoutWrite).not.toHaveBeenCalled();\n    });\n\n    it(\"should handle error tool results without matching tool calls\", () => {\n      const errorResult = {\n        type: \"user\",\n        message: {\n          content: [{\n            type: \"tool_result\",\n            tool_use_id: \"tool_456\",\n            content: \"Error: File not found\",\n            is_error: true\n          }]\n        }\n      };\n\n      lineHandler(JSON.stringify(errorResult));\n\n      // Same behavior - error results are also stored and don't output immediately\n      expect(mockStdoutWrite).not.toHaveBeenCalled();\n    });\n\n    it(\"should display tool results when matched with tool calls\", () => {\n      // First send a tool call\n      const toolCall = {\n        type: \"assistant\",\n        message: {\n          content: [{\n            id: \"tool_123\",\n            name: \"Read\",\n            input: {\n              file_path: \"/test/file.ts\"\n            }\n          }]\n        }\n      };\n\n      lineHandler(JSON.stringify(toolCall));\n      mockStdoutWrite.mockClear(); // Clear the tool call output\n\n      // Then send the result - this should display them together\n      const toolResult = {\n        type: \"user\",\n        message: {\n          content: [{\n            type: \"tool_result\",\n            tool_use_id: \"tool_123\",\n            content: \"File content line 1\\nFile content line 2\\nFile content line 3\\nMore content...\",\n            is_error: false\n          }]\n        }\n      };\n\n      lineHandler(JSON.stringify(toolResult));\n\n      const outputStrings = mockStdoutWrite.mock.calls.map(call => call[0]).join('');\n      expect(outputStrings).toContain(\"Tool Result\");\n      expect(outputStrings).toContain(\"4 lines\");\n      expect(outputStrings).toContain(\"File content line 1\");\n    });\n  });\n\n  describe(\"todo list handling\", () => {\n    let lineHandler: Function;\n\n    beforeEach(() => {\n      visualize();\n      lineHandler = mockReadlineInterface.on.mock.calls.find(\n        (call) => call[0] === \"line\"\n      )[1];\n    });\n\n    it(\"should format TodoWrite tool calls specially\", () => {\n      const todoMessage = {\n        type: \"assistant\",\n        message: {\n          content: [{\n            name: \"TodoWrite\",\n            input: {\n              todos: [\n                {\n                  status: \"completed\",\n                  content: \"Read the source file\",\n                  priority: \"high\"\n                },\n                {\n                  status: \"in_progress\", \n                  content: \"Parse the JSON data\",\n                  priority: \"medium\"\n                },\n                {\n                  status: \"pending\",\n                  content: \"Write tests for edge cases\",\n                  priority: \"low\"\n                }\n              ]\n            }\n          }]\n        }\n      };\n\n      lineHandler(JSON.stringify(todoMessage));\n\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"📋\")\n      );\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"Todo List Update\")\n      );\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"✅\")\n      );\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"🔄\")\n      );\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"⏸️\")\n      );\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"← ACTIVE\")\n      );\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"33% done\")\n      );\n    });\n\n    it(\"should handle empty todo lists\", () => {\n      const emptyTodoMessage = {\n        type: \"assistant\",\n        message: {\n          content: [{\n            name: \"TodoWrite\",\n            input: {\n              todos: []\n            }\n          }]\n        }\n      };\n\n      lineHandler(JSON.stringify(emptyTodoMessage));\n\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"Todo List Update\")\n      );\n    });\n  });\n\n  describe(\"tool call and result pairing\", () => {\n    let lineHandler: Function;\n\n    beforeEach(() => {\n      visualize();\n      lineHandler = mockReadlineInterface.on.mock.calls.find(\n        (call) => call[0] === \"line\"\n      )[1];\n    });\n\n    it(\"should pair tool calls with their results when call comes first\", () => {\n      // Send tool call first\n      const toolCall = {\n        type: \"assistant\",\n        message: {\n          content: [{\n            id: \"tool_123\",\n            name: \"Read\",\n            input: {\n              file_path: \"/test/file.ts\"\n            }\n          }]\n        }\n      };\n\n      lineHandler(JSON.stringify(toolCall));\n\n      // Then send result\n      const toolResult = {\n        type: \"user\",\n        message: {\n          content: [{\n            type: \"tool_result\",\n            tool_use_id: \"tool_123\",\n            content: \"File contents here\",\n            is_error: false\n          }]\n        }\n      };\n\n      // Clear previous calls\n      mockStdoutWrite.mockClear();\n      \n      lineHandler(JSON.stringify(toolResult));\n\n      // Should display them together now\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"Read\")\n      );\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"✅\")\n      );\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"Tool Result\")\n      );\n    });\n\n    it(\"should pair tool calls with their results when result comes first\", () => {\n      // Send result first \n      const toolResult = {\n        type: \"user\",\n        message: {\n          content: [{\n            type: \"tool_result\",\n            tool_use_id: \"tool_456\",\n            content: \"Result content\",\n            is_error: false\n          }]\n        }\n      };\n\n      lineHandler(JSON.stringify(toolResult));\n\n      // Then send call\n      const toolCall = {\n        type: \"assistant\", \n        message: {\n          content: [{\n            id: \"tool_456\",\n            name: \"Grep\",\n            input: {\n              pattern: \"test.*pattern\"\n            }\n          }]\n        }\n      };\n\n      lineHandler(JSON.stringify(toolCall));\n\n      // Should display them together\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"Grep\")\n      );\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"✅\")\n      );\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"Tool Result\")\n      );\n    });\n  });\n\n  describe(\"color formatting\", () => {\n    let lineHandler: Function;\n\n    beforeEach(() => {\n      visualize();\n      lineHandler = mockReadlineInterface.on.mock.calls.find(\n        (call) => call[0] === \"line\"\n      )[1];\n    });\n\n    it(\"should apply correct colors for different message types\", () => {\n      const messages = [\n        { type: \"system\", expected: \"\\x1b[35m\" }, // magenta\n        { type: \"user\", expected: \"\\x1b[34m\" }, // blue  \n        { type: \"assistant\", expected: \"\\x1b[32m\" }, // green\n        { type: \"tool_use\", expected: \"\\x1b[36m\" }, // cyan\n        { type: \"tool_result\", expected: \"\\x1b[33m\" }, // yellow\n      ];\n\n      messages.forEach(({ type, expected }) => {\n        mockStdoutWrite.mockClear();\n        \n        lineHandler(JSON.stringify({ type, message: \"test\" }));\n\n        const calls = mockStdoutWrite.mock.calls;\n        const hasExpectedColor = calls.some(call => \n          typeof call[0] === 'string' && call[0].includes(expected)\n        );\n        expect(hasExpectedColor).toBe(true);\n      });\n    });\n\n    it(\"should reset colors properly\", () => {\n      const message = { type: \"system\", message: \"test\" };\n      lineHandler(JSON.stringify(message));\n\n      const calls = mockStdoutWrite.mock.calls;\n      const hasResetColor = calls.some(call => \n        typeof call[0] === 'string' && call[0].includes(\"\\x1b[0m\")\n      );\n      expect(hasResetColor).toBe(true);\n    });\n\n    it(\"should use error colors when tool results are displayed\", () => {\n      // First send a tool call\n      const toolCall = {\n        type: \"assistant\",\n        message: {\n          content: [{\n            id: \"tool_123\",\n            name: \"Read\",\n            input: {\n              file_path: \"/test/file.ts\"\n            }\n          }]\n        }\n      };\n\n      lineHandler(JSON.stringify(toolCall));\n      mockStdoutWrite.mockClear(); // Clear the tool call output\n\n      // Then send an error result - this should display them together with error colors\n      const errorResult = {\n        type: \"user\",\n        message: {\n          content: [{\n            type: \"tool_result\",\n            tool_use_id: \"tool_123\",\n            content: \"Error message\",\n            is_error: true\n          }]\n        }\n      };\n\n      lineHandler(JSON.stringify(errorResult));\n\n      const outputStrings = mockStdoutWrite.mock.calls.map(call => call[0]).join('');\n      expect(outputStrings).toContain(\"\\x1b[31m\"); // red color for ERROR\n    });\n  });\n\n  describe(\"error handling\", () => {\n    let lineHandler: Function;\n\n    beforeEach(() => {\n      visualize();\n      lineHandler = mockReadlineInterface.on.mock.calls.find(\n        (call) => call[0] === \"line\"\n      )[1];\n    });\n\n    it(\"should handle invalid JSON gracefully\", () => {\n      const invalidJson = \"{ invalid json here\";\n      \n      lineHandler(invalidJson);\n\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"Parse Error\")\n      );\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"{ invalid json here\")\n      );\n    });\n\n    it(\"should handle empty lines gracefully\", () => {\n      lineHandler(\"\");\n      lineHandler(\"   \");\n      lineHandler(\"\\n\");\n\n      // Should not throw or produce error output for empty lines\n      const errorCalls = mockStdoutWrite.mock.calls.filter(call =>\n        typeof call[0] === 'string' && call[0].includes(\"Parse Error\")\n      );\n      expect(errorCalls).toHaveLength(0);\n    });\n\n    it(\"should handle malformed message structures\", () => {\n      const malformedMessages = [\n        { type: \"assistant\" }, // missing message\n        { type: \"user\", message: null }, // null message\n        { type: \"tool_result\" }, // missing required fields\n      ];\n\n      malformedMessages.forEach(message => {\n        expect(() => {\n          lineHandler(JSON.stringify(message));\n        }).not.toThrow();\n      });\n    });\n\n    it(\"should handle JSON with syntax errors\", () => {\n      const malformedJsons = [\n        '{\"type\": \"system\", \"message\": \"incomplete',\n        '{\"type\": \"user\" \"message\": \"missing colon\"}',\n        '{\"type\": \"assistant\", \"message\": {\"content\": [{\"text\": \"unclosed quote}]}}',\n        '{\"type\": []}', // invalid type\n        'null',\n        'undefined',\n        '{\"type\": \"system\", \"message\": \"test\", }', // trailing comma\n      ];\n\n      malformedJsons.forEach(json => {\n        mockStdoutWrite.mockClear();\n        lineHandler(json);\n        \n        expect(mockStdoutWrite).toHaveBeenCalledWith(\n          expect.stringContaining(\"Parse Error\")\n        );\n      });\n    });\n\n    it(\"should handle extremely long lines gracefully\", () => {\n      const longMessage = \"a\".repeat(10000);\n      const longJson = JSON.stringify({ type: \"system\", message: longMessage });\n      \n      expect(() => {\n        lineHandler(longJson);\n      }).not.toThrow();\n      \n      // Should process the message successfully even if it's large\n      expect(mockStdoutWrite).toHaveBeenCalled();\n    });\n\n    it(\"should handle non-string JSON values in content\", () => {\n      const messageWithNumbers = {\n        type: \"user\",\n        message: {\n          content: [{\n            text: 12345 // number instead of string\n          }]\n        }\n      };\n\n      expect(() => {\n        lineHandler(JSON.stringify(messageWithNumbers));\n      }).not.toThrow();\n    });\n\n    it(\"should handle circular reference errors gracefully\", () => {\n      // Create a message that would cause issues when stringifying internally\n      const messageWithUndefined = {\n        type: \"system\",\n        message: undefined,\n        subtype: \"test\"\n      };\n\n      expect(() => {\n        lineHandler(JSON.stringify(messageWithUndefined));\n      }).not.toThrow();\n    });\n  });\n\n  describe(\"final message handling\", () => {\n    let lineHandler: Function;\n    let closeHandler: Function;\n\n    beforeEach(() => {\n      visualize();\n      lineHandler = mockReadlineInterface.on.mock.calls.find(\n        (call) => call[0] === \"line\"\n      )[1];\n      closeHandler = mockReadlineInterface.on.mock.calls.find(\n        (call) => call[0] === \"close\"\n      )[1];\n    });\n\n    it(\"should display final assistant message on close\", () => {\n      const finalMessage = {\n        type: \"assistant\",\n        message: {\n          content: [{\n            type: \"text\",\n            text: \"This is the final assistant message that should be displayed fully.\"\n          }]\n        }\n      };\n\n      lineHandler(JSON.stringify(finalMessage));\n      closeHandler();\n\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"=== Final Assistant Message ===\")\n      );\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"This is the final assistant message that should be displayed fully.\")\n      );\n    });\n\n    it(\"should not display final message if last was a tool call\", () => {\n      const toolCall = {\n        type: \"assistant\",\n        message: {\n          content: [{\n            id: \"tool_123\",\n            name: \"Read\",\n            input: { file_path: \"/test\" }\n          }]\n        }\n      };\n\n      lineHandler(JSON.stringify(toolCall));\n      mockStdoutWrite.mockClear();\n      \n      closeHandler();\n\n      const finalMessageCalls = mockStdoutWrite.mock.calls.filter(call =>\n        typeof call[0] === 'string' && call[0].includes(\"=== Final Assistant Message ===\")\n      );\n      expect(finalMessageCalls).toHaveLength(0);\n    });\n\n    it(\"should not display final message if no text content\", () => {\n      const messageWithoutText = {\n        type: \"assistant\",\n        message: {\n          content: [{\n            type: \"image\",\n            data: \"base64data\"\n          }]\n        }\n      };\n\n      lineHandler(JSON.stringify(messageWithoutText));\n      mockStdoutWrite.mockClear();\n      \n      closeHandler();\n\n      const finalMessageCalls = mockStdoutWrite.mock.calls.filter(call =>\n        typeof call[0] === 'string' && call[0].includes(\"=== Final Assistant Message ===\")\n      );\n      expect(finalMessageCalls).toHaveLength(0);\n    });\n  });\n\n  describe(\"message content truncation and formatting\", () => {\n    let lineHandler: Function;\n\n    beforeEach(() => {\n      visualize();\n      lineHandler = mockReadlineInterface.on.mock.calls.find(\n        (call) => call[0] === \"line\"\n      )[1];\n    });\n\n    it(\"should truncate long user messages\", () => {\n      const longMessage = \"a\".repeat(100);\n      const userMessage = {\n        type: \"user\",\n        message: {\n          content: [{\n            text: longMessage\n          }]\n        }\n      };\n\n      lineHandler(JSON.stringify(userMessage));\n\n      const calls = mockStdoutWrite.mock.calls;\n      const hasEllipsis = calls.some(call => \n        typeof call[0] === 'string' && call[0].includes(\"...\")\n      );\n      expect(hasEllipsis).toBe(true);\n    });\n\n    it(\"should show limited lines from assistant messages\", () => {\n      const multilineMessage = {\n        type: \"assistant\",\n        message: {\n          content: [{\n            type: \"text\",\n            text: \"Line 1\\nLine 2\\nLine 3\\nLine 4\\nLine 5\"\n          }]\n        }\n      };\n\n      lineHandler(JSON.stringify(multilineMessage));\n\n      const calls = mockStdoutWrite.mock.calls;\n      const hasEllipsis = calls.some(call => \n        typeof call[0] === 'string' && call[0].includes(\"...\")\n      );\n      expect(hasEllipsis).toBe(true);\n    });\n\n    it(\"should show usage statistics when available\", () => {\n      const messageWithUsage = {\n        type: \"assistant\",\n        message: {\n          usage: {\n            input_tokens: 150,\n            output_tokens: 75\n          },\n          content: [{ type: \"text\", text: \"Response with usage stats\" }]\n        }\n      };\n\n      lineHandler(JSON.stringify(messageWithUsage));\n\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"150/75 tokens\")\n      );\n    });\n\n    it(\"should handle messages with only zero token usage\", () => {\n      const messageWithZeroUsage = {\n        type: \"assistant\",\n        message: {\n          usage: {\n            input_tokens: 0,\n            output_tokens: 0\n          },\n          content: [{ type: \"text\", text: \"Response with zero usage\" }]\n        }\n      };\n\n      lineHandler(JSON.stringify(messageWithZeroUsage));\n\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"0/0 tokens\")\n      );\n    });\n\n    it(\"should handle partial usage statistics\", () => {\n      const messageWithPartialUsage = {\n        type: \"assistant\",\n        message: {\n          usage: {\n            input_tokens: 100\n            // missing output_tokens\n          },\n          content: [{ type: \"text\", text: \"Response with partial usage\" }]\n        }\n      };\n\n      lineHandler(JSON.stringify(messageWithPartialUsage));\n\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"100/0 tokens\")\n      );\n    });\n\n    it(\"should handle messages with Unicode characters\", () => {\n      const unicodeMessage = {\n        type: \"user\",\n        message: {\n          content: [{\n            text: \"Hello 🌍 World! 中文字符 Émojis 🎉 and more: ñáéíóú\"\n          }]\n        }\n      };\n\n      lineHandler(JSON.stringify(unicodeMessage));\n\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"Hello 🌍 World!\")\n      );\n    });\n\n    it(\"should handle newlines and special characters in content\", () => {\n      const specialCharMessage = {\n        type: \"assistant\",\n        message: {\n          content: [{\n            type: \"text\",\n            text: \"Line with \\\\t tab\\\\nLine with \\\\r return\\\\nLine with \\\\\\\\backslash\"\n          }]\n        }\n      };\n\n      lineHandler(JSON.stringify(specialCharMessage));\n\n      const outputStrings = mockStdoutWrite.mock.calls.map(call => call[0]).join('');\n      expect(outputStrings).toContain(\"Line with\");\n    });\n\n    it(\"should format different content item types correctly\", () => {\n      const mixedContentMessage = {\n        type: \"assistant\",\n        message: {\n          content: [\n            { type: \"text\", text: \"Text content\" },\n            { type: \"image\", source: { type: \"base64\", media_type: \"image/png\", data: \"abc123\" } },\n            { type: \"tool_use\", id: \"tool_1\", name: \"Read\", input: { file_path: \"/test\" } }\n          ]\n        }\n      };\n\n      lineHandler(JSON.stringify(mixedContentMessage));\n\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"3 content items\")\n      );\n    });\n  });\n\n  describe(\"comprehensive JSONL parsing from stdin\", () => {\n    let lineHandler: Function;\n\n    beforeEach(() => {\n      visualize();\n      lineHandler = mockReadlineInterface.on.mock.calls.find(\n        (call) => call[0] === \"line\"\n      )[1];\n    });\n\n    it(\"should process multiple JSONL lines sequentially\", () => {\n      const jsonlLines = [\n        JSON.stringify({ type: \"system\", subtype: \"init\", message: \"Starting session\" }),\n        JSON.stringify({ type: \"user\", message: { content: [{ text: \"User request\" }] } }),\n        JSON.stringify({ type: \"assistant\", message: { content: [{ type: \"text\", text: \"Assistant response\" }] } }),\n        JSON.stringify({ type: \"result\", result: \"Final output\" })\n      ];\n\n      jsonlLines.forEach(line => {\n        lineHandler(line);\n      });\n\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"System\")\n      );\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"User\")\n      );\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"Assistant\")\n      );\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"=== Final Result ===\")\n      );\n    });\n\n    it(\"should handle interleaved tool calls and results in JSONL stream\", () => {\n      const jsonlSequence = [\n        JSON.stringify({\n          type: \"assistant\",\n          message: {\n            content: [{\n              id: \"tool_1\",\n              name: \"Read\",\n              input: { file_path: \"/test1.ts\" }\n            }]\n          }\n        }),\n        JSON.stringify({\n          type: \"assistant\", \n          message: {\n            content: [{\n              id: \"tool_2\",\n              name: \"Grep\",\n              input: { pattern: \"test\" }\n            }]\n          }\n        }),\n        JSON.stringify({\n          type: \"user\",\n          message: {\n            content: [{\n              type: \"tool_result\",\n              tool_use_id: \"tool_1\",\n              content: \"File contents 1\",\n              is_error: false\n            }]\n          }\n        }),\n        JSON.stringify({\n          type: \"user\",\n          message: {\n            content: [{\n              type: \"tool_result\",\n              tool_use_id: \"tool_2\",\n              content: \"Search results\",\n              is_error: false\n            }]\n          }\n        })\n      ];\n\n      jsonlSequence.forEach(line => {\n        lineHandler(line);\n      });\n\n      const outputStrings = mockStdoutWrite.mock.calls.map(call => call[0]).join('');\n      expect(outputStrings).toContain(\"Read\");\n      expect(outputStrings).toContain(\"Grep\");\n      expect(outputStrings).toContain(\"Tool Result\");\n    });\n\n    it(\"should handle streaming assistant responses\", () => {\n      const streamingMessages = [\n        JSON.stringify({ type: \"assistant\", message: { content: [{ type: \"text\", text: \"Let me help\" }] } }),\n        JSON.stringify({ type: \"assistant\", message: { content: [{ type: \"text\", text: \"Let me help you\" }] } }),\n        JSON.stringify({ type: \"assistant\", message: { content: [{ type: \"text\", text: \"Let me help you with that\" }] } })\n      ];\n\n      streamingMessages.forEach(line => {\n        lineHandler(line);\n      });\n\n      const assistantCalls = mockStdoutWrite.mock.calls.filter(call =>\n        typeof call[0] === 'string' && call[0].includes('Assistant')\n      );\n      expect(assistantCalls).toHaveLength(3);\n    });\n  });\n\n  describe(\"different message types comprehensive coverage\", () => {\n    let lineHandler: Function;\n\n    beforeEach(() => {\n      visualize();\n      lineHandler = mockReadlineInterface.on.mock.calls.find(\n        (call) => call[0] === \"line\"\n      )[1];\n    });\n\n    it(\"should handle info message type\", () => {\n      const infoMessage = {\n        type: \"info\",\n        message: \"Information message\",\n        level: \"info\"\n      };\n\n      lineHandler(JSON.stringify(infoMessage));\n\n      const outputStrings = mockStdoutWrite.mock.calls.map(call => call[0]).join('');\n      expect(outputStrings).toContain(\"Info\");\n    });\n\n    it(\"should handle warning message type\", () => {\n      const warningMessage = {\n        type: \"warning\",\n        message: \"Warning message\",\n        level: \"warning\"\n      };\n\n      lineHandler(JSON.stringify(warningMessage));\n\n      const outputStrings = mockStdoutWrite.mock.calls.map(call => call[0]).join('');\n      expect(outputStrings).toContain(\"Warning\");\n    });\n\n    it(\"should handle error message type\", () => {\n      const errorMessage = {\n        type: \"error\",\n        message: \"Error message\",\n        error: {\n          code: \"E001\",\n          description: \"Something went wrong\"\n        }\n      };\n\n      lineHandler(JSON.stringify(errorMessage));\n\n      const outputStrings = mockStdoutWrite.mock.calls.map(call => call[0]).join('');\n      expect(outputStrings).toContain(\"Error\");\n    });\n\n    it(\"should handle tool_use message type directly\", () => {\n      const toolUseMessage = {\n        type: \"tool_use\",\n        name: \"DirectTool\",\n        input: { parameter: \"value\" }\n      };\n\n      lineHandler(JSON.stringify(toolUseMessage));\n\n      const outputStrings = mockStdoutWrite.mock.calls.map(call => call[0]).join('');\n      expect(outputStrings).toContain(\"Tool_use\");\n    });\n\n    it(\"should handle debug message type\", () => {\n      const debugMessage = {\n        type: \"debug\",\n        message: \"Debug information\",\n        details: { step: 1, context: \"parsing\" }\n      };\n\n      lineHandler(JSON.stringify(debugMessage));\n\n      const outputStrings = mockStdoutWrite.mock.calls.map(call => call[0]).join('');\n      expect(outputStrings).toContain(\"Debug\");\n    });\n\n    it(\"should handle progress message type\", () => {\n      const progressMessage = {\n        type: \"progress\",\n        message: \"Processing files\",\n        progress: {\n          current: 5,\n          total: 10,\n          percentage: 50\n        }\n      };\n\n      lineHandler(JSON.stringify(progressMessage));\n\n      const outputStrings = mockStdoutWrite.mock.calls.map(call => call[0]).join('');\n      expect(outputStrings).toContain(\"Progress\");\n    });\n\n    it(\"should handle unknown message types gracefully\", () => {\n      const unknownMessage = {\n        type: \"custom_type\",\n        message: \"Custom message format\",\n        data: { custom: \"data\" }\n      };\n\n      expect(() => {\n        lineHandler(JSON.stringify(unknownMessage));\n      }).not.toThrow();\n\n      const outputStrings = mockStdoutWrite.mock.calls.map(call => call[0]).join('');\n      expect(outputStrings).toContain(\"Custom_type\");\n    });\n  });\n\n  describe(\"stream processing and buffering\", () => {\n    it(\"should handle rapid successive JSONL lines\", () => {\n      visualize();\n      const lineHandler = mockReadlineInterface.on.mock.calls.find(\n        (call) => call[0] === \"line\"\n      )[1];\n\n      // Simulate rapid input\n      const rapidMessages = Array.from({ length: 100 }, (_, i) => \n        JSON.stringify({ type: \"system\", message: `Message ${i}` })\n      );\n\n      rapidMessages.forEach(line => {\n        lineHandler(line);\n      });\n\n      // Should handle all messages without errors\n      expect(mockStdoutWrite.mock.calls.length).toBeGreaterThan(0);\n    });\n\n    it(\"should handle large JSON objects in stream\", () => {\n      visualize();\n      const lineHandler = mockReadlineInterface.on.mock.calls.find(\n        (call) => call[0] === \"line\"\n      )[1];\n\n      // Create a large message\n      const largeContent = \"Large content \".repeat(1000);\n      const largeMessage = {\n        type: \"assistant\",\n        message: {\n          content: [{\n            type: \"text\",\n            text: largeContent\n          }],\n          usage: {\n            input_tokens: 5000,\n            output_tokens: 3000\n          }\n        }\n      };\n\n      expect(() => {\n        lineHandler(JSON.stringify(largeMessage));\n      }).not.toThrow();\n\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"5000/3000 tokens\")\n      );\n    });\n\n    it(\"should maintain state across multiple tool call/result pairs\", () => {\n      visualize();\n      const lineHandler = mockReadlineInterface.on.mock.calls.find(\n        (call) => call[0] === \"line\"\n      )[1];\n\n      // Send multiple interleaved tool calls and results\n      const toolCall1 = JSON.stringify({\n        type: \"assistant\",\n        message: {\n          content: [{ id: \"tool_1\", name: \"Read\", input: { file_path: \"/file1\" } }]\n        }\n      });\n\n      const toolCall2 = JSON.stringify({\n        type: \"assistant\",\n        message: {\n          content: [{ id: \"tool_2\", name: \"Edit\", input: { file_path: \"/file2\" } }]\n        }\n      });\n\n      const result1 = JSON.stringify({\n        type: \"user\",\n        message: {\n          content: [{\n            type: \"tool_result\",\n            tool_use_id: \"tool_1\",\n            content: \"File 1 content\",\n            is_error: false\n          }]\n        }\n      });\n\n      const result2 = JSON.stringify({\n        type: \"user\",\n        message: {\n          content: [{\n            type: \"tool_result\",\n            tool_use_id: \"tool_2\", \n            content: \"File 2 edited\",\n            is_error: false\n          }]\n        }\n      });\n\n      // Send in mixed order: call1, call2, result1, result2\n      lineHandler(toolCall1);\n      lineHandler(toolCall2);\n      mockStdoutWrite.mockClear();\n      lineHandler(result1);\n      \n      // Should display tool1 + result1 together\n      let outputStrings = mockStdoutWrite.mock.calls.map(call => call[0]).join('');\n      expect(outputStrings).toContain(\"Read\");\n      expect(outputStrings).toContain(\"File 1 content\");\n\n      mockStdoutWrite.mockClear();\n      lineHandler(result2);\n      \n      // Should display tool2 + result2 together\n      outputStrings = mockStdoutWrite.mock.calls.map(call => call[0]).join('');\n      expect(outputStrings).toContain(\"Edit\");\n      expect(outputStrings).toContain(\"File 2 edited\");\n    });\n  });\n\n  describe(\"comprehensive tool call variations\", () => {\n    let lineHandler: Function;\n\n    beforeEach(() => {\n      visualize();\n      lineHandler = mockReadlineInterface.on.mock.calls.find(\n        (call) => call[0] === \"line\"\n      )[1];\n    });\n\n    it(\"should handle Write tool calls\", () => {\n      const writeToolCall = {\n        type: \"assistant\",\n        message: {\n          content: [{\n            id: \"tool_write\",\n            name: \"Write\",\n            input: {\n              file_path: \"/new/file.ts\",\n              content: \"New file content\"\n            }\n          }]\n        }\n      };\n\n      lineHandler(JSON.stringify(writeToolCall));\n\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"Write\")\n      );\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"/new/file.ts\")\n      );\n    });\n\n    it(\"should handle Glob tool calls\", () => {\n      const globToolCall = {\n        type: \"assistant\",\n        message: {\n          content: [{\n            id: \"tool_glob\",\n            name: \"Glob\",\n            input: {\n              pattern: \"**/*.ts\",\n              path: \"/src\"\n            }\n          }]\n        }\n      };\n\n      lineHandler(JSON.stringify(globToolCall));\n\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"Glob\")\n      );\n      // The implementation shows path first as the main argument, then pattern in details\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"/src\")\n      );\n    });\n\n    it(\"should handle WebFetch tool calls\", () => {\n      const webFetchCall = {\n        type: \"assistant\",\n        message: {\n          content: [{\n            id: \"tool_web\",\n            name: \"WebFetch\",\n            input: {\n              url: \"https://example.com\",\n              prompt: \"Extract the main content from this page\"\n            }\n          }]\n        }\n      };\n\n      lineHandler(JSON.stringify(webFetchCall));\n\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"WebFetch\")\n      );\n      // The implementation shows prompt first as the main argument, not URL\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"Extract the main content\")\n      );\n    });\n\n    it(\"should handle tool calls with multiple complex parameters\", () => {\n      const complexToolCall = {\n        type: \"assistant\",\n        message: {\n          content: [{\n            id: \"tool_complex\",\n            name: \"ComplexTool\",\n            input: {\n              query: \"search term\",\n              limit: 50,\n              offset: 10,\n              include: \"*.json\",\n              timeout: 30000,\n              old_string: \"function oldName() { return 'old'; }\",\n              new_string: \"function newName() { return 'new'; }\"\n            }\n          }]\n        }\n      };\n\n      lineHandler(JSON.stringify(complexToolCall));\n\n      const outputStrings = mockStdoutWrite.mock.calls.map(call => call[0]).join('');\n      expect(outputStrings).toContain(\"ComplexTool\");\n      expect(outputStrings).toContain(\"search term\");\n      expect(outputStrings).toContain(\"limit: 50\");\n      expect(outputStrings).toContain(\"offset: 10\");\n      expect(outputStrings).toContain(\"timeout: 30000ms\");\n      expect(outputStrings).toContain(\"replace:\");\n    });\n  });\n\n  describe(\"debug mode comprehensive behavior\", () => {\n    it(\"should display timestamps in ISO format in debug mode\", () => {\n      const mockDate = new Date('2023-12-01T10:30:45.123Z');\n      vi.spyOn(global, 'Date').mockImplementation(() => mockDate);\n      \n      visualize({ debug: true });\n      const lineHandler = mockReadlineInterface.on.mock.calls.find(\n        (call) => call[0] === \"line\"\n      )[1];\n\n      lineHandler(JSON.stringify({ type: \"system\", message: \"test\" }));\n\n      expect(mockStdoutWrite).toHaveBeenCalledWith(\n        expect.stringContaining(\"[2023-12-01T10:30:45.123Z]\")\n      );\n\n      vi.restoreAllMocks();\n    });\n\n    it(\"should handle debug mode with complex message sequences\", () => {\n      visualize({ debug: true });\n      const lineHandler = mockReadlineInterface.on.mock.calls.find(\n        (call) => call[0] === \"line\"\n      )[1];\n\n      const complexSequence = [\n        JSON.stringify({ type: \"system\", subtype: \"start\", message: \"Starting\" }),\n        JSON.stringify({\n          type: \"assistant\",\n          message: {\n            content: [{ id: \"tool_1\", name: \"Read\", input: { file_path: \"/test\" } }]\n          }\n        }),\n        JSON.stringify({\n          type: \"user\",\n          message: {\n            content: [{\n              type: \"tool_result\",\n              tool_use_id: \"tool_1\",\n              content: \"file content\",\n              is_error: false\n            }]\n          }\n        }),\n        JSON.stringify({\n          type: \"assistant\",\n          message: {\n            content: [{ type: \"text\", text: \"Final response\" }]\n          }\n        })\n      ];\n\n      complexSequence.forEach(line => {\n        lineHandler(line);\n      });\n\n      // All messages should have timestamps\n      const timestampCalls = mockStdoutWrite.mock.calls.filter(call =>\n        typeof call[0] === 'string' && call[0].includes('[')\n      );\n      expect(timestampCalls.length).toBeGreaterThan(0);\n    });\n\n    it(\"should work correctly when both debug option and argv flag are present\", () => {\n      process.argv = [\"node\", \"script.js\", \"--debug\"];\n      \n      visualize({ debug: true }); // Both option and argv\n      const lineHandler = mockReadlineInterface.on.mock.calls.find(\n        (call) => call[0] === \"line\"\n      )[1];\n\n      lineHandler(JSON.stringify({ type: \"system\", message: \"test\" }));\n\n      // Should still show timestamps (not double)\n      const calls = mockStdoutWrite.mock.calls.filter(call =>\n        typeof call[0] === 'string' && call[0].includes('[') && call[0].includes(']')\n      );\n      expect(calls.length).toBeGreaterThan(0);\n    });\n  });\n});"
  },
  {
    "path": "tests/helpers/fixtures.ts",
    "content": "/**\n * Test fixtures and mock data for repomirror tests\n */\n\nexport const mockRepoConfig = {\n  sourceRepo: \"./\",\n  targetRepo: \"../target\",\n  transformationInstructions: \"transform python to typescript\",\n};\n\nexport const mockTransformationPrompt = `Your job is to port ./ monorepo (Python) to ../target (TypeScript) and maintain the repository.\n\nYou have access to the current ./ repository as well as the ../target repository.\n\nMake a commit and push your changes after every single file edit.\n\nUse the ../target/agent/ directory as a scratchpad for your work. Store long term plans and todo lists there.\n\nThe original project was mostly tested by manually running the code. When porting, you will need to write end to end and unit tests for the project. But make sure to spend most of your time on the actual porting, not on the testing. A good heuristic is to spend 80% of your time on the actual porting, and 20% on the testing.`;\n\nexport const mockSyncScript = `#!/bin/bash\ncat .repomirror/prompt.md | \\\\\n        claude -p --output-format=stream-json --verbose --dangerously-skip-permissions --add-dir ../target | \\\\\n        tee -a .repomirror/claude_output.jsonl | \\\\\n        npx repomirror visualize --debug;`;\n\nexport const mockRalphScript = `#!/bin/bash\nwhile :; do\n  ./.repomirror/sync.sh\n  echo -e \"===SLEEP===\\\\n===SLEEP===\\\\n\"; echo 'looping';\n  sleep 10;\ndone`;\n\nexport const mockGitConfig = `[core]\n\\trepositoryformatversion = 0\n\\tfilemode = true\n\\tbare = false\n\\tlogallrefupdates = true\n[remote \"origin\"]\n\\turl = https://github.com/test/repo.git\n\\tfetch = +refs/heads/*:refs/remotes/origin/*`;\n\nexport const mockFileStructure = {\n  \"package.json\": JSON.stringify({\n    name: \"test-repo\",\n    version: \"1.0.0\",\n    description: \"Test repository\"\n  }, null, 2),\n  \"README.md\": \"# Test Repository\\n\\nThis is a test repository.\",\n  \"src\": {\n    \"index.ts\": \"export function hello() { return 'world'; }\",\n    \"utils\": {\n      \"helper.ts\": \"export function helper() { return true; }\"\n    }\n  }\n};\n\nexport const mockClaudeOutput = {\n  type: \"result\",\n  is_error: false,\n  result: mockTransformationPrompt\n};\n\nexport const mockCommandResponses = {\n  \"git rev-parse --git-dir\": { stdout: \".git\", exitCode: 0 },\n  \"git remote -v\": { \n    stdout: \"origin\\thttps://github.com/test/repo.git (fetch)\\norigin\\thttps://github.com/test/repo.git (push)\", \n    exitCode: 0 \n  },\n  \"claude\": { stdout: \"Hi there! How can I help you today?\", exitCode: 0 }\n};\n\nexport const mockInquirerResponses = {\n  sourceRepo: \"./\",\n  targetRepo: \"../target\", \n  transformationInstructions: \"transform python to typescript\"\n};"
  },
  {
    "path": "tests/helpers/index.ts",
    "content": "/**\n * Test helpers and utilities index\n */\n\nexport * from \"./test-utils\";\nexport * from \"./fixtures\";"
  },
  {
    "path": "tests/helpers/test-utils.ts",
    "content": "import { vi } from \"vitest\";\nimport { promises as fs } from \"fs\";\nimport { join } from \"path\";\nimport { tmpdir } from \"os\";\n\n/**\n * Test utility functions for repomirror tests\n */\n\n/**\n * Create a temporary directory for testing\n */\nexport async function createTempDir(prefix: string = \"repomirror-test-\"): Promise<string> {\n  const tempPath = join(tmpdir(), `${prefix}${Date.now()}-${Math.random().toString(36).substring(7)}`);\n  await fs.mkdir(tempPath, { recursive: true });\n  return tempPath;\n}\n\n/**\n * Clean up a temporary directory\n */\nexport async function cleanupTempDir(path: string): Promise<void> {\n  try {\n    await fs.rm(path, { recursive: true, force: true });\n  } catch (error) {\n    // Ignore cleanup errors in tests\n    console.warn(`Failed to cleanup temp directory ${path}:`, error);\n  }\n}\n\n/**\n * Create a mock git repository in a directory\n */\nexport async function createMockGitRepo(repoPath: string, withRemote: boolean = true): Promise<void> {\n  await fs.mkdir(join(repoPath, \".git\"), { recursive: true });\n  \n  // Create basic git config files\n  await fs.writeFile(join(repoPath, \".git\", \"config\"), `[core]\n\\trepositoryformatversion = 0\n\\tfilemode = true\n\\tbare = false\n\\tlogallrefupdates = true\n${withRemote ? `[remote \"origin\"]\n\\turl = https://github.com/test/repo.git\n\\tfetch = +refs/heads/*:refs/remotes/origin/*` : ''}\n`);\n\n  await fs.writeFile(join(repoPath, \".git\", \"HEAD\"), \"ref: refs/heads/main\\n\");\n  \n  // Create a simple file in the repo\n  await fs.writeFile(join(repoPath, \"README.md\"), \"# Test Repository\\n\");\n}\n\n/**\n * Mock console methods for testing\n */\nexport function mockConsole() {\n  const consoleMock = {\n    log: vi.fn(),\n    error: vi.fn(),\n    warn: vi.fn(),\n    info: vi.fn(),\n  };\n  \n  vi.spyOn(console, \"log\").mockImplementation(consoleMock.log);\n  vi.spyOn(console, \"error\").mockImplementation(consoleMock.error);\n  vi.spyOn(console, \"warn\").mockImplementation(consoleMock.warn);\n  vi.spyOn(console, \"info\").mockImplementation(consoleMock.info);\n  \n  return consoleMock;\n}\n\n/**\n * Mock process methods for testing\n */\nexport function mockProcess(shouldThrowOnExit: boolean = true) {\n  const processMock = {\n    exit: shouldThrowOnExit \n      ? vi.fn().mockImplementation((code?: number) => {\n          throw new Error(`Process exit called with code ${code}`);\n        })\n      : vi.fn(),\n    cwd: vi.fn(),\n  };\n  \n  vi.spyOn(process, \"exit\").mockImplementation(processMock.exit as any);\n  vi.spyOn(process, \"cwd\").mockImplementation(processMock.cwd);\n  \n  return processMock;\n}\n\n/**\n * Create a mock file structure\n */\nexport async function createMockFileStructure(\n  basePath: string, \n  structure: Record<string, string | Record<string, any>>\n): Promise<void> {\n  for (const [name, content] of Object.entries(structure)) {\n    const fullPath = join(basePath, name);\n    \n    if (typeof content === \"string\") {\n      // It's a file\n      await fs.mkdir(join(fullPath, \"..\"), { recursive: true });\n      await fs.writeFile(fullPath, content);\n    } else {\n      // It's a directory\n      await fs.mkdir(fullPath, { recursive: true });\n      await createMockFileStructure(fullPath, content);\n    }\n  }\n}\n\n/**\n * Wait for a specified amount of time (for async tests)\n */\nexport function delay(ms: number): Promise<void> {\n  return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n/**\n * Mock inquirer prompts for testing\n */\nexport function mockInquirer(responses: Record<string, any>) {\n  return {\n    prompt: vi.fn().mockResolvedValue(responses)\n  };\n}\n\n/**\n * Mock ora spinner for testing\n */\nexport function mockOra() {\n  const spinnerMock = {\n    start: vi.fn().mockReturnThis(),\n    succeed: vi.fn().mockReturnThis(),\n    fail: vi.fn().mockReturnThis(),\n    stop: vi.fn().mockReturnThis(),\n  };\n  \n  return vi.fn().mockReturnValue(spinnerMock);\n}\n\n/**\n * Mock execa for command execution testing\n */\nexport function mockExeca(responses: Record<string, { stdout?: string; stderr?: string; exitCode?: number }> = {}) {\n  return vi.fn().mockImplementation((command: string, args: string[] = []) => {\n    const fullCommand = `${command} ${args.join(\" \")}`.trim();\n    const response = responses[fullCommand] || responses[command] || { stdout: \"\", exitCode: 0 };\n    \n    if (response.exitCode && response.exitCode !== 0) {\n      const error = new Error(`Command failed: ${fullCommand}`) as any;\n      error.exitCode = response.exitCode;\n      error.stderr = response.stderr || \"\";\n      throw error;\n    }\n    \n    return Promise.resolve({\n      stdout: response.stdout || \"\",\n      stderr: response.stderr || \"\",\n      exitCode: response.exitCode || 0,\n    });\n  });\n}"
  },
  {
    "path": "tests/setup.ts",
    "content": "/**\n * Global test setup for vitest\n */\n\nimport { beforeEach, afterEach } from \"vitest\";\n\n// Global test setup\nbeforeEach(() => {\n  // Reset environment variables or global state if needed\n});\n\nafterEach(() => {\n  // Clean up any global state after each test\n});"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"module\": \"commonjs\",\n    \"lib\": [\"ES2022\"],\n    \"outDir\": \"./dist\",\n    \"rootDir\": \"./src\",\n    \"strict\": true,\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"resolveJsonModule\": true,\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"sourceMap\": true,\n    \"moduleResolution\": \"node\"\n  },\n  \"include\": [\"src/**/*\"],\n  \"exclude\": [\"node_modules\", \"dist\", \"**/*.test.ts\"]\n}"
  },
  {
    "path": "vitest.config.ts",
    "content": "import { defineConfig } from \"vitest/config\";\nimport { resolve } from \"path\";\n\nexport default defineConfig({\n  test: {\n    // Test environment configuration\n    environment: \"node\",\n    \n    // Global test setup\n    globals: true,\n    setupFiles: [\"./tests/setup.ts\"],\n    \n    // Test file patterns\n    include: [\n      \"tests/**/*.{test,spec}.{js,ts}\",\n      \"src/**/*.{test,spec}.{js,ts}\"\n    ],\n    exclude: [\n      \"**/node_modules/**\",\n      \"**/dist/**\",\n      \"**/.git/**\"\n    ],\n    \n    // Coverage configuration\n    coverage: {\n      provider: \"v8\",\n      reporter: [\"text\", \"json\", \"html\"],\n      reportsDirectory: \"coverage\",\n      include: [\"src/**/*.ts\"],\n      exclude: [\n        \"src/**/*.{test,spec}.ts\",\n        \"src/**/__tests__/**\",\n        \"dist/**\",\n        \"node_modules/**\"\n      ],\n      thresholds: {\n        global: {\n          branches: 80,\n          functions: 80,\n          lines: 80,\n          statements: 80\n        }\n      }\n    },\n    \n    // Test timeouts\n    testTimeout: 10000,\n    hookTimeout: 10000,\n    \n    // Reporter configuration\n    reporter: [\"verbose\"],\n    \n    // Watch mode configuration\n    watch: false,\n    \n    // Concurrency settings\n    maxConcurrency: 5\n  },\n  \n  // Resolve configuration for imports\n  resolve: {\n    alias: {\n      \"@\": resolve(__dirname, \"./src\"),\n      \"@tests\": resolve(__dirname, \"./tests\")\n    }\n  },\n  \n  // Define configuration for TypeScript\n  esbuild: {\n    target: \"es2022\"\n  }\n});"
  }
]