Skip to content

Comments

feat: add friendli reasoning mode and hashline-native tool descriptions#15

Open
minpeter wants to merge 12 commits intomainfrom
feat/reasoning-mode-friendli
Open

feat: add friendli reasoning mode and hashline-native tool descriptions#15
minpeter wants to merge 12 commits intomainfrom
feat/reasoning-mode-friendli

Conversation

@minpeter
Copy link
Owner

@minpeter minpeter commented Feb 23, 2026

Summary

  • add configurable Friendli reasoning mode plumbing across command handling, prompt wiring, and related tests.
  • refactor edit/read tool stack to hashline-first utilities shared via src/tools/utils/hashline and keep deterministic stale-safe behavior.
  • replace grouped tool system-context files with per-tool description text files, and make all tool definitions import those descriptions as a single source.

Summary by CodeRabbit

  • 새로운 기능

    • 추론 모드 시스템 도입(think 대체) 및 명령어/CLI 통합
    • 자동 번역(비영어→영어) 지원과 번역 토글 명령 추가
    • 해시라인(anchor) 기반 결정적 파일 편집 워크플로 도입
  • 개선사항

    • 편집/읽기 안전성 향상(파일 해시·무결성 검증, 향상된 읽기 제어)
    • 도구 출력·검색(glob/grep/read)에 해시라인 표시 및 동시성 개선
  • 문서

    • 도구 서브시스템 가이드·개별 도구 설명서 대폭 추가/정비
  • 테스트

    • 추론, 번역, 해시라인, 도구 동작 관련 단위/통합 테스트 추가

minpeter and others added 2 commits February 23, 2026 15:35
Replace /think with /reasoning-mode and add per-model Friendli reasoning controls so on/interleaved/preserved behavior can be tuned by model metadata.
Replace grouped tool system-context files with per-tool description text sources and wire all tool definitions to import those descriptions. Move hashline logic into shared tools utilities so read/edit paths consume the same implementation.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@coderabbitai
Copy link

coderabbitai bot commented Feb 23, 2026

📝 Walkthrough

Walkthrough

에이전트에 구조화된 추론 모드와 자동 번역을 도입하고, Friendli 전용 추론/인터리브 처리, 해시라인 기반 편집 도구, 도구 설명 외부화, 파일 탐색/안전 유틸 강화 및 스트리머/렌더러 확장 등을 포함하는 광범위한 기능 추가 및 리팩터링입니다.

Changes

Cohort / File(s) Summary
에이전트 코어 & 공개 API
src/agent.ts
Agent 생성/모델 래핑 흐름 재구성(createBaseModel/getProviderOptions), reasoningMode·translation 상태 및 toolRegistry 추가, 번역 전처리 훅 통합, Provider별 maxOutputTokens 처리.
추론 모드 시스템
src/reasoning-mode.ts, src/reasoning-mode.test.ts, src/commands/reasoning-mode.ts, src/commands/aliases-and-tool-fallback.test.ts
ReasoningMode 타입·REASONING_MODES·DEFAULT_REASONING_MODE·parseReasoningMode 추가 및 명령어(factory) 구현; 기존 think 토글 대체.
Friendli 추론 통합
src/friendli-reasoning.ts, src/friendli-reasoning.test.ts, src/friendli-models.ts
모델별 추론 구성, 모드 해석, interleaved 처리, chat_template_kwargs 생성 및 메시지 변환/스트립 유틸 구현.
번역 서브시스템
src/context/translation.ts, src/context/translation.test.ts, src/context/translation-integration.test.ts
비영어 감지(isNonEnglish), TRANSLATION_SYSTEM_PROMPT, translateToEnglish, TranslationModelConfig/TranslationAgentManager 인터페이스 및 통합 테스트 추가.
렌더/명령 파이프라인
src/commands/render.ts, src/commands/render.test.ts, src/commands/model.ts
thinkingEnabled → reasoningMode 전환, NEXT_USER_PROMPT_SENTINEL 및 appendNextUserPromptSentinel 추가, Friendli 인터리브 필드 적용을 위한 메시지 준비 변경.
CLI / Headless 진입점
src/entrypoints/cli.ts, src/entrypoints/headless.ts
--reasoning-mode/--translate 옵션, reasoning-mode 선택기 UI, translate 플래그 처리, 명령 매핑(think→reasoning-mode) 및 에이전트 초기화 변화.
도구 레지스트리 및 툴 API
src/tools/index.ts, src/tools/*
createTools/ToolRegistry 도입, toolRegistry 주입 경로 추가, 다수 도구의 설명을 외부 .txt로 분리(문서 외부화).
해시라인 편집 및 관련 유틸
src/tools/utils/hashline/hashline.ts, src/tools/utils/hashline/hashline.test.ts, src/tools/modify/edit-file.ts, src/tools/modify/edit-file.test.ts
라인별 해시/파일 해시, LineTag/HashlineEdit API, applyHashlineEdits 및 불일치 처리 도입 — 문자열 기반 편집에서 해시라인 기반 편집으로 전환.
탐색/읽기 안전성 개선
src/tools/explore/read-file.ts, src/tools/explore/glob.ts, src/tools/utils/safety-utils.ts, src/tools/explore/*
safeReadFileEnhanced, ignore 필터, 동시 stat, 바이너리 판정, 읽기 가드 및 메타데이터(파일 해시 등) 추가; read_file 입력 스키마 강화.
스트리밍 렌더러 확장
src/interaction/pi-tui-stream-renderer.ts, src/interaction/pi-tui-stream-renderer.test.ts
Tool input 버퍼링·부분 JSON 파싱·showRawToolIo/onFirstVisiblePart 옵션·pretty 렌더러(도구별)·해시라인 정규화 및 비동기 처리 추가.
제거/이동된 파일 및 문서
src/tools/*/system-context.txt (여러), src/AGENTS.md
여러 레거시 system-context 문서 삭제 및 도구별 .txt 문서 추가/AGENTS.md 집단 문서 교체.
테스트 및 정합성 케이스
src/*.test.ts (다수)
reasoning/translation/friendli/hashline/read/edit/glob 등 기능별 단위·통합 테스트 추가 및 기존 테스트 업데이트.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant User
participant CLI as CLI/Entrypoint
participant AgentMgr as AgentManager
participant Translator as Translation Module
participant Model as Model Provider
participant Render as Render Pipeline

User->>CLI: 입력 텍스트
CLI->>AgentMgr: preprocessUserInput(text)
AgentMgr->>AgentMgr: isTranslationEnabled?
alt 번역 필요
    AgentMgr->>Translator: translateToEnglish(text, agentManager)
    Translator-->>AgentMgr: {text: enText, translated: true, originalText}
    AgentMgr->>AgentMgr: addUserMessage(enText, originalText)
else 번역 불필요
    AgentMgr->>AgentMgr: addUserMessage(text)
end
CLI->>AgentMgr: stream(messages, reasoningMode, toolRegistry)
AgentMgr->>Model: createAgent(..., reasoningMode, providerOptions)
Model-->>Render: generateText(preparedMessages)
Render-->>User: 응답(인터리브/추론 포함)

mermaid
sequenceDiagram
participant User
participant CLI as CLI
participant AgentMgr as AgentManager
participant Friendli as Friendli Reasoning
participant Render as Render Pipeline

User->>CLI: /reasoning-mode interleaved
CLI->>AgentMgr: setReasoningMode("interleaved")
User->>CLI: 메시지 전송
CLI->>AgentMgr: stream(messages, reasoningMode="interleaved")
AgentMgr->>Friendli: resolveFriendliReasoningMode(modelId, "interleaved")
Friendli-->>AgentMgr: resolvedMode
AgentMgr->>Friendli: buildFriendliChatTemplateKwargs(modelId, resolvedMode)
Friendli-->>Render: chat_template_kwargs
Render->>Friendli: applyFriendliInterleavedField(messages, modelId, resolvedMode)
Friendli-->>Render: preparedMessages
Render->>Model: generateText(preparedMessages)
Model-->>User: 응답(인터리브 포함)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60분

Possibly related PRs

Poem

🐰
번역은 맡겨줘, 영어로 말해줄게,
해시라인은 줄을 지키고 앵커로 안심해,
친구 Friendli는 추론을 섞어주고,
모드 넷(off/on/interleaved/preserved)으로 생각을 골라,
코드는 깔끔히, 토끼는 기뻐 폴짝!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.13% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 풀 리퀘스트의 주요 변경사항을 명확하게 요약합니다. Friendli 추론 모드와 해시라인 기반 도구 설명의 추가라는 두 가지 핵심 변경을 효과적으로 나타냅니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/reasoning-mode-friendli

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link

Summary of Changes

Hello @minpeter, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the agent's capabilities by integrating advanced reasoning modes for Friendli models and overhauling file manipulation tools for improved reliability. The introduction of hashline-native editing ensures precise and deterministic modifications, while the new reasoning configurations offer greater control over model behavior. Additionally, the system prompt has been modularized with per-tool descriptions, and a user input translation feature has been added to broaden accessibility.

Highlights

  • Friendli Reasoning Mode: Introduced configurable Friendli reasoning modes ('off', 'on', 'interleaved', 'preserved') with plumbing across command handling, prompt wiring, and related tests. This allows for more granular control over how Friendli models generate their thought processes.
  • Hashline-Native Tool Refactoring: Refactored file editing and reading tools to utilize a new 'hashline-native' utility stack. This ensures deterministic, stale-safe behavior for file operations by using line-specific hashes for anchoring edits, replacing the previous string-based search and replace.
  • Per-Tool Description Files: Replaced grouped tool system-context files with individual description text files for each tool. All tool definitions now import these descriptions, establishing a single source of truth for tool documentation and improving clarity in system prompts.
  • User Input Translation: Added a new feature to automatically translate non-English user input to English before processing by the agent, improving interaction for multilingual users.
  • CLI and Headless Mode Updates: Updated CLI and headless entrypoints to support the new reasoning modes and user input translation, including new command-line arguments and interactive selectors.
Changelog
  • src/agent.ts
    • Updated model imports and added new utility functions for Friendli reasoning and user input translation.
    • Refactored model creation and message preparation to incorporate reasoningMode and interleaved fields.
    • Implemented user input translation logic within the AgentManager.
  • src/commands/aliases-and-tool-fallback.test.ts
    • Added new test suite for the reasoning mode command, including alias and unsupported mode scenarios.
  • src/commands/model.ts
    • Updated ModelInfo interface to include reasoning configuration and imported Friendli model definitions from a new dedicated file.
  • src/commands/reasoning-mode.ts
    • Added new command /reasoning-mode (with alias /think) to manage the agent's reasoning behavior.
  • src/commands/render.test.ts
    • Added new test file for prompt shaping utilities, specifically appendNextUserPromptSentinel.
  • src/commands/render.ts
    • Modified prompt rendering to support Friendli interleaved reasoning fields and a new user prompt sentinel.
  • src/commands/think.ts
    • Removed the deprecated /think command, replaced by the more comprehensive /reasoning-mode.
  • src/context/system-prompt.ts
    • Replaced monolithic tool context imports with individual tool description files, improving modularity and clarity.
    • Updated edit_file guidance to reference hashline anchors.
  • src/entrypoints/cli.ts
    • Integrated the new reasoning mode command and user input translation into the CLI.
    • Updated CLI argument parsing and interactive selectors to reflect reasoning mode changes.
    • Renamed cleanup function for broader applicability.
  • src/entrypoints/headless.ts
    • Updated headless mode to support new reasoning mode configurations and argument parsing.
    • Renamed cleanup function for broader applicability.
  • src/friendli-models.ts
    • Added new file defining Friendli model interfaces and specific reasoning configurations for different models.
  • src/friendli-reasoning.test.ts
    • Added new test file for Friendli-specific reasoning logic, covering various modes and message transformations.
  • src/friendli-reasoning.ts
    • Added new file containing core logic for Friendli reasoning mode management, including configuration, resolution, and message processing.
  • src/reasoning-mode.test.ts
    • Added new test file for parsing reasoning mode inputs and aliases.
  • src/reasoning-mode.ts
    • Added new file defining reasoning mode types, default values, and a parsing utility.
  • src/tools/AGENTS.md
    • Added new documentation file detailing the src/tools/ subsystem, its structure, conventions, and anti-patterns.
  • src/tools/execute/shell-execute.ts
    • Updated tool description to use content from a dedicated text file.
  • src/tools/execute/shell-execute.txt
    • Added new file providing a detailed description for the shell_execute tool.
  • src/tools/execute/shell-interact.ts
    • Updated tool description and guidance messages to use content from a dedicated text file and reflect new terminology.
  • src/tools/execute/shell-interact.txt
    • Added new file providing a detailed description for the shell_interact tool.
  • src/tools/execute/system-context.txt
    • Removed the old grouped system context file for shell tools.
  • src/tools/explore/glob-files.txt
    • Added new file providing a description for the glob_files tool.
  • src/tools/explore/glob.ts
    • Updated tool description to use content from a dedicated text file.
    • Refactored file globbing to improve performance and error handling for unreadable files.
  • src/tools/explore/grep-files.txt
    • Added new file providing a description for the grep_files tool.
  • src/tools/explore/grep.ts
    • Updated tool description to use content from a dedicated text file.
  • src/tools/explore/read-file.test.ts
    • Updated tests to validate hashline-formatted output and new input validations.
    • Added tests for binary file detection and specific file extension handling.
  • src/tools/explore/read-file.ts
    • Updated tool description to use content from a dedicated text file.
    • Enhanced input validation for line ranges and added file hash to output.
    • Improved error reporting for file metadata retrieval.
  • src/tools/explore/read-file.txt
    • Added new file providing a detailed description for the read_file tool, emphasizing hashline anchors.
  • src/tools/explore/safety-utils.ts
    • Refactored file read safety checks, introducing a modular guard system and improved binary file detection.
    • Integrated hashline utilities for formatting numbered lines and computing file hashes.
    • Updated ignore filter caching mechanism.
  • src/tools/explore/system-context.txt
    • Removed the old grouped system context file for explore tools.
  • src/tools/modify/AGENTS.md
    • Added new documentation file detailing the src/tools/modify/ subsystem, its structure, conventions, and anti-patterns.
  • src/tools/modify/delete-file.ts
    • Updated tool description to use content from a dedicated text file.
  • src/tools/modify/delete-file.txt
    • Added new file providing a description for the delete_file tool.
  • src/tools/modify/edit-file.test.ts
    • Rewrote all tests to validate the new hashline-native edit_file API, covering various edit operations and error conditions.
  • src/tools/modify/edit-file.ts
    • Updated tool description to use content from a dedicated text file.
    • Completely refactored edit_file to use hashline-native operations for deterministic, anchored edits, replacing the previous string-based search and replace logic.
  • src/tools/modify/edit-file.txt
    • Added new file providing a detailed description for the edit_file tool, focusing on its hashline-native features.
  • src/tools/modify/system-context.txt
    • Removed the old grouped system context file for modify tools.
  • src/tools/modify/write-file.ts
    • Updated tool description to use content from a dedicated text file.
  • src/tools/modify/write-file.txt
    • Added new file providing a description for the write_file tool.
  • src/tools/planning/load-skill.ts
    • Updated tool description to use content from a dedicated text file.
  • src/tools/planning/load-skill.txt
    • Added new file providing a description for the load_skill tool.
  • src/tools/planning/system-context.txt
    • Removed the old grouped system context file for planning tools.
  • src/tools/planning/todo-write.ts
    • Updated tool description to use content from a dedicated text file.
  • src/tools/planning/todo-write.txt
    • Added new file providing a description for the todo_write tool.
  • src/tools/utils/hashline/hashline.test.ts
    • Added new test file for the core hashline utility functions, covering hash computation, tag parsing, and edit application.
  • src/tools/utils/hashline/hashline.ts
    • Added new file containing the core logic for hashline-native file editing, including hash generation, line tagging, and applying structured edits.
Activity
  • The pull request introduces new features and refactors existing code, with no human activity recorded yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a configurable reasoning mode for Friendli models and refactors file editing and reading tools to use a 'hashline' system, improving tool descriptions and adding user input translation capabilities. However, a critical security oversight was identified in multiple file manipulation tools (read_file, write_file, edit_file, delete_file, glob_files, and grep_files). These tools lack proper path validation and sanitization, making them vulnerable to path traversal and prompt injection attacks, which could lead to full system compromise. It is strongly recommended to implement a centralized path validation utility to restrict all file operations to the intended project workspace. Additionally, there is a significant bug in the regex used for non-ASCII character detection that affects the translation feature.

Comment on lines +255 to +261
export async function executeEditFile(input: EditFileInput): Promise<string> {
const parsed = inputSchema.parse(input);
const hashlineEdits = parseHashlineToolEdits(parsed.edits);

const { newContent, replacementCount, editResults } = replace_all
? performReplaceAll(content, old_str, new_str)
: performSingleReplace(content, old_str, new_str);
const { content: loadedContent, exists } = await readExistingContent(
parsed.path
);

Choose a reason for hiding this comment

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

security-high high

The executeEditFile function lacks path validation for the path parameter. An attacker can use prompt injection to manipulate the agent into modifying arbitrary files on the system, potentially leading to Remote Code Execution (RCE) or system instability by overwriting critical configuration or system files.

Ensure that the target path is resolved and verified to be within the project's boundaries before performing any file operations.

src/agent.ts Outdated
const OUTPUT_TOKEN_MAX = 64_000;
const TRANSLATION_MAX_OUTPUT_TOKENS = 4000;

const NON_ASCII_PATTERN = /[^\\x00-]/;

Choose a reason for hiding this comment

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

high

The regex literal /[^\\x00-�]/ contains a double backslash before x00, which causes it to match literal characters \, x, 0, 0 instead of the null character escape sequence. Additionally, using the literal DEL character () in the range is less readable. This regex will incorrectly identify many ASCII characters (like space and punctuation) as non-English, triggering unnecessary translations for almost every message.

Suggested change
const NON_ASCII_PATTERN = /[^\\x00-]/;
const NON_ASCII_PATTERN = /[^\x00-\x7F]/;

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d022c4dbb4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

import { createRenderCommand } from "../commands/render";
import { createThinkCommand } from "../commands/think";
import { createToolFallbackCommand } from "../commands/tool-fallback";
import { createTranslateCommand } from "../commands/translate";

Choose a reason for hiding this comment

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

P0 Badge Remove unresolved translate command import

This import targets ../commands/translate, but there is no corresponding module in this revision (repo-wide search of this commit has no translate command file), so launching the CLI will fail during module resolution before the app can run. Please either add the missing command implementation in the same change or drop this import/registration until the file exists.

Useful? React with 👍 / 👎.

src/agent.ts Outdated
const OUTPUT_TOKEN_MAX = 64_000;
const TRANSLATION_MAX_OUTPUT_TOKENS = 4000;

const NON_ASCII_PATTERN = /[^\\x00-]/;

Choose a reason for hiding this comment

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

P1 Badge Use a real non-ASCII regex for translation trigger

The translation gate regex is malformed: /[^\\x00-�]/ treats many normal ASCII punctuation characters (including spaces) as matches, so with --translate enabled, typical English prompts like hello world are misclassified and unnecessarily sent through the translation model. That adds latency/cost and can alter user intent; use an actual non-ASCII class (for example [^^\x00-\x7F] semantics without the extra escaped backslash) so only genuinely non-ASCII input is translated.

Useful? React with 👍 / 👎.

Improve process output handling, read safety metadata flow, and loop continuation semantics while adding tool-registry injection and translation toggles. This reduces hidden failures and makes runtime behavior more deterministic across CLI/headless paths.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (20)
src/friendli-reasoning.ts (2)

74-82: requestedMode === "preserved" 케이스가 암묵적으로 처리됩니다.

"off", "on", "interleaved" 이후의 마지막 블록은 requestedMode === "preserved"임을 암묵적으로 가정합니다. REASONING_MODES에 새 모드가 추가되면 이 블록이 의도치 않게 실행될 수 있습니다. 명시적 조건을 추가하거나 exhaustive check를 통해 안전성을 확보하는 것을 권장합니다.

♻️ 수정 제안
+  if (requestedMode === "preserved") {
     if (supportsPreserved) {
       return "preserved";
     }

     if (supportsInterleaved) {
       return "interleaved";
     }

     return supportsOn ? "on" : DEFAULT_REASONING_MODE;
+  }
+
+  // 새 모드가 추가되었으나 처리되지 않은 경우를 안전하게 탐지
+  return DEFAULT_REASONING_MODE;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/friendli-reasoning.ts` around lines 74 - 82, The code implicitly treats
requestedMode === "preserved" via the final fallback which risks misbehavior if
REASONING_MODES grows; make the handling explicit by adding an explicit branch
for requestedMode === "preserved" (or convert the logic to a switch/exhaustive
check) so that supportsPreserved, supportsInterleaved, supportsOn,
DEFAULT_REASONING_MODE and requestedMode are evaluated deterministically; for
example add a top-level if (requestedMode === "preserved") return "preserved",
or replace the final return with an explicit requestedMode === "on" check and a
safe default/error for all other modes.

10-18: DEFAULT_FRIENDLI_REASONING_CONFIGsrc/friendli-models.tsDEFAULT_FRIENDLI_REASONING와 중복됩니다.

앞서 src/friendli-models.ts에서 제안한 대로 해당 상수를 내보내고 여기서 가져다 쓰면 중복을 제거할 수 있습니다. 현재 두 상수가 분리되어 있어 어느 한쪽만 수정될 경우 기본 설정이 조용히 갈라집니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/friendli-reasoning.ts` around lines 10 - 18,
DEFAULT_FRIENDLI_REASONING_CONFIG duplicates the constant
DEFAULT_FRIENDLI_REASONING in friendli-models.ts; remove the local definition
and import the shared constant instead. Update src/friendli-reasoning.ts to
import DEFAULT_FRIENDLI_REASONING from the module that defines it (ensure the
exported name and FriendliReasoningConfig type are used) and replace usages of
DEFAULT_FRIENDLI_REASONING_CONFIG with that imported symbol so there is a single
source of truth.
src/tools/AGENTS.md (1)

3-3: 하드코딩된 생성 타임스탬프는 즉시 구식이 됩니다.

**Generated:** 2026-02-23 14:40 KST는 커밋 시점 이후로는 부정확해집니다. 자동 업데이트가 없다면 이 줄을 제거하거나, 도구로 관리되는 경우에만 유지하는 것을 권장합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/AGENTS.md` at line 3, The hardcoded generated timestamp line
`**Generated:** 2026-02-23 14:40 KST` in AGENTS.md will become inaccurate;
remove this static timestamp or replace it with a dynamically-managed mechanism
(e.g., a build/generation placeholder or tooling comment) so the file isn't
pinned to a stale date — update or delete the exact `**Generated:** 2026-02-23
14:40 KST` line accordingly and ensure any CI/generator injects a timestamp if
you need one.
src/friendli-models.ts (1)

21-29: DEFAULT_FRIENDLI_REASONING를 내보내지 않아 src/friendli-reasoning.ts에 동일한 값이 중복 정의됨

src/friendli-reasoning.tsDEFAULT_FRIENDLI_REASONING_CONFIG(Lines 10–18)는 이 상수와 값이 완전히 동일합니다. 현재 비공개 상수이기 때문에 발생한 DRY 위반으로, 두 상수 중 하나만 수정될 경우 기본 설정이 조용히 엇갈릴 수 있습니다.

♻️ 수정 제안 (friendli-models.ts)
-const DEFAULT_FRIENDLI_REASONING: FriendliReasoningModelConfig = {
+export const DEFAULT_FRIENDLI_REASONING: FriendliReasoningModelConfig = {
   reasoning_toggle: "enable_thinking",
   preserved_toggle: "clear_thinking",
   interleaved_field: "reasoning_content",
   on_value: {
     reasoning_toggle: true,
     preserved_toggle: false,
   },
 };

그리고 src/friendli-reasoning.ts에서는 아래와 같이 교체합니다:

-import {
-  type FriendliReasoningModelConfig,
-  getFriendliModelById,
-} from "./friendli-models";
+import {
+  DEFAULT_FRIENDLI_REASONING,
+  type FriendliReasoningModelConfig,
+  getFriendliModelById,
+} from "./friendli-models";

-const DEFAULT_FRIENDLI_REASONING_CONFIG: FriendliReasoningConfig = {
-  reasoning_toggle: "enable_thinking",
-  preserved_toggle: "clear_thinking",
-  interleaved_field: "reasoning_content",
-  on_value: {
-    reasoning_toggle: true,
-    preserved_toggle: false,
-  },
-};
+const DEFAULT_FRIENDLI_REASONING_CONFIG: FriendliReasoningConfig =
+  DEFAULT_FRIENDLI_REASONING;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/friendli-models.ts` around lines 21 - 29, Export the
DEFAULT_FRIENDLI_REASONING constant so it is the single source of truth (add an
export on the existing const DEFAULT_FRIENDLI_REASONING) and remove the
duplicated local constant DEFAULT_FRIENDLI_REASONING_CONFIG; update the other
module that currently defines DEFAULT_FRIENDLI_REASONING_CONFIG to import
DEFAULT_FRIENDLI_REASONING and replace its local usage with the imported
constant, ensuring all references to DEFAULT_FRIENDLI_REASONING_CONFIG are
replaced with DEFAULT_FRIENDLI_REASONING.
src/reasoning-mode.test.ts (1)

5-10: 대소문자 무감지(case-insensitive) 동작 및 빈 문자열 엣지 케이스 테스트 추가 고려

parseReasoningMode는 내부적으로 .toLowerCase()를 사용하므로 "OFF", "INTERLEAVED" 등 대문자 입력도 정상 처리됩니다. 현재 테스트는 소문자만 검증하며 빈 문자열 입력("")도 다루지 않습니다.

🧪 추가 테스트 예시
  it("parses canonical reasoning modes", () => {
    expect(parseReasoningMode("off")).toBe("off");
    expect(parseReasoningMode("on")).toBe("on");
    expect(parseReasoningMode("interleaved")).toBe("interleaved");
    expect(parseReasoningMode("preserved")).toBe("preserved");
+   expect(parseReasoningMode("OFF")).toBe("off");
+   expect(parseReasoningMode("ON")).toBe("on");
  });
  it("returns null for unknown values", () => {
    expect(parseReasoningMode("something-else")).toBeNull();
+   expect(parseReasoningMode("")).toBeNull();
  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/reasoning-mode.test.ts` around lines 5 - 10, Add tests to verify
parseReasoningMode is case-insensitive and handles the empty-string edge case:
add expectations that parseReasoningMode("OFF") returns "off" and
parseReasoningMode("INTERLEAVED") returns "interleaved", and add an assertion
for parseReasoningMode("") (verify it returns the current implementation's value
for empty input, e.g., "" or the default mode your function uses). Reference the
parseReasoningMode function when adding these assertions to the existing test
block.
src/agent.ts (2)

104-104: ANTHROPIC_SELECTABLE_REASONING_MODESas const 어설션 추가를 권장합니다.

이 배열은 불변 리터럴 타입으로 의도되었으나, 현재 ReasoningMode[]로 광역 타입이 추론됩니다. as const를 사용하면 의도를 명확히 하고 실수로 인한 변경을 방지할 수 있습니다.

♻️ 수정 제안
-const ANTHROPIC_SELECTABLE_REASONING_MODES: ReasoningMode[] = ["off", "on"];
+const ANTHROPIC_SELECTABLE_REASONING_MODES = ["off", "on"] as const satisfies ReasoningMode[];

As per coding guidelines, "Use const assertions (as const) for immutable values and literal types."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/agent.ts` at line 104, The array ANTHROPIC_SELECTABLE_REASONING_MODES is
intended as an immutable set of literal ReasoningMode values but is typed as a
mutable ReasoningMode[]; change its declaration to use a const assertion so the
elements become literal types (preserving compatibility with ReasoningMode where
needed) and prevent accidental mutation—update the declaration of
ANTHROPIC_SELECTABLE_REASONING_MODES to use "as const" and adjust any downstream
usage/typing to accept readonly literal types if necessary.

110-119: isAnthropicWithReasoning 함수명이 실제 의미와 다소 어긋납니다.

이 함수는 Opus 모델에서 false를 반환하지만 Opus도 effort: "high" 방식으로 추론을 사용합니다. 실제 목적은 "thinking budget 차감이 필요한지"를 판단하는 것이므로, 함수명이 동작을 오해하게 만들 수 있습니다.

♻️ 수정 제안 (함수명 변경)
-const isAnthropicWithReasoning = (
+const isAnthropicWithThinkingBudget = (
   modelId: string,
   provider: ProviderType,
   reasoningMode: ReasoningMode
 ): boolean => {

그리고 getProviderOptions 내 호출부도 동일하게 변경:

-      maxOutputTokens: isAnthropicWithReasoning(
+      maxOutputTokens: isAnthropicWithThinkingBudget(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/agent.ts` around lines 110 - 119, The function isAnthropicWithReasoning
is misnamed and its logic wrongly excludes Opus models (which do consume
thinking budget); rename it to needsThinkingBudget (or
isThinkingBudgetApplicable) and update all call sites such as the invocation
inside getProviderOptions to use the new name, and change the logic to return
true for Anthropic models whenever reasoningMode !== "off" (i.e., remove the
!modelId.includes("opus") exclusion) so Opus models are treated as requiring
thinking budget.
src/tools/modify/AGENTS.md (1)

3-3: 하드코딩된 생성 타임스탬프는 문서가 오래될수록 오해를 유발할 수 있습니다.

**Generated:** 2026-02-23 14:40 KST 형식의 타임스탬프는 파일이 업데이트될 때 함께 갱신되지 않으면 독자에게 잘못된 최신성 정보를 줍니다. 삭제하거나 CI에서 자동 생성하는 방식을 고려해보세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/modify/AGENTS.md` at line 3, The hardcoded timestamp line
"**Generated:** 2026-02-23 14:40 KST" in AGENTS.md should be removed or replaced
with an automatically-generated value; either delete that static line from
AGENTS.md (symbol: the "**Generated:** 2026-02-23 14:40 KST" text) or change the
workflow to inject the timestamp at build/CI time (e.g., via a CI step that
writes/updates the Generated field), ensuring the repository contains no stale
hardcoded timestamp.
src/commands/aliases-and-tool-fallback.test.ts (1)

109-129: 명령어 등록 가드 로직이 두 테스트에서 중복됩니다.

Lines 110-112와 121-123에서 동일한 조건부 등록 패턴이 반복되고 있습니다. beforeEach로 이동하면 DRY 원칙을 따르고 향후 테스트 추가 시 누락 방지에 도움됩니다.

♻️ beforeEach로 이동 제안
   beforeEach(() => {
     originalMode = agentManager.getReasoningMode();
     originalProvider = agentManager.getProvider();
     originalModelId = agentManager.getModelId();
     agentManager.setProvider("friendli");
     agentManager.setModelId("MiniMaxAI/MiniMax-M2.5");
     agentManager.setReasoningMode("on");
+    if (!getCommands().has("reasoning-mode")) {
+      registerCommand(createReasoningModeCommand());
+    }
   });

그런 다음 각 it 블록에서 가드를 제거합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/aliases-and-tool-fallback.test.ts` around lines 109 - 129, Move
the duplicate conditional command-registration into a test-suite setup: add a
beforeEach that checks getCommands().has("reasoning-mode") and calls
registerCommand(createReasoningModeCommand()) when missing, and then remove the
same guard lines from both it blocks; reference the getCommands,
registerCommand, and createReasoningModeCommand helpers and ensure the two tests
("supports /think alias for /reasoning-mode" and "rejects unsupported mode for
current model") no longer contain the conditional registration.
src/tools/execute/shell-execute.txt (1)

18-24: 백그라운드 명령어 검증 관련 내용이 중복됩니다.

Line 20의 "Always verify startup with follow-up checks" 와 Line 24의 "Background commands (&) should be verified with follow-up checks"가 동일한 내용을 반복하고 있습니다. LLM 프롬프트 컨텍스트에서 토큰을 절약하기 위해 하나로 통합하는 것이 좋습니다.

♻️ 중복 제거 제안
 **Notes**:
 - Large outputs are truncated automatically
-- Background commands (`&`) should be verified with follow-up checks
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/execute/shell-execute.txt` around lines 18 - 24, The two notes
about verifying background commands are duplicated (the lines "Always verify
startup with follow-up checks (`sleep`, health check, logs)" and "Background
commands (`&`) should be verified with follow-up checks"); remove the redundancy
by keeping a single consolidated line that mentions background commands run with
`&` and that startup should always be verified with follow-up checks (e.g.,
sleep, health checks, logs), ensuring the combined sentence replaces both
original lines.
src/tools/explore/read-file.ts (1)

54-69: parsedInput.path 대신 원본 path 매개변수를 사용하고 있습니다.

Line 63에서 safeReadFileEnhanced 호출 시 path(원본 매개변수)를 사용하고 있지만, Line 64-68에서는 parsedInput의 필드를 사용하고 있습니다. path는 단순 문자열이라 기능적 차이는 없지만, 일관성을 위해 모든 필드를 parsedInput에서 참조하는 것이 좋습니다.

♻️ 일관성을 위한 수정 제안
-  const result = await safeReadFileEnhanced(path, {
+  const result = await safeReadFileEnhanced(parsedInput.path, {
     offset: parsedInput.offset,
     limit: parsedInput.limit,
     around_line: parsedInput.around_line,
     before: parsedInput.before,
     after: parsedInput.after,
   });

참고: Line 73의 stat(path) 및 Line 88의 basename(path)도 동일하게 parsedInput.path로 변경하면 완전한 일관성을 확보할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/explore/read-file.ts` around lines 54 - 69, The call mixes the
original path parameter with parsedInput fields; update usages to consistently
use parsedInput.path: replace the path argument passed to safeReadFileEnhanced
with parsedInput.path, and also change subsequent stat(path) and basename(path)
calls to stat(parsedInput.path) and basename(parsedInput.path) so all file
operations use the validated parsedInput.path value (symbols: parsedInput,
safeReadFileEnhanced, stat, basename).
src/friendli-reasoning.test.ts (1)

45-72: as ModelMessage 타입 단언 사용에 대한 참고 사항.

Lines 53, 94, 134에서 as ModelMessage 타입 단언을 사용하고 있습니다. reasoning 콘텐츠 파트나 reasoning_content 같은 추가 필드가 ModelMessage 타입 정의와 정확히 일치하지 않기 때문에 필요한 것으로 이해됩니다. 테스트 코드에서는 허용 가능하지만, ai 패키지가 향후 이러한 필드를 타입에 포함하게 되면 단언을 제거할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/friendli-reasoning.test.ts` around lines 45 - 72, The test uses inline
type assertions "as ModelMessage" for messages that include extra fields
(reasoning content), which hides type mismatches; change the test to use a
permissive type instead of forcing ModelMessage—e.g., declare messages as
(ModelMessage | Record<string, unknown>)[] or build the assistant message as
const assistantMsg: Record<string, unknown> = { role: "assistant", content:
[...] } and pass that into applyFriendliInterleavedField so you avoid unsafe "as
ModelMessage" casts; once the ai package adds the new fields to ModelMessage you
can remove the broader typing and use ModelMessage directly (references:
ModelMessage, applyFriendliInterleavedField, reasoning_content).
src/entrypoints/headless.ts (1)

182-190: --reasoning-mode에 잘못된 값이 전달될 때 조용히 기본값으로 대체됩니다.

parseReasoningModenull을 반환하면 ?? 연산자에 의해 DEFAULT_REASONING_MODE로 폴백됩니다. 사용자가 잘못된 값을 전달했을 때 경고 없이 무시되므로 디버깅이 어려울 수 있습니다. --tool-fallback-mode와 일관된 패턴이긴 하지만, stderr 경고 메시지 추가를 고려해 보세요.

💡 선택적 개선 제안
     } else if (arg === "--reasoning-mode") {
       const candidate = args[i + 1];
       if (candidate && !candidate.startsWith("--")) {
         const parsedMode = parseReasoningMode(candidate);
-        reasoningMode = parsedMode ?? DEFAULT_REASONING_MODE;
+        if (parsedMode) {
+          reasoningMode = parsedMode;
+        } else {
+          console.error(`[headless] Unknown reasoning mode "${candidate}", using default "${DEFAULT_REASONING_MODE}"`);
+        }
         i++;
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/entrypoints/headless.ts` around lines 182 - 190, The current handling of
the --reasoning-mode argument silently falls back to DEFAULT_REASONING_MODE when
parseReasoningMode(candidate) returns null; update the branch that sets
reasoningMode so that when parsedMode is null you still set reasoningMode to
DEFAULT_REASONING_MODE but also write a warning to stderr (or process.stderr)
indicating the invalid --reasoning-mode value and that DEFAULT_REASONING_MODE is
being used; locate the logic around parseReasoningMode, DEFAULT_REASONING_MODE
and the reasoningMode assignment in the --reasoning-mode branch and mirror the
existing user-visible warning pattern used for --tool-fallback-mode to keep
behavior consistent.
src/tools/modify/edit-file.ts (1)

80-90: parseAppendPrependEdit의 ref 우선순위 로직이 직관적이지 않습니다.

appendpos ?? end를, prependend ?? pos를 사용합니다. HashlineEdit 타입에서 두 연산 모두 pos 필드만 사용하므로 사용자가 end 필드를 제공했을 때의 폴백 의도가 코드만으로는 명확하지 않습니다. 간단한 인라인 주석이 있으면 유지보수에 도움이 될 것입니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/modify/edit-file.ts` around lines 80 - 90, The ref selection in
parseAppendPrependEdit is confusing because append uses ref = pos ?? end while
prepend uses ref = end ?? pos even though the resulting HashlineEdit only
carries pos; update the function to include a concise inline comment explaining
the intended fallback behavior (e.g., "pos is primary; if absent for append use
end as fallback; for prepend we prefer end so callers can specify target line
via end") and why both fields are accepted but only pos is emitted (reference
parseLineTag(ref) -> pos), so future readers understand the asymmetric ordering
rather than changing logic.
src/tools/utils/hashline/hashline.ts (3)

147-165: 배열 입력 시 prefix 스트리핑이 생략됩니다.

parseHashlineText에서 입력이 string[]인 경우 stripNewLinePrefixes를 거치지 않고 그대로 반환합니다. LLM이 배열 형태로 해시라인 접두사가 포함된 줄을 제공할 경우 접두사가 남아 있을 수 있습니다.

의도적인 설계(배열 입력은 이미 정제됨을 가정)라면 문서 주석을 추가하면 좋겠습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/utils/hashline/hashline.ts` around lines 147 - 165,
parseHashlineText currently returns string[] inputs unchanged, skipping
stripNewLinePrefixes and leaving possible leading prefixes in array elements;
change it so that when input is an Array (string[]), you run
stripNewLinePrefixes on the array before returning (i.e., call
stripNewLinePrefixes(input) and return that result) so both string and string[]
paths normalize prefixes via the stripNewLinePrefixes helper; reference
parseHashlineText and stripNewLinePrefixes when making the change.

380-456: 겹치는 편집에서의 noop 감지가 원본 기준으로 동작합니다.

applyAnnotatedEdit에서 noop 비교(Line 399-404)는 originalLines를 기준으로 합니다. 편집이 하단에서 상단으로 적용되므로 이전 편집이 같은 영역을 수정한 후에도 원본과 비교합니다.

겹치는 범위의 편집이 여러 개 있을 때(예: 5-10줄 교체 후 3-7줄 교체), 첫 번째 편집이 이미 내용을 변경했음에도 두 번째 편집이 원본 기준으로 noop을 판별하여 의도치 않게 noop으로 처리되거나 처리되지 않을 수 있습니다. 실제로 겹치는 편집은 드물겠지만, 인지해 두면 좋겠습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/utils/hashline/hashline.ts` around lines 380 - 456, The
noop-detection in applyAnnotatedEdit currently compares edit lines against
originalLines (using isSameText) which misdetects noops when earlier overlapping
edits have already mutated fileLines; change the noop comparison to use the
current file state (fileLines.slice(...)) instead of originalLines so noopEdits
and subsequent splice/trackFirstChangedLine behavior reflect prior applied
edits; keep the rest of the replace branch (editIndex, loc, current join) intact
so noopEdits still records the same metadata.

321-378: JSON.stringify 기반 편집 중복 제거

JSON.stringify(edit)로 편집을 중복 제거하는 방식은 작은 편집 세트에서는 문제없지만, lines 배열이 큰 경우 성능 비용이 있을 수 있습니다. 현재 사용 패턴에서는 괜찮지만, 대규모 편집이 예상된다면 구조적 비교를 고려할 수 있습니다.

편집 정렬 로직(하단→상단, 우선순위 기반)은 splice 인덱스 이동 문제를 올바르게 회피합니다. 👍

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/utils/hashline/hashline.ts` around lines 321 - 378, The
deduplication in buildAnnotatedEdits currently uses JSON.stringify(edit) which
can be costly when edit objects contain large nested data (e.g., big lines
arrays); replace this with a lightweight canonical key or structural comparison:
construct a small deterministic key from essential fields (e.g., edit.op,
edit.pos.line, edit.pos.col, edit.end?.line, edit.end?.col and a short hash or
length of any large payload) or implement a deep-equals function to compare
edits only by relevant properties before inserting into seenEditKeys; update the
logic around seenEditKeys, deduped, and the loop that builds the key so you
avoid stringifying entire edit objects while preserving correct deduplication
behavior.
src/tools/explore/safety-utils.ts (2)

35-35: 모듈 수준 캐시에 대한 무효화 메커니즘 부재

ignoreCache는 모듈 수준 Map으로, .gitignore 파일이 변경되어도 캐시가 갱신되지 않습니다. CLI 도구의 단일 실행에서는 문제가 되지 않지만, 장시간 실행되는 에이전트 세션에서 사용자가 .gitignore를 수정한 경우 반영되지 않을 수 있습니다.

/clear 같은 세션 초기화 시 캐시를 무효화하는 방법을 고려해 보세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/explore/safety-utils.ts` at line 35, The module-level cache
ignoreCache (Map<string, Ignore>) never gets invalidated, so updates to
.gitignore won't be picked up during long-running sessions; add a small
invalidation API (e.g., export function clearIgnoreCache() that calls
ignoreCache.clear()) and call it from the session reset/clear handler (e.g.,
wherever the /clear command or session initialization is implemented, such as
handleClearCommand or sessionReset) so the cache is flushed when a user resets
the agent/session.

152-184: ENOENT 시 가드를 통과시키는 설계에 대한 확인 필요

stat()에서 ENOENT 에러가 발생하면 가드가 null을 반환하여 통과시킵니다. 이후 safeReadFileEnhancedreadFile(path, "utf-8") (Line 266)에서 ENOENT 예외가 발생하지만, 사용자에게 표시되는 에러 메시지가 "Unable to inspect file metadata" 같은 명확한 가드 메시지가 아닌 Node.js 기본 에러 메시지가 됩니다.

의도적인 설계라면 괜찮지만, 파일 미존재 시 더 친절한 에러 메시지를 제공하려면 ENOENT도 가드에서 처리하는 것이 좋을 수 있습니다.

💡 ENOENT 가드 추가 제안
   } catch (error) {
     if (
       typeof error === "object" &&
       error !== null &&
       "code" in error &&
       (error as NodeJS.ErrnoException).code !== "ENOENT"
     ) {
       return {
         allowed: false,
         reason: `Unable to inspect file metadata for '${filePath}'.`,
       };
     }
+    if (
+      typeof error === "object" &&
+      error !== null &&
+      "code" in error &&
+      (error as NodeJS.ErrnoException).code === "ENOENT"
+    ) {
+      return {
+        allowed: false,
+        reason: `File not found: '${filePath}'.`,
+      };
+    }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/explore/safety-utils.ts` around lines 152 - 184, The current
checkFileStatGuards function lets ENOENT pass through (returns null) causing raw
Node ENOENT errors later in safeReadFileEnhanced; change checkFileStatGuards to
explicitly handle (error as NodeJS.ErrnoException).code === "ENOENT" by
returning { allowed: false, reason: `File not found: '${filePath}'.` } (or
another friendly message) so missing files are rejected with a clear guard
message; update the catch block in checkFileStatGuards to distinguish ENOENT
from other errors and return the user-friendly reason for ENOENT while keeping
the existing generic reason for other codes.
src/entrypoints/cli.ts (1)

1213-1239: --reasoning-mode에 잘못된 값 전달 시 무시되는 동작 확인

Line 1228-1232에서 --reasoning-mode 뒤에 유효하지 않은 값을 전달하면 parseReasoningModenull을 반환하고 DEFAULT_REASONING_MODE("off")로 대체됩니다. 사용자에게 오류나 경고가 표시되지 않습니다.

이는 CLI 인수 파싱에서 흔한 패턴이지만, 사용자가 오타를 냈을 때(--reasoning-mode presrved) 의도한 모드가 적용되지 않아 혼란스러울 수 있습니다. 유효하지 않은 값에 대해 stderr 경고를 출력하거나, 최소한 시작 시 활성 모드를 표시하는 것을 고려해 보세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/entrypoints/cli.ts` around lines 1213 - 1239, In parseReasoningCliOption,
when a candidate value after "--reasoning-mode" is present but
parseReasoningMode(candidate) returns null, don't silently fall back to
DEFAULT_REASONING_MODE; instead emit a warning to stderr (e.g., console.error)
that includes the invalid candidate and the fact you're using
DEFAULT_REASONING_MODE, so the user is informed; keep returning consumedArgs: 1
and mode: DEFAULT_REASONING_MODE, and reference parseReasoningCliOption,
parseReasoningMode, and DEFAULT_REASONING_MODE when making this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/agent.ts`:
- Line 33: The regex constant NON_ASCII_PATTERN is escaped incorrectly (it uses
a double backslash and a literal "�" range), so non-ASCII detection always
misbehaves and causes isNonEnglishText to return true for ASCII input; fix by
replacing the pattern with a correct ASCII-range negative character class such
as one that uses a single backslash or Unicode escape (e.g., match characters
outside \x00-\x7F or \u0000-\u007F) so NON_ASCII_PATTERN correctly detects
non-ASCII text and prevents unnecessary translation API calls; update any tests
or usages of NON_ASCII_PATTERN and validate isNonEnglishText behavior after the
change.

In `@src/entrypoints/cli.ts`:
- Around line 1560-1563: The PR references a missing module
createTranslateCommand imported from ../commands/translate and registered in the
CLI (createTranslateCommand), so either add a new src/commands/translate.ts that
exports createTranslateCommand (matching the CLI’s expected signature and
options) or remove the import and the registration call where
createTranslateCommand is used; to fix, implement and export a
createTranslateCommand function in src/commands/translate.ts with the same
command API as the other command modules (so the CLI’s command registration
succeeds), or delete the import line for createTranslateCommand and the call
that registers it in the CLI to eliminate the broken reference.

In `@src/friendli-reasoning.ts`:
- Around line 104-110: The condition in getFriendliSelectableReasoningModes that
pushes "preserved" currently only checks config.interleaved_field,
config.preserved_toggle, and !alwaysReasoning but omits hasReasoningSupport;
update the if to also require hasReasoningSupport (the same capability check
used by resolveFriendliReasoningMode) so that selectable.push("preserved") only
runs when hasReasoningSupport is true, keeping behavior consistent with
resolveFriendliReasoningMode's supportsPreserved logic (refer to
getFriendliSelectableReasoningModes, resolveFriendliReasoningMode,
hasReasoningSupport, config.interleaved_field, config.preserved_toggle,
alwaysReasoning, and the line that does selectable.push("preserved")).

In `@src/tools/execute/shell-interact.ts`:
- Line 3: The build fails because TypeScript lacks a module declaration for
".txt" imports (e.g. the import SHELL_INTERACT_DESCRIPTION in shell-interact.ts
and similar imports in src/tools/planning/todo-write.ts,
src/tools/planning/load-skill.ts, src/tools/modify/write-file.ts,
src/tools/explore/read-file.ts); add a module declaration for "*.txt" (either in
tsconfig via a types entry or by adding a .d.ts file) that exports the file
content as a string so those imports compile correctly (declare module "*.txt" {
const content: string; export default content; }).

In `@src/tools/modify/AGENTS.md`:
- Around line 20-22: Add a single blank line between the "## WHERE TO LOOK"
heading and the following Markdown table to satisfy MD058 (heading and content
must be separated by a blank line); update the AGENTS.md around the "## WHERE TO
LOOK" heading so there is one empty line before the table start (the pipe/| row)
and ensure no extra or missing blank lines remain.

In `@src/tools/modify/delete-file.ts`:
- Line 5: Create a TypeScript module declaration for "*.txt" (e.g., add a
types/txt.d.ts file) that exports the file content as a string so imports like
DELETE_FILE_DESCRIPTION in delete-file.ts and other imports (read-file.ts,
grep.ts, glob.ts, write-file.ts, edit-file.ts, shell-execute.ts,
shell-interact.ts, todo-write.ts, load-skill.ts, system-prompt.ts) type-check
under strict mode; implement the declaration as "declare module \"*.txt\" {
const content: string; export default content; }" and, if the new d.ts isn't
discovered, add its folder to tsconfig.json typeRoots so the compiler picks up
the declaration.

In `@src/tools/planning/todo-write.ts`:
- Line 7: The project imports .txt files like TODO_WRITE_DESCRIPTION causing
TypeScript errors under strict mode; add an ambient module declaration for
"*.txt" so imports are typed as strings (create a .d.ts with declare module
"*.txt" { const content: string; export default content; }) and commit it, or
alternatively adjust tsconfig settings (e.g., relax strict or skipLibCheck) if
you intentionally want to change type-checking policy; update references to
TODO_WRITE_DESCRIPTION remain unchanged after adding the declaration.

---

Duplicate comments:
In `@src/tools/explore/grep.ts`:
- Line 7: The import of "./grep-files.txt" as GREP_FILES_DESCRIPTION in grep.ts
lacks a TypeScript module declaration for .txt files; add or update a
declaration (e.g., declare module "*.txt": const value: string; export default
value) in your project's type declarations (similar to the fix referenced in
delete-file.ts) so GREP_FILES_DESCRIPTION can be imported as a string without TS
errors; update or create a .d.ts (global) file that exports .txt modules and
ensure it's included in tsconfig.

In `@src/tools/modify/write-file.ts`:
- Line 5: The import of a .txt file as WRITE_FILE_DESCRIPTION in write-file.ts
needs a proper module type declaration (same fix as referenced in delete-file.ts
review) or avoid raw import by reading the file at runtime; update the module
declaration for "*.txt" in your type declarations (or add a .d.ts) so the import
of WRITE_FILE_DESCRIPTION is valid, or change the code in write-file.ts to use
fs.readFileSync/async to load the text and assign it to WRITE_FILE_DESCRIPTION.

---

Nitpick comments:
In `@src/agent.ts`:
- Line 104: The array ANTHROPIC_SELECTABLE_REASONING_MODES is intended as an
immutable set of literal ReasoningMode values but is typed as a mutable
ReasoningMode[]; change its declaration to use a const assertion so the elements
become literal types (preserving compatibility with ReasoningMode where needed)
and prevent accidental mutation—update the declaration of
ANTHROPIC_SELECTABLE_REASONING_MODES to use "as const" and adjust any downstream
usage/typing to accept readonly literal types if necessary.
- Around line 110-119: The function isAnthropicWithReasoning is misnamed and its
logic wrongly excludes Opus models (which do consume thinking budget); rename it
to needsThinkingBudget (or isThinkingBudgetApplicable) and update all call sites
such as the invocation inside getProviderOptions to use the new name, and change
the logic to return true for Anthropic models whenever reasoningMode !== "off"
(i.e., remove the !modelId.includes("opus") exclusion) so Opus models are
treated as requiring thinking budget.

In `@src/commands/aliases-and-tool-fallback.test.ts`:
- Around line 109-129: Move the duplicate conditional command-registration into
a test-suite setup: add a beforeEach that checks
getCommands().has("reasoning-mode") and calls
registerCommand(createReasoningModeCommand()) when missing, and then remove the
same guard lines from both it blocks; reference the getCommands,
registerCommand, and createReasoningModeCommand helpers and ensure the two tests
("supports /think alias for /reasoning-mode" and "rejects unsupported mode for
current model") no longer contain the conditional registration.

In `@src/entrypoints/cli.ts`:
- Around line 1213-1239: In parseReasoningCliOption, when a candidate value
after "--reasoning-mode" is present but parseReasoningMode(candidate) returns
null, don't silently fall back to DEFAULT_REASONING_MODE; instead emit a warning
to stderr (e.g., console.error) that includes the invalid candidate and the fact
you're using DEFAULT_REASONING_MODE, so the user is informed; keep returning
consumedArgs: 1 and mode: DEFAULT_REASONING_MODE, and reference
parseReasoningCliOption, parseReasoningMode, and DEFAULT_REASONING_MODE when
making this change.

In `@src/entrypoints/headless.ts`:
- Around line 182-190: The current handling of the --reasoning-mode argument
silently falls back to DEFAULT_REASONING_MODE when parseReasoningMode(candidate)
returns null; update the branch that sets reasoningMode so that when parsedMode
is null you still set reasoningMode to DEFAULT_REASONING_MODE but also write a
warning to stderr (or process.stderr) indicating the invalid --reasoning-mode
value and that DEFAULT_REASONING_MODE is being used; locate the logic around
parseReasoningMode, DEFAULT_REASONING_MODE and the reasoningMode assignment in
the --reasoning-mode branch and mirror the existing user-visible warning pattern
used for --tool-fallback-mode to keep behavior consistent.

In `@src/friendli-models.ts`:
- Around line 21-29: Export the DEFAULT_FRIENDLI_REASONING constant so it is the
single source of truth (add an export on the existing const
DEFAULT_FRIENDLI_REASONING) and remove the duplicated local constant
DEFAULT_FRIENDLI_REASONING_CONFIG; update the other module that currently
defines DEFAULT_FRIENDLI_REASONING_CONFIG to import DEFAULT_FRIENDLI_REASONING
and replace its local usage with the imported constant, ensuring all references
to DEFAULT_FRIENDLI_REASONING_CONFIG are replaced with
DEFAULT_FRIENDLI_REASONING.

In `@src/friendli-reasoning.test.ts`:
- Around line 45-72: The test uses inline type assertions "as ModelMessage" for
messages that include extra fields (reasoning content), which hides type
mismatches; change the test to use a permissive type instead of forcing
ModelMessage—e.g., declare messages as (ModelMessage | Record<string,
unknown>)[] or build the assistant message as const assistantMsg: Record<string,
unknown> = { role: "assistant", content: [...] } and pass that into
applyFriendliInterleavedField so you avoid unsafe "as ModelMessage" casts; once
the ai package adds the new fields to ModelMessage you can remove the broader
typing and use ModelMessage directly (references: ModelMessage,
applyFriendliInterleavedField, reasoning_content).

In `@src/friendli-reasoning.ts`:
- Around line 74-82: The code implicitly treats requestedMode === "preserved"
via the final fallback which risks misbehavior if REASONING_MODES grows; make
the handling explicit by adding an explicit branch for requestedMode ===
"preserved" (or convert the logic to a switch/exhaustive check) so that
supportsPreserved, supportsInterleaved, supportsOn, DEFAULT_REASONING_MODE and
requestedMode are evaluated deterministically; for example add a top-level if
(requestedMode === "preserved") return "preserved", or replace the final return
with an explicit requestedMode === "on" check and a safe default/error for all
other modes.
- Around line 10-18: DEFAULT_FRIENDLI_REASONING_CONFIG duplicates the constant
DEFAULT_FRIENDLI_REASONING in friendli-models.ts; remove the local definition
and import the shared constant instead. Update src/friendli-reasoning.ts to
import DEFAULT_FRIENDLI_REASONING from the module that defines it (ensure the
exported name and FriendliReasoningConfig type are used) and replace usages of
DEFAULT_FRIENDLI_REASONING_CONFIG with that imported symbol so there is a single
source of truth.

In `@src/reasoning-mode.test.ts`:
- Around line 5-10: Add tests to verify parseReasoningMode is case-insensitive
and handles the empty-string edge case: add expectations that
parseReasoningMode("OFF") returns "off" and parseReasoningMode("INTERLEAVED")
returns "interleaved", and add an assertion for parseReasoningMode("") (verify
it returns the current implementation's value for empty input, e.g., "" or the
default mode your function uses). Reference the parseReasoningMode function when
adding these assertions to the existing test block.

In `@src/tools/AGENTS.md`:
- Line 3: The hardcoded generated timestamp line `**Generated:** 2026-02-23
14:40 KST` in AGENTS.md will become inaccurate; remove this static timestamp or
replace it with a dynamically-managed mechanism (e.g., a build/generation
placeholder or tooling comment) so the file isn't pinned to a stale date —
update or delete the exact `**Generated:** 2026-02-23 14:40 KST` line
accordingly and ensure any CI/generator injects a timestamp if you need one.

In `@src/tools/execute/shell-execute.txt`:
- Around line 18-24: The two notes about verifying background commands are
duplicated (the lines "Always verify startup with follow-up checks (`sleep`,
health check, logs)" and "Background commands (`&`) should be verified with
follow-up checks"); remove the redundancy by keeping a single consolidated line
that mentions background commands run with `&` and that startup should always be
verified with follow-up checks (e.g., sleep, health checks, logs), ensuring the
combined sentence replaces both original lines.

In `@src/tools/explore/read-file.ts`:
- Around line 54-69: The call mixes the original path parameter with parsedInput
fields; update usages to consistently use parsedInput.path: replace the path
argument passed to safeReadFileEnhanced with parsedInput.path, and also change
subsequent stat(path) and basename(path) calls to stat(parsedInput.path) and
basename(parsedInput.path) so all file operations use the validated
parsedInput.path value (symbols: parsedInput, safeReadFileEnhanced, stat,
basename).

In `@src/tools/explore/safety-utils.ts`:
- Line 35: The module-level cache ignoreCache (Map<string, Ignore>) never gets
invalidated, so updates to .gitignore won't be picked up during long-running
sessions; add a small invalidation API (e.g., export function clearIgnoreCache()
that calls ignoreCache.clear()) and call it from the session reset/clear handler
(e.g., wherever the /clear command or session initialization is implemented,
such as handleClearCommand or sessionReset) so the cache is flushed when a user
resets the agent/session.
- Around line 152-184: The current checkFileStatGuards function lets ENOENT pass
through (returns null) causing raw Node ENOENT errors later in
safeReadFileEnhanced; change checkFileStatGuards to explicitly handle (error as
NodeJS.ErrnoException).code === "ENOENT" by returning { allowed: false, reason:
`File not found: '${filePath}'.` } (or another friendly message) so missing
files are rejected with a clear guard message; update the catch block in
checkFileStatGuards to distinguish ENOENT from other errors and return the
user-friendly reason for ENOENT while keeping the existing generic reason for
other codes.

In `@src/tools/modify/AGENTS.md`:
- Line 3: The hardcoded timestamp line "**Generated:** 2026-02-23 14:40 KST" in
AGENTS.md should be removed or replaced with an automatically-generated value;
either delete that static line from AGENTS.md (symbol: the "**Generated:**
2026-02-23 14:40 KST" text) or change the workflow to inject the timestamp at
build/CI time (e.g., via a CI step that writes/updates the Generated field),
ensuring the repository contains no stale hardcoded timestamp.

In `@src/tools/modify/edit-file.ts`:
- Around line 80-90: The ref selection in parseAppendPrependEdit is confusing
because append uses ref = pos ?? end while prepend uses ref = end ?? pos even
though the resulting HashlineEdit only carries pos; update the function to
include a concise inline comment explaining the intended fallback behavior
(e.g., "pos is primary; if absent for append use end as fallback; for prepend we
prefer end so callers can specify target line via end") and why both fields are
accepted but only pos is emitted (reference parseLineTag(ref) -> pos), so future
readers understand the asymmetric ordering rather than changing logic.

In `@src/tools/utils/hashline/hashline.ts`:
- Around line 147-165: parseHashlineText currently returns string[] inputs
unchanged, skipping stripNewLinePrefixes and leaving possible leading prefixes
in array elements; change it so that when input is an Array (string[]), you run
stripNewLinePrefixes on the array before returning (i.e., call
stripNewLinePrefixes(input) and return that result) so both string and string[]
paths normalize prefixes via the stripNewLinePrefixes helper; reference
parseHashlineText and stripNewLinePrefixes when making the change.
- Around line 380-456: The noop-detection in applyAnnotatedEdit currently
compares edit lines against originalLines (using isSameText) which misdetects
noops when earlier overlapping edits have already mutated fileLines; change the
noop comparison to use the current file state (fileLines.slice(...)) instead of
originalLines so noopEdits and subsequent splice/trackFirstChangedLine behavior
reflect prior applied edits; keep the rest of the replace branch (editIndex,
loc, current join) intact so noopEdits still records the same metadata.
- Around line 321-378: The deduplication in buildAnnotatedEdits currently uses
JSON.stringify(edit) which can be costly when edit objects contain large nested
data (e.g., big lines arrays); replace this with a lightweight canonical key or
structural comparison: construct a small deterministic key from essential fields
(e.g., edit.op, edit.pos.line, edit.pos.col, edit.end?.line, edit.end?.col and a
short hash or length of any large payload) or implement a deep-equals function
to compare edits only by relevant properties before inserting into seenEditKeys;
update the logic around seenEditKeys, deduped, and the loop that builds the key
so you avoid stringifying entire edit objects while preserving correct
deduplication behavior.

Comment on lines +104 to +110
if (
config.interleaved_field !== null &&
config.preserved_toggle !== null &&
!alwaysReasoning
) {
selectable.push("preserved");
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

getFriendliSelectableReasoningModes에서 "preserved" 추가 조건에 hasReasoningSupport 검사 누락

reasoning: null 모델이 추가되면 이 조건이 잘못 동작합니다. model.reasoning === null인 경우 resolveFriendliReasoningCapability에서 configDEFAULT_FRIENDLI_REASONING_CONFIG(non-null 필드 포함)로 폴백하지만 hasReasoningSupportfalse가 됩니다. 현재 조건은 hasReasoningSupport를 확인하지 않으므로 "preserved"가 선택 가능 목록에 추가되고, 이는 resolveFriendliReasoningMode"preserved"를 실제로 반환하지 않는 것과 불일치합니다.

resolveFriendliReasoningModesupportsPreserved 계산 방식(supportsInterleaved && config.preserved_toggle !== null, 즉 hasReasoningSupport를 포함)과 일치시켜야 합니다.

🐛 수정 제안
  if (
+   hasReasoningSupport &&
    config.interleaved_field !== null &&
    config.preserved_toggle !== null &&
    !alwaysReasoning
  ) {
    selectable.push("preserved");
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/friendli-reasoning.ts` around lines 104 - 110, The condition in
getFriendliSelectableReasoningModes that pushes "preserved" currently only
checks config.interleaved_field, config.preserved_toggle, and !alwaysReasoning
but omits hasReasoningSupport; update the if to also require hasReasoningSupport
(the same capability check used by resolveFriendliReasoningMode) so that
selectable.push("preserved") only runs when hasReasoningSupport is true, keeping
behavior consistent with resolveFriendliReasoningMode's supportsPreserved logic
(refer to getFriendliSelectableReasoningModes, resolveFriendliReasoningMode,
hasReasoningSupport, config.interleaved_field, config.preserved_toggle,
alwaysReasoning, and the line that does selectable.push("preserved")).

@@ -1,5 +1,6 @@
import { tool } from "ai";
import { z } from "zod";
import SHELL_INTERACT_DESCRIPTION from "./shell-interact.txt";
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for *.txt module declarations in .d.ts files
echo "=== Searching for *.txt module declarations in .d.ts files ==="
rg -n --type=ts 'declare module.*\.txt' --glob '*.d.ts'

# Broader search for txt declarations
echo -e "\n=== Broader search for txt module declarations ==="
rg -n 'declare module.*txt' --glob '*.d.ts'

# Check for .txt files being imported
echo -e "\n=== Check for .txt imports in TypeScript files ==="
rg -n '\.txt' --glob '*.ts' --glob '*.tsx' | head -20

# List .d.ts files in the project
echo -e "\n=== List of .d.ts files ==="
fd -e d.ts

# Check TypeScript configuration
echo -e "\n=== Check tsconfig.json for module resolution ==="
if [ -f tsconfig.json ]; then
  cat tsconfig.json | head -50
fi

# Check if shell-interact.txt and shell-execute.txt files exist
echo -e "\n=== Check for .txt files in shell-interact/execute directories ==="
fd -e txt | grep -E 'shell-(interact|execute)'

Repository: minpeter/code-editing-agent

Length of output: 3097


.txt 파일 모듈 선언이 누락되어 있습니다.

TypeScript 모듈 선언이 없어서 컴파일 오류가 발생합니다. 이 문제는 shell-interact.ts뿐 아니라 다음 파일들에도 영향을 미칩니다:

  • src/tools/planning/todo-write.ts
  • src/tools/planning/load-skill.ts
  • src/tools/modify/write-file.ts
  • src/tools/explore/read-file.ts

tsconfig.json에 모듈 선언을 추가하거나 .d.ts 파일을 작성하세요:

declare module "*.txt" {
  const content: string;
  export default content;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/execute/shell-interact.ts` at line 3, The build fails because
TypeScript lacks a module declaration for ".txt" imports (e.g. the import
SHELL_INTERACT_DESCRIPTION in shell-interact.ts and similar imports in
src/tools/planning/todo-write.ts, src/tools/planning/load-skill.ts,
src/tools/modify/write-file.ts, src/tools/explore/read-file.ts); add a module
declaration for "*.txt" (either in tsconfig via a types entry or by adding a
.d.ts file) that exports the file content as a string so those imports compile
correctly (declare module "*.txt" { const content: string; export default
content; }).

Comment on lines +20 to +22
## WHERE TO LOOK
| Task | Location | Notes |
|------|----------|-------|
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

테이블 앞에 빈 줄을 추가하세요 (MD058).

정적 분석 도구가 경고한 대로, ## WHERE TO LOOK 헤딩과 테이블 사이에 빈 줄이 없습니다.

📝 수정 제안
 ## WHERE TO LOOK
+
 | Task | Location | Notes |
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
## WHERE TO LOOK
| Task | Location | Notes |
|------|----------|-------|
## WHERE TO LOOK
| Task | Location | Notes |
|------|----------|-------|
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 21-21: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/modify/AGENTS.md` around lines 20 - 22, Add a single blank line
between the "## WHERE TO LOOK" heading and the following Markdown table to
satisfy MD058 (heading and content must be separated by a blank line); update
the AGENTS.md around the "## WHERE TO LOOK" heading so there is one empty line
before the table start (the pipe/| row) and ensure no extra or missing blank
lines remain.

import { basename } from "node:path";
import { tool } from "ai";
import { z } from "zod";
import DELETE_FILE_DESCRIPTION from "./delete-file.txt";
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for *.txt ambient module declarations
echo "=== Searching for txt module declarations ==="
rg -n 'declare module.*\.txt' --type ts 2>/dev/null || echo "No .txt module declarations found"

echo ""
echo "=== Checking .d.ts files for txt-related declarations ==="
fd -e d.ts -type f | head -20 | xargs -I {} sh -c 'grep -l "\.txt" "{}" 2>/dev/null && echo "Found in: {}"' 2>/dev/null || echo "No .txt references in .d.ts files"

echo ""
echo "=== Listing all .d.ts files ==="
fd -e d.ts -type f

echo ""
echo "=== Checking tsconfig.json files ==="
fd -name tsconfig.json -type f | xargs -I {} sh -c 'echo "File: {}"; cat "{}"'

echo ""
echo "=== Checking for .txt imports in the codebase ==="
rg 'import.*from.*\.txt' --type ts | head -20

Repository: minpeter/code-editing-agent

Length of output: 2757


🏁 Script executed:

#!/bin/bash
echo "=== tsconfig.json content ==="
find . -name tsconfig.json -type f -exec cat {} \;

echo ""
echo "=== bunfig.toml (if exists) ==="
find . -name bunfig.toml -type f -exec cat {} \;

echo ""
echo "=== bun.lock preview (checking if it exists) ==="
ls -lh bun.lock package.json 2>/dev/null | head -5

echo ""
echo "=== package.json (checking build scripts) ==="
cat package.json | grep -A 10 '"scripts"' 2>/dev/null || echo "package.json not found or no scripts"

Repository: minpeter/code-editing-agent

Length of output: 1140


🏁 Script executed:

#!/bin/bash
echo "=== Searching for index.d.ts or global type files ==="
find . -name "*.d.ts" -o -name "*types*" -type f | grep -v node_modules | head -20

echo ""
echo "=== Checking src directory structure ==="
find src -type f | head -30

echo ""
echo "=== Looking for any type declarations in src ==="
rg 'declare' src --type ts --type tsx 2>/dev/null || echo "No declarations found in src"

Repository: minpeter/code-editing-agent

Length of output: 1096


TypeScript를 위한 .txt 모듈 선언이 필수입니다.

프로젝트에 declare module "*.txt" 선언이 전혀 없습니다. 현재 12개 이상의 파일에서 .txt 파일을 임포트하고 있지만(read-file.ts, grep.ts, glob.ts, write-file.ts, edit-file.ts, delete-file.ts, shell-execute.ts, shell-interact.ts, todo-write.ts, load-skill.ts, system-prompt.ts), strict: true로 설정된 TypeScript에서는 이들이 모두 TS2307 오류를 발생시킵니다.

다음과 같은 내용의 .d.ts 파일(예: types/txt.d.ts)을 생성하여 해결하세요:

declare module "*.txt" {
  const content: string;
  export default content;
}

그 후 tsconfig.json에 필요시 typeRoots를 추가하여 타입 파일이 감지되도록 하면 모든 .txt 임포트가 올바르게 타입 체크됩니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/modify/delete-file.ts` at line 5, Create a TypeScript module
declaration for "*.txt" (e.g., add a types/txt.d.ts file) that exports the file
content as a string so imports like DELETE_FILE_DESCRIPTION in delete-file.ts
and other imports (read-file.ts, grep.ts, glob.ts, write-file.ts, edit-file.ts,
shell-execute.ts, shell-interact.ts, todo-write.ts, load-skill.ts,
system-prompt.ts) type-check under strict mode; implement the declaration as
"declare module \"*.txt\" { const content: string; export default content; }"
and, if the new d.ts isn't discovered, add its folder to tsconfig.json typeRoots
so the compiler picks up the declaration.

import { z } from "zod";
import { TODO_DIR } from "../../context/paths";
import { getSessionId } from "../../context/session";
import TODO_WRITE_DESCRIPTION from "./todo-write.txt";
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# Search for declare module "*.txt" in TypeScript definition files
echo "=== Searching for *.txt module declarations ==="
rg -n 'declare module.*\.txt' --type ts

echo ""
echo "=== Checking all .d.ts files ==="
fd -e "d.ts" -x cat -n {}

echo ""
echo "=== Checking tsconfig files ==="
fd -e json -g 'tsconfig*.json' -x cat {}

echo ""
echo "=== Checking for *.txt imports in the project ==="
rg -n 'from.*\.txt' --type ts --type tsx

Repository: minpeter/code-editing-agent

Length of output: 723


🏁 Script executed:

#!/bin/bash

echo "=== Reading the actual file to confirm import ==="
cat -n src/tools/planning/todo-write.ts | head -20

echo ""
echo "=== Searching for all .txt file imports (better pattern) ==="
rg 'from.*\.txt' -A 2 -B 2

echo ""
echo "=== Checking if todo-write.txt file exists ==="
fd -n 'todo-write.txt'

echo ""
echo "=== Checking package.json for any ts config or type handling ==="
cat package.json | head -50

Repository: minpeter/code-editing-agent

Length of output: 7129


🏁 Script executed:

#!/bin/bash

echo "=== Searching for any declare statements related to modules ==="
rg 'declare module' --type ts

echo ""
echo "=== Checking for vite.config or bun config ==="
fd -e 'config.ts' -e 'config.js' -e 'bunfig.toml'

echo ""
echo "=== Checking if there are any .d.ts files anywhere ==="
find . -name "*.d.ts" -type f 2>/dev/null | head -20

echo ""
echo "=== Checking vite/bun configuration files ==="
cat bunfig.toml 2>/dev/null || echo "No bunfig.toml found"

echo ""
echo "=== Looking for any type declaration patterns ==="
rg 'types.*txt' --type ts

echo ""
echo "=== Checking if .txt files actually exist ==="
find . -name "*.txt" -path "*/src/*" | head -10

Repository: minpeter/code-editing-agent

Length of output: 758


.txt 파일 임포트에 대한 TypeScript 타입 선언이 없습니다

프로젝트에서 10개 이상의 파일이 .txt 파일을 임포트하지만, declare module "*.txt" 같은 앰비언트 모듈 선언이 없고 .d.ts 타입 정의 파일도 없습니다. tsconfig.json에서 strict: true가 설정되어 있기 때문에 tsc --noEmit 실행 시 타입 오류가 발생합니다.

Bun은 런타임에서 .txt 파일 임포트를 문자열로 처리하므로 기능적으로는 동작하지만, TypeScript 타입 안정성을 위해 다음 중 하나를 구현하세요:

  • src/types.d.ts 파일 생성 후 declare module "*.txt" { const content: string; export default content; } 추가
  • 또는 tsconfig의 skipLibCheck를 조정하여 타입 검사 정책 재검토
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/planning/todo-write.ts` at line 7, The project imports .txt files
like TODO_WRITE_DESCRIPTION causing TypeScript errors under strict mode; add an
ambient module declaration for "*.txt" so imports are typed as strings (create a
.d.ts with declare module "*.txt" { const content: string; export default
content; }) and commit it, or alternatively adjust tsconfig settings (e.g.,
relax strict or skipLibCheck) if you intentionally want to change type-checking
policy; update references to TODO_WRITE_DESCRIPTION remain unchanged after
adding the declaration.

minpeter and others added 9 commits February 23, 2026 15:53
Automatically translate non-English prompts before model calls while preserving original text for display/history and adding translation status feedback in CLI.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Include all currently modified files that were left out of the previous translation-focused commit.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Use the same LINE#HASH | content shape across stale mismatch snippets to reduce output format variance for model-driven edit retries.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 14

♻️ Duplicate comments (1)
src/commands/translate.ts (1)

1-14: LGTM!

이전 리뷰에서 지적된 createTranslateCommand 누락 문제가 해결되었습니다. createToggleCommand 팩토리 패턴을 올바르게 따르고 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/translate.ts` around lines 1 - 14, The createTranslateCommand
implementation is correct and follows the createToggleCommand factory pattern;
no changes required—keep the exported createTranslateCommand function that uses
agentManager.isTranslationEnabled and agentManager.setTranslationEnabled and
returns the toggle with featureName "Translation" and the provided
enabled/disabled messages.
🧹 Nitpick comments (26)
src/tools/execute/shell-interact.test.ts (1)

119-125: result.success 어서션 누락 및 기존 테스트와의 중복

이 테스트 블록은 같은 describe 내 다른 테스트들과 달리 result.success 검증을 생략하고 있습니다. 또한 Line 88의 "returns generic guidance for plain text" 테스트(interact("ls -la"))와 입력 경로가 동일하고, Line 92에서 이미 "No retained terminal context exists" 부분 문자열을 검증하고 있어 일부 중복이 발생합니다.

♻️ 제안: `result.success` 추가 및 기존 테스트와 중복 검증 정리

현재 이 테스트의 고유 가치는 전체 문장의 정확한 고정에 있습니다. 아래와 같이 result.success 를 추가하고 테스트 제목을 그 의도가 더 명확히 드러나도록 수정하는 것을 권장합니다:

-    it("explains no retained terminal context exists", async () => {
+    it("contains exact no-retained-context sentence in generic guidance", async () => {
       const result = await interact("any command");
 
+      expect(result.success).toBe(true);
       expect(result.output).toContain(
         "No retained terminal context exists. Each shell_execute command runs independently."
       );
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/execute/shell-interact.test.ts` around lines 119 - 125, Add a
missing success assertion and make this test uniquely verify the exact fixed
sentence: in the test that calls interact("any command") add
expect(result.success).toBe(true), rename the it title to indicate it verifies
the full retained-terminal-context sentence (e.g., "explains full retained
terminal context sentence"), and assert that result.output contains the entire
exact sentence "No retained terminal context exists. Each shell_execute command
runs independently."; to avoid duplication remove the overlapping substring
assertion from the other test that calls interact("ls -la") (the "returns
generic guidance for plain text" test) so the generic test stays broad while
this one verifies the full sentence.
src/interaction/tool-loop-control.test.ts (1)

17-21: normalizeFinishReason의 케이스 정규화 및 공백 처리 경계 케이스 테스트 누락

normalizeFinishReason은 내부적으로 .trim().toLowerCase()를 수행하지만, 이 동작을 직접 검증하는 테스트가 없습니다. 대문자 입력("TOOL_CALLS")이나 공백이 포함된 입력(" tool_calls ")에 대한 테스트도 추가하면 구현의 신뢰성을 높일 수 있습니다.

또한 canonical 형식("tool-calls")이 변환 없이 그대로 반환되는지에 대한 직접 검증도 없습니다.

🧪 추가 테스트 케이스 제안
  it("normalizes provider-specific tool finish reasons", () => {
    expect(normalizeFinishReason("tool_calls")).toBe("tool-calls");
    expect(normalizeFinishReason("tool_use")).toBe("tool-calls");
    expect(normalizeFinishReason("function_call")).toBe("tool-calls");
+   // case-insensitive normalization
+   expect(normalizeFinishReason("TOOL_CALLS")).toBe("tool-calls");
+   expect(normalizeFinishReason("Tool_Use")).toBe("tool-calls");
+   // whitespace trimming
+   expect(normalizeFinishReason("  tool_calls  ")).toBe("tool-calls");
+   // canonical pass-through
+   expect(normalizeFinishReason("tool-calls")).toBe("tool-calls");
+   expect(normalizeFinishReason("stop")).toBe("stop");
  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/interaction/tool-loop-control.test.ts` around lines 17 - 21, Add unit
tests to verify normalizeFinishReason's trimming and case-normalization and that
canonical values pass through unchanged: add expectations that inputs like "
TOOl_calls ", "TOOL_CALLS", and " tool_calls " normalize to "tool-calls", and
that calling normalizeFinishReason("tool-calls") returns "tool-calls" unchanged;
reference the test suite in tool-loop-control.test.ts and the
normalizeFinishReason function to locate where to add these assertions.
src/context/skills.ts (2)

64-70: ELOOP를 예상 오류 코드 집합에 추가하는 것을 고려하세요.

loadV2Skills 내의 realpath 호출(Line 330)은 심볼릭 링크 루프 시 ELOOP를 발생시킬 수 있습니다. 현재 ELOOPEXPECTED_SKILL_IO_ERROR_CODES에 포함되지 않아, 심볼릭 링크 루프가 있는 환경에서 warnUnexpectedSkillError("loadV2Skills:realpath", error)가 불필요하게 호출되어 노이즈성 경고가 발생합니다. 코드는 skill.dirPath로 안전하게 폴백되므로 동작에는 문제없지만, 경고 메시지가 사용자에게 혼란을 줄 수 있습니다.

♻️ ELOOP 추가 제안
 const EXPECTED_SKILL_IO_ERROR_CODES = new Set([
   "EACCES",
   "EISDIR",
+  "ELOOP",
   "ENOENT",
   "ENOTDIR",
   "EPERM",
 ]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/context/skills.ts` around lines 64 - 70, Add "ELOOP" to the
EXPECTED_SKILL_IO_ERROR_CODES set to suppress noisy warnings when realpath in
loadV2Skills encounters a symbolic link loop; update the set that currently
contains "EACCES","EISDIR","ENOENT","ENOTDIR","EPERM" so that ELOOP is treated
as an expected IO error and thus won't trigger
warnUnexpectedSkillError("loadV2Skills:realpath", error) (the code will still
safely fall back to skill.dirPath).

72-82: ?? null 분기가 도달 불가능하며, 타입 단언을 개선할 수 있습니다.

Line 77의 조건 typeof (error as NodeJS.ErrnoException).code === "string"이 참인 경우, .code는 이미 string으로 보장됩니다. 따라서 Line 79의 ?? null은 절대 실행되지 않는 데드 코드입니다. 또한 NodeJS.ErrnoException으로 캐스팅하는 것보다 { code: unknown } / { code: string } 같은 최소 타입을 사용하면 코딩 가이드라인의 "타입 단언 대신 타입 내로잉 활용" 원칙에 더 부합합니다.

♻️ 타입 단언 및 데드코드 개선 제안
 function getErrnoCode(error: unknown): string | null {
   if (
     typeof error === "object" &&
     error !== null &&
     "code" in error &&
-    typeof (error as NodeJS.ErrnoException).code === "string"
+    typeof (error as { code: unknown }).code === "string"
   ) {
-    return (error as NodeJS.ErrnoException).code ?? null;
+    return (error as { code: string }).code;
   }
   return null;
 }

As per coding guidelines, "Leverage TypeScript's type narrowing instead of type assertions."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/context/skills.ts` around lines 72 - 82, The getErrnoCode function
contains dead code (the "?? null" is unreachable) and uses broad
NodeJS.ErrnoException assertions; change to narrow using the 'in' check and a
local minimal type for safer narrowing: inside getErrnoCode confirm typeof error
=== "object" && error !== null && "code" in error, then read const code = (error
as { code: unknown }).code and if typeof code === "string" return code;
otherwise return null, removing the unnecessary nullish coalescing.
src/tools/modify/write-file.ts (1)

27-29: catch 블록의 existed = false 할당이 중복

existed는 이미 false로 초기화되어 있으므로 catch 블록에서 다시 할당할 필요가 없습니다.

♻️ 제안된 정리
  let existed = false;
  try {
    await stat(path);
    existed = true;
- } catch {
-   existed = false;
- }
+ } catch {}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/modify/write-file.ts` around lines 27 - 29, The catch block in
src/tools/modify/write-file.ts redundantly sets the already-initialized boolean
existed = false; remove the assignment inside the catch and leave the catch to
handle errors (or rethrow/log as appropriate); locate the try/catch that
references the existed variable and simply eliminate the `existed = false;` line
so the initial value is relied upon and no duplicate assignment remains.
src/tools/modify/write-file.test.ts (1)

43-58: 덮어쓰기 테스트에서 메타데이터 양성 검증 누락

"overwrites existing file" 테스트는 result"new content"를 포함하지 않는다는 부정 검증만 있고, bytes: / lines: 등의 양성 메타데이터 검증이 없습니다. 동일 describe 블록의 다른 테스트들과 일관성이 없으며, 잘못된 출력 형식도 이 테스트를 통과할 수 있습니다.

♻️ 메타데이터 검증 보완 제안
  expect(result).toContain("OK - overwrote existing.txt");
+ expect(result).toContain("bytes:");
+ expect(result).toContain("lines: 1");
  expect(result).not.toContain("new content");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/modify/write-file.test.ts` around lines 43 - 58, The "overwrites
existing file and indicates action" test calls executeWriteFile and only asserts
the absence of the actual file content in result; add positive metadata
assertions to match the other tests by checking that result contains the
expected metadata lines (e.g. "bytes:" and "lines:" and the "OK - overwrote
existing.txt" message). Update the test for the result variable returned from
executeWriteFile to assert result.includes("bytes:") and
result.includes("lines:") (and any other metadata your suite uses) so the test
verifies correct metadata output while still confirming the file on disk equals
newContent via readFileSync.
src/tools/explore/glob.ts (1)

16-29: insertTopByMtime의 인덱스 기반 for 루프를 .findIndex()로 교체 권장

코딩 가이드라인에 따라 인덱스 기반 for 루프보다 배열 메서드를 선호합니다. .findIndex()를 사용하면 삽입 위치를 더 간결하게 표현할 수 있습니다.

♻️ 제안된 리팩터
 function insertTopByMtime(list: FileWithMtime[], item: FileWithMtime): void {
-  let insertAt = list.length;
-  for (let i = 0; i < list.length; i++) {
-    if (item.mtime.getTime() > list[i].mtime.getTime()) {
-      insertAt = i;
-      break;
-    }
-  }
-
-  list.splice(insertAt, 0, item);
+  const found = list.findIndex(
+    (existing) => item.mtime.getTime() > existing.mtime.getTime()
+  );
+  list.splice(found === -1 ? list.length : found, 0, item);
   if (list.length > MAX_RESULTS) {
     list.pop();
   }
 }

As per coding guidelines, **/*.{ts,tsx,js,jsx}: "Prefer for...of loops over .forEach() and indexed for loops."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/explore/glob.ts` around lines 16 - 29, Replace the index-based for
loop in insertTopByMtime with an array method: use list.findIndex to compute
insertAt as the first index where item.mtime.getTime() >
list[i].mtime.getTime(), and if findIndex returns -1 set insertAt = list.length;
then splice the item and trim with MAX_RESULTS as before; update references to
list and item within insertTopByMtime only.
src/tools/utils/safety-utils.ts (2)

360-368: 인덱스 기반 for 루프를 for...of로 교체 권장

bytes.subarray(0, result.bytesRead)for...of로 순회하면 동일한 로직을 더 간결하게 표현할 수 있습니다.

♻️ 제안된 리팩터
-   let nonPrintableCount = 0;
-   for (let i = 0; i < result.bytesRead; i++) {
-     const value = bytes[i];
-     if (value === 0) {
-       return true;
-     }
-
-     if (value < 9 || (value > 13 && value < 32)) {
-       nonPrintableCount++;
-     }
-   }
+   let nonPrintableCount = 0;
+   for (const value of bytes.subarray(0, result.bytesRead)) {
+     if (value === 0) {
+       return true;
+     }
+     if (value < 9 || (value > 13 && value < 32)) {
+       nonPrintableCount++;
+     }
+   }

As per coding guidelines, **/*.{ts,tsx,js,jsx}: "Prefer for...of loops over .forEach() and indexed for loops."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/utils/safety-utils.ts` around lines 360 - 368, The loop that
iterates bytes using an index (for (let i = 0; i < result.bytesRead; i++))
should be refactored to iterate over bytes.subarray(0, result.bytesRead) with a
for...of loop to match project style; update the loop in the function using
variables bytes, result.bytesRead and nonPrintableCount so you read each value
directly (const value) and preserve the existing checks (value === 0 and
non-printable range) and early return behavior, ensuring behavior and
nonPrintableCount updates remain identical.

457-469: isErrnoException 타입 가드를 재사용하지 않고 인라인 체크 반복 (DRY 위반)

77~79번 줄에 isErrnoException 타입 가드가 이미 정의되어 있는데, checkFileStatGuards의 catch 블록에서는 동일한 typeof error === "object" && error !== null && "code" in error 체크를 인라인으로 반복하며 as NodeJS.ErrnoException 단언까지 추가합니다.

♻️ 제안된 리팩터
  } catch (error) {
-   if (
-     typeof error === "object" &&
-     error !== null &&
-     "code" in error &&
-     (error as NodeJS.ErrnoException).code !== "ENOENT"
-   ) {
+   if (isErrnoException(error) && error.code !== "ENOENT") {
      return {
        allowed: false,
        reason: `Unable to inspect file metadata for '${filePath}'.`,
      };
    }
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/utils/safety-utils.ts` around lines 457 - 469, In the catch block
of checkFileStatGuards replace the inline error checks and the explicit (error
as NodeJS.ErrnoException) assertion with the existing isErrnoException type
guard: call isErrnoException(error) to detect errno errors and only return the
disallowed result when the guard indicates a non-ENOENT errno; remove the
duplicated typeof/null/"code" checks to DRY up the code and rely on
isErrnoException for correct typing and safety while preserving the same
returned object that references filePath.
src/tools/utils/execute/process-manager.ts (3)

178-211: appendChunkflushDecoder에 동일한 버퍼 트리밍 로직이 중복됩니다.

두 함수 모두 bufferedOutput에 문자열을 추가한 뒤 동일한 trim 블록을 실행합니다. 내부 헬퍼로 추출하면 중복을 제거할 수 있습니다.

♻️ 리팩토링 제안
+    const maybeTrimBuffer = (): void => {
+      if (Buffer.byteLength(bufferedOutput, "utf-8") > MAX_IN_MEMORY_OUTPUT_BYTES) {
+        const trimmed = trimToLastBytes(bufferedOutput, TRIMMED_BUFFER_TARGET_BYTES);
+        bufferedOutput = trimmed.text;
+        droppedBytes += trimmed.droppedBytes;
+      }
+    };
+
     const appendChunk = (chunk: Buffer, decoder: TextDecoder): void => {
       const decoded = decoder.decode(chunk, { stream: true });
       bufferedOutput += decoded;
-      if (
-        Buffer.byteLength(bufferedOutput, "utf-8") > MAX_IN_MEMORY_OUTPUT_BYTES
-      ) {
-        const trimmed = trimToLastBytes(
-          bufferedOutput,
-          TRIMMED_BUFFER_TARGET_BYTES
-        );
-        bufferedOutput = trimmed.text;
-        droppedBytes += trimmed.droppedBytes;
-      }
+      maybeTrimBuffer();
       onChunk?.(decoded);
     };

     const flushDecoder = (decoder: TextDecoder): void => {
       const remaining = decoder.decode();
       if (!remaining) {
         return;
       }
       bufferedOutput += remaining;
-      if (
-        Buffer.byteLength(bufferedOutput, "utf-8") > MAX_IN_MEMORY_OUTPUT_BYTES
-      ) {
-        const trimmed = trimToLastBytes(
-          bufferedOutput,
-          TRIMMED_BUFFER_TARGET_BYTES
-        );
-        bufferedOutput = trimmed.text;
-        droppedBytes += trimmed.droppedBytes;
-      }
+      maybeTrimBuffer();
       onChunk?.(remaining);
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/utils/execute/process-manager.ts` around lines 178 - 211, The logic
that appends text to bufferedOutput and enforces MAX_IN_MEMORY_OUTPUT_BYTES is
duplicated in appendChunk and flushDecoder; extract this into a small helper
(e.g., appendAndTrim or updateBufferedOutput) that accepts the incoming
text/decoded string and the decoder result, appends it to bufferedOutput, runs
trimToLastBytes when Buffer.byteLength exceeds MAX_IN_MEMORY_OUTPUT_BYTES,
updates bufferedOutput and droppedBytes accordingly, and returns the final text
to pass to onChunk; then replace the duplicated blocks in appendChunk and
flushDecoder to call that helper and invoke onChunk with its result.

227-252: finish.then().catch() 체인 — 코딩 가이드라인에 따라 async/await로 리팩토링을 권장합니다.

코딩 가이드라인은 Promise 체인 대신 async/await 사용을 명시합니다. finishasync 함수로 전환하고 try/catch를 사용하면 가독성이 향상됩니다. 이벤트 핸들러에서는 void finish(code)로 호출하면 됩니다.

♻️ async/await 리팩토링 제안
-    const finish = (code: number | null): void => {
+    const finish = async (code: number | null): Promise<void> => {
       if (settled) {
         return;
       }
       settled = true;

       clearTimeout(timeoutHandle);
       if (signal) {
         signal.removeEventListener("abort", abortHandler);
       }

       flushDecoder(stdoutDecoder);
       flushDecoder(stderrDecoder);

       const sanitizedOutput = sanitizeOutput(bufferedOutput);
       const withDroppedPrefix =
         droppedBytes > 0
           ? `[... ${droppedBytes} bytes omitted before completion due to output volume ...]\n${sanitizedOutput}`
           : sanitizedOutput;
-      truncateOutput(withDroppedPrefix)
-        .then((truncatedOutput) => {
-          resolve({
-            exitCode: resolveExitCode(code, timedOut, cancelled, spawnFailed),
-            output: truncatedOutput.text,
-            cancelled,
-            timedOut,
-          });
-        })
-        .catch((error) => {
-          const errorMessage =
-            error instanceof Error
-              ? error.message
-              : "Failed to truncate output.";
-          resolve({
-            exitCode: resolveExitCode(code, timedOut, cancelled, spawnFailed),
-            output: `${withDroppedPrefix}\n${errorMessage}`,
-            cancelled,
-            timedOut,
-          });
-        });
+      try {
+        const truncatedOutput = await truncateOutput(withDroppedPrefix);
+        resolve({
+          exitCode: resolveExitCode(code, timedOut, cancelled, spawnFailed),
+          output: truncatedOutput.text,
+          cancelled,
+          timedOut,
+        });
+      } catch (error) {
+        const errorMessage =
+          error instanceof Error ? error.message : "Failed to truncate output.";
+        resolve({
+          exitCode: resolveExitCode(code, timedOut, cancelled, spawnFailed),
+          output: `${withDroppedPrefix}\n${errorMessage}`,
+          cancelled,
+          timedOut,
+        });
+      }
     };

이벤트 핸들러에서 반환된 프로미스를 명시적으로 처리합니다:

     child.on("close", (code) => {
-      finish(code);
+      void finish(code);
     });

As per coding guidelines: "Use async/await syntax instead of promise chains for better readability."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/utils/execute/process-manager.ts` around lines 227 - 252, The
promise chain inside finish that calls sanitizeOutput, builds withDroppedPrefix,
then uses truncateOutput(...).then(...).catch(...) should be refactored to
async/await: mark finish as async, await
truncateOutput(sanitizedOutputOrWithDroppedPrefix) inside a try block, call
resolve(...) with the awaited truncatedOutput, and handle errors in a catch
block by resolving with the fallback output (using error instanceof Error ?
error.message : "Failed to truncate output."); keep the same use of
resolveExitCode(code, timedOut, cancelled, spawnFailed) and fields (output,
cancelled, timedOut), and update event handlers that invoke finish to call it as
void finish(code) so returned promises are explicitly ignored.

267-271: 불필요한 타입 어서션 제거

child.on("error", ...) 이벤트 리스너의 error 파라미터는 @types/node에 의해 이미 Error 타입으로 정의되어 있습니다. as Error 캐스팅 없이 error.message에 직접 접근할 수 있습니다.

♻️ 수정 제안
     child.on("error", (error) => {
       spawnFailed = true;
-      bufferedOutput += `${(error as Error).message}\n`;
+      bufferedOutput += `${error.message}\n`;
       finish(null);
     });

코딩 가이드라인에 따라 불필요한 타입 어서션 대신 TypeScript의 타입 시스템을 활용합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/utils/execute/process-manager.ts` around lines 267 - 271, The error
handler attached to child via child.on("error", ...) unnecessarily casts the
error to Error; remove the redundant type assertion and access error.message
directly (e.g., change bufferedOutput += `${(error as Error).message}\n`; to use
error.message), leaving spawnFailed and finish(...) behavior intact; ensure the
handler still references spawnFailed, bufferedOutput, and finish as before and
let TypeScript's `@types/node` typing provide the Error type for the error
parameter.
src/tools/utils/execute/output-handler.ts (1)

31-51: UUID 기반 EEXIST 재시도 로직은 사실상 도달 불가능하며, 에러 코드 체크 패턴이 중복됩니다.

두 가지 개선 포인트가 있습니다.

  1. UUID v4 는 122비트의 무작위성을 제공하므로, 동일한 tempPath가 생성될 확률은 ~5.4×10⁻³⁷에 불과합니다. 3회 재시도 루프는 현실적으로 도달할 수 없는 코드입니다.
  2. Lines 38-45의 ErrnoException 코드 확인 패턴은 process-manager.tshasErrnoCode 헬퍼 함수(lines 29-36)와 동일합니다. 공유 유틸리티로 추출하면 DRY 원칙을 준수할 수 있습니다.
♻️ 단순화 제안
-async function persistFullOutput(text: string): Promise<string> {
-  for (let attempt = 0; attempt < 3; attempt += 1) {
-    const tempPath = join(tmpdir(), `cea-output-${randomUUID()}.txt`);
-    try {
-      await writeFile(tempPath, text, { encoding: "utf-8", flag: "wx" });
-      return tempPath;
-    } catch (error) {
-      if (
-        typeof error === "object" &&
-        error !== null &&
-        "code" in error &&
-        (error as NodeJS.ErrnoException).code === "EEXIST"
-      ) {
-        continue;
-      }
-      throw error;
-    }
-  }
-
-  throw new Error("Failed to persist truncated output.");
-}
+async function persistFullOutput(text: string): Promise<string> {
+  const tempPath = join(tmpdir(), `cea-output-${randomUUID()}.txt`);
+  await writeFile(tempPath, text, { encoding: "utf-8", flag: "wx" });
+  return tempPath;
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/utils/execute/output-handler.ts` around lines 31 - 51, The
persistFullOutput function's UUID collision retry loop is unnecessary and its
errno-code check duplicates the existing hasErrnoCode helper; simplify by
removing the 3-attempt loop and perform a single write attempt using the
generated tempPath, and on failure use the shared hasErrnoCode(error, "EEXIST")
helper (from process-manager.ts) to detect EEXIST, otherwise rethrow the error;
also update the final thrown error message to reflect the function name (e.g.,
"Failed to persist full output.").
src/interaction/pi-tui-stream-renderer.ts (2)

100-116: env.ts의 타입 지정 환경 변수를 우회하고 있습니다 — 권장 리팩터링

isRawToolIoEnabledByEnv()process.env.DEBUG_SHOW_RAW_TOOL_IO를 직접 읽어 src/env.tscreateEnv 파이프라인(유효성 검사, emptyStringAsUndefined: true, z.stringbool() 강제 변환)을 건너뜁니다. 또한 isTruthyEnvFlagz.stringbool()과 중복되는 파싱 로직입니다.

env 객체를 그대로 사용하면 두 함수가 모두 불필요해집니다:

♻️ 제안 리팩터링
-const isTruthyEnvFlag = (value: string | undefined): boolean => {
-  if (!value) {
-    return false;
-  }
-
-  const normalized = value.trim().toLowerCase();
-  return (
-    normalized === "1" ||
-    normalized === "true" ||
-    normalized === "yes" ||
-    normalized === "on"
-  );
-};
-
-const isRawToolIoEnabledByEnv = (): boolean => {
-  return isTruthyEnvFlag(process.env.DEBUG_SHOW_RAW_TOOL_IO);
-};

그리고 사용 위치(line 1882)를:

-    showRawToolIo: options.showRawToolIo ?? isRawToolIoEnabledByEnv(),
+    showRawToolIo: options.showRawToolIo ?? env.DEBUG_SHOW_RAW_TOOL_IO,

envsrc/env.ts에서 import해야 합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/interaction/pi-tui-stream-renderer.ts` around lines 100 - 116, The helper
functions isTruthyEnvFlag and isRawToolIoEnabledByEnv bypass the validated env
pipeline; remove those functions and instead import and use the created env
object (from src/env.ts) and its DEBUG_SHOW_RAW_TOOL_IO flag wherever used
(e.g., at the usage site referencing DEBUG_SHOW_RAW_TOOL_IO around line 1882).
Replace direct process.env access with env.DEBUG_SHOW_RAW_TOOL_IO (which already
normalizes/validates via z.stringbool/emptyStringAsUndefined), and delete the
now-unused isTruthyEnvFlag helper to avoid duplicate parsing logic.

1019-1025: parsePartialJson이 매 청크마다 전체 누적 버퍼를 재파싱합니다 (O(n²))

this.inputBuffer가 커질수록 parsePartialJson(this.inputBuffer) 호출 비용이 선형 증가합니다. syncToolInputToView의 배칭 덕분에 호출 빈도가 줄어들어 실제로는 허용 가능한 수준이지만, 대형 도구 입력에서는 성능 저하가 발생할 수 있습니다.

AI SDK 내부에서도 동일한 parsePartialJson(buffer) 패턴이 사용되며, 50KB 도구 호출을 12자 청크로 스트리밍하면 약 4,000번의 전체 재파싱이 발생한다고 알려져 있습니다. 도구 입력이 통상적으로 소형 JSON임을 감안하면 즉각적인 문제는 아닙니다만, 향후 개선 시 마지막으로 성공한 파싱 결과를 캐싱하거나 점진적 파서를 도입하는 방안을 고려할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/interaction/pi-tui-stream-renderer.ts` around lines 1019 - 1025,
appendInputChunk currently reparses the entire this.inputBuffer on every chunk
via parsePartialJson, causing O(n²) cost for large inputs; change
appendInputChunk (and related state: this.inputBuffer, parsedInput) to avoid
full reparse by caching the last successful parse boundary or using an
incremental parser: maintain a cursor/offset for the last-validated prefix
(e.g., lastValidIndex) and only feed parsePartialJson the new suffix or switch
to an incremental parse API so you update parsedInput incrementally instead of
reparsing the whole buffer each call.
src/interaction/pi-tui-stream-renderer.test.ts (1)

229-238: findLastIndex를 사용하면 더 간결하게 표현할 수 있습니다

역방향 탐색 루프는 Array.findLastIndex로 대체 가능합니다.

♻️ 제안 리팩터링
-    let previousNonEmptyLineIndex = -1;
-    for (let i = reasoningLineIndex - 1; i >= 0; i -= 1) {
-      if (lines[i].trim().length > 0) {
-        previousNonEmptyLineIndex = i;
-        break;
-      }
-    }
-
-    expect(previousNonEmptyLineIndex).toBeGreaterThan(-1);
-    expect(reasoningLineIndex).toBe(previousNonEmptyLineIndex + 1);
+    const previousNonEmptyLineIndex = lines
+      .slice(0, reasoningLineIndex)
+      .findLastIndex((line) => line.trim().length > 0);
+
+    expect(previousNonEmptyLineIndex).toBeGreaterThan(-1);
+    expect(reasoningLineIndex).toBe(previousNonEmptyLineIndex + 1);

As per coding guidelines, prefer for...of loops over indexed for loops; here findLastIndex eliminates the indexed loop entirely.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/interaction/pi-tui-stream-renderer.test.ts` around lines 229 - 238,
Replace the manual reverse indexed loop that sets previousNonEmptyLineIndex by
using Array.prototype.findLastIndex on the lines array to find the last
non-empty trimmed line before reasoningLineIndex; specifically, compute
previousNonEmptyLineIndex = lines.slice(0, reasoningLineIndex).findLastIndex(l
=> l.trim().length > 0) (or use a direct findLastIndex with index check) and
then keep the two expects
(expect(previousNonEmptyLineIndex).toBeGreaterThan(-1);
expect(reasoningLineIndex).toBe(previousNonEmptyLineIndex + 1)); update
references to previousNonEmptyLineIndex, reasoningLineIndex, and lines
accordingly.
src/context/translation.test.ts (2)

111-185: 동일 텍스트 반환 시 처리 경로에 대한 테스트 케이스 누락

translation.ts line 103의 translatedText === text 분기(모델이 입력과 동일한 텍스트를 반환하는 경우)가 테스트되지 않았습니다. 예를 들어 모델이 비어 있지 않고 원문과 동일한 텍스트를 돌려줄 때 translated: false가 올바르게 반환되는지 검증하는 케이스가 있으면 좋겠습니다.

✅ 제안 테스트 케이스
it("returns translated: false when model returns identical text", async () => {
  translatedOutput = "src/foo.ts 파일을 수정해줘"; // same as input

  const result = await translateToEnglish(
    "src/foo.ts 파일을 수정해줘",
    createAgentManagerStub()
  );

  expect(result).toEqual({
    translated: false,
    text: "src/foo.ts 파일을 수정해줘",
  });
  expect(generateTextCallCount).toBe(1);
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/context/translation.test.ts` around lines 111 - 185, Add a test that
covers the translateToEnglish branch where the model returns identical text so
the function sets translated: false; specifically, in the translation.test.ts
suite add a case that sets the stubbed model output variable (translatedOutput)
to the exact input string (e.g., "src/foo.ts 파일을 수정해줘"), calls
translateToEnglish with createAgentManagerStub(), and asserts the result equals
{ translated: false, text: "<same text>" } and that generateTextCallCount
increments to 1 to exercise the translatedText === text path in
translateToEnglish.

101-109: 시스템 프롬프트 길이 검증이 취약합니다

toBeLessThan(500)은 정당한 이유로 제약 조건을 추가하더라도 즉시 실패하는 취약한 어서션입니다. 구체적인 길이 상한 대신 포함해야 할 키워드 검사만으로 간결성을 검증하거나, 이 어서션 자체를 제거하는 것을 고려해 주세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/context/translation.test.ts` around lines 101 - 109, The test's strict
length check on TRANSLATION_SYSTEM_PROMPT is brittle; remove the
toBeLessThan(500) assertion and instead verify conciseness via keyword presence
only (keep the expect calls for "Translate", "Preserve", "file paths", "Return
only") or replace the numeric limit with a more meaningful check such as
verifying the prompt does not contain prohibited verbose phrases or that it
matches a configurable max length constant; update the test around
TRANSLATION_SYSTEM_PROMPT in translation.test.ts accordingly.
src/context/translation.ts (1)

102-108: translatedText === text 비교에서 대칭적 trim 누락

translatedTextresult.text.trim()으로 공백이 제거되어 있지만 text는 원본 그대로입니다. 비(非)ASCII 입력에 후행 공백이 있을 경우("café " 등), 모델이 동일한 내용을 반환해도 일치 비교가 실패하여 의도치 않게 translated: true가 반환됩니다.

♻️ 제안 수정
-    if (translatedText.length === 0 || translatedText === text) {
+    if (translatedText.length === 0 || translatedText === text.trim()) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/context/translation.ts` around lines 102 - 108, The equality check uses
translatedText (result.text.trim()) but compares it to the untrimmed original
text, so trailing/leading whitespace on the input can wrongly mark translations
as changed; update the comparison in the translation logic (around the
translatedText variable in src/context/translation.ts, inside the function
handling result processing) to compare translatedText against the trimmed (and
ideally Unicode-normalized) original (e.g., use text.trim() or
text.trim().normalize('NFC') for both sides) so identical content with differing
whitespace or normalization is treated as untranslated.
src/agent.ts (1)

382-396: getTranslationModelConfig 호출마다 래핑된 모델 인스턴스를 재생성합니다

translateToEnglish가 호출될 때마다 createBaseModel이 실행되어 wrapLanguageModel과 미들웨어 체인이 새로 생성됩니다. 번역 설정(모델 ID, 프로바이더, reasoning 모드)이 변경되지 않는 한 동일한 config가 반복 생성되므로, translationReasoningMode가 마지막 결과와 동일하면 캐시된 값을 반환하도록 lazy initialization을 적용할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/agent.ts` around lines 382 - 396, getTranslationModelConfig currently
calls createBaseModel on every invocation which rebuilds
wrapLanguageModel/middleware; implement lazy caching inside the class by storing
the last used key (e.g., tuple of this.modelId, this.provider,
this.toolFallbackMode, translationReasoningMode) and a cached config object
(including wrappedModel and providerOptions). In getTranslationModelConfig
compare the current key to the cached key and return the cached config when they
match; only call createBaseModel and update the cached key+config when the key
differs, preserving maxOutputTokens = TRANSLATION_MAX_OUTPUT_TOKENS.
src/tools/utils/hashline/hashline.ts (1)

323-337: JSON.stringify 기반 중복 제거는 프로퍼티 순서에 암묵적으로 의존합니다

JSON.stringify(edit) 로 생성된 키는 객체의 프로퍼티 삽입 순서가 동일할 때만 의미론적으로 동등한 편집 항목을 중복으로 인식합니다. 현재는 parseReplaceEdit/parseAppendPrependEdit이 일관된 순서로 객체를 생성하므로 실질적 문제가 없지만, 외부 호출자가 임의 순서로 HashlineEdit 객체를 전달하면 중복 제거가 실패할 수 있습니다.

♻️ 더 안정적인 키 생성 방식 제안
-    const key = JSON.stringify(edit);
+    const key = [
+      edit.op,
+      edit.op === "replace" || edit.op === "append" || edit.op === "prepend"
+        ? `${edit.pos?.line ?? ""}#${edit.pos?.hash ?? ""}`
+        : "",
+      edit.op === "replace"
+        ? `${edit.end?.line ?? ""}#${edit.end?.hash ?? ""}`
+        : "",
+      (edit.lines ?? []).join("\x00"),
+    ].join("|");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/utils/hashline/hashline.ts` around lines 323 - 337, The
deduplication in buildAnnotatedEdits relies on JSON.stringify(edit) which can
differ for objects with identical semantics but different property insertion
orders; replace JSON.stringify usage with a deterministic serializer (e.g.,
implement a serializeEdit(edit) that picks the discriminant and concatenates the
canonical fields in a fixed order such as type, start, end, oldText, newText or
the specific fields for append/prepend edits) and use that string as the key
when checking seenEditKeys and adding to deduped to guarantee stable
deduplication regardless of property order.
src/tools/modify/edit-file.test.ts (1)

15-33: LINE_REF_REGEX_TEMPLATE이 호출할 때마다 새 RegExp 인스턴스를 생성합니다

lineNumbernumber 타입이므로 실제 ReDoS 위험은 없지만, 코딩 가이드라인은 동적 RegExp 생성보다 최상위 정규식 리터럴을 권장합니다. 해시 패턴 부분을 상수로 추출하고 라인 번호는 문자열 매칭으로 처리할 수 있습니다.

♻️ 리팩터링 제안
 const FILE_HASH_REGEX = /^file_hash:\s+([0-9a-f]{8})$/m;
-const LINE_REF_REGEX_TEMPLATE = (lineNumber: number): RegExp =>
-  new RegExp(`${lineNumber}#([ZPMQVRWSNKTXJBYH]{2})\\|`);
+const LINE_HASH_CAPTURE_REGEX = /(\d+)#([ZPMQVRWSNKTXJBYH]{2})\|/;

 function extractLineRef(readOutput: string, lineNumber: number): string {
-  const matched = readOutput.match(LINE_REF_REGEX_TEMPLATE(lineNumber));
-  if (!matched?.[1]) {
+  const prefix = `${lineNumber}#`;
+  for (const line of readOutput.split("\n")) {
+    if (!line.includes(prefix)) continue;
+    const matched = line.match(LINE_HASH_CAPTURE_REGEX);
+    if (matched && Number(matched[1]) === lineNumber) {
+      return `${lineNumber}#${matched[2]}`;
+    }
+  }
+  {
     throw new Error(`Failed to extract line reference for line ${lineNumber}`);
   }
-  return `${lineNumber}#${matched[1]}`;
 }

As per coding guidelines: "Use top-level regex literals instead of creating them in loops."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/modify/edit-file.test.ts` around lines 15 - 33,
LINE_REF_REGEX_TEMPLATE currently constructs a new RegExp on every call;
instead, pull the hash token pattern into a top-level constant (e.g.,
HASH_CODE_REGEX = /#([ZPMQVRWSNKTXJBYH]{2})\|/) and change extractLineRef to
avoid dynamic RegExp creation by locating the `${lineNumber}#` substring (or
splitting lines) then applying HASH_CODE_REGEX to the following slice to capture
the two-letter code; update extractLineRef to return `${lineNumber}#${captured}`
and remove LINE_REF_REGEX_TEMPLATE.
src/entrypoints/cli.ts (2)

1271-1307: headless.ts와 동일한 파싱 유틸리티 함수 중복

parseReasoningCliOption (lines 1271–1297), parseTranslateCliOption (lines 1299–1307), parseProviderArg (lines 1221–1229)는 src/entrypoints/headless.ts의 동명 함수와 내용이 완전히 동일합니다. 공유 유틸리티 모듈로 추출하는 것을 권장합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/entrypoints/cli.ts` around lines 1271 - 1307, The three duplicated CLI
parsing functions (parseReasoningCliOption, parseTranslateCliOption,
parseProviderArg) should be extracted into a shared utility module and imported
from both src/entrypoints/cli.ts and src/entrypoints/headless.ts; create a new
file (e.g., a parsing utils module) that exports parseReasoningCliOption,
parseTranslateCliOption, parseProviderArg and any dependencies they use (e.g.,
DEFAULT_REASONING_MODE, parseReasoningMode), replace the duplicated definitions
in cli.ts and headless.ts with imports of those exported symbols, and ensure
tests/usage still reference the same function names so behavior is unchanged.

1693-1730: isNonEnglish 중복 호출 및 복잡한 부정 조건 정리 권장

두 가지 가독성 개선 사항이 있습니다:

  1. isNonEnglish(trimmed) 호출이 line 1694와 line 1719에서 중복됩니다. shouldShowTranslationLoader가 내부 스코프에 갇혀 있어 재사용이 불가합니다.
  2. line 1719의 !(isNonEnglish(trimmed) && agentManager.isTranslationEnabled())는 코딩 가이드라인에서 요구하는 것처럼 명명된 변수로 추출하면 의도가 명확해집니다.

if (shouldShowTranslationLoader) 블록(lines 1695–1698, 1700–1702)도 하나로 합칠 수 있습니다.

♻️ 리팩터 제안
+    const inputIsNonEnglish = isNonEnglish(trimmed);
     if (agentManager.isTranslationEnabled()) {
-      const shouldShowTranslationLoader = isNonEnglish(trimmed);
-      if (shouldShowTranslationLoader) {
+      if (inputIsNonEnglish) {
         addUserMessage(ui.chatContainer, ui.markdownTheme, trimmed);
         ui.tui.requestRender();
-      }
-
-      if (shouldShowTranslationLoader) {
         ui.showLoader("Translating...");
       }
       // ...
     }

-    if (!(isNonEnglish(trimmed) && agentManager.isTranslationEnabled())) {
+    const wasTranslated = inputIsNonEnglish && agentManager.isTranslationEnabled();
+    if (!wasTranslated) {
       addUserMessage(ui.chatContainer, ui.markdownTheme, contentForModel);
     } else if (originalContent) {
       addTranslatedMessage(ui.chatContainer, ui.markdownTheme, contentForModel);
     }

As per coding guidelines: "Extract complex conditions into well-named boolean variables."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/entrypoints/cli.ts` around lines 1693 - 1730, Compute isNonEnglish once
into a local boolean (e.g., isNonEnglishInput = isNonEnglish(trimmed)) and
compute a named combined flag for translation activation (e.g.,
isTranslationActive = isNonEnglishInput && agentManager.isTranslationEnabled());
replace the duplicate calls to isNonEnglish(trimmed) with these variables; merge
the two if (shouldShowTranslationLoader) blocks into one around addUserMessage +
ui.showLoader + ui.requestRender and ui.clearStatus in the finally; call
translateToEnglish(trimmed, agentManager) only when isTranslationActive and
assign contentForModel/originalContent/translationError from its result;
simplify the final message logic to use isTranslationActive and originalContent
to decide between addUserMessage(addTranslatedMessage) and preserve the
addSystemMessage(…) when translationError exists so behavior is unchanged.
src/tools/explore/grep.test.ts (1)

14-14: 정적 분석 경고는 오탐 — 선택적 리팩터

HASHLINE_ALPHABET은 사용자 입력이 아닌 컴파일 타임 상수이므로 new RegExp(...) 에서 ReDoS가 발생할 수 없습니다. 결과 패턴 [ZPMQVRWSNKTXJBYH]{2} 는 복잡성이 제한되어 있어 경고는 오탐입니다.

필요하다면 hashline 모듈에서 해당 패턴을 미리 컴파일된 정적 리터럴로 내보내면 정적 분석 경고를 없앨 수 있습니다.

Also applies to: 49-51

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/explore/grep.test.ts` at line 14, 정적 문자열 HASHLINE_ALPHABET이 컴파일 타임
상수라 ReDoS 우려가 없는 오탐이므로 new RegExp(...) 호출 대신 미리 컴파일된 정적 리터럴 RegExp를 사용해 경고를
제거하세요: 변경 대상 식별자명은 HASHLINE_ALPHABET 및 이를 new RegExp로 감싸는 모든 위치(테스트 파일의 new
RegExp 호출, 관련 hashline 모듈의 내보냄 부분 등, 추가로 언급된 49-51 줄 범위)를 찾아 문자열 패턴을 정적 RegExp
리터럴로 바꾸거나 hashline 모듈에서 미리 컴파일된 RegExp를 export 하도록 리팩터링하세요.
src/entrypoints/headless.ts (1)

157-204: headless.tscli.ts 간 파싱 유틸리티 함수 중복

parseProviderArg (lines 157–165), parseTranslateCliOption (lines 167–176), parseReasoningCliOption (lines 178–204) 세 함수는 src/entrypoints/cli.ts의 lines 1221–1229, 1299–1307, 1271–1297과 각각 내용이 동일합니다.

공유 모듈(예: src/utils/cli-arg-parsers.ts)로 추출하여 양쪽에서 임포트하면 DRY 원칙을 지키고 향후 변경 시 두 파일을 함께 수정해야 하는 문제를 없앨 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/entrypoints/headless.ts` around lines 157 - 204, Extract the duplicated
parsing functions parseProviderArg, parseTranslateCliOption, and
parseReasoningCliOption into a single shared module (e.g., cli-arg-parsers) and
export them; update headless.ts and cli.ts to import these functions instead of
duplicating them, and also export/import any required types/constants used by
parseReasoningCliOption (ReasoningMode, DEFAULT_REASONING_MODE,
parseReasoningMode, ProviderType if needed) so the behavior remains identical;
finally remove the duplicate local implementations from both files and run type
checks to ensure imports and signatures match.
ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d022c4d and d9140e0.

📒 Files selected for processing (54)
  • AGENTS.md
  • benchmark/harbor_agent.py
  • benchmark/install-agent.sh.j2
  • src/AGENTS.md
  • src/agent-reasoning-default.test.ts
  • src/agent.test.ts
  • src/agent.ts
  • src/commands/translate.test.ts
  • src/commands/translate.ts
  • src/context/environment-context.ts
  • src/context/message-history.test.ts
  • src/context/message-history.ts
  • src/context/skills.ts
  • src/context/translation-integration.test.ts
  • src/context/translation.test.ts
  • src/context/translation.ts
  • src/entrypoints/AGENTS.md
  • src/entrypoints/cli.ts
  • src/entrypoints/headless.ts
  • src/env.ts
  • src/interaction/pi-tui-stream-renderer.test.ts
  • src/interaction/pi-tui-stream-renderer.ts
  • src/interaction/tool-loop-control.test.ts
  • src/interaction/tool-loop-control.ts
  • src/tools/execute/shell-execute.ts
  • src/tools/execute/shell-interact.test.ts
  • src/tools/explore/glob.test.ts
  • src/tools/explore/glob.ts
  • src/tools/explore/grep-files.txt
  • src/tools/explore/grep.test.ts
  • src/tools/explore/grep.ts
  • src/tools/explore/read-file.test.ts
  • src/tools/explore/read-file.ts
  • src/tools/explore/read-file.txt
  • src/tools/explore/safety-utils.ts
  • src/tools/index.ts
  • src/tools/modify/edit-file.test.ts
  • src/tools/modify/edit-file.ts
  • src/tools/modify/write-file.test.ts
  • src/tools/modify/write-file.ts
  • src/tools/utils/execute/format-utils.test.ts
  • src/tools/utils/execute/format-utils.ts
  • src/tools/utils/execute/noninteractive-wrapper.test.ts
  • src/tools/utils/execute/noninteractive-wrapper.ts
  • src/tools/utils/execute/output-handler.test.ts
  • src/tools/utils/execute/output-handler.ts
  • src/tools/utils/execute/process-manager.test.ts
  • src/tools/utils/execute/process-manager.ts
  • src/tools/utils/execute/shell-detection.test.ts
  • src/tools/utils/execute/shell-detection.ts
  • src/tools/utils/hashline/hashline.test.ts
  • src/tools/utils/hashline/hashline.ts
  • src/tools/utils/safety-utils.ts
  • src/utils/tools-manager.ts
💤 Files with no reviewable changes (2)
  • benchmark/harbor_agent.py
  • src/tools/explore/safety-utils.ts
✅ Files skipped from review due to trivial changes (2)
  • src/entrypoints/AGENTS.md
  • src/utils/tools-manager.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/tools/explore/grep-files.txt
  • src/tools/execute/shell-execute.ts
  • src/tools/explore/read-file.txt

Comment on lines +3 to +6
**Generated:** 2026-02-23 14:40 KST
**Commit:** 4671d67
**Branch:** main
**Mode:** update
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

커밋 해시 및 브랜치 메타데이터가 즉시 낡아집니다

**Commit:** 4671d67는 이 PR이 머지된 이후 첫 번째 커밋부터 정확하지 않은 정보가 됩니다. **Branch:** main도 현재 PR 브랜치(feat/reasoning-mode-friendli)와 일치하지 않습니다. 이 파일이 도구에 의해 생성된다면 CI 파이프라인에서 자동으로 갱신해야 하고, 그렇지 않다면 빠르게 낡아지는 메타데이터 블록 전체를 제거하는 것을 권장합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AGENTS.md` around lines 3 - 6, The autogenerated metadata block (timestamp,
the lines beginning with "**Commit:** 4671d67" and "**Branch:** main") will
quickly become stale; remove this entire metadata block from AGENTS.md or change
the generator to stop embedding mutable fields and instead inject dynamic values
at build time (or have CI update them). Specifically, remove the hard-coded
"**Commit:**" and "**Branch:**" entries (and the generated timestamp) from the
file OR modify the doc-generation step to populate those fields during CI so
they stay accurate.

Comment on lines +31 to +32
## WHERE TO LOOK
| Task | Location | Notes |
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

WHERE TO LOOK 및 CODE MAP 테이블 앞 빈 줄 누락

## WHERE TO LOOK(line 31)과 ## CODE MAP(line 42) 헤딩 바로 다음에 테이블이 시작되어 MD058 규칙을 위반합니다. (line 17 경고는 펜스 코드 블록 내부의 | 문자에 의한 오탐입니다.)

🔧 제안 수정
 ## WHERE TO LOOK
+
 | Task | Location | Notes |
 |------|----------|-------|
 ## CODE MAP
+
 | Symbol | Type | Location | Refs | Role |
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
## WHERE TO LOOK
| Task | Location | Notes |
## WHERE TO LOOK
| Task | Location | Notes |
|------|----------|-------|
Suggested change
## WHERE TO LOOK
| Task | Location | Notes |
## CODE MAP
| Symbol | Type | Location | Refs | Role |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AGENTS.md` around lines 31 - 32, Add a blank line after the headings "##
WHERE TO LOOK" and "## CODE MAP" so their following tables are separated from
the headings (fixing the MD058 violation); locate the headings in AGENTS.md and
insert an empty line directly after each heading (immediately after the "##
WHERE TO LOOK" and "## CODE MAP" lines).

Comment on lines +44 to +49
|--------|------|----------|------|------|
| `run` | function | `src/entrypoints/cli.ts:1553` | n/a | Main interactive session loop |
| `processAgentResponse` | function | `src/entrypoints/cli.ts:1255` | n/a | Handles one stream turn and continuation |
| `shouldContinueManualToolLoop` | constant fn | `src/interaction/tool-loop-control.ts:5` | 9 | Shared continuation gate for CLI/headless |
| `executeEditFile` | function | `src/tools/modify/edit-file.ts:1042` | 32 | Primary file mutation entrypoint |
| `AgentManager` | class | `src/agent.ts:163` | 2 | Central manager for model, provider, and tool config |
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

CODE MAP의 하드코딩된 라인 번호와 참조 횟수는 유지 비용이 큽니다

:1553, :1255, :5, :1042, :163 같은 정확한 라인 번호와 Refs 열의 숫자(9, 32, 2)는 코드베이스가 변경될 때마다 즉시 부정확해집니다. 라인 번호 없이 파일 경로만 유지하거나, 이 테이블 전체를 자동 생성 단계로 관리하는 방안을 고려해 주세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AGENTS.md` around lines 44 - 49, The CODE MAP in AGENTS.md hardcodes fragile
line numbers and reference counts for symbols like run, processAgentResponse,
shouldContinueManualToolLoop, executeEditFile, and AgentManager; remove the
inline line-number suffixes (e.g., :1553) and the Refs column from the table so
it contains only symbol names, types, and file paths, or replace the table with
a small autogenerated section (a script or build step) that extracts current
line numbers and reference counts from the repository; update the table rows to
use only the file path (e.g., src/entrypoints/cli.ts) and symbol name/type and
add a note that line numbers are intentionally omitted or will be generated
automatically.

Comment on lines 10 to 12
apt-get update -qq && apt-get install -y -qq --no-install-recommends \
curl git ca-certificates unzip tar tmux ripgrep \
curl git ca-certificates unzip tar ripgrep \
&& rm -rf /var/lib/apt/lists/*
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

tmux 제거로 terminal-bench@2.0 실행이 중단될 수 있습니다.

terminal-bench는 각 태스크 실행 시 전용 컨테이너 내부에서 tmux 세션을 생성하여 터미널 녹화 및 명령 실행을 수행합니다. 따라서 에이전트의 Docker 이미지에 tmux가 설치되어 있지 않으면 벤치마크가 런타임에 실패합니다.

🐛 `tmux` 재추가 제안
-  curl git ca-certificates unzip tar ripgrep \
+  curl git ca-certificates unzip tar ripgrep tmux \
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
apt-get update -qq && apt-get install -y -qq --no-install-recommends \
curl git ca-certificates unzip tar tmux ripgrep \
curl git ca-certificates unzip tar ripgrep \
&& rm -rf /var/lib/apt/lists/*
apt-get update -qq && apt-get install -y -qq --no-install-recommends \
curl git ca-certificates unzip tar ripgrep tmux \
&& rm -rf /var/lib/apt/lists/*
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@benchmark/install-agent.sh.j2` around lines 10 - 12, The install script
removed tmux which causes terminal-bench@2.0 to fail at runtime because it
spawns tmux sessions; update the apt-get install command (the line containing
"apt-get install -y -qq --no-install-recommends \ curl git ca-certificates unzip
tar ripgrep \") to include "tmux" in the package list so the agent Docker image
has tmux available during benchmark runs.

Comment on lines +329 to +331
setReasoningMode(mode: ReasoningMode): void {
this.reasoningMode = mode;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

setReasoningMode가 현재 모델의 지원 범위를 검증하지 않습니다

setReasoningMode("preserved")를 Anthropic 모델(지원 모드: ["off", "on"])에 적용하면 예외가 발생하지 않고 "preserved"가 그대로 저장됩니다. 이후 createBaseModel에서 Anthropic 경로는 reasoningMode !== "off" 조건만 확인하므로, Friendli 전용 모드가 Anthropic thinking 예산 설정을 우발적으로 활성화할 수 있습니다. 설정 시 getSelectableReasoningModes()와 비교하는 가드를 추가하는 것을 권장합니다.

🛡️ 수정 제안
 setReasoningMode(mode: ReasoningMode): void {
+  const selectable = this.getSelectableReasoningModes();
+  if (!selectable.includes(mode)) {
+    throw new Error(
+      `Reasoning mode "${mode}" is not supported by the current model. Supported: ${selectable.join(", ")}`
+    );
+  }
   this.reasoningMode = mode;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
setReasoningMode(mode: ReasoningMode): void {
this.reasoningMode = mode;
}
setReasoningMode(mode: ReasoningMode): void {
const selectable = this.getSelectableReasoningModes();
if (!selectable.includes(mode)) {
throw new Error(
`Reasoning mode "${mode}" is not supported by the current model. Supported: ${selectable.join(", ")}`
);
}
this.reasoningMode = mode;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/agent.ts` around lines 329 - 331, setReasoningMode currently stores any
ReasoningMode without checking whether the current model supports it, which can
enable unsupported modes for Anthropic; update setReasoningMode to validate the
provided mode against getSelectableReasoningModes() (for the current model) and
reject or throw a clear error if the mode is not in that list so only supported
modes are accepted; reference the setReasoningMode method and use
getSelectableReasoningModes() and the same model-selection logic used by
createBaseModel to determine valid modes before assigning this.reasoningMode.

Comment on lines +19 to +48
const RG_RESULT_LINE_PATTERN = /^(.*?)[:-](\d+)[:-](.*)$/;

function formatHashlineCompatibleMatches(rawMatches: string): string {
if (!rawMatches) {
return rawMatches;
}

return rawMatches
.split("\n")
.map((line) => {
if (line.length === 0 || line === "--") {
return line;
}

const matched = line.match(RG_RESULT_LINE_PATTERN);
if (!matched) {
return line;
}

const [, filePath, lineNumberText, content] = matched;
const lineNumber = Number.parseInt(lineNumberText, 10);
if (!Number.isFinite(lineNumber) || lineNumber < 1) {
return line;
}

const tag = formatLineTag(lineNumber, content);
return `${filePath}:${tag}|${content}`;
})
.join("\n");
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

-숫자- 패턴을 포함한 파일명에서 잘못된 파싱 가능

rg는 매칭 라인에 : 구분자를, 컨텍스트 라인에 - 구분자를 별도로 사용합니다.

RG_RESULT_LINE_PATTERN = /^(.*?)[:-](\d+)[:-](.*)$/의 지연(lazy) (.*?)[:-](\d+)[:-] 패턴을 가장 처음 만나는 위치에서 매칭합니다. 예를 들어 foo-2-bar.ts:5:content라는 ripgrep 출력 라인에서:

  • (.*?) = "foo"-2-"bar.ts:5:content" 으로 매칭 → filePath="foo", lineNumber=2, content="bar.ts:5:content" (잘못된 파싱)
  • 정상 파싱: filePath="foo-2-bar.ts", lineNumber=5, content="content"

두 구분자를 하나의 문자 클래스 [:-]로 통합한 것이 원인입니다. 두 패턴을 분리하면 이 모호성을 제거할 수 있습니다.

🐛 수정 제안: 매칭 라인과 컨텍스트 라인 패턴 분리
-const RG_RESULT_LINE_PATTERN = /^(.*?)[:-](\d+)[:-](.*)$/;
+// match lines: file:linenum:content
+const RG_MATCH_LINE_PATTERN = /^(.*?):(\d+):(.*)$/;
+// context lines: file-linenum-content
+const RG_CONTEXT_LINE_PATTERN = /^(.*?)-(\d+)-(.*)$/;

 function formatHashlineCompatibleMatches(rawMatches: string): string {
   // ...
-      const matched = line.match(RG_RESULT_LINE_PATTERN);
+      const matched =
+        line.match(RG_MATCH_LINE_PATTERN) ??
+        line.match(RG_CONTEXT_LINE_PATTERN);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/explore/grep.ts` around lines 19 - 48, The grep parser mis-parses
filenames containing '-' because RG_RESULT_LINE_PATTERN uses a single character
class '[:-]' for both separators; update the pattern used by
formatHashlineCompatibleMatches so it distinguishes match-line separators from
context-line separators (e.g., require ':' between filePath and lineNumber and
between lineNumber and content for actual match lines, and keep separate
handling for context lines), by replacing RG_RESULT_LINE_PATTERN with a regex
that specifically expects ':' as the separators for matched result lines and
adjust any logic in formatHashlineCompatibleMatches accordingly to avoid
treating '-' inside filenames as the file/line delimiter.

Comment on lines +74 to 84
function parseAppendPrependEdit(
op: "append" | "prepend",
edit: HashlineToolEdit
): HashlineEdit {
const ref = op === "append" ? (edit.pos ?? edit.end) : (edit.end ?? edit.pos);
return {
startLine,
endLine,
context: contextSnippet,
op,
pos: ref ? parseLineTag(ref) : undefined,
lines: parseHashlineText(edit.lines),
};
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

prepend 연산이 pos 대신 end를 우선 참조하여 스키마 설명과 불일치합니다

appendpos ?? end를 사용하는 반면, prependend ?? pos를 사용합니다. 스키마에서 end는 *"Range end anchor for replace operations"*로 명시되어 있어, prepend에서 end를 우선하면 두 필드를 동시에 전달하는 호출자(예: 모델)가 예상치 못한 위치에 삽입될 수 있습니다. append와 일관성 있게 pos ?? end를 사용하는 것이 더 직관적입니다.

🐛 수정 제안
-  const ref = op === "append" ? (edit.pos ?? edit.end) : (edit.end ?? edit.pos);
+  const ref = edit.pos ?? edit.end;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/modify/edit-file.ts` around lines 74 - 84, In
parseAppendPrependEdit, the reference for the "prepend" branch uses (edit.end ??
edit.pos) which is inconsistent with "append" and the schema; change the ref
assignment so both branches use pos first: set const ref = edit.pos ?? edit.end
when op === "prepend" (i.e., make the ternary for op === "append" ? (edit.pos ??
edit.end) : (edit.pos ?? edit.end)), leaving calls to parseLineTag(ref) and
parseHashlineText(edit.lines) unchanged; update the function
parseAppendPrependEdit accordingly.


return `${head}\n ... (${totalLines - PREVIEW_LINES * 2} lines omitted) ...\n${tail}`;
}
import WRITE_FILE_DESCRIPTION from "./write-file.txt";
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for existing *.txt module declaration in the project

echo "=== Searching for *.txt module declarations in *.d.ts files ==="
rg -l '\.txt' --type ts -g '*.d.ts' 2>/dev/null || echo "No *.d.ts files found with .txt references"

echo ""
echo "=== Checking all *.d.ts files for module declarations ==="
find . -name '*.d.ts' -type f | head -20

echo ""
echo "=== Checking tsconfig.json ==="
fd 'tsconfig' --type f -e json --exec cat {}

echo ""
echo "=== Checking for any .txt import statements ==="
rg 'import.*\.txt' --type ts --type tsx

echo ""
echo "=== Checking package.json for Bun version and configuration ==="
cat package.json | head -30

Repository: minpeter/code-editing-agent

Length of output: 1605


🏁 Script executed:

#!/bin/bash
# Check the actual write-file.ts file

echo "=== Reading src/tools/modify/write-file.ts ==="
cat -n src/tools/modify/write-file.ts | head -60

echo ""
echo "=== Checking if write-file.txt exists ==="
find . -name "write-file.txt" -o -name "*.txt" | grep -E '\.(txt)$'

echo ""
echo "=== Checking src/tools/modify directory ==="
ls -la src/tools/modify/ 2>/dev/null || echo "Directory does not exist"

Repository: minpeter/code-editing-agent

Length of output: 2962


🏁 Script executed:

#!/bin/bash
# Check TypeScript compilation status

echo "=== Running TypeScript type check ==="
cd . && npx tsc --noEmit 2>&1 | grep -E "(write-file|\.txt|TS2307)" || echo "No .txt-related type errors found"

echo ""
echo "=== Checking for any ambient type declarations or Bun-specific handling ==="
find . -name "*.d.ts" -type f -exec grep -l "module\|declare" {} \; 2>/dev/null || echo "No .d.ts files found"

echo ""
echo "=== Checking for bun.lockb or other Bun-specific files ==="
ls -la | grep -E "bun\.|bunfig"

echo ""
echo "=== Checking Bun documentation/types availability ==="
rg -n "bun" tsconfig.json || echo "No Bun references in tsconfig"
</parameter>

Repository: minpeter/code-editing-agent

Length of output: 22854


TypeScript가 .txt 파일 임포트에 대한 타입 선언을 찾을 수 없음

Bun 런타임은 .txt 파일을 네이티브로 지원하지만, TypeScript 컴파일러는 대응하는 타입 선언이 없으면 TS2307: Cannot find module './write-file.txt' or its corresponding type declarations 오류를 발생시킵니다. 현재 프로젝트에 *.txt 모듈을 위한 타입 선언이 없어 타입 검사가 실패하고 있습니다.

*.d.ts 파일을 생성하여 아래와 같이 *.txt 모듈을 string으로 선언하세요:

// src/types/assets.d.ts 또는 tsconfig에 포함되는 다른 위치
declare module "*.txt" {
  const content: string;
  export default content;
}

그 후 tsconfig.jsoninclude 배열에 해당 파일이 포함되는지 확인하거나, typeRoots 옵션을 명시적으로 설정하세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/modify/write-file.ts` at line 5, TypeScript cannot resolve the
import of WRITE_FILE_DESCRIPTION from "./write-file.txt"; create a declaration
file (e.g., src/types/assets.d.ts) that declares modules matching "*.txt" as
exporting a string, ensure the file is included by tsconfig.json (add it to the
"include" array or adjust "typeRoots"), then re-run type checking so the import
of WRITE_FILE_DESCRIPTION in write-file.ts is accepted.

Comment on lines +82 to +97
function trimToLastBytes(
text: string,
maxBytes: number
): { droppedBytes: number; text: string } {
const bytes = Buffer.from(text, "utf-8");
if (bytes.length <= maxBytes) {
return { text, droppedBytes: 0 };
}

const start = bytes.length - maxBytes;

return {
text: bytes.subarray(start).toString("utf-8"),
droppedBytes: start,
};
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

trimToLastBytes에서 멀티바이트 UTF-8 문자 경계 분할이 발생할 수 있습니다.

bytes.subarray(start).toString("utf-8")에서 start가 멀티바이트 시퀀스(예: 한국어/이모지 등 2~4바이트 문자) 중간을 가리킬 경우, 결과 문자열의 첫 부분에 U+FFFD 대체 문자가 삽입됩니다. 이 출력이 LLM에 전달되면 노이즈가 될 수 있습니다.

UTF-8 연속 바이트(0x800xBF)를 건너뛰어 문자 경계로 정렬하면 수정됩니다.

🐛 수정 제안
 function trimToLastBytes(
   text: string,
   maxBytes: number
 ): { droppedBytes: number; text: string } {
   const bytes = Buffer.from(text, "utf-8");
   if (bytes.length <= maxBytes) {
     return { text, droppedBytes: 0 };
   }

-  const start = bytes.length - maxBytes;
+  let start = bytes.length - maxBytes;
+  // UTF-8 연속 바이트(0x80–0xBF)를 건너뛰어 문자 경계 정렬
+  while (start < bytes.length && (bytes[start]! & 0xc0) === 0x80) {
+    start++;
+  }

   return {
     text: bytes.subarray(start).toString("utf-8"),
     droppedBytes: start,
   };
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/utils/execute/process-manager.ts` around lines 82 - 97, The
function trimToLastBytes can cut a UTF-8 multi-byte character in half causing a
U+FFFD replacement char; fix trimToLastBytes by advancing the computed start
index forward until it lands on a UTF-8 character boundary (i.e., skip bytes in
the continuation range 0x80–0xBF), then use that adjusted start for
bytes.subarray(...) and compute droppedBytes accordingly; ensure the edge-case
where the adjusted start equals bytes.length returns an empty string and
droppedBytes = original bytes.length.

}

const rawContent = await readFile(path, "utf-8");
const allLines = rawContent.split("\n");
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

rawContent.split("\n")이 CRLF 줄바꿈을 처리하지 못합니다

모듈 상단에 LINE_SPLIT_PATTERN = /\r?\n/가 이미 정의되어 있음에도 safeReadFileEnhanced는 단순히 "\n"으로 분리합니다. CRLF(\r\n) 형식 파일을 읽으면 각 줄에 \r이 남아 numberedContent1#XX|line1\r처럼 잘못된 내용이 표시됩니다.

🐛 제안된 수정
-  const allLines = rawContent.split("\n");
+  const allLines = rawContent.split(LINE_SPLIT_PATTERN);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const allLines = rawContent.split("\n");
const allLines = rawContent.split(LINE_SPLIT_PATTERN);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/utils/safety-utils.ts` at line 574, The code in
safeReadFileEnhanced currently does rawContent.split("\n") which leaves stray
"\r" from CRLF files; change the split to use the module-level
LINE_SPLIT_PATTERN (e.g., rawContent.split(LINE_SPLIT_PATTERN)) so CRLF and LF
are handled consistently and numberedContent won't contain trailing "\r"; update
the reference to the allLines variable accordingly and ensure any downstream
logic that expects plain lines continues to work with the regex-split result.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant