Skip to content

feat: add commit attribution with per-file AI contribution tracking#3115

Merged
wenshao merged 76 commits into
mainfrom
feat/commit-attribution
May 8, 2026
Merged

feat: add commit attribution with per-file AI contribution tracking#3115
wenshao merged 76 commits into
mainfrom
feat/commit-attribution

Conversation

@wenshao

@wenshao wenshao commented Apr 10, 2026

Copy link
Copy Markdown
Collaborator

Motivation

When developers use AI agents to write code, there is no way to distinguish AI-generated changes from human-authored ones in git history. This creates challenges for:

  • Open-source projects requiring AI contribution disclosure
  • Enterprises needing compliance audits on AI-assisted code

This PR adds automatic, per-file AI contribution tracking that attaches structured metadata to commits without polluting commit messages.

How it works

AI edits file             git commit succeeds
     |                           |
     v                           v
 EditTool / WriteFileTool   ShellTool detects git commit
     |                           |
     v                           v
 recordEdit(path,           generateNotePayload(name, baseDir)
   oldContent, newContent)       |
     |                           v
     v                      buildGitNotesCommand(note)
 CommitAttributionService        |
 (singleton Map)                 v
     |                      git notes --ref=refs/notes/ai-attribution
     |                        add -f -m '<json>' HEAD
     |                           |
     +---------------------------+
                                 v
                        clearAttributions()

Example output

git notes --ref=refs/notes/ai-attribution show HEAD
{
  "version": 1,
  "generator": "Qwen-Coder",
  "files": {
    "src/services/commitAttribution.ts": {
      "aiChars": 3200,
      "humanChars": 0,
      "percent": 100,
      "surface": "cli"
    },
    "src/tools/shell.ts": {
      "aiChars": 1500,
      "humanChars": 0,
      "percent": 100,
      "surface": "cli"
    }
  },
  "summary": {
    "aiPercent": 100,
    "aiChars": 4700,
    "humanChars": 0,
    "totalFilesTouched": 2,
    "surfaces": ["cli"]
  },
  "surfaceBreakdown": {
    "cli": { "aiChars": 4700, "percent": 100 }
  },
  "excludedGenerated": [],
  "excludedGeneratedCount": 0,
  "promptCount": 3
}

Note on aiChars / humanChars: these fields are heuristic
diff-size proxies, not literal character counts. The value is
(addedLines + deletedLines) × 40 for text files and a flat 1024
for binaries, with the per-file AI accumulator clamped to that
ceiling. Practical consequences: a commit adding 1000
one-character lines and one adding 1000 thousand-character lines
both report aiChars = 40000; a 5 MB image change and a 1-byte
binary tweak both report 1024. percent and
summary.aiPercent are robust to this (numerator and denominator
use the same proxy), so consumers aggregating attribution across
commits should rely on the percentage field rather than summing
raw aiChars as if they were byte counts.

Design decisions

Decision Rationale
Git notes instead of commit message Keeps commit history clean; notes are opt-in to push
Singleton service Attribution must accumulate across multiple Edit/Write calls before a single commit
30 KB size limit Sized for the most restrictive Windows CreateProcess argv ceiling (~32 KB UTF-16); the JSON note travels as a separate execFile argv entry alongside git, the path, and other args, so we cap the payload itself well below that
5s timeout on git notes add Prevents blocking the user; git notes is local and normally sub-millisecond
Tied to gitCoAuthor toggle If the user opts out of AI attribution, notes are also skipped - single control point
Relative paths in JSON Uses config.getTargetDir() as base to avoid leaking absolute directory structures
Model name sanitization Internal codenames (e.g. qwen-72b) are replaced with Qwen-Coder

Edge case behavior

Scenario Behavior
Commit fails or is aborted Per-file attributions are preserved (so unstaged AI work survives a hook rejection); on the next successful commit, validateOnDiskHashes() drops any entry whose on-disk content has diverged so AI is not credited for human edits made between attempts
gitCoAuthor disabled Notes skipped, attribution data cleared
git commit --amend New note attached to the new HEAD; old orphan note stays
User edits (modified_by_user) Not tracked - only AI-originated changes are recorded
Note JSON exceeds 30 KB Silently skipped with debug warning; commit succeeds normally
git notes add fails Non-fatal; warning logged; commit result unaffected
No AI edits before commit hasAttributions() returns false, entire flow skipped

Changed files

New files

File Lines Purpose
services/commitAttribution.ts 249 Singleton service: tracks per-file AI char additions/removals
services/attributionTrailer.ts 80 Generates shell-safe git notes commands with size guard
services/commitAttribution.test.ts 165 14 tests
services/attributionTrailer.test.ts 127 7 tests

Modified files

File Change
tools/edit.ts +10 lines: call recordEdit() after successful AI edit
tools/write-file.ts +12 lines: call recordEdit() after successful AI write
tools/shell.ts +100 lines: attachCommitAttribution() method
tools/shell.test.ts +4 lines: reset attribution singleton in beforeEach

Test plan

Automated

  • Unit tests pass: 5832 / 5836 (4 skipped) across 240 core test files
  • Attribution-specific suites:
    • commitAttribution.test.ts: 27 tests (character contribution, snapshot/restore, baselines, generated-file detection, surface, prompt counters)
    • attributionTrailer.test.ts: 7 tests (shell-safe note command, size guard, formatting)
    • tools/edit.test.ts / write-file.test.ts / shell.test.ts: 136 tests (integration with recordEdit and attachCommitAttribution)
    • core/client.test.ts: 69 tests incl. 3 asserting attribution snapshot fires on UserQuery + ToolResult and skips Retry
  • TypeScript compiles with 0 errors
  • ESLint + Prettier pass (verified by pre-commit hook)

End-to-end (scratch repo + real CLI + real LLM)

  • Pure AI edit → commit → git notes contain correct aiChars / aiPercent: 100 / surface: cli
  • Mixed AI (write_file) + shell-echo append → humanChars > 0, percent reflects real ratio
  • Generated file (package-lock.json) excluded from files, appears in excludedGenerated
  • Initial commit in empty repo (no HEAD~1) falls back to diff-tree --root and records correctly
  • general.gitCoAuthor: false → commit succeeds with no git notes written
  • gh pr create --body "..." → body auto-appended with 🤖 Generated with Qwen Code
  • Session resume: write file in turn 1 (no commit) → --resume → commit in turn 2 → notes contain the turn-1 edits (requires the ToolResult-turn snapshot fix in this PR)
  • clearAttributions(false) on failed commit preserves getPromptsSinceLastCommit() counter; next successful commit resets it

…ia git notes

Track character-level AI vs human contributions per file and store
detailed attribution metadata as git notes (refs/notes/ai-attribution)
after each successful git commit. This enables open-source AI disclosure
and enterprise compliance audits without polluting commit messages.
@github-actions

Copy link
Copy Markdown
Contributor

📋 Review Summary

This PR introduces a commit attribution system that tracks character-level AI contributions per file and stores them as git notes after successful commits. The implementation is well-structured with comprehensive test coverage (21 new tests), follows existing code conventions, and provides a non-blocking mechanism for AI contribution tracking. The approach of using git notes keeps commit messages clean while enabling compliance audits.

🔍 General Feedback

  • Good architectural decision: Using git notes (refs/notes/ai-attribution) instead of polluting commit messages is a clean approach
  • Well-tested: 21 new tests covering edge cases including singleton behavior, path handling, and sanitization
  • Defensive design: Non-failure behavior (attribution failures don't block commits) is appropriate for this feature
  • Security-conscious: Includes sanitization of internal model codenames and size limits on git notes
  • Consistent patterns: Follows existing service/tool patterns in the codebase (singleton service, trailer utility)
  • Test hygiene: Proper singleton reset in beforeEach for shell tests

🎯 Specific Feedback

🟡 High

  • File: commitAttribution.ts:198-218 - The calculateCharDiff method uses a line-based multiset diff approach, which may not accurately reflect character-level changes for multi-line edits. For example, replacing one long line with another would count the entire line lengths rather than the actual character differences. Consider using a more granular diff algorithm (e.g., character-level LCS or existing diff libraries) if accurate character-level attribution is important for compliance.

  • File: shell.ts:540-545 - The 5-second timeout for git notes execution may be too aggressive for repositories with large notes or slow storage. While non-fatal, this could silently drop attribution data frequently. Consider increasing to 10-15 seconds or making it configurable.

🟢 Medium

  • File: commitAttribution.ts:104-107 - The getFileAttribution method returns a defensive copy, but getAttributions returns a map of copies. This is slightly inconsistent - consider returning an array of readonly objects or a deep copy for getAttributions() to prevent any potential mutation of the internal map values.

  • File: attributionTrailer.ts:23-25 - The shellEscapeSingleQuote function handles single quotes correctly, but the implementation could benefit from a comment explaining why this specific escaping pattern ('\\'') is used, as it's non-obvious to developers unfamiliar with shell escaping.

  • File: edit.ts:418-423 and write-file.ts:242-247 - The attribution tracking is duplicated in both files. Consider extracting this into a helper function or having both tools call a common method to reduce duplication and ensure consistent behavior.

  • File: commitAttribution.ts:175 - The overallAiPercent calculation always returns 100 or 0 based on whether there were any changes. This seems like it should calculate an actual percentage based on AI vs human contributions. The current implementation doesn't track human contributions separately, so the percentage calculation appears incomplete.

🔵 Low

  • File: commitAttribution.ts:1 - The copyright header says "Google LLC" but this appears to be a new feature. Verify the copyright assignment is correct for new code in this repository.

  • File: attributionTrailer.ts:16-17 - The comment for MAX_NOTE_BYTES mentions "Linux's typical 2 MB" but the actual limit is set to 128KB. Consider adding a comment explaining why 128KB was chosen (e.g., "conservative limit to ensure compatibility across platforms and git configurations").

  • File: shell.ts:528 - The debug log uses debugLogger.debug() but earlier logs in the same file use debugLogger.warn() for non-fatal issues. Consider standardizing the log level for successful attribution attachment.

  • File: commitAttribution.test.ts:100-106 - The test "should return defensive copies from getFileAttribution" modifies aiCharsAdded but doesn't verify the internal state was unaffected until after the modification. Consider adding an assertion before the modification to ensure the test is more robust.

  • File: attributionTrailer.test.ts:67-78 - The test for oversized notes creates 2000 files with specific properties. Consider extracting the size threshold to a constant and testing at the boundary (e.g., one file under the limit, one file over) for more precise coverage.

✅ Highlights

  • Excellent safety measures: 128KB size limit, 5s timeout, non-blocking failure mode, and proper cleanup in finally blocks demonstrate thoughtful error handling
  • Smart sanitization: The sanitizeModelName method with multiple pattern matching protects against leaking internal model codenames
  • Clean separation of concerns: commitAttribution.ts (service logic) and attributionTrailer.ts (git notes command generation) are well-separated with single responsibilities
  • Comprehensive test coverage: Tests cover edge cases like empty files, path escaping, size limits, singleton behavior, and sanitization patterns
  • Proper git integration: Respects gitCoAuthor toggle and uses relative paths to avoid leaking absolute directory structures
  • Good test hygiene: Reset of singleton in shell.test.ts beforeEach prevents test pollution

@github-actions

github-actions Bot commented Apr 10, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Summary

Package Lines Statements Functions Branches
CLI 56.07% 56.07% 71.97% 79.37%
Core 76.53% 76.53% 79.44% 82.19%
CLI Package - Full Text Report
-------------------|---------|----------|---------|---------|-------------------
File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------------|---------|----------|---------|---------|-------------------
All files          |   56.07 |    79.37 |   71.97 |   56.07 |                   
 src               |   67.99 |    62.34 |   74.19 |   67.99 |                   
  gemini.tsx       |   59.52 |    58.88 |   66.66 |   59.52 | ...62,770-773,781 
  ...ractiveCli.ts |   69.53 |    57.42 |   72.72 |   69.53 | ...21-768,776-783 
  ...liCommands.ts |   73.92 |     72.5 |     100 |   73.92 | ...40-264,289,389 
  ...ActiveAuth.ts |     100 |     87.5 |     100 |     100 | 66-80             
 ...cp-integration |    46.3 |    63.01 |   55.88 |    46.3 |                   
  acpAgent.ts      |   48.12 |    63.38 |   62.06 |   48.12 | ...91-793,807-815 
  authMethods.ts   |   12.19 |      100 |       0 |   12.19 | 11-31,34-38,41-50 
  errorCodes.ts    |       0 |        0 |       0 |       0 | 1-22              
  ...DirContext.ts |     100 |      100 |     100 |     100 |                   
 ...ration/service |   68.65 |    83.33 |   66.66 |   68.65 |                   
  filesystem.ts    |   68.65 |    83.33 |   66.66 |   68.65 | ...32,77-94,97-98 
 ...ration/session |   74.86 |    68.57 |    82.6 |   74.86 |                   
  ...ryReplayer.ts |   65.93 |    75.67 |   81.81 |   65.93 | ...40-255,268-269 
  Session.ts       |   73.52 |    66.12 |   82.92 |   73.52 | ...2322,2328-2331 
  ...entTracker.ts |   90.85 |    84.84 |      90 |   90.85 | ...35,199,251-260 
  index.ts         |       0 |        0 |       0 |       0 | 1-40              
  ...ssionUtils.ts |   84.21 |    77.77 |     100 |   84.21 | ...37-153,209-211 
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...ssion/emitters |   96.01 |    90.75 |    92.3 |   96.01 |                   
  BaseEmitter.ts   |   76.92 |    66.66 |      80 |   76.92 | 23-24,39-40,55-56 
  ...ageEmitter.ts |     100 |    89.47 |     100 |     100 | 109,111           
  PlanEmitter.ts   |     100 |      100 |     100 |     100 |                   
  ...allEmitter.ts |   98.06 |     92.3 |     100 |   98.06 | 227-228,327,335   
  index.ts         |       0 |        0 |       0 |       0 | 1-10              
 ...ession/rewrite |   89.69 |    85.89 |   94.11 |   89.69 |                   
  LlmRewriter.ts   |   80.53 |    79.31 |     100 |   80.53 | ...17-119,170-174 
  ...Middleware.ts |   95.83 |    85.71 |     100 |   95.83 | 119,127-129       
  TurnBuffer.ts    |     100 |      100 |     100 |     100 |                   
  config.ts        |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 src/commands      |   62.18 |      100 |    9.52 |   62.18 |                   
  auth.ts          |   46.91 |      100 |       0 |   46.91 | ...,91-98,101-102 
  channel.ts       |   56.66 |      100 |       0 |   56.66 | 15-19,27-34       
  extensions.tsx   |   96.55 |      100 |      50 |   96.55 | 37                
  hooks.tsx        |   66.66 |      100 |       0 |   66.66 | 20-24             
  mcp.ts           |   94.73 |      100 |      50 |   94.73 | 28                
  review.ts        |   51.85 |      100 |       0 |   51.85 | 24-35,38          
 src/commands/auth |   66.16 |    79.82 |   78.94 |   66.16 |                   
  handler.ts       |   47.07 |    74.68 |   35.29 |   47.07 | ...-968,1058-1068 
  ...veSelector.ts |     100 |    96.66 |     100 |     100 | 58                
  ...outerOAuth.ts |   89.02 |    78.99 |   96.87 |   89.02 | ...18-622,716-718 
 ...mmands/channel |    39.2 |    79.45 |      50 |    39.2 |                   
  ...l-registry.ts |    8.57 |      100 |       0 |    8.57 | 6-21,24-42        
  config-utils.ts  |   91.89 |      100 |   66.66 |   91.89 | 20-25             
  configure.ts     |    14.7 |      100 |       0 |    14.7 | 18-21,23-84       
  pairing.ts       |   26.31 |      100 |       0 |   26.31 | ...30,40-50,52-65 
  pidfile.ts       |   96.34 |    86.95 |     100 |   96.34 | 49,59,91          
  start.ts         |   31.15 |       52 |   69.23 |   31.15 | ...73-476,485-487 
  status.ts        |   17.54 |      100 |       0 |   17.54 | 15-26,32-77       
  stop.ts          |      20 |      100 |       0 |      20 | 14-48             
 ...nds/extensions |   84.53 |    88.95 |   81.81 |   84.53 |                   
  consent.ts       |   71.65 |    89.28 |   42.85 |   71.65 | ...85-141,156-162 
  disable.ts       |     100 |      100 |     100 |     100 |                   
  enable.ts        |     100 |      100 |     100 |     100 |                   
  install.ts       |    75.6 |    66.66 |   66.66 |    75.6 | ...39-142,145-153 
  link.ts          |     100 |      100 |     100 |     100 |                   
  list.ts          |     100 |      100 |     100 |     100 |                   
  new.ts           |     100 |      100 |     100 |     100 |                   
  settings.ts      |   99.15 |      100 |   83.33 |   99.15 | 151               
  uninstall.ts     |    37.5 |      100 |   33.33 |    37.5 | 23-45,57-64,67-70 
  update.ts        |   96.32 |      100 |     100 |   96.32 | 101-105           
  utils.ts         |   60.24 |    28.57 |     100 |   60.24 | ...81,83-87,89-93 
 ...les/mcp-server |       0 |        0 |       0 |       0 |                   
  example.ts       |       0 |        0 |       0 |       0 | 1-60              
 src/commands/mcp  |   92.29 |    86.08 |   88.88 |   92.29 |                   
  add.ts           |     100 |    98.03 |     100 |     100 | 293               
  list.ts          |   91.22 |    80.76 |      80 |   91.22 | ...19-121,146-147 
  reconnect.ts     |   76.72 |    71.42 |   85.71 |   76.72 | 35-48,153-175     
  remove.ts        |     100 |       80 |     100 |     100 | 21-25             
 ...ommands/review |   11.57 |      100 |       0 |   11.57 |                   
  cleanup.ts       |   17.94 |      100 |       0 |   17.94 | ...01-106,108-109 
  deterministic.ts |   13.75 |      100 |       0 |   13.75 | ...22-738,740-741 
  fetch-pr.ts      |   11.36 |      100 |       0 |   11.36 | ...80-201,203-204 
  load-rules.ts    |   11.32 |      100 |       0 |   11.32 | ...41-153,155-156 
  pr-context.ts    |    6.22 |      100 |       0 |    6.22 | ...97-312,314-315 
  presubmit.ts     |    9.35 |      100 |       0 |    9.35 | ...62-287,289-290 
 ...nds/review/lib |      30 |      100 |       0 |      30 |                   
  gh.ts            |   22.58 |      100 |       0 |   22.58 | ...49,53-54,62-69 
  git.ts           |   22.72 |      100 |       0 |   22.72 | 15-18,29-39,43-44 
  paths.ts         |   52.94 |      100 |       0 |   52.94 | ...26,37-38,42-43 
 src/config        |   92.24 |    82.54 |   84.72 |   92.24 |                   
  auth.ts          |   87.87 |    81.35 |     100 |   87.87 | ...20-221,237-238 
  config.ts        |   86.36 |    82.53 |   72.72 |   86.36 | ...1339,1361-1362 
  keyBindings.ts   |   95.95 |       50 |     100 |   95.95 | 160-163           
  ...idersScope.ts |      92 |       90 |     100 |      92 | 11-12             
  sandboxConfig.ts |    58.9 |    61.53 |   66.66 |    58.9 | ...54-68,73,77-89 
  settings.ts      |   83.13 |    82.55 |   85.71 |   83.13 | ...35-936,941-944 
  ...ingsSchema.ts |     100 |      100 |     100 |     100 |                   
  ...tedFolders.ts |   96.29 |       94 |     100 |   96.29 | ...88-190,205-206 
 ...nfig/migration |   94.89 |    78.94 |   83.33 |   94.89 |                   
  index.ts         |   94.87 |    88.88 |     100 |   94.87 | 91-92             
  scheduler.ts     |   96.55 |    77.77 |     100 |   96.55 | 19-20             
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...ation/versions |   94.74 |       96 |     100 |   94.74 |                   
  ...-v2-shared.ts |     100 |      100 |     100 |     100 |                   
  v1-to-v2.ts      |   81.75 |    90.19 |     100 |   81.75 | ...28-229,231-247 
  v2-to-v3.ts      |     100 |      100 |     100 |     100 |                   
  v3-to-v4.ts      |     100 |      100 |     100 |     100 |                   
 src/constants     |   11.97 |     87.5 |   16.66 |   11.97 |                   
  ...dardApiKey.ts |     100 |      100 |     100 |     100 |                   
  codingPlan.ts    |    8.75 |     87.5 |   16.66 |    8.75 | ...22-327,335-347 
 src/core          |     100 |      100 |     100 |     100 |                   
  auth.ts          |     100 |      100 |     100 |     100 |                   
  initializer.ts   |     100 |      100 |     100 |     100 |                   
  theme.ts         |     100 |      100 |     100 |     100 |                   
 src/dualOutput    |   63.09 |    64.51 |   55.55 |   63.09 |                   
  ...tputBridge.ts |   62.94 |    65.51 |   56.25 |   62.94 | ...22-323,331-334 
  ...utContext.tsx |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-8               
 src/export        |       0 |        0 |       0 |       0 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-7               
 src/generated     |     100 |      100 |     100 |     100 |                   
  git-commit.ts    |     100 |      100 |     100 |     100 |                   
 src/i18n          |   48.26 |    76.19 |   38.88 |   48.26 |                   
  index.ts         |   26.92 |    76.92 |   26.66 |   26.92 | ...38-239,249-260 
  languages.ts     |    98.7 |       75 |     100 |    98.7 | 110               
 src/i18n/locales  |       0 |        0 |       0 |       0 |                   
  ca.js            |       0 |        0 |       0 |       0 | 1-2146            
  de.js            |       0 |        0 |       0 |       0 | 1-2069            
  en.js            |       0 |        0 |       0 |       0 | 1-2119            
  fr.js            |       0 |        0 |       0 |       0 | 1-2102            
  ja.js            |       0 |        0 |       0 |       0 | 1-1560            
  pt.js            |       0 |        0 |       0 |       0 | 1-2060            
  ru.js            |       0 |        0 |       0 |       0 | 1-2065            
  zh-TW.js         |       0 |        0 |       0 |       0 | 1-1681            
  zh.js            |       0 |        0 |       0 |       0 | 1-1920            
 ...nonInteractive |   72.67 |    72.14 |   74.07 |   72.67 |                   
  session.ts       |   76.86 |    70.45 |   85.71 |   76.86 | ...78-779,787-797 
  types.ts         |    42.5 |      100 |   33.33 |    42.5 | ...80-581,584-585 
 ...active/control |   77.55 |    88.23 |      80 |   77.55 |                   
  ...rolContext.ts |    7.69 |        0 |       0 |    7.69 | 47-79             
  ...Dispatcher.ts |   91.66 |    91.83 |   88.88 |   91.66 | ...54-372,388,391 
  ...rolService.ts |       8 |        0 |       0 |       8 | 46-179            
 ...ol/controllers |    7.04 |       80 |   13.33 |    7.04 |                   
  ...Controller.ts |   19.32 |      100 |      60 |   19.32 | 81-118,127-210    
  ...Controller.ts |       0 |        0 |       0 |       0 | 1-56              
  ...Controller.ts |    3.96 |      100 |   11.11 |    3.96 | ...61-379,389-494 
  ...Controller.ts |   14.06 |      100 |       0 |   14.06 | ...82-117,130-133 
  ...Controller.ts |    5.21 |      100 |       0 |    5.21 | ...21-433,442-471 
 .../control/types |       0 |        0 |       0 |       0 |                   
  serviceAPIs.ts   |       0 |        0 |       0 |       0 | 1                 
 ...Interactive/io |   97.59 |    93.06 |   95.18 |   97.59 |                   
  ...putAdapter.ts |   97.33 |    91.89 |   98.07 |   97.33 | ...1343,1368-1369 
  ...putAdapter.ts |      96 |    91.66 |   85.71 |      96 | 51-52             
  ...nputReader.ts |     100 |    94.73 |     100 |     100 | 67                
  ...putAdapter.ts |   98.28 |      100 |      90 |   98.28 | 81-82,122-123     
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/patches       |       0 |        0 |       0 |       0 |                   
  is-in-ci.ts      |       0 |        0 |       0 |       0 | 1-17              
 src/remoteInput   |   86.98 |       75 |   85.71 |   86.98 |                   
  ...utContext.tsx |     100 |      100 |     100 |     100 |                   
  ...putWatcher.ts |   88.12 |    76.08 |   91.66 |   88.12 | ...21-222,233-236 
  index.ts         |       0 |        0 |       0 |       0 | 1-8               
 src/services      |   90.37 |    89.75 |   94.28 |   90.37 |                   
  ...mandLoader.ts |     100 |     92.3 |     100 |     100 | 89                
  ...killLoader.ts |     100 |    96.29 |     100 |     100 | 44                
  ...andService.ts |    93.5 |      100 |      80 |    93.5 | 107,150-153       
  ...mandLoader.ts |   86.83 |    83.87 |     100 |   86.83 | ...30-335,340-345 
  ...omptLoader.ts |   75.32 |    80.64 |   83.33 |   75.32 | ...05-206,272-273 
  ...mandLoader.ts |     100 |      100 |     100 |     100 |                   
  ...nd-factory.ts |      91 |     90.9 |     100 |      91 | 123,132-139       
  ...ation-tool.ts |     100 |    95.45 |     100 |     100 | 125               
  commandUtils.ts  |      96 |       90 |     100 |      96 | 48                
  ...and-parser.ts |   90.69 |    85.71 |     100 |   90.69 | 63-66             
  ...ionService.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...ght/generators |   85.95 |    86.42 |   90.47 |   85.95 |                   
  DataProcessor.ts |   85.68 |    86.46 |   92.85 |   85.68 | ...1110,1114-1121 
  ...tGenerator.ts |   98.21 |    85.71 |     100 |   98.21 | 46                
  ...teRenderer.ts |   45.45 |      100 |       0 |   45.45 | 13-51             
 .../insight/types |       0 |       50 |      50 |       0 |                   
  ...sightTypes.ts |       0 |        0 |       0 |       0 |                   
  ...sightTypes.ts |       0 |        0 |       0 |       0 | 1                 
 ...mpt-processors |   97.27 |    94.04 |     100 |   97.27 |                   
  ...tProcessor.ts |     100 |      100 |     100 |     100 |                   
  ...eProcessor.ts |   94.52 |    84.21 |     100 |   94.52 | 46-47,93-94       
  ...tionParser.ts |     100 |      100 |     100 |     100 |                   
  ...lProcessor.ts |   97.41 |    95.65 |     100 |   97.41 | 95-98             
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/services/tips |   97.35 |    83.07 |     100 |   97.35 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  tipHistory.ts    |   92.45 |       70 |     100 |   92.45 | ...22,144,151,160 
  tipRegistry.ts   |     100 |    95.23 |     100 |     100 | 33                
  tipScheduler.ts  |     100 |    91.66 |     100 |     100 | 55                
 src/test-utils    |   93.75 |    83.33 |      80 |   93.75 |                   
  ...omMatchers.ts |   69.69 |       50 |      50 |   69.69 | 32-35,37-39,45-47 
  ...andContext.ts |     100 |      100 |     100 |     100 |                   
  render.tsx       |     100 |      100 |     100 |     100 |                   
 src/ui            |   63.21 |    68.42 |   51.28 |   63.21 |                   
  App.tsx          |     100 |      100 |     100 |     100 |                   
  AppContainer.tsx |   65.87 |    62.67 |   66.66 |   65.87 | ...2279,2283-2287 
  ...tionNudge.tsx |    9.58 |      100 |       0 |    9.58 | 24-94             
  ...ackDialog.tsx |   29.23 |      100 |       0 |   29.23 | 25-75             
  ...tionNudge.tsx |    7.69 |      100 |       0 |    7.69 | 25-103            
  colors.ts        |   52.72 |      100 |   23.52 |   52.72 | ...52,54-55,60-61 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  keyMatchers.ts   |   91.83 |       90 |     100 |   91.83 | 25-26,54-55       
  ...tic-colors.ts |     100 |      100 |     100 |     100 |                   
  textConstants.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/ui/auth       |   53.26 |    65.51 |      68 |   53.26 |                   
  AuthDialog.tsx   |   67.75 |    64.95 |    65.9 |   67.75 | ...1271,1273,1275 
  ...nProgress.tsx |       0 |        0 |       0 |       0 | 1-64              
  useAuth.ts       |    34.3 |    70.37 |     100 |    34.3 | ...14-920,922-937 
 src/ui/commands   |   59.85 |    77.91 |   60.78 |   59.85 |                   
  aboutCommand.ts  |     100 |    85.71 |     100 |     100 | 36                
  agentsCommand.ts |   72.97 |      100 |      20 |   72.97 | ...32,37-38,42-44 
  ...odeCommand.ts |     100 |      100 |     100 |     100 |                   
  arenaCommand.ts  |   33.13 |    67.64 |    37.5 |   33.13 | ...60-565,644-649 
  authCommand.ts   |     100 |      100 |     100 |     100 |                   
  btwCommand.ts    |   95.59 |    71.42 |     100 |   95.59 | 72,154-159        
  bugCommand.ts    |   77.35 |    66.66 |      50 |   77.35 | 21-22,60-69       
  clearCommand.ts  |   90.58 |    73.68 |      50 |   90.58 | ...46,74-75,93-94 
  ...essCommand.ts |   63.39 |       48 |      50 |   63.39 | ...48-149,163-166 
  ...extCommand.ts |    6.17 |      100 |      10 |    6.17 | ...21-522,527-528 
  copyCommand.ts   |     100 |      100 |     100 |     100 |                   
  deleteCommand.ts |     100 |      100 |     100 |     100 |                   
  ...ryCommand.tsx |   66.11 |    76.74 |   55.55 |   66.11 | ...05-306,315-323 
  docsCommand.ts   |   96.07 |     87.5 |      50 |   96.07 | 20-21             
  doctorCommand.ts |     100 |    93.33 |     100 |     100 | 21                
  dreamCommand.ts  |      75 |    66.66 |   66.66 |      75 | 22-27,44-47       
  editorCommand.ts |     100 |      100 |     100 |     100 |                   
  exportCommand.ts |   56.93 |    91.66 |   33.33 |   56.93 | ...52-353,361-362 
  ...onsCommand.ts |   45.08 |    85.71 |   27.27 |   45.08 | ...37-238,247-248 
  forgetCommand.ts |   26.82 |      100 |      50 |   26.82 | 18-51             
  helpCommand.ts   |     100 |      100 |     100 |     100 |                   
  hooksCommand.ts  |   19.04 |       25 |      20 |   19.04 | ...86-187,204-205 
  ideCommand.ts    |   57.33 |    57.69 |   35.29 |   57.33 | ...05-306,310-324 
  initCommand.ts   |   84.33 |    72.72 |     100 |   84.33 | 68,82-87,89-94    
  ...ghtCommand.ts |    72.8 |    66.66 |   83.33 |    72.8 | ...31-245,250-273 
  ...ageCommand.ts |   89.39 |    82.35 |   76.92 |   89.39 | ...22-325,348-349 
  ...elsCommand.ts |     100 |      100 |     100 |     100 |                   
  mcpCommand.ts    |   86.66 |      100 |      50 |   86.66 | 14-15             
  memoryCommand.ts |   86.66 |      100 |      50 |   86.66 | 14-15             
  modelCommand.ts  |   42.19 |       65 |      50 |   42.19 | ...35-168,175-193 
  ...onsCommand.ts |     100 |      100 |     100 |     100 |                   
  planCommand.ts   |   78.82 |    76.92 |     100 |   78.82 | 30-35,51-56,68-73 
  quitCommand.ts   |   93.93 |      100 |      50 |   93.93 | 15-16             
  recapCommand.ts  |   21.81 |      100 |      50 |   21.81 | 24-73             
  ...berCommand.ts |   32.43 |      100 |      50 |   32.43 | 23-57             
  renameCommand.ts |   85.61 |    78.18 |     100 |   85.61 | ...15-322,329-334 
  ...oreCommand.ts |    92.3 |     87.5 |     100 |    92.3 | ...,83-88,129-130 
  resumeCommand.ts |     100 |      100 |     100 |     100 |                   
  rewindCommand.ts |      80 |      100 |      50 |      80 | 19-21             
  ...ngsCommand.ts |     100 |      100 |     100 |     100 |                   
  ...hubCommand.ts |   81.43 |    65.21 |      80 |   81.43 | ...70-173,176-179 
  skillsCommand.ts |   15.04 |      100 |      25 |   15.04 | ...90-106,109-136 
  statsCommand.ts  |   83.91 |    81.25 |      50 |   83.91 | ...31-132,142-145 
  ...ineCommand.ts |     100 |      100 |     100 |     100 |                   
  ...aryCommand.ts |    6.51 |      100 |      50 |    6.51 | 28-323            
  tasksCommand.ts  |   77.45 |    73.43 |     100 |   77.45 | ...55-159,181-186 
  ...tupCommand.ts |     100 |      100 |     100 |     100 |                   
  themeCommand.ts  |     100 |      100 |     100 |     100 |                   
  toolsCommand.ts  |   95.23 |      100 |      50 |   95.23 | 18-19             
  trustCommand.ts  |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
  vimCommand.ts    |   54.54 |      100 |      50 |   54.54 | 19-29             
 src/ui/components |   59.75 |    73.87 |   62.82 |   59.75 |                   
  AboutBox.tsx     |     100 |      100 |     100 |     100 |                   
  AnsiOutput.tsx   |   65.57 |      100 |      50 |   65.57 | 69-90             
  ApiKeyInput.tsx  |   18.91 |      100 |       0 |   18.91 | 30-95             
  AppHeader.tsx    |   88.88 |    81.81 |     100 |   88.88 | 34-40,42          
  ...odeDialog.tsx |     9.7 |      100 |       0 |     9.7 | 35-47,50-182      
  AsciiArt.ts      |     100 |      100 |     100 |     100 |                   
  ...Indicator.tsx |   14.63 |      100 |       0 |   14.63 | 18-56             
  ...TextInput.tsx |   67.83 |    72.09 |      50 |   67.83 | ...30-232,250,259 
  Composer.tsx     |   79.31 |    57.14 |     100 |   79.31 | ...-77,95,133,146 
  ...entPrompt.tsx |     100 |      100 |     100 |     100 |                   
  ...ryDisplay.tsx |   75.89 |    62.06 |     100 |   75.89 | ...,88,93-108,113 
  ...geDisplay.tsx |   68.42 |    57.14 |     100 |   68.42 | 16-17,31-32,42-50 
  ...ification.tsx |   28.57 |      100 |       0 |   28.57 | 16-36             
  ...gProfiler.tsx |       0 |        0 |       0 |       0 | 1-36              
  ...ogManager.tsx |    12.4 |      100 |       0 |    12.4 | 61-457            
  ...ngsDialog.tsx |    8.44 |      100 |       0 |    8.44 | 37-195            
  ExitWarning.tsx  |     100 |      100 |     100 |     100 |                   
  ...hProgress.tsx |    87.8 |    33.33 |     100 |    87.8 | 28-31,56          
  ...ustDialog.tsx |     100 |      100 |     100 |     100 |                   
  Footer.tsx       |   79.67 |    58.06 |     100 |   79.67 | ...98-102,104-108 
  ...ngSpinner.tsx |   54.28 |       50 |      50 |   54.28 | 31-48,61          
  Header.tsx       |   98.38 |    92.59 |     100 |   98.38 | 139,141           
  Help.tsx         |   98.74 |    68.75 |     100 |   98.74 | 74,129            
  ...emDisplay.tsx |   62.72 |     37.5 |     100 |   62.72 | ...18-327,330,333 
  ...ngeDialog.tsx |     100 |      100 |     100 |     100 |                   
  InputPrompt.tsx  |   81.63 |    76.68 |   83.33 |   81.63 | ...1339,1404,1454 
  ...Shortcuts.tsx |   20.87 |      100 |       0 |   20.87 | ...6,49-51,67-125 
  ...Indicator.tsx |     100 |    91.42 |     100 |     100 | 65,74             
  ...firmation.tsx |   91.42 |      100 |      50 |   91.42 | 26-31             
  MainContent.tsx  |   57.66 |    54.54 |     100 |   57.66 | ...89-200,209-223 
  ...elsDialog.tsx |   16.07 |    89.18 |      50 |   16.07 | ...58-159,162-648 
  MemoryDialog.tsx |   53.35 |    51.21 |   57.14 |   53.35 | ...55,367,380-382 
  ...geDisplay.tsx |       0 |        0 |       0 |       0 | 1-41              
  ModelDialog.tsx  |   76.59 |    54.54 |     100 |   76.59 | ...60-476,533-537 
  ...tsDisplay.tsx |     100 |    96.96 |     100 |     100 | 234               
  ...fications.tsx |   18.18 |      100 |       0 |   18.18 | 15-58             
  ...onsDialog.tsx |    2.13 |      100 |       0 |    2.13 | 62-133,148-1004   
  ...ryDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...icePrompt.tsx |   88.14 |    83.87 |     100 |   88.14 | ...01-105,133-138 
  PrepareLabel.tsx |   91.66 |    76.19 |     100 |   91.66 | 73-75,77-79,110   
  ...geDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...ngDisplay.tsx |   21.42 |      100 |       0 |   21.42 | 13-39             
  ...hProgress.tsx |   85.25 |    88.46 |     100 |   85.25 | 121-147           
  ...dSelector.tsx |    4.45 |      100 |       0 |    4.45 | 28-92,100-328     
  ...ionPicker.tsx |   94.76 |    87.17 |     100 |   94.76 | 99,132,253-261    
  ...onPreview.tsx |   91.73 |    78.26 |     100 |   91.73 | ...,70-71,126-128 
  ...ryDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...putPrompt.tsx |   72.56 |       80 |      40 |   72.56 | ...06-109,114-117 
  ...ngsDialog.tsx |   66.88 |    73.52 |     100 |   66.88 | ...11-819,825-826 
  ...ionDialog.tsx |    87.8 |      100 |   33.33 |    87.8 | 36-39,44-51       
  ...putPrompt.tsx |    15.9 |      100 |       0 |    15.9 | 20-63             
  ...Indicator.tsx |   57.14 |      100 |       0 |   57.14 | 12-15             
  ...MoreLines.tsx |      28 |      100 |       0 |      28 | 18-40             
  ...ionPicker.tsx |   17.59 |      100 |       0 |   17.59 | 55-172            
  StatsDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...yTodoList.tsx |   94.17 |       80 |     100 |   94.17 | 56-57,131-134     
  ...nsDisplay.tsx |   84.09 |    57.14 |     100 |   84.09 | ...16-118,125-127 
  ThemeDialog.tsx  |   89.95 |    46.15 |      75 |   89.95 | ...71-173,243-245 
  Tips.tsx         |   93.75 |       75 |     100 |   93.75 | 39-40             
  TodoDisplay.tsx  |     100 |      100 |     100 |     100 |                   
  ...tsDisplay.tsx |     100 |     87.5 |     100 |     100 | 31-32             
  TrustDialog.tsx  |     100 |    81.81 |     100 |     100 | 71-86             
  ...ification.tsx |   36.36 |      100 |       0 |   36.36 | 15-22             
  ...ackDialog.tsx |    7.84 |      100 |       0 |    7.84 | 24-134            
 ...nts/agent-view |    25.2 |       90 |      10 |    25.2 |                   
  ...atContent.tsx |    8.79 |      100 |       0 |    8.79 | 53-265,271-273    
  ...tChatView.tsx |   21.05 |      100 |       0 |   21.05 | 21-39             
  ...tComposer.tsx |    9.95 |      100 |       0 |    9.95 | 57-308            
  AgentFooter.tsx  |   17.07 |      100 |       0 |   17.07 | 28-66             
  AgentHeader.tsx  |   15.38 |      100 |       0 |   15.38 | 27-64             
  AgentTabBar.tsx  |    8.13 |      100 |       0 |    8.13 | 39-59,64-187      
  ...oryAdapter.ts |     100 |    91.83 |     100 |     100 | 103,109-110,138   
  index.ts         |       0 |        0 |       0 |       0 | 1-12              
 ...mponents/arena |   45.72 |    70.53 |   60.86 |   45.72 |                   
  ArenaCards.tsx   |   73.06 |    71.79 |   85.71 |   73.06 | ...83-185,321-326 
  ...ectDialog.tsx |   83.48 |    69.86 |   88.88 |   83.48 | ...88-392,409-410 
  ...artDialog.tsx |   10.15 |      100 |       0 |   10.15 | 27-161            
  ...tusDialog.tsx |    5.63 |      100 |       0 |    5.63 | 33-75,80-288      
  ...topDialog.tsx |    6.17 |      100 |       0 |    6.17 | 33-213            
 ...ackground-view |    71.2 |    79.71 |   78.94 |    71.2 |                   
  ...sksDialog.tsx |   71.23 |    78.83 |   73.33 |   71.23 | ...1063,1137-1139 
  ...TasksPill.tsx |   70.83 |    86.95 |     100 |   70.83 | 44,77-89,97-105   
 ...nts/extensions |   45.28 |    33.33 |      60 |   45.28 |                   
  ...gerDialog.tsx |   44.31 |    34.14 |      75 |   44.31 | ...71-480,483-488 
  index.ts         |       0 |        0 |       0 |       0 | 1-9               
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...tensions/steps |   54.77 |    94.23 |   66.66 |   54.77 |                   
  ...ctionStep.tsx |   95.12 |    92.85 |   85.71 |   95.12 | 84-86,89          
  ...etailStep.tsx |    6.18 |      100 |       0 |    6.18 | 17-128            
  ...nListStep.tsx |   88.35 |    94.73 |      80 |   88.35 | 51-52,58-71,105   
  ...electStep.tsx |   13.46 |      100 |       0 |   13.46 | 20-70             
  ...nfirmStep.tsx |   19.56 |      100 |       0 |   19.56 | 23-65             
  index.ts         |     100 |      100 |     100 |     100 |                   
 ...mponents/hooks |   72.24 |    70.52 |      80 |   72.24 |                   
  ...etailStep.tsx |   96.52 |       75 |     100 |   96.52 | 33,37,50,59       
  ...etailStep.tsx |   93.27 |    73.68 |     100 |   93.27 | 41-42,99-104,110  
  ...abledStep.tsx |     100 |      100 |     100 |     100 |                   
  ...sListStep.tsx |     100 |      100 |     100 |     100 |                   
  ...entDialog.tsx |   36.09 |    47.05 |      50 |   36.09 | ...49,453-466,470 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-13              
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...components/mcp |    20.2 |    84.61 |   81.81 |    20.2 |                   
  ...ealthPill.tsx |   68.42 |    85.71 |     100 |   68.42 | 40-46             
  ...entDialog.tsx |    3.64 |      100 |       0 |    3.64 | 41-717            
  constants.ts     |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-30              
  types.ts         |     100 |      100 |     100 |     100 |                   
  utils.ts         |   96.42 |    87.09 |     100 |   96.42 | 21,96-97          
 ...ents/mcp/steps |    6.65 |      100 |       0 |    6.65 |                   
  ...icateStep.tsx |     5.1 |      100 |       0 |     5.1 | 34-95,98-334      
  ...electStep.tsx |   10.95 |      100 |       0 |   10.95 | 16-88             
  ...etailStep.tsx |    5.26 |      100 |       0 |    5.26 | 31-247            
  ...rListStep.tsx |    5.88 |      100 |       0 |    5.88 | 20-176            
  ...etailStep.tsx |   10.41 |      100 |       0 |   10.41 | ...1,67-79,82-139 
  ToolListStep.tsx |    7.14 |      100 |       0 |    7.14 | 16-146            
 ...nents/messages |   79.88 |    78.93 |   69.84 |   79.88 |                   
  ...ionDialog.tsx |   77.35 |    74.54 |    62.5 |   77.35 | ...90,508,526-528 
  BtwMessage.tsx   |     100 |      100 |     100 |     100 |                   
  ...upDisplay.tsx |   97.67 |    83.33 |     100 |   97.67 | 119,142,150       
  ...onMessage.tsx |   91.93 |    82.35 |     100 |   91.93 | 57-59,61,63       
  ...nMessages.tsx |   77.35 |      100 |      70 |   77.35 | ...31-244,248-260 
  DiffRenderer.tsx |   93.19 |    86.17 |     100 |   93.19 | ...09,237-238,304 
  ...ssMessage.tsx |    12.5 |      100 |       0 |    12.5 | 18-59             
  ...edMessage.tsx |   16.66 |      100 |       0 |   16.66 | 22-38             
  ...sMessages.tsx |   55.67 |       40 |   28.57 |   55.67 | ...20-125,133-145 
  ...ryMessage.tsx |   12.82 |      100 |       0 |   12.82 | 22-59             
  ...onMessage.tsx |   73.55 |    55.81 |   33.33 |   73.55 | ...41-443,450-452 
  ...upMessage.tsx |   76.95 |    82.08 |     100 |   76.95 | ...24-251,273-288 
  ToolMessage.tsx  |   90.68 |    81.35 |   91.66 |   90.68 | ...58-663,690-692 
 ...ponents/shared |    82.5 |    77.29 |   92.64 |    82.5 |                   
  ...ctionList.tsx |   99.03 |    95.65 |     100 |   99.03 | 85                
  ...tonSelect.tsx |     100 |      100 |     100 |     100 |                   
  EnumSelector.tsx |     100 |    96.42 |     100 |     100 | 58                
  MaxSizedBox.tsx  |   83.01 |    86.25 |   88.88 |   83.01 | ...12-513,618-619 
  MultiSelect.tsx  |    6.29 |      100 |       0 |    6.29 | 35-42,45-176      
  ...tonSelect.tsx |     100 |      100 |     100 |     100 |                   
  ...eSelector.tsx |     100 |       60 |     100 |     100 | 40-45             
  TextInput.tsx    |   74.84 |    57.14 |      75 |   74.84 | ...90-194,206-212 
  ...apsedTime.tsx |     100 |      100 |     100 |     100 |                   
  ...Indicator.tsx |     100 |      100 |     100 |     100 |                   
  text-buffer.ts   |   83.62 |    75.62 |   97.61 |   83.62 | ...2272,2300,2368 
  ...er-actions.ts |   86.71 |    67.79 |     100 |   86.71 | ...07-608,809-811 
 ...ents/subagents |   32.77 |    33.33 |    12.5 |   32.77 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  reducers.tsx     |    12.1 |      100 |       0 |    12.1 | 33-190            
  types.ts         |     100 |      100 |     100 |     100 |                   
  utils.ts         |   13.69 |    33.33 |   16.66 |   13.69 | ...1,56-57,60-102 
 ...bagents/create |    9.13 |      100 |       0 |    9.13 |                   
  ...ionWizard.tsx |    7.28 |      100 |       0 |    7.28 | 34-299            
  ...rSelector.tsx |   14.75 |      100 |       0 |   14.75 | 26-85             
  ...onSummary.tsx |    4.26 |      100 |       0 |    4.26 | 27-331            
  ...tionInput.tsx |    8.63 |      100 |       0 |    8.63 | 23-177            
  ...dSelector.tsx |   33.33 |      100 |       0 |   33.33 | 20-21,26-27,36-63 
  ...nSelector.tsx |    37.5 |      100 |       0 |    37.5 | 20-21,26-27,36-58 
  ...EntryStep.tsx |   12.76 |      100 |       0 |   12.76 | 34-78             
  ToolSelector.tsx |    4.16 |      100 |       0 |    4.16 | 31-253            
 ...bagents/manage |    8.39 |      100 |       0 |    8.39 |                   
  ...ctionStep.tsx |   10.25 |      100 |       0 |   10.25 | 21-103            
  ...eleteStep.tsx |   20.93 |      100 |       0 |   20.93 | 23-62             
  ...tEditStep.tsx |   25.53 |      100 |       0 |   25.53 | ...2,37-38,51-124 
  ...ctionStep.tsx |    2.29 |      100 |       0 |    2.29 | 28-449            
  ...iewerStep.tsx |   13.72 |      100 |       0 |   13.72 | 18-73             
  ...gerDialog.tsx |    6.74 |      100 |       0 |    6.74 | 35-341            
 ...agents/runtime |   81.76 |    58.24 |   92.85 |   81.76 |                   
  ...onDisplay.tsx |   81.76 |    58.24 |   92.85 |   81.76 | ...14-716,719-722 
 ...mponents/views |   42.16 |    69.23 |   21.42 |   42.16 |                   
  ContextUsage.tsx |     4.7 |      100 |       0 |     4.7 | ...52-167,170-456 
  DoctorReport.tsx |     9.8 |      100 |       0 |     9.8 | 25-54,57-131      
  ...sionsList.tsx |   87.69 |    73.68 |     100 |   87.69 | 65-72             
  McpStatus.tsx    |   89.53 |    60.52 |     100 |   89.53 | ...72,175-177,262 
  SkillsList.tsx   |   27.27 |      100 |       0 |   27.27 | 18-35             
  ToolsList.tsx    |     100 |      100 |     100 |     100 |                   
 src/ui/contexts   |   76.84 |    78.03 |   84.31 |   76.84 |                   
  ...ewContext.tsx |   65.77 |      100 |      75 |   65.77 | ...22-225,231-241 
  AppContext.tsx   |      80 |       50 |     100 |      80 | 19-20             
  ...ewContext.tsx |   93.37 |    68.57 |      50 |   93.37 | ...94-195,222-226 
  ...deContext.tsx |     100 |      100 |     100 |     100 |                   
  ...igContext.tsx |   81.81 |       50 |     100 |   81.81 | 15-16             
  ...ssContext.tsx |   81.88 |    82.26 |     100 |   81.88 | ...1153,1159-1161 
  ...owContext.tsx |   89.28 |       80 |   66.66 |   89.28 | 34,47-48,60-62    
  ...onContext.tsx |   43.28 |     62.5 |    62.5 |   43.28 | ...56-259,263-266 
  ...gsContext.tsx |   83.33 |       50 |     100 |   83.33 | 17-18             
  ...usContext.tsx |     100 |      100 |     100 |     100 |                   
  ...ngContext.tsx |   71.42 |       50 |     100 |   71.42 | 17-20             
  ...nsContext.tsx |   88.88 |       50 |     100 |   88.88 | 145-146           
  ...teContext.tsx |   85.71 |       50 |     100 |   85.71 | 175-176           
  ...deContext.tsx |   76.08 |    72.72 |     100 |   76.08 | 47-48,52-59,77-78 
 src/ui/editors    |   93.33 |    85.71 |   66.66 |   93.33 |                   
  ...ngsManager.ts |   93.33 |    85.71 |   66.66 |   93.33 | 49,63-64          
 src/ui/hooks      |    80.8 |    81.38 |   85.56 |    80.8 |                   
  ...dProcessor.ts |   83.12 |    82.56 |     100 |   83.12 | ...88-389,408-435 
  keyToAnsi.ts     |    3.92 |      100 |       0 |    3.92 | 19-77             
  ...dProcessor.ts |    94.8 |    70.58 |     100 |    94.8 | ...76-277,282-283 
  ...dProcessor.ts |   72.85 |    57.85 |   61.53 |   72.85 | ...84,808,827-831 
  ...amingState.ts |   12.22 |      100 |       0 |   12.22 | 54-158            
  ...agerDialog.ts |   88.23 |      100 |     100 |   88.23 | 20,24             
  ...ationFrame.ts |      32 |       60 |     100 |      32 | 42-44,51-90       
  ...odeCommand.ts |   58.82 |      100 |     100 |   58.82 | 28,33-48          
  ...enaCommand.ts |      85 |      100 |     100 |      85 | 23-24,29          
  ...aInProcess.ts |   19.81 |    66.66 |      25 |   19.81 | 57-175            
  ...Completion.ts |   92.77 |    89.09 |     100 |   92.77 | ...86-187,220-223 
  ...ifications.ts |   92.07 |    96.29 |     100 |   92.07 | 116-124           
  ...tIndicator.ts |     100 |    93.75 |     100 |     100 | 63                
  ...waySummary.ts |   96.22 |    69.69 |     100 |   96.22 | 125-127,169       
  ...ndTaskView.ts |   94.11 |    76.92 |     100 |   94.11 | 119-123,216,222   
  ...ketedPaste.ts |    23.8 |      100 |       0 |    23.8 | 19-37             
  ...lanUpdates.ts |     100 |       92 |     100 |     100 | 59,158            
  ...ompletion.tsx |   91.28 |    79.59 |     100 |   91.28 | ...20-221,259-269 
  ...dMigration.ts |   90.62 |       75 |     100 |   90.62 | 38-40             
  useCompletion.ts |    92.4 |     87.5 |     100 |    92.4 | 68-69,93-94,98-99 
  ...nitMessage.ts |     100 |      100 |     100 |     100 |                   
  ...extualTips.ts |   76.92 |       50 |     100 |   76.92 | 55,68,71-75,88-96 
  ...eteCommand.ts |   33.33 |       50 |     100 |   33.33 | 30,34,41-90       
  ...ialogClose.ts |   18.18 |      100 |     100 |   18.18 | 75-130            
  ...oublePress.ts |   53.12 |       75 |     100 |   53.12 | 33-35,41-54       
  ...orSettings.ts |     100 |      100 |     100 |     100 |                   
  ...Completion.ts |   99.12 |     97.7 |     100 |   99.12 | 182-183           
  ...ionUpdates.ts |   93.45 |     92.3 |     100 |   93.45 | ...83-287,300-306 
  ...agerDialog.ts |   88.88 |      100 |     100 |   88.88 | 21,25             
  ...backDialog.ts |   54.88 |       50 |   33.33 |   54.88 | ...71-173,195-196 
  useFocus.ts      |     100 |      100 |     100 |     100 |                   
  ...olderTrust.ts |     100 |      100 |     100 |     100 |                   
  ...ggestions.tsx |   67.46 |       90 |      50 |   67.46 | ...09-130,149-150 
  ...miniStream.ts |   75.84 |    72.89 |   91.66 |   75.84 | ...2293,2306-2314 
  ...BranchName.ts |    90.9 |     92.3 |     100 |    90.9 | 19-20,55-58       
  ...oryManager.ts |   93.15 |    93.75 |     100 |   93.15 | 44,107-110        
  ...ooksDialog.ts |    87.5 |      100 |     100 |    87.5 | 19,23             
  ...stListener.ts |     100 |      100 |     100 |     100 |                   
  ...nAuthError.ts |   76.19 |       50 |     100 |   76.19 | 39-40,43-45       
  ...putHistory.ts |   92.59 |    85.71 |     100 |   92.59 | 63-64,72,94-96    
  ...storyStore.ts |     100 |    94.11 |     100 |     100 | 69                
  useKeypress.ts   |     100 |      100 |     100 |     100 |                   
  ...rdProtocol.ts |   36.36 |      100 |       0 |   36.36 | 24-31             
  ...unchEditor.ts |    9.67 |      100 |       0 |    9.67 | 11-32,39-90       
  ...gIndicator.ts |     100 |      100 |     100 |     100 |                   
  useLogger.ts     |   21.05 |      100 |       0 |   21.05 | 15-37             
  useMCPHealth.ts  |   70.58 |       75 |      50 |   70.58 | 42-47,59-62       
  ...elsCommand.ts |     100 |      100 |     100 |     100 |                   
  useMcpDialog.ts  |    87.5 |      100 |     100 |    87.5 | 19,23             
  ...moryDialog.ts |    87.5 |      100 |     100 |    87.5 | 19,23             
  ...oryMonitor.ts |     100 |      100 |     100 |     100 |                   
  ...ssageQueue.ts |     100 |      100 |     100 |     100 |                   
  ...delCommand.ts |     100 |       75 |     100 |     100 | 22                
  ...raseCycler.ts |   84.74 |    76.47 |     100 |   84.74 | ...49,52-53,69-71 
  useQwenAuth.ts   |     100 |      100 |     100 |     100 |                   
  ...lScheduler.ts |   84.52 |    93.33 |     100 |   84.52 | ...27-232,328-338 
  ...oryCommand.ts |       0 |        0 |       0 |       0 | 1-7               
  ...umeCommand.ts |   97.24 |    76.92 |     100 |   97.24 | 104-105,145       
  ...ompletion.tsx |   90.59 |    83.33 |     100 |   90.59 | ...01,104,137-140 
  ...ectionList.ts |   96.96 |    95.69 |     100 |   96.96 | ...82-183,237-240 
  ...sionPicker.ts |   90.23 |    71.69 |     100 |   90.23 | ...78-279,283-284 
  ...ngsCommand.ts |   18.75 |      100 |       0 |   18.75 | 10-25             
  ...ellHistory.ts |   91.74 |    79.41 |     100 |   91.74 | ...74,122-123,133 
  ...oryCommand.ts |       0 |        0 |       0 |       0 | 1-73              
  ...Completion.ts |   78.99 |    81.48 |   94.11 |   78.99 | ...77-579,587-624 
  ...tateAndRef.ts |     100 |      100 |     100 |     100 |                   
  useStatusLine.ts |     100 |    98.79 |     100 |     100 | 257               
  ...eateDialog.ts |   88.23 |      100 |     100 |   88.23 | 14,18             
  ...tification.ts |     100 |    85.71 |     100 |     100 | 47                
  ...alProgress.ts |   53.06 |       50 |   66.66 |   53.06 | ...53,61-68,79-85 
  ...rminalSize.ts |   76.19 |      100 |      50 |   76.19 | 21-25             
  ...emeCommand.ts |   67.01 |    29.41 |     100 |   67.01 | ...10-111,115-116 
  useTimer.ts      |   88.09 |    85.71 |     100 |   88.09 | 44-45,51-53       
  ...lMigration.ts |       0 |        0 |       0 |       0 |                   
  ...rustModify.ts |     100 |      100 |     100 |     100 |                   
  ...elcomeBack.ts |   87.36 |     90.9 |     100 |   87.36 | ...,94-96,114-115 
  vim.ts           |   83.77 |    80.31 |     100 |   83.77 | ...55,759-767,776 
 src/ui/layouts    |   89.51 |    86.95 |     100 |   89.51 |                   
  ...AppLayout.tsx |   89.53 |    86.66 |     100 |   89.53 | 50-52,92-97       
  ...AppLayout.tsx |   89.47 |     87.5 |     100 |   89.47 | 58-63             
 ...i/manageModels |   93.61 |       48 |     100 |   93.61 |                   
  manageModels.ts  |   93.61 |       48 |     100 |   93.61 | ...63-166,179,209 
 src/ui/models     |   80.24 |    79.16 |   71.42 |   80.24 |                   
  ...ableModels.ts |   80.24 |    79.16 |   71.42 |   80.24 | ...,61-71,123-125 
 ...noninteractive |     100 |      100 |    7.14 |     100 |                   
  ...eractiveUi.ts |     100 |      100 |    7.14 |     100 |                   
 src/ui/state      |   94.91 |    81.81 |     100 |   94.91 |                   
  extensions.ts    |   94.91 |    81.81 |     100 |   94.91 | 68-69,88          
 src/ui/themes     |   98.53 |    70.31 |     100 |   98.53 |                   
  ansi-light.ts    |     100 |      100 |     100 |     100 |                   
  ansi.ts          |     100 |      100 |     100 |     100 |                   
  atom-one-dark.ts |     100 |      100 |     100 |     100 |                   
  ayu-light.ts     |     100 |      100 |     100 |     100 |                   
  ayu.ts           |     100 |      100 |     100 |     100 |                   
  color-utils.ts   |     100 |      100 |     100 |     100 |                   
  default-light.ts |     100 |      100 |     100 |     100 |                   
  default.ts       |     100 |      100 |     100 |     100 |                   
  ...inal-theme.ts |   88.59 |    85.45 |     100 |   88.59 | ...57-261,266-270 
  dracula.ts       |     100 |      100 |     100 |     100 |                   
  github-dark.ts   |     100 |      100 |     100 |     100 |                   
  github-light.ts  |     100 |      100 |     100 |     100 |                   
  googlecode.ts    |     100 |      100 |     100 |     100 |                   
  no-color.ts      |     100 |      100 |     100 |     100 |                   
  qwen-dark.ts     |     100 |      100 |     100 |     100 |                   
  qwen-light.ts    |     100 |      100 |     100 |     100 |                   
  ...tic-tokens.ts |     100 |      100 |     100 |     100 |                   
  ...-of-purple.ts |     100 |      100 |     100 |     100 |                   
  theme-manager.ts |   87.98 |    82.89 |     100 |   87.98 | ...48-357,362-363 
  theme.ts         |     100 |    38.02 |     100 |     100 | ...34-449,457-461 
  xcode.ts         |     100 |      100 |     100 |     100 |                   
 src/ui/utils      |   78.17 |    86.91 |   86.87 |   78.17 |                   
  ...Colorizer.tsx |   82.78 |    88.23 |     100 |   82.78 | ...10-111,197-223 
  ...nRenderer.tsx |   52.41 |    36.36 |      50 |   52.41 | ...49-151,171-180 
  ...wnDisplay.tsx |   86.79 |    88.88 |     100 |   86.79 | ...06-315,348-373 
  ...eRenderer.tsx |   94.45 |    81.25 |   94.11 |   94.45 | ...65,477,480-483 
  ...dWorkUtils.ts |     100 |      100 |     100 |     100 |                   
  ...boardUtils.ts |   59.61 |    58.82 |     100 |   59.61 | ...,86-88,107-149 
  commandUtils.ts  |    84.7 |    88.13 |      90 |    84.7 | ...63-164,260-279 
  computeStats.ts  |     100 |      100 |     100 |     100 |                   
  customBanner.ts  |   90.68 |    91.22 |     100 |   90.68 | ...13,324-327,334 
  displayUtils.ts  |   88.37 |    72.22 |     100 |   88.37 | 23,25,29,31,33    
  formatters.ts    |   95.23 |    98.27 |     100 |   95.23 | 117-120           
  gradientUtils.ts |     100 |      100 |     100 |     100 |                   
  highlight.ts     |   98.63 |       95 |     100 |   98.63 | 93                
  ...oryMapping.ts |     100 |    94.28 |     100 |     100 | 34,56             
  isNarrowWidth.ts |     100 |      100 |     100 |     100 |                   
  ...olDetector.ts |    8.23 |      100 |       0 |    8.23 | ...31-132,135-136 
  layoutUtils.ts   |     100 |      100 |     100 |     100 |                   
  ...nUtilities.ts |   69.84 |    85.71 |     100 |   69.84 | 75-91,100-101     
  ...ToolGroups.ts |    98.3 |    95.65 |     100 |    98.3 | 48-49             
  ...lsBySource.ts |     100 |    95.23 |     100 |     100 | 84                
  ...mConstants.ts |     100 |      100 |     100 |     100 |                   
  ...storyUtils.ts |   57.81 |    67.14 |      90 |   57.81 | ...64,412,417-439 
  ...ickerUtils.ts |     100 |      100 |     100 |     100 |                   
  ...izedOutput.ts |   94.94 |      100 |   88.88 |   94.94 | 112-117           
  ...wOptimizer.ts |     100 |    96.77 |     100 |     100 | 69                
  terminalSetup.ts |    4.37 |      100 |       0 |    4.37 | 44-393            
  textUtils.ts     |   97.35 |    94.38 |   91.66 |   97.35 | ...50-251,386-387 
  todoSnapshot.ts  |   89.11 |    93.18 |     100 |   89.11 | ...,66-78,180-181 
  updateCheck.ts   |     100 |    80.95 |     100 |     100 | 30-42             
 ...i/utils/export |   56.77 |     40.8 |   79.41 |   56.77 |                   
  collect.ts       |   55.92 |    50.58 |   86.36 |   55.92 | ...25-640,642-647 
  index.ts         |     100 |      100 |     100 |     100 |                   
  normalize.ts     |   57.47 |    20.51 |      80 |   57.47 | ...09-310,324-359 
  types.ts         |       0 |        0 |       0 |       0 | 1                 
  utils.ts         |      40 |      100 |       0 |      40 | 11-13             
 ...ort/formatters |    3.38 |      100 |       0 |    3.38 |                   
  html.ts          |    9.61 |      100 |       0 |    9.61 | ...28,34-76,82-84 
  json.ts          |      50 |      100 |       0 |      50 | 14-15             
  jsonl.ts         |     3.5 |      100 |       0 |     3.5 | 14-76             
  markdown.ts      |    0.94 |      100 |       0 |    0.94 | 13-295            
 src/utils         |   74.06 |    89.77 |    94.6 |   74.06 |                   
  acpModelUtils.ts |     100 |      100 |     100 |     100 |                   
  apiPreconnect.ts |   96.52 |    97.05 |     100 |   96.52 | 166-169           
  checks.ts        |   33.33 |      100 |       0 |   33.33 | 23-28             
  cleanup.ts       |   84.12 |    93.33 |      80 |   84.12 | 75,106-115        
  commands.ts      |     100 |      100 |     100 |     100 |                   
  commentJson.ts   |   85.29 |    89.47 |     100 |   85.29 | 48-57             
  ...Calculator.ts |     100 |      100 |     100 |     100 |                   
  deepMerge.ts     |     100 |       90 |     100 |     100 | 41-43,49          
  ...ScopeUtils.ts |   97.56 |    88.88 |     100 |   97.56 | 67                
  doctorChecks.ts  |   68.59 |    64.28 |     100 |   68.59 | ...63-269,293-309 
  ...putCapture.ts |   90.65 |    86.17 |     100 |   90.65 | ...72,370,372-373 
  ...arResolver.ts |   94.28 |    88.46 |     100 |   94.28 | 28-29,125-126     
  errors.ts        |   98.63 |    96.15 |     100 |   98.63 | 67-68             
  events.ts        |     100 |      100 |     100 |     100 |                   
  gitUtils.ts      |   91.91 |    84.61 |     100 |   91.91 | 78-81,124-127     
  ...AutoUpdate.ts |   90.76 |    93.33 |   88.88 |   90.76 | 103-114           
  ...lationInfo.ts |     100 |      100 |     100 |     100 |                   
  languageUtils.ts |   97.89 |    96.42 |     100 |   97.89 | 132-133           
  math.ts          |       0 |        0 |       0 |       0 | 1-15              
  ...onfigUtils.ts |     100 |      100 |     100 |     100 |                   
  ...iveHelpers.ts |   96.79 |    93.28 |     100 |   96.79 | ...76-477,575,588 
  osc.ts           |    97.5 |      100 |   88.88 |    97.5 | 195-196           
  package.ts       |   88.88 |       80 |     100 |   88.88 | 33-34             
  processUtils.ts  |     100 |      100 |     100 |     100 |                   
  readStdin.ts     |   79.62 |       90 |      80 |   79.62 | 33-40,52-54       
  relaunch.ts      |   98.07 |    76.92 |     100 |   98.07 | 70                
  resolvePath.ts   |   66.66 |       25 |     100 |   66.66 | 12-13,16,18-19    
  sandbox.ts       |       0 |        0 |       0 |       0 | 1-980             
  settingsUtils.ts |   86.36 |    90.67 |   94.44 |   86.36 | ...39,570,633-645 
  spawnWrapper.ts  |     100 |      100 |     100 |     100 |                   
  ...upProfiler.ts |     100 |    95.83 |     100 |     100 | 110               
  ...upWarnings.ts |     100 |      100 |     100 |     100 |                   
  stdioHelpers.ts  |     100 |       60 |     100 |     100 | 23,32             
  systemInfo.ts    |   92.52 |     90.9 |   83.33 |   92.52 | 63-69,184         
  ...InfoFields.ts |   86.91 |    65.78 |     100 |   86.91 | ...16-117,138-139 
  ...iffPreview.ts |   94.11 |    83.33 |     100 |   94.11 | 13                
  ...entEmitter.ts |     100 |      100 |     100 |     100 |                   
  ...upWarnings.ts |   91.17 |    82.35 |     100 |   91.17 | 67-68,73-74,77-78 
  version.ts       |     100 |       50 |     100 |     100 | 11                
  windowTitle.ts   |     100 |      100 |     100 |     100 |                   
  ...WithBackup.ts |    62.1 |    77.77 |     100 |    62.1 | 93,107,118-157    
-------------------|---------|----------|---------|---------|-------------------
Core Package - Full Text Report
-------------------|---------|----------|---------|---------|-------------------
File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------------|---------|----------|---------|---------|-------------------
All files          |   76.53 |    82.19 |   79.44 |   76.53 |                   
 src               |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/__mocks__/fs  |       0 |        0 |       0 |       0 |                   
  promises.ts      |       0 |        0 |       0 |       0 | 1-48              
 src/agents        |   84.07 |    76.02 |   90.66 |   84.07 |                   
  ...transcript.ts |   88.76 |    75.43 |     100 |   88.76 | ...82,306-307,434 
  ...ent-resume.ts |   78.67 |    69.51 |   76.66 |   78.67 | ...88-992,995-997 
  ...ound-tasks.ts |   94.19 |    86.17 |     100 |   94.19 | ...15-616,633-634 
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/agents/arena  |    76.9 |    66.66 |   78.94 |    76.9 |                   
  ...gentClient.ts |   79.47 |    88.88 |   81.81 |   79.47 | ...68-183,189-204 
  ArenaManager.ts  |   75.84 |     62.9 |   78.57 |   75.84 | ...1889,1895-1896 
  arena-events.ts  |   64.44 |      100 |      50 |   64.44 | ...71-175,178-183 
  diff-summary.ts  |    87.5 |    73.46 |     100 |    87.5 | ...32-133,137-138 
  index.ts         |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...gents/backends |    76.3 |    86.23 |   72.41 |    76.3 |                   
  ITermBackend.ts  |   97.97 |    93.93 |     100 |   97.97 | ...78-180,255,307 
  ...essBackend.ts |   91.06 |     90.9 |   82.35 |   91.06 | ...51-271,330,430 
  TmuxBackend.ts   |    90.7 |    76.55 |   97.36 |    90.7 | ...87,697,743-747 
  detect.ts        |   31.25 |      100 |       0 |   31.25 | 34-88             
  index.ts         |     100 |      100 |     100 |     100 |                   
  iterm-it2.ts     |     100 |     92.1 |     100 |     100 | 37-38,106         
  tmux-commands.ts |    6.64 |      100 |    3.03 |    6.64 | ...93-363,386-503 
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...agents/runtime |   79.81 |    75.21 |      66 |   79.81 |                   
  agent-core.ts    |   73.97 |    69.03 |   48.48 |   73.97 | ...1299,1326-1372 
  agent-events.ts  |     100 |      100 |     100 |     100 |                   
  ...t-headless.ts |   79.09 |    69.76 |   52.38 |   79.09 | ...78-379,382-383 
  ...nteractive.ts |   79.71 |    79.62 |      75 |   79.71 | ...54,456,458,461 
  ...statistics.ts |   98.19 |    82.35 |     100 |   98.19 | 127,151,192,225   
  agent-types.ts   |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/config        |   75.25 |    78.08 |   61.96 |   75.25 |                   
  config.ts        |   73.01 |    75.78 |   56.56 |   73.01 | ...2958,2962-2974 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  models.ts        |     100 |      100 |     100 |     100 |                   
  storage.ts       |   95.72 |     93.1 |   91.66 |   95.72 | ...06-207,241-242 
 ...nfirmation-bus |   98.29 |    97.14 |     100 |   98.29 |                   
  message-bus.ts   |   98.14 |    97.05 |     100 |   98.14 | 42-43             
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/constants     |    4.95 |      100 |       0 |    4.95 |                   
  codingPlan.ts    |    4.95 |      100 |       0 |    4.95 | ...79-291,299-309 
 src/core          |   82.43 |    82.21 |   88.52 |   82.43 |                   
  baseLlmClient.ts |   96.77 |    96.42 |      80 |   96.77 | 123-126           
  client.ts        |   76.22 |    77.57 |   86.66 |   76.22 | ...1321,1325-1341 
  ...tGenerator.ts |    72.1 |    61.11 |     100 |    72.1 | ...63,365,372-375 
  ...lScheduler.ts |   77.12 |    81.01 |   92.68 |   77.12 | ...2200,2252-2256 
  geminiChat.ts    |   88.21 |    84.05 |   86.11 |   88.21 | ...1137,1204-1205 
  geminiRequest.ts |     100 |      100 |     100 |     100 |                   
  ...htProtocol.ts |    9.09 |      100 |       0 |    9.09 | 34-42,45-49,52-87 
  logger.ts        |   82.25 |    81.81 |     100 |   82.25 | ...57-361,407-421 
  ...tyDefaults.ts |     100 |      100 |     100 |     100 |                   
  ...olExecutor.ts |   92.59 |       75 |      50 |   92.59 | 41-42             
  ...on-helpers.ts |   85.71 |    70.58 |     100 |   85.71 | ...90-191,205-214 
  ...issionFlow.ts |   98.59 |    94.73 |     100 |   98.59 | 93                
  prompts.ts       |    88.8 |    88.05 |      75 |    88.8 | ...-898,1101-1102 
  tokenLimits.ts   |     100 |    89.47 |     100 |     100 | 51-52             
  ...okTriggers.ts |   99.31 |    91.02 |     100 |   99.31 | 124,135           
  turn.ts          |   96.42 |    88.88 |     100 |   96.42 | ...00,413-414,462 
 ...ntentGenerator |    94.6 |    79.35 |   92.68 |    94.6 |                   
  ...tGenerator.ts |   96.49 |    79.35 |      90 |   96.49 | ...24,481,637,693 
  converter.ts     |   94.38 |    79.78 |     100 |   94.38 | ...40-541,551,734 
  index.ts         |       0 |        0 |       0 |       0 | 1-21              
 ...ntentGenerator |   91.53 |    71.64 |   93.33 |   91.53 |                   
  ...tGenerator.ts |      90 |    70.96 |   92.85 |      90 | ...80-286,304-305 
  index.ts         |     100 |       80 |     100 |     100 | 50                
 ...ntentGenerator |   91.08 |    76.14 |   85.71 |   91.08 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...tGenerator.ts |   91.04 |    76.14 |   85.71 |   91.04 | ...23,533-534,562 
 ...ntentGenerator |   79.48 |    83.73 |   89.47 |   79.48 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  converter.ts     |   74.92 |    80.85 |   86.95 |   74.92 | ...1410,1431-1437 
  errorHandler.ts  |     100 |      100 |     100 |     100 |                   
  index.ts         |   43.85 |    14.28 |      50 |   43.85 | ...,87-91,102-103 
  ...tGenerator.ts |   48.78 |    91.66 |   77.77 |   48.78 | ...10-163,166-167 
  pipeline.ts      |   93.62 |    84.76 |     100 |   93.62 | ...78-479,487,547 
  ...ingOptions.ts |       0 |        0 |       0 |       0 | 1                 
  ...CallParser.ts |   90.66 |     88.4 |     100 |   90.66 | ...15-319,349-350 
  ...kingParser.ts |     100 |    96.87 |     100 |     100 | 42                
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...rator/provider |   96.67 |    89.74 |   94.87 |   96.67 |                   
  dashscope.ts     |   97.22 |    87.69 |   93.33 |   97.22 | ...10-211,287-288 
  deepseek.ts      |   95.55 |    90.56 |     100 |   95.55 | ...31-132,145-146 
  default.ts       |   94.62 |    86.36 |   85.71 |   94.62 | 85-86,156-158     
  index.ts         |     100 |      100 |     100 |     100 |                   
  minimax.ts       |     100 |      100 |     100 |     100 |                   
  modelscope.ts    |     100 |      100 |     100 |     100 |                   
  openrouter.ts    |     100 |      100 |     100 |     100 |                   
  types.ts         |       0 |        0 |       0 |       0 |                   
 src/extension     |   60.62 |    79.43 |   79.03 |   60.62 |                   
  ...-converter.ts |   62.35 |    47.82 |      90 |   62.35 | ...90-791,800-832 
  ...ionManager.ts |   46.92 |    82.19 |   67.44 |   46.92 | ...1386,1396-1415 
  ...onSettings.ts |   93.46 |    93.05 |     100 |   93.46 | ...17-221,228-232 
  ...-converter.ts |   54.88 |    94.44 |      60 |   54.88 | ...35-146,158-192 
  github.ts        |   44.94 |    88.52 |      60 |   44.94 | ...53-359,398-451 
  index.ts         |     100 |      100 |     100 |     100 |                   
  marketplace.ts   |   97.29 |    93.75 |     100 |   97.29 | ...64,184-185,274 
  npm.ts           |   48.66 |    76.08 |      75 |   48.66 | ...18-420,427-431 
  override.ts      |   94.11 |    88.88 |     100 |   94.11 | 63-64,81-82       
  settings.ts      |   66.26 |      100 |      50 |   66.26 | 81-108,143-149    
  storage.ts       |   94.73 |       90 |     100 |   94.73 | 41-42             
  ...ableSchema.ts |     100 |      100 |     100 |     100 |                   
  variables.ts     |   88.75 |    83.33 |     100 |   88.75 | ...28-231,234-237 
 src/followup      |   46.35 |     92.3 |   71.87 |   46.35 |                   
  followupState.ts |      96 |    89.74 |     100 |      96 | 159-161,218-219   
  index.ts         |     100 |      100 |     100 |     100 |                   
  overlayFs.ts     |   95.06 |       84 |     100 |   95.06 | 78,108,122,133    
  speculation.ts   |   13.22 |      100 |   16.66 |   13.22 | 88-458,518-568    
  ...onToolGate.ts |     100 |    96.29 |     100 |     100 | 93                
  ...nGenerator.ts |   36.67 |    95.12 |   33.33 |   36.67 | ...24-326,361-391 
 src/generated     |       0 |        0 |       0 |       0 |                   
  git-commit.ts    |       0 |        0 |       0 |       0 | 1-10              
 src/hooks         |    80.6 |    84.37 |   84.16 |    80.6 |                   
  ...okRegistry.ts |   86.48 |    77.08 |     100 |   86.48 | ...41-344,362-369 
  ...bortSignal.ts |     100 |      100 |     100 |     100 |                   
  ...terpolator.ts |   96.66 |    93.33 |     100 |   96.66 | 66-67             
  ...HookRunner.ts |   96.68 |    87.23 |     100 |   96.68 | 110-112,231-233   
  ...Aggregator.ts |   96.37 |    90.54 |     100 |   96.37 | ...89,291-292,365 
  ...entHandler.ts |   95.58 |    84.37 |   92.59 |   95.58 | ...29,682-683,693 
  hookPlanner.ts   |   84.13 |    76.59 |      90 |   84.13 | ...38,144,162-173 
  hookRegistry.ts  |   88.83 |    86.36 |     100 |   88.83 | ...21,326,330,334 
  hookRunner.ts    |   53.63 |    72.22 |   61.11 |   53.63 | ...23-724,733-734 
  hookSystem.ts    |   75.47 |      100 |   56.41 |   75.47 | ...75-576,582-583 
  ...HookRunner.ts |   75.51 |     61.9 |      80 |   75.51 | ...05-406,424-425 
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...SkillHooks.ts |   78.75 |       75 |   66.66 |   78.75 | 62-66,137-152     
  ...oksManager.ts |    96.5 |     91.8 |     100 |    96.5 | ...90,209-210,223 
  ssrfGuard.ts     |   77.22 |    85.36 |     100 |   77.22 | ...57,261-267,273 
  trustedHooks.ts  |       0 |        0 |       0 |       0 | 1-124             
  types.ts         |   90.15 |    91.02 |   85.18 |   90.15 | ...91-392,452-456 
  urlValidator.ts  |     100 |      100 |     100 |     100 |                   
 src/ide           |   74.28 |    83.39 |   78.33 |   74.28 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  detect-ide.ts    |     100 |      100 |     100 |     100 |                   
  ide-client.ts    |    64.2 |    81.48 |   66.66 |    64.2 | ...9-970,999-1007 
  ide-installer.ts |   89.06 |    79.31 |     100 |   89.06 | ...36,143-147,160 
  ideContext.ts    |     100 |      100 |     100 |     100 |                   
  process-utils.ts |   84.84 |    71.79 |     100 |   84.84 | ...37,151,193-194 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/lsp           |   33.92 |    45.16 |   45.76 |   33.92 |                   
  ...nfigLoader.ts |   70.27 |    35.89 |   94.73 |   70.27 | ...20-422,426-432 
  ...ionFactory.ts |    4.29 |      100 |       0 |    4.29 | ...20-371,377-394 
  ...Normalizer.ts |   23.09 |    13.72 |   30.43 |   23.09 | ...04-905,909-924 
  ...verManager.ts |   13.52 |    81.25 |   29.16 |   13.52 | ...75-694,700-730 
  ...eLspClient.ts |   17.89 |      100 |       0 |   17.89 | ...37-244,254-258 
  ...LspService.ts |   45.87 |    62.13 |   66.66 |   45.87 | ...1282,1299-1309 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/mcp           |   78.69 |    75.34 |   75.92 |   78.69 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...h-provider.ts |   86.95 |      100 |   33.33 |   86.95 | ...,93,97,101-102 
  ...h-provider.ts |   73.82 |    53.92 |     100 |   73.82 | ...88-895,902-904 
  ...en-storage.ts |   98.62 |    97.72 |     100 |   98.62 | 87-88             
  oauth-utils.ts   |   70.58 |    85.29 |    90.9 |   70.58 | ...70-290,315-344 
  ...n-provider.ts |   89.83 |    95.83 |   45.45 |   89.83 | ...43,147,151-152 
 .../token-storage |   79.48 |    86.66 |   86.36 |   79.48 |                   
  ...en-storage.ts |     100 |      100 |     100 |     100 |                   
  ...en-storage.ts |   82.75 |    82.35 |   92.85 |   82.75 | ...62-172,180-181 
  ...en-storage.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...en-storage.ts |   68.14 |    82.35 |   64.28 |   68.14 | ...81-295,298-314 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/memory        |   61.59 |    74.87 |   66.44 |   61.59 |                   
  const.ts         |     100 |      100 |     100 |     100 |                   
  dream.ts         |   65.65 |    73.33 |      50 |   65.65 | 50,107-148        
  ...entPlanner.ts |   57.84 |    72.72 |   33.33 |   57.84 | ...35,140-147,152 
  entries.ts       |   59.84 |       70 |      50 |   59.84 | ...72-180,183-189 
  extract.ts       |    95.2 |    79.16 |     100 |    95.2 | 81-86,125         
  ...entPlanner.ts |   63.08 |    65.71 |   41.17 |   63.08 | ...17,222-223,332 
  ...ionPlanner.ts |       0 |        0 |       0 |       0 | 1                 
  forget.ts        |    8.04 |      100 |       0 |    8.04 | 67-342            
  governance.ts    |       0 |        0 |       0 |       0 | 1-352             
  indexer.ts       |   83.87 |    45.45 |     100 |   83.87 | ...50,56-57,69-70 
  manager.ts       |   73.32 |    78.94 |   74.35 |   73.32 | ...1163,1176-1178 
  memoryAge.ts     |   90.47 |    77.77 |     100 |   90.47 | 50-51             
  paths.ts         |   55.47 |    89.47 |   85.71 |   55.47 | ...,88-89,105-113 
  prompt.ts        |   93.36 |    71.42 |     100 |   93.36 | ...58,161,228-229 
  recall.ts        |   79.56 |    69.38 |   88.88 |   79.56 | ...40-245,269-280 
  ...ceSelector.ts |   91.86 |    77.27 |     100 |   91.86 | ...04,106-107,115 
  scan.ts          |   87.91 |    68.42 |     100 |   87.91 | ...47-48,58,82-87 
  status.ts        |   10.52 |      100 |       0 |   10.52 | 41-98             
  store.ts         |   94.44 |    83.33 |     100 |   94.44 | 56-57,92-93       
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/mocks         |       0 |        0 |       0 |       0 |                   
  msw.ts           |       0 |        0 |       0 |       0 | 1-9               
 src/models        |   89.49 |    85.58 |   87.14 |   89.49 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...tor-config.ts |   88.67 |     90.9 |     100 |   88.67 | 112,118,121-130   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...nfigErrors.ts |   74.22 |       44 |   84.61 |   74.22 | ...,67-74,106-117 
  ...igResolver.ts |   98.63 |    92.53 |     100 |   98.63 | 161,323,329       
  modelRegistry.ts |     100 |    98.21 |     100 |     100 | 182               
  modelsConfig.ts  |   85.37 |    83.54 |   81.57 |   85.37 | ...1210,1239-1240 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/output        |     100 |      100 |     100 |     100 |                   
  ...-formatter.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/permissions   |   71.18 |    88.73 |   48.57 |   71.18 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...on-manager.ts |   81.42 |    86.66 |      80 |   81.42 | ...19-820,827-836 
  rule-parser.ts   |   95.99 |    93.18 |     100 |   95.99 | ...-864,1013-1015 
  ...-semantics.ts |   58.28 |    85.27 |    30.2 |   58.28 | ...1604-1614,1643 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/prompts       |   83.63 |      100 |    87.5 |   83.63 |                   
  mcp-prompts.ts   |   18.18 |      100 |       0 |   18.18 | 11-19             
  ...t-registry.ts |     100 |      100 |     100 |     100 |                   
 src/qwen          |   86.03 |    79.48 |   97.18 |   86.03 |                   
  ...tGenerator.ts |   98.64 |    98.18 |     100 |   98.64 | 105-106           
  qwenOAuth2.ts    |   85.01 |    74.81 |   93.33 |   85.01 | ...,986-1002,1032 
  ...kenManager.ts |   83.79 |    76.22 |     100 |   83.79 | ...63-768,789-794 
 src/services      |      86 |    84.16 |   89.69 |      86 |                   
  ...ionTrailer.ts |     100 |      100 |     100 |     100 |                   
  ...llRegistry.ts |   97.82 |    94.73 |     100 |   97.82 | 172-173           
  ...ionService.ts |   95.42 |       94 |     100 |   95.42 | ...79,336,338-342 
  ...ingService.ts |   82.99 |     83.2 |   81.81 |   82.99 | ...1118,1135-1136 
  ...ttribution.ts |   91.73 |    87.71 |      90 |   91.73 | ...80-685,826-827 
  cronScheduler.ts |   97.56 |    92.98 |     100 |   97.56 | 62-63,77,155      
  ...eryService.ts |   80.43 |    95.45 |      75 |   80.43 | ...19-134,140-141 
  fileReadCache.ts |     100 |      100 |     100 |     100 |                   
  ...temService.ts |   89.76 |     85.1 |   88.88 |   89.76 | ...89,191,266-273 
  ...ratedFiles.ts |      96 |    88.23 |     100 |      96 | 119-120,146-147   
  gitInit.ts       |     100 |      100 |     100 |     100 |                   
  gitService.ts    |   68.75 |     92.3 |   55.55 |   68.75 | ...12-122,125-129 
  ...reeService.ts |   71.83 |    68.47 |    91.3 |   71.83 | ...89-790,806,822 
  ...ionService.ts |   98.13 |     97.8 |   95.45 |   98.13 | ...32-333,380-381 
  ...orRegistry.ts |   96.76 |    92.15 |     100 |   96.76 | ...95-396,449-450 
  sessionRecap.ts  |   10.71 |      100 |       0 |   10.71 | 48-161            
  ...ionService.ts |   85.48 |    71.62 |      96 |   85.48 | ...73-983,987-988 
  sessionTitle.ts  |   93.95 |    70.37 |     100 |   93.95 | ...36-239,270-271 
  ...ionService.ts |   83.01 |    78.66 |   87.75 |   83.01 | ...1458,1464-1469 
  ...UseSummary.ts |    94.7 |    88.67 |     100 |    94.7 | ...69-171,221-222 
 ...icrocompaction |   98.62 |    86.44 |     100 |   98.62 |                   
  microcompact.ts  |   98.62 |    86.44 |     100 |   98.62 | 138,142           
 src/skills        |    86.7 |    83.88 |   93.61 |    86.7 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...activation.ts |     100 |     93.1 |     100 |     100 | 93,112            
  skill-load.ts    |   89.72 |    80.76 |     100 |   89.72 | ...28,248,260-262 
  skill-manager.ts |   82.68 |    79.32 |   90.32 |   82.68 | ...1135,1142-1146 
  symlinkScope.ts  |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/subagents     |   82.78 |    80.18 |   91.11 |   82.78 |                   
  ...tin-agents.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...-selection.ts |     100 |      100 |     100 |     100 |                   
  ...nt-manager.ts |   76.75 |    72.09 |   87.09 |   76.75 | ...1176,1198-1199 
  types.ts         |     100 |      100 |     100 |     100 |                   
  validation.ts    |   92.46 |    95.18 |     100 |   92.46 | 51-56,69-74,78-83 
 src/telemetry     |   70.05 |       83 |   75.11 |   70.05 |                   
  config.ts        |     100 |      100 |     100 |     100 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...-exporters.ts |   46.37 |      100 |   44.44 |   46.37 | ...85,88-89,92-93 
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...t.circular.ts |       0 |        0 |       0 |       0 | 1-111             
  ...-processor.ts |   91.28 |    83.67 |   92.85 |   91.28 | ...66-171,186-187 
  ...t.circular.ts |       0 |        0 |       0 |       0 | 1-128             
  loggers.ts       |    51.9 |       64 |   57.77 |    51.9 | ...1214,1231-1251 
  metrics.ts       |    74.9 |    82.95 |   74.54 |    74.9 | ...58-978,981-992 
  sanitize.ts      |      80 |    83.33 |     100 |      80 | 35-36,41-42       
  sdk.ts           |   88.97 |    77.77 |     100 |   88.97 | ...80-281,300-304 
  ...etry-utils.ts |     100 |      100 |     100 |     100 |                   
  ...l-decision.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |   79.09 |    85.59 |   83.33 |   79.09 | ...1134,1137-1166 
  uiTelemetry.ts   |   92.97 |    96.96 |   81.25 |   92.97 | ...93-194,200-207 
 ...ry/qwen-logger |   68.01 |    80.21 |   64.91 |   68.01 |                   
  event-types.ts   |       0 |        0 |       0 |       0 |                   
  qwen-logger.ts   |   68.01 |       80 |   64.28 |   68.01 | ...1042,1080-1081 
 src/test-utils    |   93.07 |    95.55 |   73.52 |   93.07 |                   
  config.ts        |     100 |      100 |     100 |     100 |                   
  ...st-helpers.ts |   94.11 |       90 |     100 |   94.11 | 69-70             
  index.ts         |     100 |      100 |     100 |     100 |                   
  mock-tool.ts     |   91.02 |    96.77 |   68.96 |   91.02 | ...32,196-197,210 
  ...aceContext.ts |     100 |      100 |     100 |     100 |                   
 src/tools         |   74.89 |    80.75 |   81.96 |   74.89 |                   
  ...erQuestion.ts |    88.8 |    76.74 |    90.9 |    88.8 | ...36-337,344-345 
  cron-create.ts   |   97.61 |    88.88 |   83.33 |   97.61 | 30-31             
  cron-delete.ts   |   96.55 |      100 |   83.33 |   96.55 | 26-27             
  cron-list.ts     |   96.36 |      100 |   83.33 |   96.36 | 25-26             
  diffOptions.ts   |     100 |      100 |     100 |     100 |                   
  edit.ts          |   78.01 |    84.76 |   73.33 |   78.01 | ...86-687,774-824 
  exitPlanMode.ts  |   84.61 |    85.71 |     100 |   84.61 | ...60-163,177-189 
  glob.ts          |   90.63 |    88.33 |   84.61 |   90.63 | ...28,171,302,305 
  grep.ts          |   79.19 |    85.71 |   78.94 |   79.19 | ...20,560,569-576 
  ls.ts            |   96.74 |    90.27 |     100 |   96.74 | 176-181,212,216   
  lsp.ts           |   72.69 |    60.09 |   90.32 |   72.69 | ...1208,1210-1211 
  ...nt-manager.ts |   52.09 |     65.9 |   47.36 |   52.09 | ...02-520,523-560 
  mcp-client.ts    |   29.65 |    71.05 |   46.87 |   29.65 | ...1434,1438-1441 
  mcp-tool.ts      |   90.92 |    88.88 |   96.42 |   90.92 | ...89-590,640-641 
  memory-config.ts |       0 |        0 |       0 |       0 | 1-48              
  ...iable-tool.ts |     100 |    84.61 |     100 |     100 | 102,109           
  monitor.ts       |   92.16 |    83.45 |      92 |   92.16 | ...15,544-547,560 
  ...nforcement.ts |   82.03 |    89.47 |     100 |   82.03 | 137-148,197-210   
  read-file.ts     |   95.09 |     88.6 |      90 |   95.09 | ...99,271-274,277 
  ripGrep.ts       |   94.59 |    85.71 |   93.33 |   94.59 | ...60,463,541-542 
  ...-transport.ts |    6.34 |        0 |       0 |    6.34 | 47-145            
  send-message.ts  |   88.77 |    91.66 |   83.33 |   88.77 | 44-45,68-76       
  shell.ts         |   69.51 |    80.44 |   88.23 |   69.51 | ...3244,3293-3299 
  skill-utils.ts   |     100 |      100 |     100 |     100 |                   
  skill.ts         |   88.11 |    91.17 |   84.61 |   88.11 | ...95,399,422-444 
  task-stop.ts     |   92.94 |    96.15 |   85.71 |   92.94 | 39-40,54-64       
  todoWrite.ts     |   85.42 |    84.09 |   84.61 |   85.42 | ...05-410,432-433 
  tool-error.ts    |     100 |      100 |     100 |     100 |                   
  tool-names.ts    |     100 |      100 |     100 |     100 |                   
  tool-registry.ts |   67.94 |    68.42 |   68.57 |   67.94 | ...59-660,668-669 
  tools.ts         |    87.6 |    89.79 |   88.23 |    87.6 | ...31-432,448-454 
  web-fetch.ts     |   88.44 |    76.92 |    92.3 |   88.44 | ...05-306,308-309 
  write-file.ts    |    79.2 |    79.26 |   83.33 |    79.2 | ...25-628,640-675 
 src/tools/agent   |   83.24 |    84.07 |   83.33 |   83.24 |                   
  agent-context.ts |     100 |      100 |     100 |     100 |                   
  agent.ts         |   83.37 |    84.21 |   82.92 |   83.37 | ...1536,1545-1549 
  fork-subagent.ts |   78.26 |    71.42 |      80 |   78.26 | 54-72,104-105     
 src/utils         |   87.39 |     87.2 |   91.88 |   87.39 |                   
  LruCache.ts      |       0 |        0 |       0 |       0 | 1-41              
  ...ssageQueue.ts |     100 |      100 |     100 |     100 |                   
  ...cFileWrite.ts |   76.08 |    44.44 |     100 |   76.08 | 61-70,72          
  bareMode.ts      |   27.27 |      100 |       0 |   27.27 | 9-15,18-19        
  browser.ts       |    7.69 |      100 |       0 |    7.69 | 17-56             
  ...igResolver.ts |     100 |      100 |     100 |     100 |                   
  cronDisplay.ts   |   42.85 |    23.07 |     100 |   42.85 | 26-31,33-45,47-54 
  cronParser.ts    |   89.74 |    85.71 |     100 |   89.74 | ...,63-64,183-186 
  debugLogger.ts   |   96.12 |    93.75 |   93.75 |   96.12 | 164-168           
  editHelper.ts    |   93.63 |     83.9 |     100 |   93.63 | ...28-429,463-464 
  editor.ts        |   97.61 |    95.71 |     100 |   97.61 | ...70-271,273-274 
  ...arResolver.ts |   94.28 |    88.88 |     100 |   94.28 | 28-29,125-126     
  ...entContext.ts |     100 |    95.45 |     100 |     100 | 83                
  errorParsing.ts  |    97.7 |    97.05 |     100 |    97.7 | 72-73             
  ...rReporting.ts |   88.46 |       90 |     100 |   88.46 | 69-74             
  errors.ts        |   70.92 |       80 |   53.33 |   70.92 | ...03-219,223-229 
  fetch.ts         |   70.18 |    71.42 |   71.42 |   70.18 | ...42,148,161,186 
  fileUtils.ts     |   89.18 |    85.06 |   94.73 |   89.18 | ...91-898,902-908 
  forkedAgent.ts   |   62.98 |    54.54 |      75 |   62.98 | ...23-432,434-447 
  formatters.ts    |   54.54 |       50 |     100 |   54.54 | 12-16             
  ...eUtilities.ts |   89.21 |    86.66 |     100 |   89.21 | 16-17,49-55,65-66 
  ...rStructure.ts |   94.36 |    94.28 |     100 |   94.36 | ...17-120,330-335 
  getPty.ts        |    12.5 |      100 |       0 |    12.5 | 21-34             
  ...noreParser.ts |    92.3 |    89.36 |     100 |    92.3 | ...15-116,186-187 
  gitUtils.ts      |   38.88 |    84.61 |      50 |   38.88 | ...2,51-74,97-148 
  iconvHelper.ts   |     100 |      100 |     100 |     100 |                   
  ...rePatterns.ts |     100 |      100 |     100 |     100 |                   
  ...ionManager.ts |     100 |     90.9 |     100 |     100 | 26                
  ...lPromptIds.ts |     100 |      100 |     100 |     100 |                   
  jsonl-utils.ts   |   59.57 |    89.74 |   45.45 |   59.57 | ...53-286,292-298 
  ...-detection.ts |     100 |      100 |     100 |     100 |                   
  ...yDiscovery.ts |   83.85 |    79.36 |     100 |   83.85 | ...15,318,410-413 
  ...tProcessor.ts |   93.63 |       90 |     100 |   93.63 | ...96-302,384-385 
  ...Inspectors.ts |   61.53 |      100 |      50 |   61.53 | 18-23             
  ...kerChecker.ts |   82.55 |    78.57 |     100 |   82.55 | 68-69,79-84,92-98 
  notebook.ts      |   94.35 |    84.78 |     100 |   94.35 | ...10,122,174-176 
  openaiLogger.ts  |   86.27 |    82.14 |     100 |   86.27 | ...05-107,130-135 
  partUtils.ts     |     100 |      100 |     100 |     100 |                   
  pathReader.ts    |     100 |      100 |     100 |     100 |                   
  paths.ts         |   92.82 |    91.02 |     100 |   92.82 | ...71-372,374-376 
  pdf.ts           |   93.68 |    87.05 |     100 |   93.68 | ...96-297,321-325 
  projectPath.ts   |     100 |      100 |     100 |     100 |                   
  ...ectSummary.ts |   89.39 |    72.41 |     100 |   89.39 | ...37-142,193-196 
  ...tIdContext.ts |     100 |      100 |     100 |     100 |                   
  proxyUtils.ts    |     100 |      100 |     100 |     100 |                   
  ...rDetection.ts |   58.57 |       76 |     100 |   58.57 | ...4,88-89,95-100 
  ...noreParser.ts |   85.45 |    85.18 |     100 |   85.45 | ...59,65-66,72-73 
  rateLimit.ts     |   92.55 |    85.92 |     100 |   92.55 | ...70-272,309-310 
  readManyFiles.ts |   87.96 |    86.95 |     100 |   87.96 | ...05-207,223-234 
  retry.ts         |   89.81 |    88.05 |     100 |   89.81 | ...29,350,357-358 
  ripgrepUtils.ts  |   46.53 |    84.37 |   66.66 |   46.53 | ...32-233,245-322 
  ...sDiscovery.ts |   97.42 |    92.85 |     100 |   97.42 | ...04,182-183,202 
  ...tchOptions.ts |   63.85 |    64.28 |   83.33 |   63.85 | ...29-130,187-188 
  safeJsonParse.ts |   74.07 |    83.33 |     100 |   74.07 | 40-46             
  ...nStringify.ts |     100 |      100 |     100 |     100 |                   
  ...aConverter.ts |   90.78 |    87.87 |     100 |   90.78 | ...41-42,93,95-96 
  ...aValidator.ts |   93.43 |    77.04 |     100 |   93.43 | ...46,155-158,212 
  ...r-launcher.ts |   76.92 |     91.3 |   66.66 |   76.92 | ...34,136,157-195 
  ...orageUtils.ts |   92.41 |    82.82 |     100 |   92.41 | ...39,423-430,441 
  shell-utils.ts   |   82.93 |    89.55 |     100 |   82.93 | ...1522,1529-1533 
  ...lAstParser.ts |   95.58 |    85.79 |     100 |   95.58 | ...1059-1061,1071 
  ...nlyChecker.ts |   95.75 |    92.47 |     100 |   95.75 | ...00-301,313-314 
  sideQuery.ts     |     100 |    92.85 |     100 |     100 | 43                
  ...tGenerator.ts |     100 |      100 |     100 |     100 |                   
  ...ameContext.ts |     100 |      100 |     100 |     100 |                   
  symlink.ts       |   77.77 |       50 |     100 |   77.77 | 44,54-59          
  ...emEncoding.ts |   96.36 |    91.17 |     100 |   96.36 | 59-60,124-125     
  terminalSafe.ts  |     100 |      100 |     100 |     100 |                   
  ...Serializer.ts |   98.72 |       90 |     100 |   98.72 | 42-43,134,201-203 
  testUtils.ts     |   53.33 |      100 |   33.33 |   53.33 | ...53,59-64,70-72 
  textUtils.ts     |      60 |      100 |   66.66 |      60 | 36-55             
  thoughtUtils.ts  |     100 |    92.85 |     100 |     100 | 71                
  ...-converter.ts |   94.59 |    85.71 |     100 |   94.59 | 35-36             
  tool-utils.ts    |    93.6 |     91.3 |     100 |    93.6 | ...58-159,162-163 
  truncation.ts    |     100 |       92 |     100 |     100 | 52,71             
  windowsPath.ts   |   89.47 |    79.31 |     100 |   89.47 | ...57-58,62,90-91 
  ...aceContext.ts |   93.71 |    88.88 |   93.33 |   93.71 | ...24-225,249-251 
  xml.ts           |     100 |      100 |     100 |     100 |                   
  yaml-parser.ts   |      92 |    84.31 |     100 |      92 | 49-53,65-69       
 ...ils/filesearch |   96.34 |    91.66 |     100 |   96.34 |                   
  crawlCache.ts    |     100 |      100 |     100 |     100 |                   
  crawler.ts       |   96.87 |    94.44 |     100 |   96.87 | 83-84             
  fileSearch.ts    |   93.29 |    86.76 |     100 |   93.29 | ...40-241,243-244 
  ignore.ts        |     100 |      100 |     100 |     100 |                   
  result-cache.ts  |     100 |     92.3 |     100 |     100 | 46                
 ...uest-tokenizer |   56.63 |    74.52 |   74.19 |   56.63 |                   
  ...eTokenizer.ts |   41.86 |    76.47 |   69.23 |   41.86 | ...70-443,453-507 
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...tTokenizer.ts |   68.39 |    69.49 |    90.9 |   68.39 | ...24-325,327-328 
  ...ageFormats.ts |      76 |      100 |   33.33 |      76 | 45-48,55-56       
  textTokenizer.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |       0 |        0 |       0 |       0 | 1                 
-------------------|---------|----------|---------|---------|-------------------

For detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run.

wenshao added 4 commits April 10, 2026 23:29
…ted file exclusion

- Replace line-based diff with a prefix/suffix character-level algorithm
  for precise contribution calculation (e.g. "Esc"→"esc" = 1 char, not whole line)
- Compute real AI vs human contribution percentages at commit time by analyzing
  git diff --stat output: humanChars = max(0, diffSize - trackedAiChars)
- Add generated file exclusion (lock files, dist/, .min.js, .d.ts, etc.)
  ported from an existing generatedFiles.ts
- Add file deletion tracking via recordDeletion()
- Update git notes payload format: {aiChars, humanChars, percent} per file
  with real percentages instead of hardcoded 100%
… PR attribution

Align with the full attribution feature set:
- Surface tracking: read QWEN_CODE_ENTRYPOINT env var (cli/ide/api/sdk),
  include surfaceBreakdown in git notes payload
- Prompt counting: incrementPromptCount() hooked into client.ts message
  loop, tracks promptCount/permissionPromptCount/escapeCount
- Session persistence: toSnapshot()/restoreFromSnapshot() for serializing
  attribution state; ChatRecordingService.recordAttributionSnapshot()
  writes to session JSONL; client.ts restores on session resume
- PR attribution: addAttributionToPR() in shell.ts detects `gh pr create`
  and appends "🤖 Generated with Qwen Code (N-shotted by Qwen-Coder)"
- Session baseline: saves content hash on first AI edit of each file
  for precise human/AI contribution detection
- generatePRAttribution() method for programmatic access
…iled commit counter preservation

- Handle initial commit (no HEAD~1) by detecting parent with rev-parse
  and falling back to --root for first commit in repo
- Exclude Cron-triggered messages from promptCount (not user-initiated)
- Add commitSucceeded parameter to clearAttributions() so failed/disabled
  commits don't reset the prompts-since-last-commit counter
- Add test for clearAttributions(false) behavior
- Normalize path.relative() to forward slashes for Windows compatibility
- Use diff-tree --root for initial commits (git diff --root is invalid)
- Replace String.replace() with indexOf+slice to avoid $& special patterns
- Fix clearAttributions(false→true) when co-author disabled but commit succeeded
- Use real newlines instead of literal \n in PR attribution text
- Add surface fallback in restoreFromSnapshot for version compatibility
- Fix single-quote regex to not assume bash supports \' escaping
- Case-insensitive directory matching in generated file detection
- Handle renamed file brace notation in parseDiffStat
# Conflicts:
#	packages/core/src/core/client.ts
# Conflicts:
#	packages/core/src/core/client.ts
#	packages/core/src/services/chatRecordingService.ts
@wenshao wenshao force-pushed the feat/commit-attribution branch from 74d152d to 180daa1 Compare April 19, 2026 00:30
wenshao added 2 commits April 19, 2026 09:09
…ool edits

Previously, recordAttributionSnapshot() only ran at the start of UserQuery
and Cron turns — before the tools for that turn had executed. A session
that wrote a file in turn 1 and committed in turn 2 (across process
boundaries via --resume) lost the tracked edit: the last persisted
snapshot was the turn-1-start snapshot (empty fileStates), so on resume
the attribution service restored empty state and no git notes were
attached to the commit.

Move the snapshot call out of the UserQuery/Cron conditional and run it
on every non-Retry turn. ToolResult turns are scheduled right after
tools execute, so their start-of-turn snapshot now captures any edits
those tools made. Retry turns are skipped since the state is unchanged
from the prior turn.

Added unit tests asserting the snapshot fires for ToolResult/UserQuery
turns and skips Retry turns.

Verified end-to-end in a scratch repo: write-file in turn 1 (no commit)
→ exit → --resume → commit in turn 2 → git notes now contain the
recorded file with correct aiChars and promptCount: 2.
Collapse the two back-to-back messageType !== Retry blocks in
sendMessageStream into one, and refresh chatRecordingService's
recordAttributionSnapshot doc comment to reflect that snapshots fire
on every non-retry turn (not just after user prompts).
wenshao added 4 commits April 24, 2026 15:35
# Conflicts:
#	packages/core/src/services/chatRecordingService.ts
…oggles

Matches the shape used upstream in Claude Code's `attribution.{commit,pr}`
so users can disable the PR body line without losing the commit-message
Co-authored-by trailer (or vice versa). The previous boolean forced both
to move together, which conflated two different surfaces.

- settingsSchema: gitCoAuthor becomes an object with nested commit/pr
  booleans, each `showInDialog: true` so both appear in /settings.
- Config constructor accepts legacy boolean (coerced to { commit: v, pr: v })
  so stored preferences from the pre-split schema carry over.
- shell.ts: attachCommitAttribution and addCoAuthorToGitCommit read .commit;
  addAttributionToPR reads .pr.
Legacy gitCoAuthor was a single boolean and shipped ~4 months ago; the
previous commit split it into { commit, pr } sub-toggles. Without a
migration, users who had set gitCoAuthor: false would see the settings
dialog show the default (true) for both sub-toggles — misleading and
likely to flip their preference on the next save because getNestedValue
returns undefined when asked for .commit on a boolean.

- New v3-to-v4 migration expands boolean → { commit: v, pr: v },
  preserves already-object values, resets invalid values to {} with a
  warning.
- SETTINGS_VERSION bumped 3 → 4; existing integration assertions use the
  constant so the next bump is a single-line change.
- Regenerate vscode-ide-companion settings.schema.json to reflect the
  new nested shape.
- Docs: split the single gitCoAuthor row into .commit and .pr.
The migration already treats any non-boolean, non-object value as invalid
(reset to {} with warning), but the existing test only exercised the
string "yes" branch. Add parameterized cases for null, array, and number
so a future regression that accepts these in the valid bucket gets caught.
Also cover partial objects — the migration must not paternalistically
fill defaults; that responsibility lives in normalizeGitCoAuthor at the
Config boundary.
Comment thread packages/core/src/tools/shell.ts Outdated
Comment thread packages/core/src/tools/shell.ts Outdated
wenshao added 3 commits April 30, 2026 17:28
# Conflicts:
#	packages/core/src/services/chatRecordingService.ts
#	packages/core/src/tools/shell.ts
Two critical issues called out in review:

1. attachCommitAttribution treated the final shell exit code as proof
   that `git commit` itself failed. For compound commands like
   `git commit -m "x" && npm test`, the commit can succeed and a later
   step can fail; the previous code then cleared attribution without
   writing the git note. Now we snapshot HEAD before the command (via
   `git rev-parse HEAD` through child_process.execFile, kept independent
   of the mockable ShellExecutionService) and detect commit creation by
   HEAD movement, so attribution lands whenever a new commit was created
   regardless of later steps.

2. addAttributionToPR spliced the configured generator name into the
   user-approved `gh pr create --body "..."` argument verbatim. A name
   containing `"`, `$`, a backtick, or `'` could break the command or be
   evaluated as command substitution. Now we shell-escape the appended
   text per the surrounding quote style before splicing.

Tests cover the new escape paths for both double- and single-quoted
bodies, including a generator name designed to break interpolation
(`$(rm -rf /) "danger" \`eval\``) and one with an apostrophe.
# Conflicts:
#	packages/cli/src/ui/commands/tasksCommand.ts
#	packages/core/src/tools/edit.ts
#	packages/core/src/tools/shell.ts
#	packages/core/src/tools/write-file.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces structured AI contribution attribution for commits (via git notes) and PR descriptions (via gh pr create body modification), along with settings/migrations and session persistence to keep attribution state across resumes.

Changes:

  • Add a singleton CommitAttributionService to track per-file AI edits and generate git-notes JSON payloads.
  • Extend ShellTool to (a) attach git notes after git commit and (b) append attribution to gh pr create --body ....
  • Split general.gitCoAuthor into { commit, pr } with CLI migration + docs/schema updates.

Reviewed changes

Copilot reviewed 27 out of 27 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
packages/vscode-ide-companion/schemas/settings.schema.json Updates VS Code settings schema to split commit vs PR attribution toggles.
packages/core/src/tools/write-file.ts Records AI-originated writes into attribution tracking.
packages/core/src/tools/shell.ts Detects commits to attach git notes; appends PR attribution to gh pr create; adds helpers for quoting and diff parsing.
packages/core/src/tools/shell.test.ts Updates config shape in tests; adds PR attribution tests; resets attribution singleton between tests.
packages/core/src/tools/edit.ts Records AI-originated edits into attribution tracking.
packages/core/src/services/generatedFiles.ts Adds generated/vendored file detection for attribution exclusion.
packages/core/src/services/generatedFiles.test.ts Unit tests for generated-file detection rules.
packages/core/src/services/commitAttribution.ts Implements attribution tracking, snapshot/restore, note payload generation, prompt counters, and model-name sanitization.
packages/core/src/services/commitAttribution.test.ts Unit tests for attribution service behavior and payload generation.
packages/core/src/services/chatRecordingService.ts Adds a new chat record subtype + recorder method for attribution snapshots.
packages/core/src/services/attributionTrailer.ts Builds git-notes command strings and enforces a max payload size.
packages/core/src/services/attributionTrailer.test.ts Unit tests for git-notes command generation and formatting.
packages/core/src/core/client.ts Increments prompt counters and records/restores attribution snapshots for session resume.
packages/core/src/core/client.test.ts Tests snapshot recording on ToolResult/UserQuery turns and skipping on Retry.
packages/core/src/config/config.ts Splits gitCoAuthor into {commit, pr} and normalizes legacy boolean shape.
packages/core/src/config/config.test.ts Tests defaults and legacy boolean normalization for the new config shape.
packages/cli/src/utils/settingsUtils.ts Updates settings dialog ordering to surface the two new toggles.
packages/cli/src/ui/components/subagents/runtime/AgentExecutionDisplay.test.tsx Formatting-only change (single-line expectation).
packages/cli/src/config/settingsSchema.ts Updates CLI settings schema to define gitCoAuthor as an object with commit/pr.
packages/cli/src/config/settings.ts Bumps settings version from 3 → 4.
packages/cli/src/config/migration/versions/v3-to-v4.ts Adds migration expanding legacy boolean general.gitCoAuthor into {commit, pr}.
packages/cli/src/config/migration/versions/v3-to-v4.test.ts Unit tests for the new V3→V4 migration behavior.
packages/cli/src/config/migration/index.ts Registers the new migration in the migration chain.
packages/cli/src/config/migration/index.test.ts Updates integration expectations to the new current settings version.
integration-tests/terminal-capture/subagent-flicker-regression.ts Formatting-only import compaction.
integration-tests/cli/settings-migration.test.ts Updates expected migrated settings version to 4.
docs/users/configuration/settings.md Documents the new general.gitCoAuthor.commit and .pr settings keys.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/core/src/tools/shell.ts
Comment thread packages/vscode-ide-companion/schemas/settings.schema.json Outdated
Comment thread packages/core/src/services/commitAttribution.ts
Comment thread packages/core/src/tools/shell.ts Outdated
Comment thread packages/core/src/tools/shell.ts Outdated
Comment thread packages/core/src/services/attributionTrailer.ts Outdated
Six items called out on PR #3115 by Copilot:

- shell.ts: addAttributionToPR's bash quote escaping doesn't apply to
  cmd.exe / PowerShell, where `\$` and `'\''` aren't honored. Skip the
  PR body rewrite entirely on Windows — losing PR attribution there is
  preferable to corrupting the user-approved `gh pr create` command.

- attributionTrailer.ts + shell.ts call site: buildGitNotesCommand used
  bash-style single-quote escaping on the JSON note, which is broken on
  Windows. Switched to argv form (`{ command, args }`) and routed the
  invocation through child_process.execFile so shell quoting is bypassed
  entirely. Tests updated to assert the argv shape.

- commitAttribution.ts: when a tracked file's aiChars exceeded the diff
  --stat-derived diffSize (long-line edits where diffSize ≈ lines * 40),
  humanChars clamped to 0 but aiChars stayed inflated, leaving aiChars +
  humanChars > the committed change magnitude. Clamp aiChars to diffSize
  so the totals stay consistent.

- shell.ts parseDiffStat: only normalized rename brace notation
  (`{old => new}`). Cross-directory renames emit `old/path => new/path`
  without braces, leaving diffSizes keyed by the full string. Added a
  second normalization step.

- shell.ts: addAttributionToPR docstring claimed `(X% N-shotted)` but
  the implementation only emits `(N-shotted by Generator)`. Updated the
  docstring to match the actual behavior.

- settingsSchema.ts + generator: gitCoAuthor went from boolean to object
  in the V4 migration. The exported JSON Schema now wraps the field in
  `anyOf: [boolean, object]` (via a new `legacyTypes` hint on
  SettingDefinition) so users with a stored boolean don't see a spurious
  IDE warning before their next launch runs the migration.
…ation, sudo/env shifts, dir-stack

gpt-5.5 review (issue 4389405179):

1. realpathOrSelf falls back to the non-canonical input when the
   leaf doesn't exist (deleted file). recordEdit stored the entry
   under the canonical path; lookup post-deletion misses on macOS
   where /var ↔ /private/var. Canonicalise the parent and rejoin
   the basename for missing leaves so deleted-file getFileAttribution
   still resolves the canonical key. Test updated to assert the
   lookup-after-unlink path explicitly.

2. validateOnDiskHashes read the LIVE working-tree, so a user who
   `git add`'d AI's content and then made additional unstaged edits
   would have the entry dropped on a commit whose blob still matched
   AI's hash. Replace with `validateAgainst(getContent)` that takes
   a caller-supplied reader; attachCommitAttribution now passes a
   reader that fetches the COMMITTED blob via `git show HEAD:<rel>`.
   Working-tree validation kept as `validateAgainstWorkingTree` for
   code paths without a committed ref. Returns null = no comparison
   signal (entry preserved). Tests cover all three readers
   (committed-blob via stub, working-tree, null-passthrough).

deepseek-v4-pro review #1: sanitiseAttribution defaults missing
contentHash to '' on legacy-snapshot restore. recordEdit's
divergence check would then trip on every subsequent edit and
silently reset all the AI work. Skip the divergence check when
existing.contentHash is empty — we have no baseline to compare
against, so don't drop. Test added covering legacy-snapshot
preservation through validateAgainst.

deepseek #4: validateAgainst now logs every entry drop via
debugLogger.debug so a 3am operator can see WHICH entry got
dropped and tied to which canonical key.

deepseek #8: GIT_NAMESPACE removed from GIT_ENV_SHIFTS_REPO. It
prefixes ref names within the same repo but doesn't redirect git
to a different on-disk repository, so a commit underneath it still
lands in our cwd's repo. Doc comment explains the distinction.

deepseek #9: pushd/popd treated as cwd-shifting alongside cd in
gitCommitContext / isAmendCommit / findAttributableCommitSegment.
pushd reuses cdTargetMayChangeRepo (relative-no-escape stays
in-repo); popd unconditionally flips cwdShifted because we don't
track the bash dir-stack.

deepseek #10: sudo's value-taking flag table now has a parallel
SUDO_FLAGS_SHIFT_CWD set covering -D / --chdir (Linux sudo 1.9.2+).
Any segment whose sudo wrapper sees one of those flags returns null
from tokeniseSegment — same contract as env -C / --chdir and
GIT_DIR=...

328 tests pass; typecheck clean both packages.
@wenshao

wenshao commented May 6, 2026

Copy link
Copy Markdown
Collaborator Author

Follow-up — addressing two recent review batches

Re: gpt-5.5 issue comment (4389405179) + deepseek-v4-pro review (4237493276) — landed in b3a06a7c4.

gpt-5.5 #1 (deleted-file canonicalisation)realpathOrSelf now canonicalises the parent and rejoins the basename for missing leaves, so getFileAttribution(deletedPath) still resolves the canonical key recordEdit stored before the file was removed (macOS /var/private/var). The test that previously failed at commitAttribution.test.ts:200 now passes; new explicit lookup-after-unlink assertion added.

gpt-5.5 #2 (working-tree vs committed blob) ✅ Refactored validateOnDiskHashesvalidateAgainst(getContent) taking a caller-supplied content reader. attachCommitAttribution now passes a reader that fetches the COMMITTED blob (git show HEAD:<rel>) so a user who git add'd AI's content and then made additional unstaged edits no longer has the entry dropped on a commit whose blob still matches AI's hash. The legacy live-disk reader is exposed as validateAgainstWorkingTree for code paths without a committed ref. New tests cover all three reader shapes (committed-blob stub, working-tree, null-passthrough).

deepseek-v4-pro #1 (empty contentHash on legacy snapshot) ✅ Skip the divergence reset when existing.contentHash is empty — that's a legacy snapshot with no baseline; we'd otherwise reset every entry on first recordEdit/validate call and silently wipe all the AI work. Test added covering legacy-snapshot survival through validateAgainst.

deepseek-v4-pro #4 (silent deletes)validateAgainst now logs every entry drop via debugLogger.debug with the canonical key — a 3am operator can correlate validateAgainst: dropping stale attribution for /path/to/file (hash diverged) with what they expected to see in the note.

deepseek-v4-pro #8 (GIT_NAMESPACE) ✅ Removed from GIT_ENV_SHIFTS_REPO. It prefixes refs within the same repo but doesn't redirect to a different on-disk repository, so a commit underneath it still lands in our cwd's repo. Doc comment explains the distinction (vs GIT_DIR / GIT_WORK_TREE / GIT_COMMON_DIR / GIT_INDEX_FILE).

deepseek-v4-pro #9 (pushd/popd) ✅ All three commit-walk sites (gitCommitContext, isAmendCommit, findAttributableCommitSegment) now treat pushd <DIR> like cd <DIR> (reusing cdTargetMayChangeRepo's relative-no-escape heuristic) and popd as unconditionally cwd-shifting (we don't track the bash dir-stack).

deepseek-v4-pro #10 (sudo --chdir) ✅ Added SUDO_FLAGS_SHIFT_CWD = {-D, --chdir} (Linux sudo 1.9.2+). Any segment whose sudo wrapper sees one of those flags returns null from tokeniseSegment — same contract as env -C / --chdir and a leading GIT_DIR=... assignment.

Deferred with rationale:

  • deepseek Where is the config saved? #2/如何自定义密钥文件 .env可能与其他文件冲突 #3 (test coverage for shouldClear and timeout detection): both branches require execFile-mock plumbing that's invasive in shell.test.ts (the existing test pattern intercepts at ShellExecutionService, not child_process). The shouldClear logic is exercised end-to-end by the existing attachCommitAttribution integration tests; the timeout branch is bounded enough that I'd rather add it alongside a broader child_process test fixture in a follow-up than introduce a one-off mock here.

  • deepseek TypeError in Authentication Selection Interface #5 (pickBool warns on unrecognised string): current behaviour is intentional — unknown strings fall through to false (safer-by-default than the previous "default-to-true on mismatch"). A warn log is a nice-to-have but doesn't change behaviour. Skipping for now.

  • deepseek OpenAI API Error: 401 Incorecct API Key provided #6 (isPlainObject duplicated): minor refactor; landed isPlainObject is local to restoreFromSnapshot's envelope check (~6 LOC). Worth deduplicating but doesn't change behaviour.

  • deepseek API Key是要设成阿里云的API Key吗? #7 (validateOnDiskHashes O(N) sync I/O): substantially mitigated by gpt-5.5 Where is the config saved? #2's switch — validateAgainst from the commit path runs git show per file, and the call sites that need it scope to "files actually in the commit" rather than all tracked files. The remaining one-shot git show per tracked file is bounded (typical commits touch <20 files) and faster than reading from disk in many cases.

328 tests pass; typecheck clean both packages.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 30 out of 30 changed files in this pull request and generated no new comments.

wenshao added 9 commits May 7, 2026 00:12
…der, intent-aware migration

Round 1 of multi-pass audit on b3a06a7. Three correctness fixes:

1. validateAgainst was iterating ALL fileAttributions but the
   committed-blob reader (git show HEAD:<rel>) returns HEAD's
   pre-AI content for files NOT in the just-made commit. Result:
   pending unstaged AI work was silently wiped on every commit
   because the divergence check ran against the wrong baseline
   for unrelated files. Fix: build the committed scope first via
   matchCommittedFiles, scope the reader to that set (return null
   for everything else), validate, then RE-run matchCommittedFiles
   to pick up dropped entries. The validateAgainstWorkingTree
   wrapper had no production caller — removed it and its test.

2. The committed-blob reader used symbolic `HEAD` instead of the
   captured postHead SHA — same TOCTOU concern buildGitNotesCommand
   already addressed. A post-commit hook moving HEAD between
   capture and the reader's `git show` would silently compare
   against the wrong commit's content and trip the divergence
   check spuriously. Pin the reader to `git show <postHead>:<rel>`.

3. v3→v4 migration's invalid-string fallback used to reset to {}.
   Combined with the runtime pickBool's "absent → schema default
   true" rule, that silently re-enabled attribution for users who
   hand-edited `"gitCoAuthor": "off"` to disable. Migration now
   recognises enable-intent strings (true/yes/on/1/enabled) and
   disable-intent strings (false/no/off/0/disabled/'') and maps
   them to {commit, pr} explicitly. Unrecognised strings fall to
   {commit: false, pr: false} with a warning — same safer-by-default
   contract as runtime pickBool. Test grid covers all 11 cases.

Also tidied the FileAttribution.contentHash JSDoc to reference
the renamed `validateAgainst` (was still pointing at the dropped
`validateOnDiskHashes` name).

1085 tests pass; typecheck clean both packages.
…Bool inputs

Round 2 of multi-pass audit. Two cleanups, no behaviour changes:

1. addCoAuthorToGitCommit and addAttributionToPR each carried their
   own copy of the matchRange / isInside / "pick LAST non-nested
   match" logic (~25 LOC duplicated). Extracted to module-level
   helpers `matchSpan`, `isMatchInside`, and `pickOuterLastMatch<T>`
   so a future bug fix can't apply to only one of the two
   rewriters. Behaviour identical — same algorithm, same edge cases.

2. normalizeGitCoAuthor's pickBool silently maps unrecognised
   strings to false (safer-by-default vs the old "default-to-true
   on mismatch" policy, but a user who hand-edited
   `{ commit: "maybe" }` had no signal that their setting was being
   ignored). Add a `gitCoAuthorLogger.warn` listing the accepted
   forms so a debug-mode user can see the actual coercion. Known
   disable-intent strings (false/no/off/0/empty) stay silent —
   they're explicit user intent. Also pass the field name so the
   warning identifies which sub-toggle (commit vs pr) was bad.

1101 tests pass; typecheck clean.
Round 3 of multi-pass audit. One real correctness fix.

Edit and WriteFile preserve the file's BOM and CRLF line-ending
choice when writing back, so the on-disk bytes can include a leading
U+FEFF and CRLFs even when AI's recordEdit input was given with LF
and no BOM. The committed-blob reader's `git show <sha>:<rel>`
returns those raw bytes verbatim, and computeContentHash hashed them
as-is — so a UTF-8 BOM file or a CRLF-line-ending file would always
have a mismatch between AI's recorded hash and the on-disk hash, and
validateAgainst would drop the entry on every commit.

Add `canonicaliseForHash`: strips a leading U+FEFF and normalises
CRLF→LF before computing the SHA-256. Both sides (recordEdit when
storing the post-write hash, and validateAgainst when comparing to
the on-disk read) flow through computeContentHash, so the
canonicalisation is symmetric. The hash is metadata used only for
divergence detection — collapsing these visual differences is the
right comparison semantics.

Three regression tests added: BOM-only, CRLF-only, and BOM+CRLF
combined. All exercise the typical case where AI's recordEdit input
is LF + no BOM but the on-disk content (post-writeTextFile) has the
file's preserved BOM/lineEnding choice.
…d file

Round 4 of multi-pass audit + Copilot finding from review 4236842362
(I missed it in the previous refresh).

recordEdit's existing prior-state check was symmetric on diverged
oldContent but ASYMMETRIC on a fresh file lifetime: when AI creates
`foo.ts` (oldContent=null), then user `rm foo.ts`, then AI
re-creates `foo.ts` (oldContent=null again), the second recordEdit
saw `existing` (from the first lifetime) and SKIPPED the divergence
check (because oldContent === null bails out of that branch). The
accumulator carried 100 chars from the deleted file plus 5 chars
from the new content = 155, vs the actual 5 on disk. Subsequent
generateNotePayload's clamp against `(adds+dels) * 40` couldn't
catch this — the diff size for a 1-line addition is 40, far above
the actual content size.

Add a fresh-file-lifetime branch: when `existing` is set AND the
caller reports `oldContent === null`, reset aiContribution and
aiCreated before counting the new contribution. The new edit is
treated as a brand-new file at the same path (which is what the
caller's null oldContent means semantically).

Test added covering the exact `AI create → delete → AI re-create`
flow. Also verified `should treat new files as ai-created` and
`should accumulate contributions across multiple edits` still pass.
Round 5 of multi-pass audit. Two related correctness/efficiency
fixes around the cwd-shift parser and the preHead capture.

1. `git -C .` (and `-C ./`, `-C.`) is a no-op cwd shift but the
   "any -C → cwd-shifted" rule was treating it the same as
   `-C /tmp/other`, suppressing attribution for what's effectively
   `git commit` with an explicit current-dir marker. Add an
   `isNoopCwdTarget` helper used in both the spaced (`-C .`) and
   attached (`-C.`) branches of `parseGitInvocation`. `--git-dir`
   / `--work-tree` are left unconditional — those aren't cwd in the
   same sense.

2. preHead was being captured for ANY hasCommit, including the
   non-attributable cases (`cd /elsewhere && git commit`,
   `git -C /other commit`). The only consumer of preHead is the
   `attachCommitAttribution` call inside the `attributableInCwd`
   branch — there is intentionally NO cleanup branch for the
   non-attributable case (see the existing comment around the
   `else if (commitCtx.hasCommit)` non-branch). The execFileSync
   for `getGitHeadSync` is dead work in that path: ~10–50 ms
   blocking the event loop before the user's real command spawns.
   Gate the capture on `attributableInCwd` to match the consumer.

Tests added for the three -C dot-form variants. Full suite green:
146 in shell.test.ts, 56 in commitAttribution.test.ts.
Round 7 of multi-pass audit. Two related fixes around how
`shell-quote` handles env-var references and how the cwd-shift
detector reads them.

1. `shell-quote.parse` collapses `$NAME` references it cannot
   resolve to the empty string. The downstream cwd-shift checks
   (`cdTargetMayChangeRepo`'s `target.includes('$')` repo-shift
   detector, and the new `isNoopCwdTarget` no-op detector) were
   designed to catch env-var targets but received `''` instead of
   `$NAME` from `tokeniseSegment` and silently failed. Concretely,
   `cd $HOME && git commit` and `git -C $HOME commit` would both
   pass through as in-cwd attributable, stamping our trailer onto
   commits that land in whatever repo `$HOME`/`$REPO_ROOT`
   resolves to at runtime.

   Pass an env getter `(key) => '$' + key` to `shell-quote.parse`
   inside `tokeniseSegment` so unresolved references stay literal
   in tokens (`['cd', '$HOME']` instead of `['cd', '']`).
   `target.includes('$')` now fires correctly, and the no-op
   detector sees `$HOME` (non-`.`) and rejects it. KEY=value
   leading-env detection is unaffected (shell-quote doesn't
   interpolate inside KEY=value tokens).

2. Even with env preservation, an `''` target can still slip
   through (literal `-C ""`, escaped quotes, edge cases in
   shell-quote). Round 5's `isNoopCwdTarget` accepted `''` as a
   no-op alongside `'.'` / `'./'`, which would re-introduce the
   attribution-on-wrong-repo problem if any path produced an
   empty token. Tighten to `'.'` and `'./'` only — the only
   missed cases are literal `-C ""` (malformed, won't actually
   commit) and the rare `-C $PWD` (now also caught conservatively,
   since `$PWD` becomes literal `$PWD` and isn't `.` or `./`).

Tests added for `cd $HOME` / `cd $REPO_ROOT && git commit` and
`git -C $HOME commit` / `git -C "" commit`. Full suite green
(150 in shell.test.ts, 58 in commitAttribution.test.ts).

@tanzhenxin tanzhenxin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

Most of the prior round is closed: the note write is SHA-pinned, the three-layer hash-divergence detection holds up against paste-replace and delete-then-recreate, and the V3→V4 migration now honours user opt-out intent on string forms. The PR description was rewritten and matches what the code emits. Two residuals remain — one carried over from last round, one newly surfaced.

1. The diff phase still races against HEAD

The previous round identified that the note was being attached to a symbolic HEAD, with a window in which a post-commit hook could move HEAD before the note landed. That fix went in for the note write itself — the note now correctly attaches to the commit the user cared about. But the content of the note (which files changed, how big the changes were, what got deleted, what got renamed) is still computed by reading symbolic HEAD several times inside getCommittedFileInfo. Several awaited git subprocesses run between the postHead capture and these reads, so any post-commit hook, husky/lefthook auto-amender, chained git tag -m ..., or parallel git process that moves HEAD in that window leaves the note attached to commit A but describing commit B's contents. It's the same TOCTOU class as the previous critical, half-closed. Threading postHead (and the captured preHead) through the diff calls — so the range becomes ${preHead}..${postHead} instead of HEAD~1..HEAD — closes it.

2. aiChars reads as a character count but isn't one

The field is emitted as a plain integer named aiChars, and the PR description's example shows values like 3200 / 1500 / 4700 — anyone parsing the git note will read these as literal character counts. They aren't. Internally, the value is (added_lines + deleted_lines) × 40 for text files and a flat 1024 for binary files, with the per-file AI accumulator clamped against that ceiling. The consequence: a commit adding 1000 one-character lines and a commit adding 1000 thousand-character lines both report aiChars = 40000; a 5 MB image change and a 1-byte binary tweak both report 1024. The aiPercent is fine in most cases (the same heuristic appears on both sides of the ratio), but anyone aggregating raw aiChars for compliance or audit reporting — which is the motivation in the PR description — gets systematically wrong numbers.

Verdict

COMMENT — the architectural work is solid and most prior criticals are closed; these two are scoped fixes or doc clarifications, not rework.

…stic

Addresses tanzhenxin's review (4240760004) — two residuals after
the prior pinning round.

1. Diff phase still races against HEAD.

   The note write itself was already pinned to the captured `postHead`
   (`git notes add -f <postHead>`), but the *content* of the note —
   `getCommittedFileInfo`'s probe + diff calls and the multi-commit
   guard's `rev-list --count` — were still going through symbolic
   `HEAD` / `HEAD~1` / `HEAD@{1}`. Several awaited subprocesses run
   between the postHead capture and these reads, so a husky / lefthook
   auto-amender, signed-commits hook, chained `git tag -m`, or
   parallel git process moving HEAD in that window would leave the
   note attached to commit A but describing commit B's contents.
   Same TOCTOU class as the prior critical, half-closed.

   Thread `postHead` (and `preHead` for amend) through
   `getCommittedFileInfo`. Probes become `rev-parse --verify
   ${postHead}~1` and `log -1 --pretty=%P ${postHead}`; diffs become
   `${postHead}~1..${postHead}` (parent case),
   `${preHead}..${postHead}` (amend — preHead is the pre-amend SHA
   captured before the user's command and is exactly what HEAD@{1}
   resolved to at parse time, with the added benefit that it can't be
   GC'd between capture and use), and `diff-tree --root <postHead>`
   (root commit). The amend branch keeps the existing reflog-vs-
   no-reflog warning, just driven off `preHead` instead of HEAD@{1}.

   Same pin applied to `countCommitsAfter` (now `${preHead}..
   ${postHead}`) and `countCommitsFromRoot` (now `${postHead}`).

   Why parent case uses `${postHead}~1` and NOT `${preHead}`: in
   `git reset HEAD~3 && git commit` chains the captured preHead
   points well above postHead's parent, and `${preHead}..${postHead}`
   would describe the reset-away commits as deletions, drastically
   over-attributing. The actual parent of the just-landed commit is
   what we want, and `${postHead}~1` is the SHA-pinned form of that.

2. `aiChars` reads as a literal char count but isn't.

   The field is emitted as a plain integer named `aiChars`; the PR
   description's example shows values like 3200 / 1500 / 4700 that
   anyone parsing the note will read as literal character counts.
   Internally it's `(addedLines + deletedLines) × 40` for text and a
   flat 1024 for binary, with the per-file AI accumulator clamped
   against that ceiling. So 1000 one-character lines and 1000
   thousand-character lines both report aiChars=40000, and a 5 MB
   image change and a 1-byte binary tweak both report 1024. Anyone
   aggregating raw aiChars for compliance reporting gets
   systematically wrong numbers.

   Add a comprehensive doc block on `FileAttributionDetail` (and
   `CommitAttributionNote`) calling out the heuristic explicitly,
   noting that `percent` / `summary.aiPercent` are the correct
   fields for aggregation since both numerator and denominator use
   the same proxy. Also expand the `APPROX_CHARS_PER_LINE` /
   `BINARY_DIFF_SIZE_FALLBACK` const docs to point at the same
   caveat. (Not renaming the fields — that'd break any downstream
   consumer already parsing the existing schema; the doc is the
   minimum-disruption call here.)

208 attribution tests pass; type-check clean.
@wenshao

wenshao commented May 7, 2026

Copy link
Copy Markdown
Collaborator Author

@tanzhenxin both residuals addressed in 8c33120.

1. Diff-phase TOCTOU (now fully closed)

getCommittedFileInfo and the two rev-list --count helpers used to drive the multi-commit guard now SHA-pin every probe and every diff to the captured postHead (and preHead for amend). Concretely:

  • Parent probes: rev-parse --verify ${postHead}~1, log -1 --pretty=%P ${postHead}.
  • Diff range (parent case): ${postHead}~1..${postHead} for --name-only / --name-status / --numstat.
  • Diff range (amend): ${preHead}..${postHead}. preHead was captured synchronously before the user's command spawned and is exactly what HEAD@{1} resolved to at parse time, with the added benefit that it can't be GC'd between capture and use. The reflog-vs-no-reflog warning becomes a probe of ${preHead} itself.
  • Diff range (root commit): diff-tree --root <postHead>.
  • Multi-commit guard: rev-list --count ${preHead}..${postHead} (or just ${postHead} for new-repo first commit).

Why parent case uses ${postHead}~1 and not ${preHead}: in chains like git reset HEAD~3 && git commit the captured preHead points well above postHead's parent, and ${preHead}..${postHead} would describe the reset-away commits as deletions and drastically over-attribute. The pinned form ${postHead}~1 is the actual parent of the just-landed commit.

2. aiChars naming clarity

Not renaming the field (it would break any downstream consumer already parsing the existing schema), but added a comprehensive doc block on FileAttributionDetail and CommitAttributionNote calling out the heuristic explicitly:

  • The value is (addedLines + deletedLines) × 40 for text and a flat 1024 for binary, with the per-file AI accumulator clamped to that ceiling.
  • 1000 one-character lines and 1000 thousand-character lines both report aiChars = 40000; a 5 MB image change and a 1-byte binary tweak both report 1024.
  • percent / summary.aiPercent are the fields consumers should rely on for cross-commit aggregation since both numerator and denominator use the same proxy. Raw aiChars / humanChars are useful for ordering files within the same payload but should not be summed across unrelated commits as if they were byte counts.

The APPROX_CHARS_PER_LINE / BINARY_DIFF_SIZE_FALLBACK const docs point at the same caveat. PR description (and the example payload) updated separately to cross-reference this so the values aren't presented as literal char counts.

208 attribution tests pass; type-check clean.

@tanzhenxin

Copy link
Copy Markdown
Collaborator

The follow-up commit cleanly closes both findings — every git probe in the analysis phase is now SHA-pinned to the captured commit, and the schema doc explains the aiChars proxy plainly enough that consumers won't mistake it for a literal char count. The amend path's switch from reflog-based HEAD@{1}..HEAD to captured-SHA-based pre/post pinning is a nice extra robustness win — it drops the reflog-GC dependency and incidentally also handles the chained --amend && --amend case that the prior approach silently missed.

Two things to clear before merging: the branch is currently conflicting with main, and the Test (windows-latest, 24.x) CI job is failing. Once those are resolved this looks ready to me.

wenshao added 2 commits May 7, 2026 10:46
…ttribution

# Conflicts:
#	packages/cli/src/config/settingsSchema.ts
#	scripts/generate-settings-schema.ts
… compat

Windows CI failure on the two new rename tests (visible at PR #3115's
`Test (windows-latest, *)` jobs):

  AssertionError: expected undefined to be defined
  ❯ src/services/commitAttribution.test.ts:572:66 (basic move)
  AssertionError: expected 11 to be 22 (merge into existing)

Root cause: `path.join(canonicalRepoRoot, ...renamedRel.split('/'))`
calls `path.win32.join` on Windows, which forces backslash separators
regardless of input form. The test's `fs.realpathSync` mock returns
forward-slash paths (matching the macOS `/var` ↔ `/private/var`
fixture style), so `recordEdit` stores keys like
`/private/var/repo/src/old.ts`. The rename's joined target then came
out as `\\private\\var\\repo\\src\\new.ts`, the mock left it
unchanged (no `/var/` prefix to translate), and the subsequent
`fileAttributions.get(renamedAbs)` / `getFileAttribution(...)` lookups
missed the just-set entry — the rename silently dropped attribution.

The fix: build the joined path with `path.posix.join` against a
forward-slash-normalised `posixRepoRoot`, then let `realpathOrSelf`
canonicalise to the platform's storage form. This way:

  - On real Windows production: posix-joined `D:/repo/src/new.ts` is
    accepted by `fs.realpathSync` (Win32 API takes mixed slashes) and
    returned in backslash form, matching what `recordEdit` stored.
  - On real Linux/macOS production: forward-slash throughout, no-op.
  - In the symlink-aware test (any platform): forward-slash matches
    the mock-fixture storage form.

`matchCommittedFiles` already does the inverse normalisation
(`.split(path.sep).join('/')` for the relative-form check), so the
in/out paths line up either way.

Skipped adding a path.sep-mocked Linux-side regression because the
ESM module namespace doesn't allow `vi.spyOn` on path's exports.
The Windows CI job is the regression catcher; a focused-rerun
should now go green.
@wenshao

wenshao commented May 7, 2026

Copy link
Copy Markdown
Collaborator Author

@tanzhenxin both blockers cleared.

Branch conflict with main: Resolved in merge commit b8d242d. Two conflicts both touched additive expansions of the same interfaces — SettingDefinition.legacyTypes (mine) vs jsonSchemaOverride (main's #3710), and the JsonSchemaProperty anyOf/oneOf/allOf triple — kept both in each case and regenerated settings.schema.json. No semantic conflict.

Test (windows-latest, *) failures: Two commitAttribution.test.ts rename tests were failing on Windows runners (same two on each of windows-latest, 20.x / 22.x / 24.x). The applyCommittedRenames move was using path.join(canonicalRepoRoot, ...renamedRel.split('/')), and on Windows path.join is path.win32.join which forces backslash separators — so the joined key came out as \private\var\repo\src\new.ts while recordEdit had stored /private/var/repo/src/new.ts, and the subsequent lookup missed the just-moved entry. Fixed in af5a1d9 by switching to path.posix.join against a forward-slash-normalised repo root, then letting realpathOrSelf canonicalise to the platform's storage form. On Windows production fs.realpathSync accepts mixed slashes and returns the platform's backslash form (matching what recordEdit would have stored from a normal Windows path), so production paths still align; on Linux/macOS the path is forward-slash throughout. (The Linux-runner test passes today because path.posix.join === path.join there; CI Windows is the regression catcher.)

CI should be green on the next run.

tanzhenxin
tanzhenxin previously approved these changes May 7, 2026

@tanzhenxin tanzhenxin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed against the latest revision. The prior critical issues (HEAD-symbolic diff analysis TOCTOU and aiChars units mismatch) are addressed. Merge conflicts are resolved and CI is green on all completed jobs.

Approving.

The SHA-pinning round (8c33120) replaced symbolic `HEAD~1..HEAD` /
`HEAD@{1}..HEAD` with `${postHead}~1..${postHead}` and
`${preHead}..${postHead}` in `getCommittedFileInfo` and the rev-list
helpers, but three docstrings / inline comments still described the
old shapes:

- `isAmendCommit` JSDoc said the amend switch goes from `HEAD~1..HEAD`
  to `HEAD@{1}..HEAD`. Updated to reference `${postHead}~1..${postHead}`
  and `${preHead}..${postHead}`, with the why (amended commit's parent
  is the original's parent so the standard parent diff lumps both
  commits' changes).
- `attachCommitAttribution`'s amend branch comment had the same drift;
  updated to mention `${preHead}..${postHead}` directly.
- `getCommittedFileInfo` JSDoc said it diffs "HEAD against its parent
  (HEAD~1)" and listed "--amend with no reflog" as an analysis-failure
  case. Updated to mention postHead-pinning and the preHead-driven
  amend bail (the reflog-GC dependency was dropped in the SHA-pin
  round).

The remaining `HEAD~1..HEAD` references at countCommitsAfter:1959 and
getCommittedFileInfo:2523 are intentional — they describe the old
buggy shape as contrast for why we pin now.

No code change; tests + tsc still clean.
tanzhenxin
tanzhenxin previously approved these changes May 7, 2026

@tanzhenxin tanzhenxin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

The two residuals from the prior round are closed. The diff/rev-list phase is now SHA-pinned to the captured post-commit head (and to the captured pre-amend head on the amend path), matching the note write — a post-commit hook moving HEAD between analysis and the note write can no longer leave the note attached to one commit while describing another. The aiChars / humanChars field naming stays, but the schema docstring now spells out plainly that the values are heuristic diff-size proxies (lines × 40 for text, 1024 for binary), with the worst-case examples called out — consumers parsing the note as literal character counts will see the caveat.

Verdict

APPROVE — feature is in good shape; remaining items are minor / followup-class.

wenshao added 4 commits May 7, 2026 13:55
Round 13 audit found a real bug: `sudo --chdir=/tmp git commit`,
`env -C/tmp git commit`, `env --chdir=/tmp git commit`, and
`sudo -D/tmp git commit` were all silently slipping through the
cwd-shift detector and getting our `Co-authored-by` trailer stamped
onto commits that landed in a different repo.

Root cause: `shell-quote` tokenises both the long attached form
(`--chdir=/tmp`) and the short attached form (`-C/tmp`) as a single
argv entry. The previous SHIFT_CWD detector did set-membership only
against the bare flag (`{'-C', '--chdir'}` for env;
`{'-D', '--chdir'}` for sudo), so the attached-form tokens never
matched and `tokeniseSegment` returned a normally-attributable
`['git', 'commit', ...]` segment.

Fix: introduce `isShiftCwdFlag(flag, set)` that catches:
  - bare set-membership (existing behavior),
  - long attached: `--name=...` when `--name` is in the set,
  - short attached: `-Xanything` when `-X` is in the set and the
    token is longer than the flag itself.

The flag does NOT need to consume an extra value token in the
attached-form case (the value is already embedded), so the existing
TAKES_VALUE bookkeeping is unaffected — we just bail with `null`
from `tokeniseSegment` before reaching the value-skip step.

Tests added: `env --chdir=`, `env -C/...` (attached), `sudo --chdir=`,
`sudo -D/...` (attached) — each is asserted NOT to add a co-author
trailer. 154 shell tests pass; type-check + lint clean.
Adds three regression cases to the existing "git -C <path>" suppression
test: the short attached form `-C/path` (single shell-quote token)
and the long attached forms `--git-dir=/path` / `--work-tree=/path`.
parseGitInvocation already had the prefix checks at lines 416/425, but
no test exercised them — paired with the b89b655 sudo/env attached-
form fix this round closes the family of "shell-quote single-token
flag with embedded value" cases that the bare set-membership checks
would otherwise miss.

157 shell tests pass; type-check clean.
The addCoAuthorToGitCommit body capture has a known truncation case
when an inner unescaped `"` appears inside the captured body — handled
for `$(...)` command substitution with an explicit bailout, but not
for backtick command substitution. The trade-off was unspoken; spell
it out so a future reviewer doesn't read the asymmetry as an
oversight.

Bare-backtick bodies (`\`func()\`` markdown-style) are common in
commit messages, have no inner `"`, and the regex captures them
correctly. Pathological backtick-with-inner-quote bodies (`\`cmd
"with" quotes\``) are a near-zero-traffic case where bash itself
already interprets the backticks as command substitution, so the
user has likely already broken their own command before our rewrite
runs. Bailing on any backtick would lose attribution for the common
case to defend against the rare one.

Also drops a stray blank line in commitAttribution.test.ts left over
from an earlier regression-test attempt.
Round 13 follow-on. Both `addCoAuthorToGitCommit` and
`addAttributionToPR` ran their `-m` / `--body` regex against the full
segment string, including any trailing shell comment. For a command
like `git commit -m "real" # -m "fake"` (a human-authored script
might leave a comment-out flag in place), `lastMatchOf` would pick
the comment's `-m "fake"`, splice the `Co-authored-by:` trailer in
there, and bash would silently discard the entire segment as a
comment — leaving the actual commit unattributed. Same shape for
`gh pr create --body "real" # --body "fake"`.

Fix: introduce `findUnquotedCommentStart(s)` — a bash-aware position
scanner that tracks single/double-quote state and treats `#` as a
comment marker only when it begins a word (start of input or
preceded by whitespace), not when it appears inside a quoted region
or mid-token like `foo#bar`. Both rewriters slice the segment to
`[0, commentStart)` before running their regex, so the trailer can
only land in the live (pre-comment) part.

Tests added:
  - `git commit -m "real" # -m "fake"` — trailer lands in `"real"`
    body BEFORE the `#`, comment's `-m "fake"` is left untouched.
  - `git commit -m "fix #123 add feature"` — `#` inside the quoted
    body is correctly NOT treated as a comment; the `#123` stays
    inside the body and the trailer is appended.

159 shell tests pass; type-check clean.

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Downgraded from Comment to Comment: self-PR; CI still has pending checks.

Comment thread integration-tests/cli/settings-migration.test.ts
Comment thread packages/core/src/tools/shell.ts
…+ cover legacy gitCoAuthor migration end-to-end

Two residuals from this morning's review pass.

1. ANm7O — `addAttributionToPR` silently skipped for `--body-file`,
   `--fill`, and bare `gh pr create` (editor) flows.

   The rewriter only knows how to splice into an inline `--body`/`-b`
   argv entry. For a `gh pr create` that uses `--body-file path`,
   `--fill` (uses commit messages), or no body flag at all (editor
   prompt), there's no inline body to splice into and the function
   returned the unmodified command. Users with `gitCoAuthor.pr`
   enabled would see PRs created without the attribution line and
   have no signal as to why.

   Add a debugLogger.warn at the no-match path naming the unsupported
   flows and pointing the user at the inline form. Don't try to
   handle `--body-file` automatically — that would mean mutating the
   user's file on disk, which is well outside what an unprompted
   command rewriter should do; `--fill` and editor flows have no body
   in argv at all and can't be rewritten without re-architecting.

   Tests added for `--body-file <path>`, `--fill`, and bare
   `gh pr create` — each is asserted to leave the command unchanged
   (no `Generated with Qwen Code` line spliced in).

2. ANm7L — settings-migration integration suite didn't cover the
   exact V3 legacy shape this PR introduces.

   `v3-to-v4.test.ts` already pins the migration body, but the end-
   to-end CLI load → migrate → write path could regress without the
   integration suite noticing. The existing v3LegacyDisableSettings
   fixture has no `general.gitCoAuthor` field, so the V3→V4 step
   technically fires but doesn't exercise the new boolean-expansion
   logic.

   Add a `v3GitCoAuthorBooleanSettings` fixture and a paired test
   case that writes `general: { gitCoAuthor: false }` at $version 3,
   runs the same `mcp list` CLI invocation, and asserts the saved
   file has $version 4 plus `general.gitCoAuthor` exactly
   `{ commit: false, pr: false }` — with sibling general.* keys and
   unrelated top-level sections preserved.

162 shell tests pass; type-check + lint clean.

@tanzhenxin tanzhenxin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

Re-stating approve after the prior one was auto-dismissed by the new commits. Nothing in the follow-up batch regresses the earlier round; one commit (the attached-form env / sudo cwd-shift fix) actually closes a real cross-repo attribution bug — sudo --chdir=/tmp git commit and env -C/tmp git commit would otherwise have stamped the Co-authored-by trailer onto commits landing in a different repository. The shell-comment-bypass fix for git commit -m "real" # -m "fake" and the no-inline-body warn for gh pr create are also legitimate scoped improvements, and the new integration test for the V3 boolean gitCoAuthor migration closes the only end-to-end gap from the self-review pass.

Verdict

APPROVE — feature is in good shape; the five follow-up commits sharpen edge-case handling without changing the architecture.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type/feature-request New feature or enhancement request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants