Skip to content

Conversation

@neopilotai
Copy link
Contributor

@neopilotai neopilotai commented Dec 4, 2025

PR Type

Enhancement, Tests


Description

  • Implements comprehensive agent loop orchestration with streaming responses, retry logic, and function call execution management

  • Adds Unicode-aware text buffer with full editing capabilities (insert, delete, undo/redo, selection, clipboard)

  • Introduces patch parsing and application system with fuzzy matching for file operations

  • Implements command approval and safety assessment system with three approval modes (suggest, auto-edit, full-auto)

  • Adds configuration management with YAML/JSON support and project documentation discovery

  • Implements file content caching with LRU eviction and directory traversal utilities

  • Adds command execution handler with approval policy enforcement and sandbox management

  • Implements macOS Seatbelt sandbox policy enforcement for secure command execution

  • Adds raw command execution with process group management and signal handling

  • Implements unified diff generation with ANSI colorization and statistics

  • Adds terminal chat utilities for context window calculations and message deduplication

  • Implements agent logging infrastructure with async file persistence

  • Adds model availability checking and caching utilities

  • Implements session tracking with globally unique IDs and metadata management

  • Adds comprehensive test suite covering agent cancellation, termination, network errors, rate limiting, and edge cases

  • Includes tests for text buffer operations, patch application, external editor integration, and configuration loading

  • Adds regression tests for function call ID propagation, process group termination, and cancellation race conditions


Diagram Walkthrough

flowchart LR
  A["User Input"] --> B["TextBuffer<br/>Editing"]
  B --> C["AgentLoop<br/>Orchestration"]
  C --> D["KhulnaSoft API<br/>Streaming"]
  D --> E["Function Call<br/>Execution"]
  E --> F["Approval<br/>System"]
  F --> G["Sandbox<br/>Execution"]
  G --> H["Patch<br/>Application"]
  H --> I["File<br/>Operations"]
  C --> J["Config<br/>Management"]
  J --> K["Project Docs<br/>Discovery"]
  C --> L["Session<br/>Tracking"]
  C --> M["Agent<br/>Logging"]
  E --> N["Command<br/>Safety"]
  G --> O["macOS<br/>Seatbelt"]
Loading

File Walkthrough

Relevant files
Enhancement
29 files
agent-loop.ts
Agent loop orchestration with streaming and error handling

axilo-cli/src/utils/agent/agent-loop.ts

  • New file implementing the core agent loop orchestration for the Axilo
    CLI agentic assistant
  • Handles streaming responses from the KhulnaSoft API with retry logic
    for transient errors
  • Manages function call execution (shell commands, patches) with user
    approval workflows
  • Implements cancellation and termination semantics with generation
    tracking to handle stray events
+1023/-0
text-buffer.ts
Unicode-aware text buffer with editing and history             

axilo-cli/src/lib/text-buffer.ts

  • New file implementing a Unicode-aware text buffer with cursor and
    viewport management
  • Supports editing operations (insert, delete, backspace, word-wise
    deletion) with undo/redo
  • Provides external editor integration via openInExternalEditor() for
    git-style workflows
  • Includes selection, clipboard, and comprehensive input handling for
    terminal UI
+852/-0 
apply-patch.ts
Patch parsing and application with fuzzy matching               

axilo-cli/src/utils/agent/apply-patch.ts

  • New file implementing patch parsing and application logic with unified
    diff format support
  • Parses patch text into structured actions (add, update, delete files)
    with fuzzy matching
  • Provides helpers to identify affected files and apply commits to the
    filesystem
  • Includes CLI mode for standalone patch processing via stdin
+644/-0 
approvals.ts
Command approval and safety assessment system                       

axilo-cli/src/lib/approvals.ts

  • New file implementing approval policy evaluation for commands and
    patches
  • Supports three approval modes: suggest, auto-edit, and full-auto with
    sandbox constraints
  • Validates shell expressions and apply_patch commands against writable
    path restrictions
  • Maintains a curated list of known-safe commands (git, rg, cat, etc.)
+542/-0 
config.ts
Configuration management with project doc discovery           

axilo-cli/src/utils/config.ts

  • New file implementing configuration loading and persistence with
    YAML/JSON support
  • Discovers and loads project documentation (axilo.md) with size limits
  • Manages user instructions and combines them with project docs for
    runtime config
  • Implements first-run bootstrap to create default config files in
    ~/.axilo
+356/-0 
context_files.ts
File content caching and directory traversal utilities     

axilo-cli/src/utils/singlepass/context_files.ts

  • Implements LRU file cache for storing file contents with mtime/size
    validation
  • Provides file collection utilities with ignore pattern support using
    glob patterns
  • Generates ASCII directory structure representation from file paths
  • Handles recursive directory traversal with symlink skipping and cache
    invalidation
+409/-0 
handle-exec-command.ts
Command execution with approval and sandbox management     

axilo-cli/src/utils/agent/handle-exec-command.ts

  • Implements command execution handler with approval policy enforcement
  • Manages session-level cache of user-approved commands via
    deriveCommandKey()
  • Handles sandbox execution on macOS with fallback for containerized
    environments
  • Provides user permission prompts with "always approve" option for
    command classes
+315/-0 
parsers.ts
Tool call parsing and command safety validation                   

axilo-cli/src/utils/parsers.ts

  • Parses tool call arguments and validates command arrays from JSON
  • Implements safe command detection with shell operator allowlist
  • Provides lazy-loaded dependency imports with fallback stubs for
    testing
  • Handles bash script parsing and validates command composition safety
+240/-0 
context_limit.ts
Context size tracking and hierarchical breakdown reporting

axilo-cli/src/utils/singlepass/context_limit.ts

  • Computes file and cumulative size maps for context limit tracking
  • Builds directory-to-children mapping for hierarchical size
    visualization
  • Prints ASCII tree showing file sizes and percentage of context limit
  • Provides directory breakdown reporting for context usage analysis
+208/-0 
raw-exec.ts
Raw command execution with process group and signal management

axilo-cli/src/utils/agent/sandbox/raw-exec.ts

  • Implements process spawning with stdin ignored to prevent hangs
    (ripgrep workaround)
  • Manages process group detachment for reliable signal propagation to
    child processes
  • Implements graceful termination with SIGTERM fallback to SIGKILL via
    AbortSignal
  • Buffers stdout/stderr with 100KB limit and maps exit codes from
    signals
+199/-0 
code_diff.ts
Unified diff generation with ANSI colorization and statistics

axilo-cli/src/utils/singlepass/code_diff.ts

  • Generates unified diffs using the diff library with 5-line context
  • Applies ANSI color codes to diff output (green for additions, red for
    deletions)
  • Counts added/removed lines from diff content
  • Summarizes file operations with colored output and statistics
+190/-0 
macos-seatbelt.ts
macOS Seatbelt sandbox policy enforcement                               

axilo-cli/src/utils/agent/sandbox/macos-seatbelt.ts

  • Wraps command execution with macOS Seatbelt sandbox policy
  • Generates read-only policy with configurable writable root paths
  • Includes extensive sysctl allowlist for hardware information access
  • Adds common writable roots like ~/.pyenv and config directory
+141/-0 
terminal-chat-utils.ts
Terminal chat utilities for deduplication and context tracking

axilo-cli/src/components/chat/terminal-chat-utils.ts

  • Utility functions for terminal chat UI including context window
    calculations and message deduplication
  • maxTokensForModel() returns context length estimates based on model
    name
  • uniqueById() deduplicates response items by ID and collapses
    consecutive identical user messages
  • calculateContextPercentRemaining() computes remaining context
    percentage for UI display
+113/-0 
log.ts
Agent logging system with async file persistence                 

axilo-cli/src/utils/agent/log.ts

  • Logging infrastructure for agent operations with optional file-based
    persistence
  • AsyncLogger queues and asynchronously writes log entries to
    timestamped files
  • Creates symlink to axilo-cli-latest.log for convenient tailing
  • EmptyLogger provides no-op implementation when DEBUG env var is not
    set
+129/-0 
parse-apply-patch.ts
Patch parsing for file operation extraction                           

axilo-cli/src/utils/agent/parse-apply-patch.ts

  • Parser for patch format with create, update, and delete file
    operations
  • Validates patch structure with *** Begin Patch and *** End Patch
    markers
  • Extracts file paths and content changes with line addition/deletion
    counts
+112/-0 
terminal.ts
Terminal renderer lifecycle and cleanup management             

axilo-cli/src/utils/terminal.ts

  • Terminal management utilities for Ink renderer lifecycle and cleanup
  • setInkRenderer() configures renderer with optional FPS debugging
  • onExit() ensures proper unmounting and terminal state restoration
  • Prevents duplicate cleanup execution across multiple exit signals
+82/-0   
use-message-grouping.ts
Message grouping hook for display batching                             

axilo-cli/src/components/chat/use-message-grouping.ts

  • React hook for grouping response items for display batching
  • Counts auto-approved tool call groups and user messages in visible
    window
  • Returns grouped batches of recent items for efficient rendering
+81/-0   
model-utils.ts
Model availability checking and caching utilities               

axilo-cli/src/utils/model-utils.ts

  • Background model loader and cache for available KhulnaSoft models
  • getAvailableModels() fetches and caches model list from /models
    endpoint
  • isModelSupportedForResponses() validates model support with timeout
    and fallback
  • preloadModels() initiates background fetch during CLI startup
+91/-0   
exec.ts
Command execution with sandbox routing                                     

axilo-cli/src/utils/agent/exec.ts

  • Execution wrapper for commands with sandbox selection and timeout
    handling
  • Routes to execWithSeatbelt (macOS) or rawExec (other platforms)
  • execApplyPatch() applies patch operations with error handling
  • getBaseCmd() extracts base command name for logging
+67/-0   
context.ts
Task context data structures and rendering                             

axilo-cli/src/utils/singlepass/context.ts

  • Data structures and rendering for task context in single-pass mode
  • TaskContext includes prompt, file paths, directory structure, and file
    contents
  • renderTaskContext() formats context as XML-like structure with CDATA
    sections
  • Emphasizes requirement for full file contents in modifications
+64/-0   
format-command.ts
Command formatting for user display                                           

axilo-cli/src/lib/format-command.ts

  • Formats command arrays for user display with proper quoting and
    escaping
  • Detects and strips bash -lc wrapper to show actual command
  • Falls back to shell-quote for complex commands
+53/-0   
use-confirmation.ts
Confirmation prompt queue management hook                               

axilo-cli/src/hooks/use-confirmation.ts

  • React hook managing confirmation prompt queue for user decisions
  • Maintains queue of pending confirmations and displays current prompt
  • Provides requestConfirmation() and submitConfirmation() for async flow
+62/-0   
save-rollout.ts
Conversation rollout persistence with debouncing                 

axilo-cli/src/utils/storage/save-rollout.ts

  • Saves conversation rollouts to home directory with debouncing
  • Creates timestamped JSON files in ~/.axilo/sessions/
  • Includes session metadata (timestamp, ID, instructions) and response
    items
+61/-0   
approximate-tokens-used.ts
Token usage approximation utility                                               

axilo-cli/src/utils/approximate-tokens-used.ts

  • Estimates token count from response items using character-based
    heuristic
  • Assumes ~4 characters per token for quick approximation
  • Handles message content, function calls, and tool outputs
+51/-0   
session.ts
Session tracking and metadata management                                 

axilo-cli/src/utils/session.ts

  • Session tracking with globally unique ID and model information
  • Exports TerminalChatSession type with metadata (user, version,
    timestamp)
  • Provides getters/setters for session ID and current model
+53/-0   
file_ops.ts
File operation schema definitions                                               

axilo-cli/src/utils/singlepass/file_ops.ts

  • Zod schemas for file operations (create, update, delete, move)
  • FileOperation type with mutually exclusive fields for different
    operation types
  • EditedFiles container for ordered list of operations
+47/-0   
check-in-git.ts
Git repository detection utility                                                 

axilo-cli/src/utils/check-in-git.ts

  • Synchronous check for whether directory is inside a Git repository
  • Uses git rev-parse --is-inside-work-tree command
  • Returns boolean based on exit code (ignores stdout/stderr)
+31/-0   
input-utils.ts
Input item creation with image support                                     

axilo-cli/src/utils/input-utils.ts

  • Creates input items for KhulnaSoft API with text and image support
  • Reads image files, detects MIME type, and encodes as base64 data URLs
  • Returns ResponseInputItem.Message with mixed content array
+31/-0   
short-path.ts
Path shortening for display                                                           

axilo-cli/src/utils/short-path.ts

  • Path shortening utilities for display purposes
  • shortenPath() replaces home directory with ~ and truncates long paths
  • shortCwd() applies shortening to current working directory
+27/-0   
Tests
30 files
text-buffer-crlf.test.ts
TextBuffer CRLF normalization test                                             

axilo-cli/tests/text-buffer-crlf.test.ts

  • New test file verifying TextBuffer handles Windows-style CRLF line
    endings correctly
  • Tests insertStr() method splits on both \r and \r\n sequences
  • Validates cursor position after multi-line insertion
+14/-0   
apply-patch.test.ts
Comprehensive patch application and file operation tests 

axilo-cli/tests/apply-patch.test.ts

  • Tests patch processing for file updates, additions, and deletions
  • Validates file identification and change assembly logic
  • Tests move/rename operations and combined multi-file operations
  • Includes in-memory filesystem mock for isolated testing
+318/-0 
text-buffer.test.ts
Text buffer editing and cursor movement test suite             

axilo-cli/tests/text-buffer.test.ts

  • Tests basic text editing operations (insert, backspace, undo/redo)
  • Validates cursor movement with Unicode surrogate pair handling
  • Tests selection, copy/paste, and vertical navigation with preferred
    column preservation
  • Covers input handling for terminal DEL bytes and Delete key semantics
+264/-0 
text-buffer-gaps.test.ts
Text buffer feature gap documentation and future tests     

axilo-cli/tests/text-buffer-gaps.test.ts

  • Documents missing features via .fails() marked tests for future
    implementation
  • Covers soft-tab insertion, undo grouping, word-wise operations, and
    search functionality
  • Tests paragraph navigation, independent scrolling, and configurable
    tab length
  • Serves as regression prevention and implementation roadmap
+250/-0 
agent-thinking-time.test.ts
Agent thinking time counter regression test suite               

axilo-cli/tests/agent-thinking-time.test.ts

  • Tests per-task thinking time counter reset between requests
  • Validates global thinking time accumulation across multiple turns
  • Uses fake timers to simulate 10s and 0.5s response delays
  • Marked with .fails() to track regression until implementation is fixed
+177/-0 
agent-cancel.test.ts
Agent cancellation and output suppression tests                   

axilo-cli/tests/agent-cancel.test.ts

  • Tests that cancellation prevents function_call_output emission
  • Validates suppression of output after both slow and fast command
    execution
  • Mocks KhulnaSoft SDK and command execution with configurable delays
  • Ensures cleanup occurs when cancel() is invoked mid-execution
+173/-0 
agent-network-errors.test.ts
Agent network error handling and retry tests                         

axilo-cli/tests/agent-network-errors.test.ts

  • Tests retry logic on APIConnectionTimeoutError with eventual success
  • Validates system message emission on premature connection closure
  • Mocks KhulnaSoft SDK with programmable failure scenarios
  • Verifies resilience to network transients
+179/-0 
agent-terminate.test.ts
Agent termination and state cleanup tests                               

axilo-cli/tests/agent-terminate.test.ts

  • Tests hard termination suppresses function_call_output emission
  • Validates that terminate() rejects subsequent run() calls
  • Mocks long-running command with AbortSignal handling
  • Ensures cleanup and state consistency after termination
+175/-0 
agent-server-retry.test.ts
Agent server error retry and resilience tests                       

axilo-cli/tests/agent-server-retry.test.ts

  • Tests automatic retry up to 3 times on 5xx server errors
  • Validates success after retries and system message on final failure
  • Mocks KhulnaSoft SDK with configurable failure counts
  • Verifies resilience to transient server errors
+166/-0 
multiline-input-test.ts
Multiline text editor component tests                                       

axilo-cli/tests/multiline-input-test.ts

  • Tests multiline text editor rendering and text updates
  • Validates backspace behavior and character deletion
  • Tests caret highlighting under cursor position
  • Covers submit callback on Escape key with text content
+164/-0 
project-doc.test.ts
Project documentation integration tests                                   

axilo-cli/tests/project-doc.test.ts

  • Tests project documentation loading and merging into instructions
  • Validates opt-out flag prevents doc inclusion
  • Tests truncation and warning for oversized documentation files
  • Uses temporary directories to avoid polluting real filesystem
+57/-0   
dummy.test.ts
Dummy test placeholder                                                                     

axilo-cli/tests/dummy.test.ts

  • Minimal placeholder test ensuring test framework is functional
+4/-0     
agent-function-call-id.test.ts
Function call ID propagation regression test                         

axilo-cli/tests/agent-function-call-id.test.ts

  • Regression test ensuring AgentLoop correctly copies function call IDs
    from KhulnaSoft responses into subsequent function_call_output items
  • Mocks the KhulnaSoft SDK to simulate chat-style function calls with id
    field
  • Validates that the call_id parameter is properly set when submitting
    tool results
+149/-0 
agent-cancel-race.test.ts
Agent cancellation race condition test                                     

axilo-cli/tests/agent-cancel-race.test.ts

  • Test reproducing race condition where user cancels task but model
    response has already started streaming
  • Verifies that partial answers from canceled runs do not appear in the
    UI
  • Uses fake KhulnaSoft stream to simulate immediate response emission
+142/-0 
agent-project-doc.test.ts
Project documentation inclusion in agent instructions       

axilo-cli/tests/agent-project-doc.test.ts

  • Tests that project documentation (axilo.md) is properly included in
    agent instructions
  • Creates temporary project directory with fixture files and validates
    config loading
  • Verifies end-to-end flow from loadConfig through AgentLoop to
    KhulnaSoft SDK
+141/-0 
agent-cancel-prev-response.test.ts
Agent cancellation clears previous response ID                     

axilo-cli/tests/agent-cancel-prev-response.test.ts

  • Tests that cancel() properly clears previous_response_id state between
    runs
  • Simulates function call followed by cancellation and new command
  • Validates that second run does not include stale response IDs
+150/-0 
agent-rate-limit-error.test.ts
Rate limit error handling test                                                     

axilo-cli/tests/agent-rate-limit-error.test.ts

  • Test for KhulnaSoft rate limit error handling in AgentLoop
  • Mocks SDK to throw rate limit error and verifies user-friendly system
    message is emitted
  • Validates that agent resolves without throwing on rate limit errors
+127/-0 
agent-generic-network-error.test.ts
Generic network error handling tests                                         

axilo-cli/tests/agent-generic-network-error.test.ts

  • Tests generic network error handling (ECONNRESET, HTTP 500) in
    AgentLoop
  • Verifies that network failures emit friendly system messages instead
    of throwing
  • Validates graceful degradation on connection and server errors
+132/-0 
text-buffer-word.test.ts
TextBuffer word navigation and deletion tests                       

axilo-cli/tests/text-buffer-word.test.ts

  • Tests for word-wise navigation and deletion in TextBuffer component
  • Covers wordRight, wordLeft, deleteWordLeft, deleteWordRight operations
  • Validates cursor positioning and text modifications with word
    boundaries
+115/-0 
agent-cancel-early.test.ts
Early agent cancellation before function call                       

axilo-cli/tests/agent-cancel-early.test.ts

  • Tests cancellation of agent run before function call is received
  • Validates that previous_response_id is cleared when no call IDs are
    captured
  • Simulates slow stream and early cancellation scenario
+127/-0 
approvals.test.ts
Command auto-approval safety assessment tests                       

axilo-cli/src/lib/approvals.test.ts

  • Unit tests for canAutoApprove() command safety assessment function
  • Tests safe commands (ls, cat, pwd), bash-wrapped commands, and unsafe
    redirects
  • Validates approval categorization for various command types
+92/-0   
agent-max-tokens-error.test.ts
Max tokens context length error handling test                       

axilo-cli/tests/agent-max-tokens-error.test.ts

  • Test for context length exceeded error handling in AgentLoop
  • Mocks SDK to throw max_tokens error and validates system message
    emission
  • Verifies graceful handling of context window overflow
+92/-0   
raw-exec-process-group.test.ts
Process group termination on abort regression test             

axilo-cli/tests/raw-exec-process-group.test.ts

  • Regression test ensuring rawExec() terminates entire process group on
    abort
  • Validates that grandchild processes spawned via bash are properly
    killed
  • Uses negative PID process group trick (POSIX-only, skipped on Windows)
+64/-0   
agent-invalid-request-error.test.ts
Invalid request error handling test                                           

axilo-cli/tests/agent-invalid-request-error.test.ts

  • Test for invalid request (4xx) error handling in AgentLoop
  • Mocks SDK to throw invalid_request_error and validates system message
  • Verifies graceful error handling for malformed requests
+88/-0   
model-utils-network-error.test.ts
Model utilities offline resilience tests                                 

axilo-cli/tests/model-utils-network-error.test.ts

  • Tests offline resilience of model utilities when network is
    unavailable
  • Validates graceful fallback when API key is absent or network errors
    occur
  • Ensures isModelSupportedForResponses() returns true despite failures
+70/-0   
external-editor.test.ts
External editor integration test                                                 

axilo-cli/tests/external-editor.test.ts

  • Test for external editor integration with TextBuffer
  • Mocks spawnSync to simulate editor modifications
  • Validates buffer replacement with editor-saved contents
+56/-0   
text-buffer-copy-paste.test.ts
TextBuffer multi-line copy/paste tests                                     

axilo-cli/tests/text-buffer-copy-paste.test.ts

  • Tests multi-line copy/paste behavior in TextBuffer
  • Validates that pasted content merges final line with following text
  • Ensures parity with Rust reference implementation
+50/-0   
cancel-exec.test.ts
Process execution cancellation tests                                         

axilo-cli/tests/cancel-exec.test.ts

  • Tests process cancellation via AbortSignal in rawExec()
  • Validates rapid termination when abort is triggered
  • Confirms process completes normally when not aborted
+46/-0   
parse-apply-patch.test.ts
Patch parsing unit tests                                                                 

axilo-cli/src/lib/parse-apply-patch.test.ts

  • Unit tests for parseApplyPatch() function
  • Tests parsing of create, update, delete operations in single patch
  • Validates error handling for invalid patch format
+45/-0   
api-key.test.ts
API key configuration tests                                                           

axilo-cli/tests/api-key.test.ts

  • Tests for API key configuration and runtime override
  • Validates that setApiKey() updates exported KHULNASOFT_API_KEY
    reference
  • Uses module cache reset to isolate tests
+37/-0   
Miscellaneous
1 files
parse-apply-patch.ts
Patch parsing for file operation extraction                           

axilo-cli/src/lib/parse-apply-patch.ts

  • Duplicate of patch parsing functionality (same as
    src/utils/agent/parse-apply-patch.ts)
  • Provides parser for create, update, delete file operations
  • Validates patch format and extracts operation details
+112/-0 
Configuration changes
1 files
typings.d.ts
TypeScript type stubs for external libraries                         

axilo-cli/src/typings.d.ts

  • TypeScript declaration stubs for external libraries without type
    definitions
  • Provides minimal type definitions for shell-quote and diff libraries
  • Covers only APIs used by Axilo codebase
+65/-0   
Additional files
101 files
.dockerignore +0/-120 
Dockerfile +29/-0   
README.md +85/-0   
action.yml +10/-0   
main.py +727/-0 
requirements.txt +15/-0   
__init__.py +3/-0     
conftest.py +72/-0   
test_integration.py +232/-0 
test_edge_cases.py +180/-0 
test_graphql.py +166/-0 
test_main.py +182/-0 
test_models.py +131/-0 
dependabot.yml +41/-0   
ci.yml +58/-0   
dco.yml +50/-0   
issue-manager.yml +51/-0   
label-approved.yml +27/-0   
labeler.yml +33/-0   
latest-changes.yml +46/-0   
people.yml +110/-0 
commit-msg +22/-0   
pre-commit +4/-0     
pre-push +21/-0   
.prettierignore +2/-0     
.prettierrc +0/-10   
.prettierrc.toml +8/-0     
AXILO.md +0/-182 
Dockerfile +0/-69   
LICENSE +201/-0 
Makefile +0/-214 
README.md +335/-72
.dockerignore +47/-0   
.editorconfig +9/-0     
.eslintrc.cjs +107/-0 
ci.yml +163/-0 
.nvmrc +1/-0     
CONTRIBUTING.md +45/-0   
Dockerfile +76/-0   
README.md +170/-0 
SECURITY.md +30/-0   
build.mjs +78/-0   
docker-compose.yml +29/-0   
README.md +48/-0   
run.sh +65/-0   
.gitkeep [link]   
task.yaml +88/-0   
run.sh +68/-0   
.gitkeep [link]   
task.yaml +5/-0     
screenshot_details.md +38/-0   
run.sh +68/-0   
.gitkeep [link]   
task.yaml +11/-0   
index.html +233/-0 
run.sh +68/-0   
.gitkeep [link]   
task.yaml +17/-0   
Clustering.ipynb +231/-0 
README.md +103/-0 
analysis.md +22/-0   
analysis_dbscan.md +20/-0   
cluster_prompts.py +547/-0 
prompting_guide.md +127/-0 
ignore-react-devtools-plugin.js +16/-0   
package.json +77/-0   
require-shim.js +11/-0   
build_container.sh +3/-0     
init_firewall.sh +96/-0   
run_in_container.sh +52/-0   
app.tsx +103/-0 
cli.tsx +393/-0 
cli_singlepass.tsx +39/-0   
approval-mode-overlay.tsx +47/-0   
message-history.tsx +80/-0   
multiline-editor.tsx +409/-0 
terminal-chat-command-review.tsx +172/-0 
terminal-chat-input-thinking.tsx +173/-0 
terminal-chat-input.tsx +409/-0 
terminal-chat-new-input.tsx +506/-0 
terminal-chat-past-rollout.tsx +61/-0   
terminal-chat-response-item.tsx +259/-0 
terminal-chat-tool-call-item.tsx +106/-0 
terminal-chat.tsx +400/-0 
terminal-header.tsx +84/-0   
terminal-message-history.tsx +76/-0   
help-overlay.tsx +90/-0   
history-overlay.tsx +237/-0 
model-overlay.tsx +108/-0 
onboarding-approval-mode.tsx +35/-0   
singlepass-cli-app.tsx +681/-0 
typeahead-overlay.tsx +163/-0 
index.js +1293/-0
index.js +1/-0     
option-map.js +26/-0   
select-option.js +27/-0   
select.js +53/-0   
theme.js +32/-0   
use-select-state.js +158/-0 
use-select.js +17/-0   
Additional files not shown

Summary by CodeRabbit

  • New Features

    • Interactive terminal CLI with multi-line editor, approval modes, model switching, history/help overlays, command review, and single-pass edit flow.
    • Automated "People" GitHub Action to generate contributor and sponsor data.
  • Documentation

    • Major README overhaul plus CONTRIBUTING, SECURITY, prompting guides, examples, and per-package READMEs added.
  • Chores

    • CI/workflows, linting and Git hooks added; packaging and Docker support for the CLI updated; Apache-2.0 license included.
  • Tests

    • Extensive unit and integration test suites for CLI and GitHub Action.

✏️ Tip: You can customize this high-level summary in your review settings.

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

We failed to fetch the diff for pull request #3

You can try again by commenting this pull request with @sourcery-ai review, or contact us for help.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 4, 2025

Walkthrough

Adds a new Dockerized Python "people" GitHub Action, many GitHub workflows, a full Axilo CLI (Node + React Ink) with agent, approval and patch utilities, examples, tests, tooling (Dockerfiles, Husky hooks, lint/config), and repo-level docs/license/config changes; removes some root-level build artifacts (Dockerfile, Makefile, AXILO.md, .dockerignore).

Changes

Cohort / File(s) Summary
GitHub Action — People
.github/actions/people/*
New Dockerized Python action that queries GitHub GraphQL (issues, discussions, PRs, sponsors), computes contributor/expert metrics, emits YAML outputs, and includes Dockerfile, action.yml, README, requirements, and comprehensive tests.
Workflows & Dependabot
.github/workflows/*, .github/dependabot.yml
New CI and automation workflows (ci, dco, labeler, issue-manager, latest-changes, people, etc.) and Dependabot config added.
Root repo: license, docs & ignores
LICENSE, README.md, .gitignore, .prettierrc.toml, .prettierignore, .prettierrc (deleted), .dockerignore (deleted), Dockerfile (deleted), Makefile (deleted), AXILO.md (deleted)
Adds Apache-2.0 LICENSE; replaces/rewrites README; migrates Prettier to TOML; reorganizes .gitignore; removes legacy root Dockerfile, Makefile, AXILO.md, and root .dockerignore.
Husky & Git hooks
.husky/*
Adds commit-msg, pre-commit, pre-push hooks for commitlint, lint-staged, and pre-push test enforcement.
Axilo CLI packaging & tooling
axilo-cli/package.json, axilo-cli/build.mjs, axilo-cli/require-shim.js, axilo-cli/ignore-react-devtools-plugin.js, axilo-cli/.eslintrc.cjs, axilo-cli/.nvmrc, axilo-cli/.editorconfig, axilo-cli/.dockerignore
New package manifest and build tooling for axilo-cli, ESM require-shim, esbuild plugin, ESLint/editorconfig, Node version pin, and CLI-scoped .dockerignore.
Axilo CLI Docker & helper scripts
axilo-cli/Dockerfile, axilo-cli/docker-compose.yml, axilo-cli/scripts/*
New multi-stage Dockerfile, docker-compose, and container helper scripts (build, run, init_firewall).
Axilo CLI core & runners
axilo-cli/src/cli.tsx, axilo-cli/src/cli_singlepass.tsx, axilo-cli/src/app.tsx, axilo-cli/src/components/singlepass-cli-app.tsx
New CLI entrypoints, single-pass runner, and top-level App wiring for interactive and single-pass modes.
Approval & patching engine
axilo-cli/src/lib/approvals.ts, axilo-cli/src/lib/approvals.test.ts, axilo-cli/src/utils/agent/apply-patch.ts
New policy-driven command auto-approval, shell-expression safety validation, apply_patch parsing and patch-application engine with tests.
Agent, model & config
axilo-cli/src/utils/agent/agent-loop.ts, axilo-cli/src/utils/model-utils.ts, axilo-cli/src/utils/config.ts
New AgentLoop streaming class, model-loading helpers, and centralized config / API-key management and project-doc discovery.
Terminal UI components & hooks
axilo-cli/src/components/**, axilo-cli/src/hooks/**
Large set of new Ink-based components (TerminalChat, inputs, overlays, message renderers), vendor widgets, and hooks (confirmation, terminal size, message grouping).
Vendor widgets & utilities
axilo-cli/src/components/vendor/**
Bundled select/text-input/spinner implementations and spinner catalog for Ink UI.
Examples, docs & guides (axilo-cli)
axilo-cli/examples/**, axilo-cli/README.md, axilo-cli/CONTRIBUTING.md, axilo-cli/SECURITY.md, notebooks
Multiple example projects with run scripts, templates, clustering notebook, and new docs (README, CONTRIBUTING, SECURITY).
Tests
.github/actions/people/tests/*, axilo-cli/src/**/*.test.*
New unit and integration tests for the people action, approvals, format utilities, models, and other CLI subsystems.

Sequence Diagram(s)

sequenceDiagram
    participant Scheduler as GitHub Scheduler / Manual Trigger
    participant Runner as GitHub Actions Runner (container)
    participant PeopleApp as people/app/main.py
    participant GitHubAPI as GitHub GraphQL API
    participant Git as Git (repo)

    Scheduler->>Runner: start "people" workflow
    Runner->>PeopleApp: launch container & run python module
    PeopleApp->>GitHubAPI: GraphQL queries (issues, discussions, PRs, sponsors) [paginated]
    GitHubAPI-->>PeopleApp: return paginated edges
    PeopleApp->>PeopleApp: aggregate metrics, render YAML files
    PeopleApp->>Git: write files, git commit, create branch, push
    PeopleApp->>GitHubAPI: create Pull Request for updated docs/data
    GitHubAPI-->>Runner: PR creation response
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

  • Areas needing extra attention:
    • axilo-cli/src/lib/approvals.ts — shell parsing, path-constraint and safety logic.
    • axilo-cli/src/utils/agent/apply-patch.ts — patch parsing, fuzzy hunk alignment, and filesystem safety checks.
    • axilo-cli/src/utils/agent/agent-loop.ts — streaming, function-call handling, cancellability, and side-effects.
    • .github/actions/people/app/main.py — GraphQL pagination/error handling, git operations, and PR creation.
    • Terminal input components (multiline-editor, terminal-chat-input*) — raw-stdin handling and complex keyboard flows.
    • init_firewall.sh and Dockerfiles/scripts — privileged ops, platform assumptions, and security implications.

Poem

🐇 I hopped through diffs with whiskers keen,
New actions, CLIs, and screens unseen.
From GraphQL hops to patching art,
I stitched the changes — tiny heart.
Carrot-powered tests, accept my keen! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title '💄 Standardize YAML formatting and quotes' refers to formatting changes, but the changeset contains extensive agent functionality, editing utilities, patch handling, execution safety, configuration, UI components, and tests—far beyond YAML formatting standardization. Revise the title to accurately reflect the major changes, such as 'Add agent loop, patch system, approval workflows, and comprehensive CLI infrastructure' or break into multiple focused PRs.
Docstring Coverage ⚠️ Warning Docstring coverage is 56.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b80b966 and 1061ce9.

📒 Files selected for processing (1)
  • axilo-cli/src/utils/agent/agent-loop.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
axilo-cli/src/utils/agent/agent-loop.ts (1)
axilo-cli/src/utils/config.ts (4)
  • AppConfig (208-214)
  • KHULNASOFT_TIMEOUT_MS (34-35)
  • apiKeyManager (167-167)
  • KHULNASOFT_BASE_URL (36-36)

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.

@qodo-code-review
Copy link

qodo-code-review bot commented Dec 4, 2025

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🔴
Path traversal deletion

Description: Deleting files via fs.unlinkSync without path validation can remove arbitrary files if a
crafted patch references sensitive paths (e.g., '../../../.ssh/authorized_keys'); although
absolute paths are rejected on write, remove_file lacks checks against absolute or
path-traversal inputs.
apply-patch.ts [610-613]

Referred Code
function remove_file(p: string): void {
  fs.unlinkSync(p);
}
Path traversal write

Description: Writing files permits relative paths like '../../secret' since only absolute paths are
blocked; without normalizing and constraining against a repository root, patch text can
escape the workspace and overwrite arbitrary files.
apply-patch.ts [602-609]

Referred Code
    throw new DiffError("We do not support absolute paths.");
  }
  const parent = path.dirname(p);
  if (parent !== ".") {
    fs.mkdirSync(parent, { recursive: true });
  }
  fs.writeFileSync(p, content, "utf8");
}
Remote command execution

Description: The exposed 'shell' function tool accepts arbitrary command arrays from the model; if
approvalPolicy or getCommandConfirmation flows are bypassed or misconfigured, this enables
command execution risks—ensure strict approval gating and sanitization within
handleExecCommand.
agent-loop.ts [516-538]

Referred Code
type: "function",
name: "shell",
description: "Runs a shell command, and returns its output.",
strict: false,
parameters: {
  type: "object",
  properties: {
    command: { type: "array", items: { type: "string" } },
    workdir: {
      type: "string",
      description: "The working directory for the command.",
    },
    timeout: {
      type: "number",
      description:
        "The maximum time to wait for the command to complete in milliseconds.",
    },
  },
  required: ["command"],
  additionalProperties: false,
},



 ... (clipped 2 lines)
Secret exposure risk

Description: KHULNASOFT_API_KEY is exported as a mutable global and may be logged or exposed
inadvertently elsewhere; consider minimizing in-memory lifetime and avoiding global state
for secrets to reduce accidental leakage.
config.ts [35-41]

Referred Code
  parseInt(process.env["KHULNASOFT_TIMEOUT_MS"] || "0", 10) || undefined;
export const KHULNASOFT_BASE_URL = process.env["KHULNASOFT_BASE_URL"] || "";
export let KHULNASOFT_API_KEY = process.env["KHULNASOFT_API_KEY"] || "";

export function setApiKey(apiKey: string): void {
  KHULNASOFT_API_KEY = apiKey;
}
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Missing Audit Logs: Newly added command execution paths and user approval decisions are not logged in a
structured audit trail including user/session, action, and outcome.

Referred Code
export async function handleExecCommand(
  args: ExecInput,
  config: AppConfig,
  policy: ApprovalPolicy,
  getCommandConfirmation: (
    command: Array<string>,
    applyPatch: ApplyPatchCommand | undefined,
  ) => Promise<CommandConfirmation>,
  abortSignal?: AbortSignal,
): Promise<HandleExecCommandResult> {
  const { cmd: command } = args;

  const key = deriveCommandKey(command);

  // 1) If the user has already said "always approve", skip
  //    any policy & never sandbox.
  if (alwaysApprovedCommands.has(key)) {
    return execCommand(
      args,
      /* applyPatch */ undefined,
      /* runInSandbox */ false,



 ... (clipped 85 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Broad Catch Paths: Error handling emits generic system messages but may swallow details and lacks consistent
structured logging/telemetry for retries and aborts, making some failures hard to
diagnose.

Referred Code
} catch (err) {
  // Handle known transient network/streaming issues so they do not crash the
  // CLI. We currently match Node/undici's `ERR_STREAM_PREMATURE_CLOSE`
  // error which manifests when the HTTP/2 stream terminates unexpectedly
  // (e.g. during brief network hiccups).

  const isPrematureClose =
    err instanceof Error &&
    // eslint-disable-next-line
    ((err as any).code === "ERR_STREAM_PREMATURE_CLOSE" ||
      err.message?.includes("Premature close"));

  if (isPrematureClose) {
    try {
      this.onItem({
        id: `error-${Date.now()}`,
        type: "message",
        role: "system",
        content: [
          {
            type: "input_text",



 ... (clipped 106 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Unstructured Logging: Logging uses free-form text via log() and console-style outputs without guarantees against
sensitive data inclusion or structured format for monitoring.

Referred Code
public cancel(): void {
  if (this.terminated) {
    return;
  }
  if (isLoggingEnabled()) {
    log(
      `AgentLoop.cancel() invoked – currentStream=${Boolean(
        this.currentStream,
      )} execAbortController=${Boolean(
        this.execAbortController,
      )} generation=${this.generation}`,
    );
  }
  (
    this.currentStream as { controller?: { abort?: () => void } } | null
  )?.controller?.abort?.();

  this.canceled = true;
  this.execAbortController?.abort();
  if (isLoggingEnabled()) {
    log("AgentLoop.cancel(): execAbortController.abort() called");



 ... (clipped 56 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Path Handling Risk: The patch application writes files from patch input with limited path validation,
potentially allowing path traversal or unintended file writes within the project tree.

Referred Code
function open_file(p: string): string {
  return fs.readFileSync(p, "utf8");
}

function write_file(p: string, content: string): void {
  if (path.isAbsolute(p)) {
    throw new DiffError("We do not support absolute paths.");
  }
  const parent = path.dirname(p);
  if (parent !== ".") {
    fs.mkdirSync(parent, { recursive: true });
  }
  fs.writeFileSync(p, content, "utf8");
}

function remove_file(p: string): void {
  fs.unlinkSync(p);
}

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link

qodo-code-review bot commented Dec 4, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Prevent state sharing between instances

Move the module-scoped alreadyProcessedResponses set into the AgentLoop class as
a private instance member to prevent state from being shared between different
agent instances.

axilo-cli/src/utils/agent/agent-loop.ts [31-34]

-const alreadyProcessedResponses = new Set();
-
 type AgentLoopParams = {
   model: string;

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical state-sharing bug where alreadyProcessedResponses being in the module scope would cause incorrect behavior across multiple AgentLoop instances.

High
Prevent configuration data loss on save

Modify the saveConfig function to persist all relevant fields from the
AppConfig, including memory and fullAutoErrorMode, to prevent configuration data
loss.

axilo-cli/src/utils/config.ts [344-353]

+const storedConfig: StoredConfig = {
+  model: config.model,
+};
+
+if (config.memory !== undefined) {
+  storedConfig.memory = config.memory;
+}
+if (config.fullAutoErrorMode) {
+  storedConfig.fullAutoErrorMode = config.fullAutoErrorMode;
+}
+
 const ext = extname(targetPath).toLowerCase();
 if (ext === ".yaml" || ext === ".yml") {
-  writeFileSync(targetPath, dumpYaml({ model: config.model }), "utf-8");
+  writeFileSync(targetPath, dumpYaml(storedConfig), "utf-8");
 } else {
   writeFileSync(
     targetPath,
-    JSON.stringify({ model: config.model }, null, 2),
+    JSON.stringify(storedConfig, null, 2) + "\n",
     "utf-8",
   );
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a bug in saveConfig where it only saves the model field, leading to the loss of other configuration settings like memory and fullAutoErrorMode upon saving.

High
Fix resource leak from temp files

Fix a resource leak in openInExternalEditor by ensuring temporary files and
directories are cleaned up using a try...finally block.

axilo-cli/src/lib/text-buffer.ts [143-148]

 // Prepare a temporary file with the current contents.  We use mkdtempSync
 // to obtain an isolated directory and avoid name collisions.
 const tmpDir = fs.mkdtempSync(pathMod.join(os.tmpdir(), "axilo-edit-"));
-const filePath = pathMod.join(tmpDir, "buffer.txt");
+try {
+  const filePath = pathMod.join(tmpDir, "buffer.txt");
 
-fs.writeFileSync(filePath, this.getText(), "utf8");
+  fs.writeFileSync(filePath, this.getText(), "utf8");
 
+  // One snapshot for undo semantics *before* we mutate anything.
+  this.pushUndo();
+
+  // The child inherits stdio so the user can interact with the editor as if
+  // they had launched it directly.
+  const { status, error } = spawnSync(editor, [filePath], {
+    stdio: "inherit",
+  });
+
+  if (error) {
+    throw error;
+  }
+  if (typeof status === "number" && status !== 0) {
+    throw new Error(`External editor exited with status ${status}`);
+  }
+
+  // Read the edited contents back in – normalise line endings to \n.
+  let newText = fs.readFileSync(filePath, "utf8");
+  newText = newText.replace(/\r\n?/g, "\n");
+
+  // Update buffer.
+  this.lines = newText.split("\n");
+  if (this.lines.length === 0) {
+    this.lines = [""];
+  }
+
+  // Position the caret at EOF.
+  this.cursorRow = this.lines.length - 1;
+  this.cursorCol = cpLen(this.line(this.cursorRow));
+
+  // Reset scroll offsets so the new end is visible.
+  this.scrollRow = Math.max(0, this.cursorRow - 1);
+  this.scrollCol = 0;
+
+  this.version++;
+} finally {
+  fs.rmSync(tmpDir, { recursive: true, force: true });
+}
+

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a resource leak where temporary files are not cleaned up, and proposes a robust try...finally block to ensure cleanup, which is a critical fix for application stability.

Medium
Validate patch format for create operations

Add validation to the patch parser to ensure all lines in a 'create' block start
with the '+' prefix, returning null for malformed patches.

axilo-cli/src/utils/agent/parse-apply-patch.ts [83-89]

 if (lastOp?.type === "create") {
+  if (!line.startsWith(HUNK_ADD_LINE_PREFIX)) {
+    // Malformed patch: lines in a 'create' block must start with '+'
+    return null;
+  }
   lastOp.content = appendLine(
     lastOp.content,
     line.slice(HUNK_ADD_LINE_PREFIX.length),
   );
   continue;
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that the patch parser for 'create' operations lacks validation, which could lead to corrupted file content, and proposes adding a check to ensure correctness.

Medium
Fix race condition in model validation

Modify the Promise.race in isModelSupportedForResponses to use a unique value
(e.g., null) for the timeout case, distinguishing it from an empty model list
from the API.

axilo-cli/src/utils/model-utils.ts [59-91]

 export async function isModelSupportedForResponses(
   model: string | undefined | null,
 ): Promise<boolean> {
   if (
     typeof model !== "string" ||
     model.trim() === "" ||
     RECOMMENDED_MODELS.includes(model)
   ) {
     return true;
   }
 
   const MODEL_LIST_TIMEOUT_MS = 2_000;
 
   try {
-    const models = await Promise.race<Array<string>>([
+    const models = await Promise.race<Array<string> | null>([
       getAvailableModels(),
-      new Promise<Array<string>>((resolve) =>
-        setTimeout(() => resolve([]), MODEL_LIST_TIMEOUT_MS),
+      new Promise<null>((resolve) =>
+        setTimeout(() => resolve(null), MODEL_LIST_TIMEOUT_MS),
       ),
     ]);
 
-    // If the timeout fired we get an empty list → treat as supported to avoid
-    // false negatives.
-    if (models.length === 0) {
+    // If the timeout fired (models is null) or the API returned an empty list,
+    // we default to assuming the model is supported to avoid false negatives.
+    if (models === null || models.length === 0) {
       return true;
     }
 
     return models.includes(model.trim());
   } catch {
     // Network or library failure → don't block start‑up.
     return true;
   }
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a logical flaw in the Promise.race where a successful but empty API response is indistinguishable from a timeout, leading to incorrect validation behavior.

Medium
Fix race condition with broken symlinks

Replace fsSync.unlinkSync with fsSync.rmSync({ force: true }) to prevent a crash
when updating a broken symlink for the log file.

axilo-cli/src/utils/agent/log.ts [80-121]

 export function initLogger(): Logger {
   if (logger) {
     return logger;
   } else if (!process.env["DEBUG"]) {
     logger = new EmptyLogger();
     return logger;
   }
 
   const isMac = process.platform === "darwin";
   const isWin = process.platform === "win32";
 
   // On Mac and Windows, os.tmpdir() returns a user-specifc folder, so prefer
   // it there. On Linux, use ~/.local/oai-axilo so logs are not world-readable.
   const logDir =
     isMac || isWin
       ? path.join(os.tmpdir(), "oai-axilo")
       : path.join(os.homedir(), ".local", "oai-axilo");
   fsSync.mkdirSync(logDir, { recursive: true });
   const logFile = path.join(logDir, `axilo-cli-${now()}.log`);
   // Write the empty string so the file exists and can be tail'd.
   fsSync.writeFileSync(logFile, "");
 
   // Symlink to axilo-cli-latest.log on UNIX because Windows is funny about
   // symlinks.
   if (!isWin) {
     const latestLink = path.join(logDir, "axilo-cli-latest.log");
     try {
+      // Atomically remove and recreate the symlink.
+      fsSync.rmSync(latestLink, { force: true });
       fsSync.symlinkSync(logFile, latestLink, "file");
     } catch (err: unknown) {
-      const error = err as NodeJS.ErrnoException;
-      if (error.code === "EEXIST") {
-        fsSync.unlinkSync(latestLink);
-        fsSync.symlinkSync(logFile, latestLink, "file");
-      } else {
-        throw err;
-      }
+      // In case of other errors (e.g., permissions), let it fail.
+      // The rmSync should handle the EEXIST case.
+      throw err;
     }
   }
 
   logger = new AsyncLogger(logFile);
   return logger;
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies a potential crash when handling a broken symlink and proposes a more robust solution using fs.rmSync with the force: true option.

Low
Fix unreliable file cache validation

Refactor the file cache validation logic to use a direct equality check (===)
for modification times instead of Math.abs(...) < 1 for improved clarity and
robustness.

axilo-cli/src/utils/singlepass/context_files.ts [371-379]

 const cEntry = FILE_CONTENTS_CACHE.get(filePath);
-if (
-  cEntry &&
-  Math.abs(cEntry.mtime - st.mtime.getTime()) < 1 &&
-  cEntry.size === st.size
-) {
+if (cEntry && cEntry.mtime === st.mtime.getTime() && cEntry.size === st.size) {
   // same mtime, same size => use cache
   results.push({ path: filePath, content: cEntry.content });
 } else {

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 4

__

Why: The suggestion correctly simplifies the mtime comparison to a strict equality check, which is more direct and readable, although the original code's behavior is functionally equivalent for integer timestamps.

Low
General
Remove duplicated code block

Remove a duplicated code block in the cancel() method that checks
this.pendingAborts.size to improve code clarity and maintainability.

axilo-cli/src/utils/agent/agent-loop.ts [126-152]

 // If we have *not* seen any function_call IDs yet there is nothing that
 // needs to be satisfied in a follow‑up request.  In that case we clear
 // the stored lastResponseId so a subsequent run starts a clean turn.
 if (this.pendingAborts.size === 0) {
   try {
     this.onLastResponseId("");
   } catch {
     /* ignore */
   }
 }
 
 // NOTE: We intentionally do *not* clear `lastResponseId` here.  If the
 // stream produced a `function_call` before the user cancelled, KhulnaSoft now
 // expects a corresponding `function_call_output` that must reference that
 // very same response ID.  We therefore keep the ID around so the
 // follow‑up request can still satisfy the contract.
 
-// If we have *not* seen any function_call IDs yet there is nothing that
-// needs to be satisfied in a follow‑up request.  In that case we clear
-// the stored lastResponseId so a subsequent run starts a clean turn.
-if (this.pendingAborts.size === 0) {
-  try {
-    this.onLastResponseId("");
-  } catch {
-    /* ignore */
-  }
-}
-

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 4

__

Why: The suggestion correctly identifies a duplicated block of code within the cancel() method, and removing it improves code maintainability and readability without changing functionality.

Low
  • Update

Copy link
Contributor

@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: 5

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

🟠 Major comments (16)
axilo-cli/examples/prompting_guide.md-11-13 (1)

11-13: Specify language identifiers for fenced code blocks.

Fenced code blocks are missing language tags, which impacts syntax highlighting and readability. Add appropriate language identifiers (bash for shell commands, markdown for content, etc.) to all code blocks.

Apply this diff to add language identifiers:

-```
+```bash
 axilo --help
-```
+```

-```
+```bash
 axilo "write 2-3 sentences on what you can do"
-```
+```

-```
+```bash
 mkdir first-task && cd first-task
 git init
 axilo "Create a file poem.html that renders a poem about the nature of intelligence and programming by you, Axilo. Add some nice CSS and make it look like it's framed on a wall"
-```
+```

-```
+```markdown
 poem.html has been added.

 Highlights:
 - Centered "picture frame" on a warm wall‑colored background using flexbox.
 - Double‑border with drop‑shadow to suggest a wooden frame hanging on a wall.
 - Poem is pre‑wrapped and nicely typeset with Georgia/serif fonts, includes title and small signature.
 - Responsive tweaks keep the frame readable on small screens.

 Open poem.html in a browser and you'll see the poem elegantly framed on the wall.
-```
+```

-```
+```markdown
 Refactor: simplify model names across static documentation

 Can you update docs_site to use a better model naming convention on the site.
@@ -108,7 +108,7 @@ Replace confusing model identifiers with a simplified version wherever they're u

 Write what you changed or tried to do to final_output.md
-```
+```

Also applies to: 17-19, 23-27, 33-43, 95-109

axilo-cli/src/components/vendor/ink-select/use-select-state.js-112-119 (1)

112-119: State update during render is an anti-pattern.

Calling dispatch and setLastOptions directly in the render body (outside a hook) can cause issues with React's rendering cycle, including potential infinite loops or unexpected re-renders. This should be moved into a useEffect.

   const [lastOptions, setLastOptions] = useState(options);
-  if (options !== lastOptions && !isDeepStrictEqual(options, lastOptions)) {
-    dispatch({
-      type: "reset",
-      state: createDefaultState({ visibleOptionCount, defaultValue, options }),
-    });
-    setLastOptions(options);
-  }
+  useEffect(() => {
+    if (options !== lastOptions && !isDeepStrictEqual(options, lastOptions)) {
+      dispatch({
+        type: "reset",
+        state: createDefaultState({ visibleOptionCount, defaultValue, options }),
+      });
+      setLastOptions(options);
+    }
+  }, [options, lastOptions, visibleOptionCount, defaultValue]);
.husky/commit-msg-5-8 (1)

5-8: The commitlint detection logic may not work correctly.

The check command -v commitlint looks for a global commitlint binary, but commitlint is typically run via npx and may not be in PATH. This check will likely always fail and trigger installation on every commit, even when commitlint is already installed as a dev dependency.

Consider removing this auto-install logic and documenting that npm install should be run first, or check for the package in node_modules instead:

-# Install commitlint if not present
-if ! command -v commitlint >/dev/null 2>&1; then
-  echo "Installing commitlint..."
-  npm install --save-dev @commitlint/{cli,config-conventional}
-fi
+# Commitlint should be installed via npm install

Committable suggestion skipped: line range outside the PR's diff.

axilo-cli/examples/camerascii/run.sh-63-68 (1)

63-68: Add error handling for directory change and Axilo launch.

Line 63 is missing error handling for the cd command, which could cause the script to execute in the wrong directory if it fails. Additionally, the yq/jq pipeline and axilo command should validate success.

Based on the static analysis hint, apply this diff to add proper error handling:

-cd "run_$new_run_number"
+cd "run_$new_run_number" || {
+  echo "Error: Failed to change to run_$new_run_number directory"
+  exit 1
+}
 
 # Launch Axilo
 echo "Launching..."
-description=$(yq -o=json '.' ../../task.yaml | jq -r '.description')
-axilo "$description"
+description=$(yq -o=json '.' ../../task.yaml 2>/dev/null | jq -r '.description' 2>/dev/null) || {
+  echo "Error: Failed to read task description from ../../task.yaml"
+  exit 1
+}
+axilo "$description" || {
+  echo "Error: Axilo command failed"
+  exit 1
+}
axilo-cli/.github/workflows/ci.yml-156-163 (1)

156-163: Update to the non-deprecated release action.

The actions/create-release@v1 action is deprecated. Use softprops/action-gh-release@v2 instead, which is already used in the docker job.

Apply this diff:

       - name: Create GitHub Release
-        uses: actions/create-release@v1
+        uses: softprops/action-gh-release@v2
+        if: startsWith(github.ref, 'refs/tags/')
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-        with:
-          tag_name: ${{ github.ref }}
-          release_name: Release ${{ github.ref }}
-          draft: false
-          prerelease: false
.github/actions/people/action.yml-8-10 (1)

8-10: Token input is not passed to the Docker container.

The token input is defined but not passed to the Docker container as an environment variable or argument. The container won't have access to the token.

 runs:
   using: "docker"
   image: "Dockerfile"
+  env:
+    INPUT_TOKEN: ${{ inputs.token }}

Alternatively, if the Dockerfile's entrypoint expects arguments:

 runs:
   using: "docker"
   image: "Dockerfile"
+  args:
+    - ${{ inputs.token }}
.github/workflows/people.yml-87-93 (1)

87-93: Update to codecov-action@v4.

The codecov/codecov-action@v3 action uses a Node.js 16 runner which is deprecated by GitHub. Update to v4 to avoid future workflow failures.

Apply this diff:

       - name: Upload coverage to Codecov
         if: github.event_name != 'workflow_dispatch' || github.event.inputs.debug_enabled != 'true'
-        uses: codecov/codecov-action@v3
+        uses: codecov/codecov-action@v4
.github/workflows/people.yml-39-43 (1)

39-43: Update to setup-python@v5.

The setup-python@v4 action uses a Node.js 16 runner which is deprecated by GitHub. Update to v5 to avoid future workflow failures.

Apply this diff:

-      - name: Set up Python ${{ env.PYTHON_VERSION }}
-        uses: actions/setup-python@v4
+      - name: Set up Python ${{ env.PYTHON_VERSION }}
+        uses: actions/setup-python@v5
.github/workflows/people.yml-28-30 (1)

28-30: Move env.DOCKER_IMAGE out of container.image — env context is not supported there.

GitHub Actions only supports github, needs, strategy, matrix, vars, and inputs contexts in the container.image field. Using ${{ env.DOCKER_IMAGE }} will fail at runtime. Set the image reference directly (e.g., image: myregistry/myimage:tag) or use a job-level variable via the vars context.

axilo-cli/src/components/chat/terminal-message-history.tsx-33-36 (1)

33-36: Potential unsafe non-null assertion.

On line 34, batch.map(({ item }) => item!) uses a non-null assertion, but according to the BatchEntry type definition (lines 10-13), either item or group can be present—not both. This means item could be undefined for group entries, making the non-null assertion potentially unsafe.

Consider filtering or providing a fallback:

  const [messages, debug] = useMemo(
-    () => [batch.map(({ item }) => item!), process.env["DEBUG"]],
+    () => [batch.filter(({ item }) => item != null).map(({ item }) => item!), process.env["DEBUG"]],
    [batch],
  );

Or, if group entries should be handled differently, adjust the logic to account for both entry types.

.github/actions/people/tests/test_graphql.py-55-73 (1)

55-73: Same async/await concern in error test.

The test_get_graphql_response_error test has the same issue: async decorator but no await on line 68-71.

Ensure consistency with the actual function signature. If sync, this test will pass but won't actually test async behavior. If async, the missing await means the coroutine is never executed and the test passes incorrectly.

axilo-cli/src/components/chat/message-history.tsx-24-28 (1)

24-28: Unsafe non-null assertion on optional item property.

Line 28 uses item! but BatchEntry.item is optional (item?: ResponseItem). When a BatchEntry contains a group instead of an item, this will produce undefined values in the messages array, which could cause runtime errors when rendering.

Filter out entries without item:

 const MessageHistory: React.FC<MessageHistoryProps> = ({
   batch,
   headerProps,
 }) => {
-  const messages = batch.map(({ item }) => item!);
+  const messages = batch
+    .filter((entry): entry is BatchEntry & { item: ResponseItem } => entry.item != null)
+    .map(({ item }) => item);

Or if groups should also be rendered, the component needs additional logic to handle GroupedResponseItem.

axilo-cli/Dockerfile-49-60 (1)

49-60: USER directive confusion: appuser is created but node is used.

The Dockerfile creates a non-root appuser (lines 50-52) but then switches to node on line 60. The appuser setup is effectively dead code. Additionally, placing USER node after CMD is unusual—Dockerfile instructions after CMD will still execute during build, but the ordering suggests intent confusion.

Consider one of these approaches:

  1. Remove appuser entirely if you intend to use the built-in node user:
-# Create a non-root user
-RUN addgroup -S appuser && adduser -S appuser -G appuser
-RUN chown -R appuser:appuser /app
-USER appuser
-
 # Health check
 HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
     CMD node -e "require('http').get('http://localhost:3000/health')"

 # Default command
 CMD ["node", "dist/cli.js"]
-USER node
+USER node
  1. Use appuser consistently and remove the USER node line.
axilo-cli/scripts/run_in_container.sh-50-52 (1)

50-52: Fix argument handling to avoid word splitting issues.

Using "$@" inside a double-quoted string with other text causes incorrect expansion. The $@ is intended to pass multiple arguments, but when embedded in a string like "cd ... && axilo ... \"$@\"", only $1 is used and remaining args are dropped or mishandled.

Apply this diff to fix the argument handling:

-docker exec axilo bash -c "cd \"$WORK_DIR\" && axilo --dangerously-auto-approve-everything -q \"$@\""
+CMD="$*"
+docker exec axilo bash -c "cd \"$WORK_DIR\" && axilo --dangerously-auto-approve-everything -q $CMD"

Alternatively, if a single command string is always expected:

-docker exec axilo bash -c "cd \"$WORK_DIR\" && axilo --dangerously-auto-approve-everything -q \"$@\""
+docker exec axilo bash -c "cd \"$WORK_DIR\" && axilo --dangerously-auto-approve-everything -q \"$1\""

The first approach uses $* which expands all positional parameters as a single string (separated by the first character of IFS). The second approach is cleaner if only a single quoted command string is expected per the usage examples.

axilo-cli/src/components/chat/terminal-chat.tsx-191-208 (1)

191-208: Potential race condition in initial input processing.

The effect clears initialPrompt and initialImagePaths immediately (lines 203-204) before confirming agent?.run() was actually called. If agent is undefined on the first run, the prompts are cleared but never processed. When agent becomes available on a subsequent render, initialPrompt will already be empty.

Consider deferring the clear until after confirming the agent ran:

   useEffect(() => {
     const processInitialInputItems = async () => {
       if (
         (!initialPrompt || initialPrompt.trim() === "") &&
         (!initialImagePaths || initialImagePaths.length === 0)
       ) {
         return;
       }
+      if (!agent) {
+        return; // Wait for agent to be ready
+      }
       const inputItems = [
         await createInputItem(initialPrompt || "", initialImagePaths || []),
       ];
       // Clear them to prevent subsequent runs
       setInitialPrompt("");
       setInitialImagePaths([]);
-      agent?.run(inputItems);
+      agent.run(inputItems);
     };
     processInitialInputItems();
   }, [agent, initialPrompt, initialImagePaths]);
axilo-cli/src/components/vendor/ink-text-input.tsx-294-300 (1)

294-300: Bug: Boundary checks use the wrong variable.

The boundary checks on lines 294-300 compare cursorOffset (the old cursor position) instead of nextCursorOffset (the computed new position). This means the clamping logic never actually takes effect.

Apply this diff to fix the boundary checks:

-      if (cursorOffset < 0) {
+      if (nextCursorOffset < 0) {
         nextCursorOffset = 0;
       }

-      if (cursorOffset > originalValue.length) {
+      if (nextCursorOffset > originalValue.length) {
         nextCursorOffset = originalValue.length;
       }
🟡 Minor comments (23)
axilo-cli/src/components/vendor/ink-select/option-map.js-1-25 (1)

1-25: Duplicate option values will cause data loss.

If two options share the same value, the second entry will overwrite the first in the Map (line 19: items.push([option.value, item])). The linked list remains intact, but Map lookups would only find the later item. Consider validating uniqueness or documenting this constraint.

axilo-cli/src/components/vendor/ink-select/use-select-state.js-4-80 (1)

4-80: Add a default case to the reducer.

The reducer switch statement lacks a default case. If an unknown action type is dispatched, the function will implicitly return undefined, which could corrupt state.

     case "reset": {
       return action.state;
     }
+    default: {
+      return state;
+    }
   }
 };
axilo-cli/examples/camerascii/task.yaml-2-5 (1)

2-5: Clarify the screenshot reference.

The description instructs users to "Take a look at the screenshot details," but no screenshot is provided in this directory or mentioned in the context. Ensure the referenced screenshot exists at a documented location, or update the description to be self-contained.

axilo-cli/CONTRIBUTING.md-36-36 (1)

36-36: Update branch reference to match actual workflow.

The documentation references "main branch," but this PR targets the "dev" branch. Update the branch name to reflect the actual branching strategy used in this repository.

Apply this diff if "dev" is the primary branch:

-4. Once approved, it will be merged into the main branch
+4. Once approved, it will be merged into the dev branch
axilo-cli/src/components/vendor/ink-spinner.tsx-21-36 (1)

21-36: Fallback safely for unknown spinner types

If type is anything other than "dots" or "ball", frames becomes an empty array, and frames[frame] will be undefined while frame keeps incrementing. It’s safer to always fall back to the "dots" frames:

 export default function Spinner({
   type = "dots",
 }: {
   type?: string;
 }): JSX.Element {
-  const frames = spinnerTypes[type || "dots"] || [];
+  const frames = spinnerTypes[type] ?? spinnerTypes.dots;
   const interval = 80;
   const [frame, setFrame] = useState(0);

This ensures a valid animation even when callers pass an unexpected type.

axilo-cli/examples/build-codex-demo/run.sh-49-60 (1)

49-60: Handle potential cd failure into the new run directory

If cd "run_$new_run_number" fails (e.g., mkdir error, permissions), the script will keep running in the previous directory and execute axilo from the wrong place. Mirroring the earlier cd runs || exit 1 makes this safer.

- cd "run_$new_run_number"
+ cd "run_$new_run_number" || exit 1

This also resolves the ShellCheck SC2164 warning.

axilo-cli/.github/workflows/ci.yml-61-61 (1)

61-61: Fix the tag detection conditional.

The condition contains(github.ref, 'refs/tags/') will match any ref containing that substring, including branches like feature/refs/tags/test. Use startsWith() instead for precise tag detection.

Apply this diff:

-    if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
+    if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')

Apply the same fix to line 128:

-    if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
+    if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')

Also applies to: 128-128

axilo-cli/.github/workflows/ci.yml-149-153 (1)

149-153: Remove redundant NODE_AUTH_TOKEN environment variable.

The inline .npmrc file (line 150) creates a literal token at runtime, which npm will use directly. The NODE_AUTH_TOKEN environment variable (line 153) is unused because npm prioritizes the literal token in .npmrc over environment variables. Either use the inline .npmrc with a literal token (current approach), or remove the inline echo and rely on setup-node to create .npmrc with NODE_AUTH_TOKEN substitution—not both.

axilo-cli/src/components/help-overlay.tsx-46-46 (1)

46-46: Replace HTML entity with plain ampersand.

The &amp; HTML entity will render literally in the terminal rather than as &. Use a plain & character instead for correct terminal display.

Apply this diff:

         <Text>
-          <Text color="cyan">/history</Text> – show command &amp; file history
+          <Text color="cyan">/history</Text> – show command & file history
           for this session
         </Text>
         <Text>
-          <Text color="cyan">/clear</Text> – clear screen &amp; context
+          <Text color="cyan">/clear</Text> – clear screen & context
         </Text>

Also applies to: 50-50

axilo-cli/examples/prompt-analyzer/task.yaml-10-10 (1)

10-10: Fix typo in description.

"reaons" should be "reasons" in the phrase "primarily for aesthetic reaons".

Apply this diff:

-     - Click "cluster" and see progress stream in a small window (primarily for aesthetic reaons)
+     - Click "cluster" and see progress stream in a small window (primarily for aesthetic reasons)
axilo-cli/examples/impossible-pong/run.sh-63-63 (1)

63-63: Add error handling for directory change.

The script changes into the newly created run directory without error handling. If the directory creation failed silently or the directory is inaccessible, the script will continue in the wrong directory and potentially cause issues.

Apply this diff to add error handling:

-cd "run_$new_run_number"
+cd "run_$new_run_number" || exit 1
axilo-cli/examples/prompt-analyzer/run.sh-63-63 (1)

63-63: Add error handling for directory change.

The script changes into the newly created run directory without error handling. This is the same issue found in the impossible-pong example's run.sh.

Apply this diff:

-cd "run_$new_run_number"
+cd "run_$new_run_number" || exit 1
axilo-cli/src/cli.tsx-150-152 (1)

150-152: URL inconsistency with branding.

The error message refers to "KhulnaSoft API key" but the URL points to platform.openai.com. Ensure branding consistency.

README.md-269-269 (1)

269-269: Typo: "siginificantly" → "significantly".

-This project is under active development and the code will likely change pretty siginificantly. We'll update this message once that's complete!
+This project is under active development and the code will likely change pretty significantly. We'll update this message once that's complete!
README.md-123-123 (1)

123-123: Missing word in Node.js requirement.

-| Node.js                     | **22 newer** (LTS recommended)                                  |
+| Node.js                     | **22 or newer** (LTS recommended)                               |
axilo-cli/src/components/singlepass-cli-app.tsx-440-443 (1)

440-443: Error details are discarded; consider logging or displaying them.

The caught error is not logged or shown to the user, making debugging difficult. At minimum, log the error to help diagnose API failures.

     } catch (err) {
       setShowSpinner(false);
+      // eslint-disable-next-line no-console
+      console.error("[SinglePassApp] API error:", err);
       setState("error");
     }
README.md-20-20 (1)

20-20: Broken anchor link due to special character mismatch.

The heading "Non‑interactive / CI mode" uses a non-breaking hyphen (U+2011: ) while the TOC link #non‑interactive--ci-mode uses a regular hyphen. This causes the link to not resolve correctly.

Either use standard hyphens in the heading or update the anchor to match. For example:

-1. [Non‑interactive / CI mode](#non‑interactive--ci-mode)
+1. [Non-interactive / CI mode](#non-interactive--ci-mode)

And update the heading on line 156 to use a regular hyphen as well.

Committable suggestion skipped: line range outside the PR's diff.

README.md-365-365 (1)

365-365: Use markdown link format for email address.

The bare URL/email should use proper markdown formatting.

-Have you discovered a vulnerability or have concerns about model output? Please e‑mail **[email protected]** and we will respond promptly.
+Have you discovered a vulnerability or have concerns about model output? Please e‑mail **[[email protected]](mailto:[email protected])** and we will respond promptly.
axilo-cli/src/components/singlepass-cli-app.tsx-446-478 (1)

446-478: File operation errors are silently swallowed.

All catch blocks in applyFileOps discard errors without logging. This can mask partial failures (e.g., permission denied, disk full) and leave the user thinking all changes applied successfully.

Consider logging errors or collecting them to display a summary:

       } catch {
-        /* ignore */
+        // eslint-disable-next-line no-console
+        console.error(`[applyFileOps] Failed to delete: ${op.path}`);
       }

Committable suggestion skipped: line range outside the PR's diff.

axilo-cli/src/components/chat/multiline-editor.tsx-240-248 (1)

240-248: Dead code in isCtrlE detection.

The condition on lines 245-248 is contradictory: if input === "e", then input.charCodeAt(0) is 101 (ASCII for 'e'), not 5 (Ctrl+E). This branch can never be true.

       const isCtrlE =
         (key.ctrl && (input === "e" || input === "\x05")) ||
-        input === "\x05" ||
-        (!key.ctrl &&
-          input === "e" &&
-          input.length === 1 &&
-          input.charCodeAt(0) === 5);
+        input === "\x05";
axilo-cli/src/components/chat/terminal-chat-response-item.tsx-245-256 (1)

245-256: Global setOptions may cause race conditions with concurrent renders.

The marked library's setOptions modifies global state. If multiple Markdown components render simultaneously with different terminal widths, they could interfere with each other. Also, size.rows is in the dependency array but unused.

Consider using marked.parse with inline options or creating a dedicated parser instance:

   const rendered = React.useMemo(() => {
-    // Configure marked for this specific render
-    setOptions({
-      // @ts-expect-error missing parser, space props
-      renderer: new TerminalRenderer({ ...options, width: size.columns }),
-    });
-    const parsed = parse(children, { async: false }).trim();
+    const renderer = new TerminalRenderer({ ...options, width: size.columns });
+    // @ts-expect-error missing parser, space props
+    const parsed = parse(children, { async: false, renderer }).trim();
     return parsed;
-    // eslint-disable-next-line react-hooks/exhaustive-deps -- options is an object of primitives
-  }, [children, size.columns, size.rows]);
+  }, [children, size.columns]);
.github/actions/people/app/main.py-622-635 (1)

622-635: Potential KeyError if maintainer has no GitHub activity.

If a login in maintainers_logins doesn't appear in authors (e.g., the maintainer hasn't commented on any discussions or PRs), line 626 will raise a KeyError.

Consider adding a guard:

     maintainers = []
     for login in maintainers_logins:
+        if login not in authors:
+            logging.warning(f"Maintainer {login} not found in authors")
+            continue
         user = authors[login]

Committable suggestion skipped: line range outside the PR's diff.

axilo-cli/src/components/chat/terminal-chat-input.tsx-104-120 (1)

104-120: Modulo operation can produce negative index with shift+tab.

When _key.shift is true and s is 0, the calculation (s + (-1)) % (suggestions.length + 1) results in -1 % 4 = -1, which is a negative index. This will cause suggestions[-2] to be undefined when accessed later.

Apply this diff to handle the wrap-around correctly:

         if (_key.tab) {
+          const len = suggestions.length + 1;
           setSelectedSuggestion(
-            (s) => (s + (_key.shift ? -1 : 1)) % (suggestions.length + 1),
+            (s) => (s + (_key.shift ? len - 1 : 1)) % len,
           );
🧹 Nitpick comments (54)
axilo-cli/examples/prompting_guide.md (3)

85-89: Fix hyphenation for compound adjective.

Line 85 uses "longer form input" which should be hyphenated as "longer-form input" when modifying a noun.

Apply this diff:

-For more complex tasks requiring longer form input, you can write the instructions as a file on your local machine:
+For more complex tasks requiring longer-form input, you can write the instructions as a file on your local machine:

91-91: Strengthen phrasing on feedback loop.

Line 91 could be more direct by replacing "give feedback" with "provide feedback."

Apply this diff:

-If Axilo doesn't get it right on the first try, give feedback to fix when you're in interactive mode!
+If Axilo doesn't get it right on the first try, provide feedback to fix when you're in interactive mode!

113-113: Remove redundant phrase.

Line 113 contains a redundancy: "heavy lifting up front" can be simplified to "heavy lifting."

Apply this diff:

-Axilo can be surprisingly self-sufficient for bigger tasks where your preference might be for the agent to do some heavy lifting up front, and allow you to refine its work later.
+Axilo can be surprisingly self-sufficient for bigger tasks where your preference might be for the agent to do some heavy lifting, and allow you to refine its work later.
.github/workflows/dco.yml (3)

2-2: Use block syntax for the event trigger to match YAML formatting conventions.

For consistency with the rest of the workflow and standard GitHub Actions practices, consider using block syntax instead of the inline flow syntax:

-on: [pull_request]
+on:
+  - pull_request

23-26: Add -r flag to read and quote variables to fix shellcheck warnings.

The read command without the -r flag can mangle backslashes in input (SC2162), and the loop should quote variables for robustness:

-unsigned=$(git log --format='%h %s' "$base..$head" | while read sha _; do
-  git show -s --format='%B' "$sha" | grep -qi '^Signed-off-by:' || echo "$sha"
+unsigned=$(git log --format='%h %s' "$base..$head" | while read -r sha _; do
+  git show -s --format='%B' "$sha" | grep -qi '^Signed-off-by:' || echo "$sha"
 done)

28-50: Quote variables in shell output for consistency and to avoid word-splitting issues.

Consistently quote environment variables in echo statements, even when word-splitting is unlikely (SC2086). This follows shell best practices:

           if [ -n "$unsigned" ]; then
             echo "::error ::❌  DCO check failed."
-            echo "## ❌ DCO Check Failed" >> $GITHUB_STEP_SUMMARY
+            echo "## ❌ DCO Check Failed" >> "$GITHUB_STEP_SUMMARY"
             echo ""
             echo "Commits missing the 'Signed-off-by:' footer:"
             echo "$unsigned"
             echo ""
             echo "🛠 **Quick fix (make ONE signed commit):**"
             echo "    git fetch origin"
-            git reset --soft origin/${GITHUB_BASE_REF}
+            git reset --soft "origin/${GITHUB_BASE_REF}"
             echo "    git commit -s -m \"<your message>\""
             echo "    git push --force-with-lease"
             echo ""
             echo "🔧 **Fix individual commits:**"
-            git rebase -i origin/${GITHUB_BASE_REF} --exec \"git commit --amend -s --no-edit\"
+            git rebase -i "origin/${GITHUB_BASE_REF}" --exec \"git commit --amend -s --no-edit\"
             echo "    git push --force-with-lease"
             echo ""
             echo "💡  Or edit the commit message in GitHub UI and add:"
             echo "    Signed-off-by: Your Name <[email protected]>"
             exit 1
           else
-            echo "## ✅ DCO Check Passed" >> $GITHUB_STEP_SUMMARY
+            echo "## ✅ DCO Check Passed" >> "$GITHUB_STEP_SUMMARY"
             echo "All commits are properly signed off!" >> $GITHUB_STEP_SUMMARY
.prettierrc.toml (1)

1-5: Consider alphabetically sorting configuration properties for consistency.

The configuration properties are in mixed order (printWidth, quoteProps, semi, tabWidth, trailingComma). Sorting them alphabetically improves scannability and consistency.

-printWidth = 80
-quoteProps = "consistent"
-semi = true
-tabWidth = 2
-trailingComma = "all"
+printWidth = 80
+quoteProps = "consistent"
+semi = true
+tabWidth = 2
+trailingComma = "all"

(Note: If property order affects Prettier's config parsing, verify before applying.)

axilo-cli/src/components/onboarding/onboarding-approval-mode.tsx (2)

7-8: Misplaced TODO comment.

This TODO references cli-spinners and Node v20.9.0, but this component doesn't appear to use spinners. Consider moving this comment to the relevant file or removing if no longer applicable.


14-16: Placeholder onChange handler does nothing.

The onChange={() => {}} means user selections have no effect. The commented-out code suggests intended behavior with onReviewCommand, but ReviewDecision and onReviewCommand aren't defined in this component's scope.

Would you like me to help implement the onChange handler, or should this be tracked as a follow-up issue?

axilo-cli/src/components/vendor/ink-select/use-select-state.js (1)

143-147: Verify options dependency is necessary in the effect.

The useEffect includes options in its dependency array, but options isn't used in the effect body—only state.value, state.previousValue, and onChange are referenced. This could cause unnecessary onChange calls when options change. Consider removing options from the dependencies.

   useEffect(() => {
     if (state.value && state.previousValue !== state.value) {
       onChange?.(state.value);
     }
-  }, [state.previousValue, state.value, options, onChange]);
+  }, [state.previousValue, state.value, onChange]);
axilo-cli/.editorconfig (1)

7-9: Remove redundant section.

Lines 7-9 duplicate the indentation rules already defined in the [*] section (lines 3-5). Since all files inherit these settings, the JS/TS-specific section is unnecessary.

Apply this diff to remove the redundancy:

 root = true
 
 [*]
 indent_style = space
 indent_size = 2
-
-[*.{js,ts,jsx,tsx}]
-indent_style = space
-indent_size = 2
axilo-cli/.nvmrc (1)

1-1: Consider using a more flexible version specifier.

The exact version 22.0.0 prevents automatic patch updates. Using 22 or lts/iron would allow nvm to use the latest patch version of Node.js 22.x, which is safer for security updates.

Apply this diff:

-22.0.0
+22
.github/dependabot.yml (1)

16-17: Double-check Dependabot team assignee format

Dependabot’s assignees usually expect GitHub usernames or org/team slugs without the @. The value "@neopilotai/team" may not resolve correctly; consider using "neopilotai/team" or individual usernames, and confirm against the current Dependabot docs.

Also applies to: 36-37

.husky/pre-push (1)

4-5: Use portable newlines with /bin/sh

With #!/usr/bin/env sh, many shells will print \n literally for echo "\n…" instead of inserting a blank line. For more portable output, prefer either printf '\n🔍 Running pre-push checks...\n' (and similar for other messages) or a separate echo for blank lines.

Also applies to: 8-9, 13-14, 17-18, 21-21

axilo-cli/.dockerignore (1)

4-5: Verify interaction between .dockerignore and your Dockerfile

Ignoring package-lock.json / yarn.lock and Dockerfile is fine only if your axilo-cli/Dockerfile never copies them explicitly. If the Dockerfile does COPY package-lock.json . (or similar), these patterns will cause build failures or non-deterministic installs. Consider allowing lockfiles (and Dockerfile) into the build context, or adjust the Dockerfile accordingly.

Also applies to: 36-38, 40-47

axilo-cli/require-shim.js (1)

10-11: Avoid overwriting an existing global require

If a runtime or test harness already defines globalThis.require, this will silently replace it. To be a safer shim, only install it when missing:

-import { createRequire } from "module";
-globalThis.require = createRequire(import.meta.url);
+import { createRequire } from "module";
+
+if (typeof globalThis.require === "undefined") {
+  globalThis.require = createRequire(import.meta.url);
+}
axilo-cli/src/components/chat/terminal-chat-past-rollout.tsx (1)

51-57: Prefer stable keys for items.map if available

Using the array index as key works but can cause unnecessary remounts if items are reordered or spliced. If ResponseItem exposes a stable identifier (e.g., item.id), consider using that instead of key here.

.github/workflows/issue-manager.yml (1)

30-30: Pin the action to a specific version or commit SHA.

Using @master for the action reference can lead to unexpected breaking changes. Consider pinning to a specific version tag or commit SHA for stability.

-      - uses: khulnasoft/issue-manager@master
+      - uses: khulnasoft/[email protected]  # or specific SHA
.github/workflows/label-approved.yml (1)

17-17: Pin the action to a specific version or commit SHA.

Using @master for the action reference can lead to unexpected breaking changes. Consider pinning to a specific version tag or commit SHA for stability.

-      - uses: khulnasoft/label-approved@master
+      - uses: khulnasoft/[email protected]  # or specific SHA
axilo-cli/examples/camerascii/run.sh (1)

26-27: Add error handling for the YAML parsing pipeline.

The yq and jq pipeline could fail if ../task.yaml is missing or malformed. Consider adding error handling to provide a clear failure message.

Apply this diff to add error handling:

 # Grab task name for logging
-task_name=$(yq -o=json '.' ../task.yaml | jq -r '.name')
+task_name=$(yq -o=json '.' ../task.yaml 2>/dev/null | jq -r '.name' 2>/dev/null) || {
+  echo "Error: Failed to read task name from ../task.yaml"
+  exit 1
+}
 echo "Checking for runs for task: $task_name"
axilo-cli/.eslintrc.cjs (1)

42-43: Address the FIXME comment about explicit function return types.

The commented-out rule @typescript-eslint/explicit-function-return-type would improve type safety. Consider enabling it if the codebase is ready for explicit return type annotations.

Do you want me to open an issue to track enabling this rule and updating the codebase to comply?

axilo-cli/src/components/chat/use-message-grouping.ts (1)

46-75: Consider refactoring the duplicated grouping logic.

Lines 46-72 contain repetitive code for detecting and grouping function_call items at different positions in lastFew. The logic can be simplified to reduce duplication.

Consider this refactored approach:

const batch: Array<{ item?: ResponseItem; group?: GroupedResponseItem }> = [];

// Find the first function_call in the last three items
const firstFuncCallIdx = lastFew.findIndex(item => item?.type === 'function_call');

if (firstFuncCallIdx !== -1) {
  const toolCall = parseToolCall(lastFew[firstFuncCallIdx]);
  batch.push({
    group: {
      label: toolCall?.autoApproval?.group || "Running command",
      items: lastFew.slice(firstFuncCallIdx),
    },
  });
  
  // Add any items before the function_call individually
  for (let i = 0; i < firstFuncCallIdx; i++) {
    batch.push({ item: lastFew[i] });
  }
} else {
  lastFew.forEach((item) => batch.push({ item }));
}
axilo-cli/src/components/history-overlay.tsx (1)

169-173: Consider reformatting the nested ternary for readability.

The nested ternary with Array.isArray checks is functional but could be clearer with explicit formatting or early returns.

-    const cmdArray: Array<string> | undefined = Array.isArray(argsObj?.["cmd"])
-      ? (argsObj!["cmd"] as Array<string>)
-      : Array.isArray(argsObj?.["command"])
-      ? (argsObj!["command"] as Array<string>)
-      : undefined;
+    const cmdArray: Array<string> | undefined =
+      Array.isArray(argsObj?.["cmd"])
+        ? (argsObj!["cmd"] as Array<string>)
+        : Array.isArray(argsObj?.["command"])
+          ? (argsObj!["command"] as Array<string>)
+          : undefined;
axilo-cli/examples/prompt-analyzer/template/cluster_prompts.py (3)

1-1: Script is not executable but has shebang.

The static analysis indicates the shebang is present but the file is not executable. Either remove the shebang or make the file executable.

chmod +x axilo-cli/examples/prompt-analyzer/template/cluster_prompts.py

165-165: Add strict=True to zip() to catch length mismatches.

If the embedding API unexpectedly returns fewer vectors than requested, silent truncation would corrupt the cache mapping. Using strict=True surfaces this issue immediately.

-        cache.update(dict(zip(texts_to_embed, new_embeddings)))
+        cache.update(dict(zip(texts_to_embed, new_embeddings, strict=True)))

448-448: Remove unused scatter variable.

The scatter variable is assigned but never used. Remove the assignment or use _ as a placeholder if the return value is intentionally discarded.

-    scatter = plt.scatter(xy[:, 0], xy[:, 1], c=labels, cmap="tab20", s=20, alpha=0.8)
+    plt.scatter(xy[:, 0], xy[:, 1], c=labels, cmap="tab20", s=20, alpha=0.8)
axilo-cli/scripts/build_container.sh (1)

1-3: Add error handling for robustness.

The script lacks set -e which means failures in the docker build command won't cause the script to exit with an error code. This can mask build failures in CI pipelines.

 #!/bin/bash
+set -euo pipefail
 
 docker build -t axilo -f axilo-cli/Dockerfile axilo-cli
.github/actions/people/requirements.txt (1)

1-15: Consider updating pinned dependencies.

Most dependencies are pinned to versions from mid-2023. While no other security issues were flagged, consider updating to receive bug fixes and improvements. Using a range (like pyyaml>=5.3.1,<6.0.0) for more packages could ease maintenance.

.github/workflows/people.yml (1)

38-49: Consider removing redundant Python setup.

The workflow runs in a container (${{ env.DOCKER_IMAGE }}) that likely already has Python 3.11 installed, but then runs setup-python@v4 and pip install again. This is redundant if the container image includes all necessary dependencies. Consider either:

  • Removing the container and using the Python setup actions directly on the runner, or
  • Removing the Python setup steps and ensuring the container has all dependencies pre-installed
axilo-cli/examples/prompt-analyzer/template/README.md (1)

53-53: Standardize Markdown emphasis style.

The emphasis uses underscores _(none)_ but should use asterisks *(none)* for consistency with Markdown best practices.

Apply this diff:

-| `--cache`              | _(none)_                 | embed­ding cache path (JSON). Speeds up repeated runs – new texts are appended automatically.         |
+| `--cache`              | *(none)*                 | embed­ding cache path (JSON). Speeds up repeated runs – new texts are appended automatically.         |
axilo-cli/src/cli_singlepass.tsx (2)

39-39: Clarify the purpose of the empty default export.

The empty default export export default {}; appears unnecessary since the module already exports a named function runSinglePass. Unless this serves a specific purpose (e.g., module compatibility or avoiding default export side effects), consider removing it.

-export default {};

17-23: Minor: Non-standard hyphen character in comments.

The comment uses non-breaking hyphens (‑) instead of regular hyphens (-) in "long‑running", "render‑options", and "Ctrl+C". While this doesn't affect functionality, standard ASCII hyphens are more conventional in code comments for consistency.

axilo-cli/src/components/chat/message-history.tsx (1)

14-22: Unused props in component signature.

groupCounts, items, userMsgCount, confirmationPrompt, and loading are defined in MessageHistoryProps but not destructured or used in the component. This suggests either incomplete implementation or props that should be removed.

Either implement usage for these props or remove them from the interface:

 type MessageHistoryProps = {
   batch: Array<BatchEntry>;
-  groupCounts: Record<string, number>;
-  items: Array<ResponseItem>;
-  userMsgCount: number;
-  confirmationPrompt: React.ReactNode;
-  loading: boolean;
   headerProps: TerminalHeaderProps;
 };
axilo-cli/src/components/chat/terminal-chat-command-review.tsx (2)

98-102: Inconsistent deny message for 'n' shortcut.

Line 101 uses "Don't do that, keep going though" but DEFAULT_DENY_MESSAGE on line 12-13 is "Don't do that, but keep trying to fix the problem". This creates inconsistent messaging between pressing 'n' directly vs. submitting empty input.

Use the constant for consistency:

       } else if (input === "n") {
         onReviewCommand(
           ReviewDecision.NO_CONTINUE,
-          "Don't do that, keep going though",
+          DEFAULT_DENY_MESSAGE,
         );

34-51: Props inspection via type assertion is pragmatic but fragile.

The approach of inspecting confirmationPrompt.props.commandForDisplay works but couples this component tightly to the internal structure of TerminalChatToolCallCommand. If that component's props change, this logic breaks silently.

Consider passing commandForDisplay directly as a prop to TerminalChatCommandReview for explicit dependency:

export function TerminalChatCommandReview({
  confirmationPrompt,
  commandForDisplay,  // Add explicit prop
  onReviewCommand,
}: {
  confirmationPrompt: React.ReactNode;
  commandForDisplay?: string;
  onReviewCommand: (decision: ReviewDecision, customMessage?: string) => void;
}): React.ReactElement {
  const showAlwaysApprove = React.useMemo(() => {
    if (!commandForDisplay) return true;
    const baseCmd = commandForDisplay.split("\n")[0]?.trim().split(/\s+/)[0] ?? "";
    return baseCmd !== "apply_patch";
  }, [commandForDisplay]);
axilo-cli/examples/impossible-pong/template/index.html (2)

141-142: Consider clamping ball position after wall bounce.

The current wall bounce logic reverses velocity but doesn't constrain the ball's position. With high velocities (especially in "insane" mode with acceleration), the ball could get stuck outside the canvas boundaries.

Apply this diff to clamp the ball position:

     // Wall bounce
-    if (ball.y < 0 || ball.y > canvas.height) ball.vy *= -1;
+    if (ball.y - ballRadius < 0 || ball.y + ballRadius > canvas.height) {
+      ball.vy *= -1;
+      ball.y = Math.max(ballRadius, Math.min(canvas.height - ballRadius, ball.y));
+    }

144-161: Consider preventing rapid repeated paddle collisions.

The collision detection works correctly, but if the ball gets stuck inside a paddle hitbox (due to high velocity or timing), it could trigger multiple bounces per frame.

Consider adding a cooldown or checking that ball.vx has the correct sign before bouncing:

     // Paddle collision
     let paddle = ball.x < canvas.width / 2 ? player : ai;
     if (
       ball.x - ballRadius < paddle.x + paddleWidth &&
       ball.x + ballRadius > paddle.x &&
       ball.y > paddle.y &&
-      ball.y < paddle.y + paddleHeight
+      ball.y < paddle.y + paddleHeight &&
+      ((paddle === player && ball.vx < 0) || (paddle === ai && ball.vx > 0))
     ) {
       ball.vx *= -1;

This ensures the ball is moving toward the paddle before bouncing.

axilo-cli/scripts/init_firewall.sh (2)

27-28: Consider adding more allowed domains or simplifying the loop.

The loop currently processes only one domain (api.openai.com). The backslash continuation and loop structure suggest this was designed for multiple domains.

Either add additional required domains:

 for domain in \
-    "api.openai.com"; do
+    "api.openai.com" \
+    "other-required-domain.com"; do

Or simplify to a direct assignment if only one domain is needed:

-for domain in \
-    "api.openai.com"; do
+domain="api.openai.com"
     echo "Resolving $domain..."
     ips=$(dig +short A "$domain")
     ...
-done

90-96: Clarify comment to match the actual domain being verified.

The comment mentions "KhulnaSoft API" while the code checks api.openai.com. This inconsistency could cause confusion.

Apply this diff for clarity:

-# Verify KhulnaSoft API access
+# Verify KhulnaSoft API (api.openai.com) access
 if ! curl --connect-timeout 5 https://api.openai.com >/dev/null 2>&1; then

Alternatively, if these are distinct services, use consistent terminology.

axilo-cli/src/components/chat/terminal-chat-input-thinking.tsx (1)

7-65: Consider removing or re-enabling the commented thinking texts.

The thinkingTexts array contains 50+ creative messages that are commented out, leaving only "Thinking" active. Commented code adds maintenance burden without providing functionality.

Options:

  1. Remove the commented code if the feature was intentionally disabled
  2. Re-enable the messages if the variety adds value to the UX
  3. Move to a feature flag if you want to toggle this behavior

Do you want me to open an issue to track this decision?

axilo-cli/src/cli.tsx (1)

254-259: Ternary formatting could be clearer.

The nested ternary is slightly misaligned. Consider aligning the ? and : operators consistently or using if/else for readability.

 const approvalPolicy: ApprovalPolicy =
   cli.flags.fullAuto || cli.flags.approvalMode === "full-auto"
     ? AutoApprovalMode.FULL_AUTO
-    : cli.flags.autoEdit
-    ? AutoApprovalMode.AUTO_EDIT
-    : AutoApprovalMode.SUGGEST;
+    : cli.flags.autoEdit || cli.flags.approvalMode === "auto-edit"
+      ? AutoApprovalMode.AUTO_EDIT
+      : AutoApprovalMode.SUGGEST;

Note: The approvalMode === "auto-edit" case from the flag description isn't handled—if that's intentional, ignore this; otherwise, add support for it.

axilo-cli/src/components/typeahead-overlay.tsx (1)

74-94: Sort stability concern when no query is present.

Returning 0 from a comparator doesn't guarantee stable sort across all JavaScript engines. To truly preserve the original initialItems order when q.length === 0, you need to compare by original index.

-  const ranked = filtered.sort((a, b) => {
+  const ranked = [...filtered].sort((a, b) => {
     if (a.value === currentValue) {
       return -1;
     }
     if (b.value === currentValue) {
       return 1;
     }

     // Preserve original order when no query is present so we keep any caller
     // defined prioritisation (e.g. recommended models).
     if (q.length === 0) {
-      return 0;
+      // Use original index for stable ordering
+      return items.indexOf(a) - items.indexOf(b);
     }

     const ia = a.label.toLowerCase().indexOf(q);
     const ib = b.label.toLowerCase().indexOf(q);
     if (ia !== ib) {
       return ia - ib;
     }
     return a.label.localeCompare(b.label);
   });
axilo-cli/src/components/chat/terminal-chat.tsx (1)

155-175: Dead code in timer cleanup else-branch.

At line 165-167, handle is always null in the else-branch because it's only assigned within the if-block. The clearInterval(null) call is harmless but unnecessary.

   useEffect(() => {
     let handle: ReturnType<typeof setInterval> | null = null;
     // Only tick the "thinking…" timer when the agent is actually processing
     // a request *and* the user is not being asked to review a command.
     if (loading && confirmationPrompt == null) {
       setThinkingSeconds(0);
       handle = setInterval(() => {
         setThinkingSeconds((s) => s + 1);
       }, 1000);
     } else {
-      if (handle) {
-        clearInterval(handle);
-      }
       setThinkingSeconds(0);
     }
     return () => {
       if (handle) {
         clearInterval(handle);
       }
     };
   }, [loading, confirmationPrompt]);
axilo-cli/src/hooks/use-confirmation.ts (1)

50-55: Consider wrapping submitConfirmation in useCallback.

Unlike requestConfirmation, submitConfirmation is not wrapped in useCallback, meaning it's recreated on every render. This could cause unnecessary re-renders in components that use it as a dependency.

-  // Called whenever user picks Yes / No
-  const submitConfirmation = (result: ConfirmationResult) => {
+  // Called whenever user picks Yes / No
+  const submitConfirmation = useCallback((result: ConfirmationResult) => {
     if (current) {
       current.resolve(result);
       advanceQueue();
     }
-  };
+  }, [current, advanceQueue]);
.github/actions/people/tests/integration/test_integration.py (2)

5-8: Unused imports.

MagicMock and Github are imported but never used in this file.

 from pathlib import Path
-from unittest.mock import patch, MagicMock
+from unittest.mock import patch
 
 import pytest
-from github import Github
 
 from app.main import (

221-222: Consider explicit encoding when reading file.

For portability, specify the encoding explicitly when opening files.

-        with open(output_file) as f:
+        with open(output_file, encoding="utf-8") as f:
             data = json.load(f)
axilo-cli/src/components/vendor/ink-text-input.tsx (2)

64-66: Prefer strict equality (!==) over loose equality (!=).

For consistency and to avoid potential type coercion issues, use strict inequality.

-      if (lastMatch != 0) {
+      if (lastMatch !== 0) {
         lastMatch += 1;
       }

238-260: Consider reusing findPrevWordJump to reduce duplication.

The logic for Meta+backspace/delete (lines 239-255) duplicates the regex-based word boundary search already implemented in findPrevWordJump. Consolidating would improve maintainability.

       } else if (key.meta && (key.backspace || key.delete)) {
-        const regex = /[\s,.;!?]+/g;
-        let lastMatch = 0;
-        let currentMatch: RegExpExecArray | null;
-
-        const stringToCursorOffset = originalValue
-          .slice(0, cursorOffset)
-          .replace(/[\s,.;!?]+$/, "");
-
-        // Loop through all matches
-        while ((currentMatch = regex.exec(stringToCursorOffset)) !== null) {
-          lastMatch = currentMatch.index;
-        }
-
-        // Include the last match unless it is the first character
-        if (lastMatch != 0) {
-          lastMatch += 1;
-        }
-
-        nextValue =
-          stringToCursorOffset.slice(0, lastMatch) +
-          originalValue.slice(cursorOffset, originalValue.length);
-        nextCursorOffset = lastMatch;
+        const jumpTo = findPrevWordJump(originalValue, cursorOffset);
+        nextValue =
+          originalValue.slice(0, jumpTo) +
+          originalValue.slice(cursorOffset);
+        nextCursorOffset = jumpTo;
       }
axilo-cli/src/components/singlepass-cli-app.tsx (1)

33-59: localStorage is unavailable in Node.js — consider removing the dead code path.

In a CLI/Node.js environment, localStorage is never defined, so lines 35-40 and 63-68 are effectively dead code. The file-based fallback is always used. Removing the localStorage branches would simplify the code.

axilo-cli/src/components/chat/multiline-editor.tsx (2)

363-364: Accessing private properties via type assertion is fragile.

The code accesses scrollRow and scrollCol using as any, which bypasses type safety and will break silently if TextBuffer's internals change.

Consider exposing these as part of TextBuffer's public API (e.g., getScrollPosition()) to make the contract explicit and maintainable.


27-34: Consider isolating test-only patches to a separate module.

The EventEmitter.prototype patches are well-documented but pollute the global prototype. Consider moving them to a dedicated test setup file (e.g., vitest.setup.ts) to keep production code clean and make the test-only nature explicit.

axilo-cli/src/app.tsx (1)

37-37: Unnecessary function initializer for constant value.

useState(() => false) uses lazy initialization, which is only beneficial for expensive computations. For a simple boolean, useState(false) is cleaner and equivalent.

-  const [accepted, setAccepted] = useState(() => false);
+  const [accepted, setAccepted] = useState(false);
.github/actions/people/app/main.py (1)

503-523: Dead code: commented-out issue expert aggregation.

The get_experts function has extensive commented-out code for issue experts. If this migration is permanent, consider removing the dead code to improve readability. If it's temporary, add a TODO with context.

axilo-cli/src/components/chat/terminal-chat-response-item.tsx (1)

127-142: Consider extracting content type mapping for clarity.

The nested ternary chain works but is slightly hard to read. This is a minor style suggestion.

+const getContentText = (c: { type: string; text?: string; refusal?: string; filename?: string }): string => {
+  switch (c.type) {
+    case "output_text":
+    case "input_text":
+      return c.text ?? "";
+    case "refusal":
+      return c.refusal ?? "";
+    case "input_image":
+      return "<Image>";
+    case "input_file":
+      return c.filename ?? "";
+    default:
+      return "";
+  }
+};

Comment on lines +154 to +157
"from openai import KhulnaSoft\n",
"import os\n",
"\n",
"client = KhulnaSoft(api_key=os.environ.get(\"KHULNASOFT_API_KEY\", \"<your KhulnaSoft API key if not set as env var>\"))\n",
Copy link
Contributor

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

🌐 Web query:

OpenAI Python client import OpenAI class December 2025

💡 Result:

Yes — the modern OpenAI Python client (v1+) uses the OpenAI class. Example:

  • Install: pip install openai
  • Import and use:
    from openai import OpenAI
    client = OpenAI()
    resp = client.chat.completions.create(model="gpt-4o", messages=[{"role":"user","content":"Hello"}])

Sources: [1], [2]

Sources
[1] openai/openai-python (README & examples). GitHub.
[2] openai-quickstart-python (example quickstart). GitHub.


Fix the OpenAI import statement.

Line 154 shows from openai import KhulnaSoft which is incorrect. The correct import should be from openai import OpenAI, and line 157 should instantiate OpenAI() not KhulnaSoft(). Additionally, the environment variable name should be OPENAI_API_KEY not KHULNASOFT_API_KEY.

The corrected code cell should be:

from openai import OpenAI
import os

client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY", "<your OpenAI API key if not set as env var>"))
🤖 Prompt for AI Agents
In axilo-cli/examples/prompt-analyzer/template/Clustering.ipynb around lines 154
to 157, the import and client instantiation are wrong: replace "from openai
import KhulnaSoft" with "from openai import OpenAI", instantiate OpenAI()
instead of KhulnaSoft(), and use the environment variable OPENAI_API_KEY
(including updating the default placeholder string) when passing api_key via
os.environ.get.


## Reporting a Vulnerability

If you discover a security vulnerability in Axilo CLI, please report it to [email protected]. Please do not report security vulnerabilities through public GitHub issues.
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Replace placeholder email address.

The email [email protected] is a placeholder and will prevent users from reporting security vulnerabilities. Replace it with a valid security contact email before merging.

🤖 Prompt for AI Agents
In axilo-cli/SECURITY.md around line 9, the security contact email is a
placeholder ([email protected]); replace it with the project's valid security
contact address (e.g., [email protected] or the official security team's
mailbox), update the line so the document points to the real reporting email,
and commit the change before merging.

NeoPilot and others added 3 commits December 4, 2025 23:24
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: KhulnaSoft bot <[email protected]>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: KhulnaSoft bot <[email protected]>
@khulnasoft-bot khulnasoft-bot added the enhancement New feature or request label Dec 4, 2025
@khulnasoft-bot khulnasoft-bot self-assigned this Dec 4, 2025
Copy link
Contributor

@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: 12

🧹 Nitpick comments (6)
axilo-cli/src/utils/agent/agent-loop.ts (1)

490-496: Model prefix check may match unintended models.

this.model.startsWith("o") will match any model starting with "o" (e.g., hypothetical "other-model"). Consider using a more explicit check.

-            if (this.model.startsWith("o")) {
+            if (/^o[34]-/.test(this.model)) {
               reasoning = { effort: "high" };
               if (this.model === "o3-mini" || this.model === "o4-mini") {
axilo-cli/src/utils/agent/apply-patch.ts (1)

79-79: DiffError should call super with the message.

The DiffError class extends Error but relies on implicit super call behavior. For proper error handling and stack traces, explicitly pass the message.

-export class DiffError extends Error {}
+export class DiffError extends Error {
+  constructor(message: string) {
+    super(message);
+    this.name = 'DiffError';
+  }
+}
axilo-cli/src/utils/model-utils.ts (2)

14-38: Model cache is never invalidated.

The modelsPromise is set once and never cleared. For long-running CLI sessions, this could result in stale model lists if models are added/removed from the account. Consider adding a TTL or cache invalidation mechanism.

 let modelsPromise: Promise<Array<string>> | null = null;
+let modelsCacheTime: number = 0;
+const MODELS_CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
 
 async function fetchModels(): Promise<Array<string>> {
   // ...existing code...
 }
 
 export async function getAvailableModels(): Promise<Array<string>> {
-  if (!modelsPromise) {
+  const now = Date.now();
+  if (!modelsPromise || (now - modelsCacheTime > MODELS_CACHE_TTL_MS)) {
     modelsPromise = fetchModels();
+    modelsCacheTime = now;
   }
   return modelsPromise;
 }

35-37: Silent failure on fetch error may hide configuration issues.

When fetchModels catches an error, it returns an empty array without logging. This could mask issues like invalid API keys or network problems that the user should know about.

Consider logging the error when logging is enabled:

   } catch (err) {
+    if (isLoggingEnabled()) {
+      log(`Failed to fetch models: ${err instanceof Error ? err.message : String(err)}`);
+    }
     return [];
   }

Note: You'll need to import log and isLoggingEnabled from ./agent/log.js.

axilo-cli/src/utils/config.ts (2)

150-158: Non-cryptographic hash for key fingerprint is weak.

The djb2-style hash produces only ~8 hex characters and is not collision-resistant. While the comment acknowledges this, consider using a proper cryptographic hash if this fingerprint is used for any security-sensitive logging or auditing.

+import { createHash } from 'crypto';
+
   private getKeyFingerprint(): string {
-    // Simple hash function - in production, consider using a proper cryptographic hash
-    let hash = 0;
-    for (let i = 0; i < this.apiKey.length; i++) {
-      const char = this.apiKey.charCodeAt(i);
-      hash = ((hash << 5) - hash) + char;
-      hash = hash & hash; // Convert to 32bit integer
-    }
-    return `fp_${Math.abs(hash).toString(16).substring(0, 8)}`;
+    if (!this.apiKey) return 'fp_none';
+    const hash = createHash('sha256').update(this.apiKey).digest('hex');
+    return `fp_${hash.substring(0, 8)}`;
   }

224-259: Symlink traversal during directory tree walk should be protected with fs.realpathSync().

The function uses dirname() for traversal, which doesn't follow symlinks, but existsSync() does. If startDir or any intermediate directory is a symlink, it could traverse outside the intended hierarchy. Wrap the initial resolvePath() with fs.realpathSync() to resolve symlinks before traversal, ensuring the walk stays within the real filesystem structure.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4b20d08 and b80b966.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (8)
  • .husky/commit-msg (1 hunks)
  • axilo-cli/Dockerfile (1 hunks)
  • axilo-cli/src/cli.tsx (1 hunks)
  • axilo-cli/src/utils/agent/agent-loop.ts (1 hunks)
  • axilo-cli/src/utils/agent/apply-patch.ts (1 hunks)
  • axilo-cli/src/utils/config.ts (1 hunks)
  • axilo-cli/src/utils/config.ts.bak (1 hunks)
  • axilo-cli/src/utils/model-utils.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • axilo-cli/src/cli.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • .husky/commit-msg
🧰 Additional context used
🧬 Code graph analysis (2)
axilo-cli/src/utils/model-utils.ts (1)
axilo-cli/src/utils/config.ts (1)
  • apiKeyManager (167-167)
axilo-cli/src/utils/agent/agent-loop.ts (1)
axilo-cli/src/utils/config.ts (4)
  • AppConfig (208-214)
  • KHULNASOFT_TIMEOUT_MS (34-35)
  • apiKeyManager (167-167)
  • KHULNASOFT_BASE_URL (36-36)
🔇 Additional comments (5)
axilo-cli/Dockerfile (1)

67-68: The review comment references code that does not exist in the actual Dockerfile. Lines 67-68 in the current Dockerfile are an ENTRYPOINT directive and a comment, not the tarball copy operations mentioned. The actual Dockerfile correctly copies dist directories directly from the builder stage (lines 43-44) and runs the CLI via node /app/packages/cli/dist/index.js (line 69), which is the proper approach for this monorepo setup. No tarball generation occurs in the build process—pnpm build produces standard dist directories that are directly copied and executed. This review comment does not apply to the current code.

Likely an incorrect or invalid review comment.

axilo-cli/src/utils/agent/apply-patch.ts (1)

25-56: LGTM on assemble_changes function.

The logic correctly handles ADD, UPDATE, and DELETE cases, and properly skips unchanged files.

axilo-cli/src/utils/model-utils.ts (1)

60-91: Well-designed graceful degradation pattern.

The timeout race with fallback to true ensures startup isn't blocked by slow network or API issues. The logic correctly handles undefined/empty models and uses RECOMMENDED_MODELS as a fast path.

axilo-cli/src/utils/config.ts (1)

313-459: Config loading and bootstrap logic is well-structured.

The loadConfig function handles JSON/YAML fallback gracefully, merges project docs with instructions, and bootstraps first-run defaults without blocking on errors. The separation of concerns is clean.

axilo-cli/src/utils/agent/agent-loop.ts (1)

480-482: The code referenced in this review comment does not exist in the repository.

The file axilo-cli/src/utils/agent/agent-loop.ts cannot be found. Additionally, the types ResponseInputItem and ResponseItem, the function stageItem, and the variable turnInput do not appear anywhere in the codebase. The repository uses a monorepo structure under packages/ rather than an axilo-cli/ directory.

This review comment appears to reference a different codebase or an outdated version of the code.

Likely an incorrect or invalid review comment.

Comment on lines +50 to +52
RUN addgroup -S appuser && adduser -S appuser -G appuser
RUN chown -R appuser:appuser /app
USER appuser
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Contradictory USER statements; clarify intended runtime user.

The Dockerfile switches users multiple times:

  • Line 50–52: Create appuser and set USER appuser
  • Line 60: USER node (overrides appuser)
  • Line 72: USER root (for firewall sudoers setup)
  • Line 76: USER node (final runtime user)

This is confusing. Clarify which user should run the application at runtime. If the app should run as appuser, then:

  • Avoid switching to node on line 60
  • After firewall setup (line 75), switch back to appuser instead of node

If node is the intended user, consider consolidating setup to use node throughout (except where root is explicitly needed for sudoers/firewall configuration).

-USER appuser
-...
-CMD ["node", "dist/cli.js"]
-USER node
+USER appuser
+...
+CMD ["node", "dist/cli.js"]

(or adjust consistently depending on your intended runtime user)

Also applies to: 60-60, 72-72, 76-76

🤖 Prompt for AI Agents
axilo-cli/Dockerfile lines 50-52, 60, 72, 76: the Dockerfile currently switches
users multiple times (creates appuser and sets USER appuser, then switches to
node, back to root, then node), which is contradictory—choose a single intended
runtime user and make the USER usage consistent; if runtime should be appuser,
remove the USER node at line 60, ensure any steps needing node are run as root
or appuser, and after the root-only firewall/sudoers setup (around line 72)
switch back to USER appuser instead of node (also update chown to match appuser
if necessary); if runtime should be node, create/use node throughout (omit
creating appuser or change chown to node) and only switch to root for
firewall/sudoers tasks then back to USER node; in short, consolidate the user
switches to a single runtime user and update chown/ownership and the USER lines
accordingly so there are no conflicting USER declarations.

Comment on lines +59 to +76
CMD ["node", "dist/cli.js"]
USER node

# Install global packages
ENV NPM_CONFIG_PREFIX=/usr/local/share/npm-global
ENV PATH=$PATH:/usr/local/share/npm-global/bin

# Install axilo
COPY --from=builder /app/dist/axilo.tgz ./axilo.tgz
RUN npm install -g axilo.tgz

# Copy and set up firewall script
COPY scripts/init_firewall.sh /usr/local/bin/
USER root
RUN chmod +x /usr/local/bin/init_firewall.sh && \
echo "node ALL=(root) NOPASSWD: /usr/local/bin/init_firewall.sh" > /etc/sudoers.d/node-firewall && \
chmod 0440 /etc/sudoers.d/node-firewall
USER node No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Move all setup instructions before CMD; instructions after CMD are semantically confusing.

In Docker, instructions placed after CMD execute during the build phase but violate conventional Dockerfile structure. All setup (ENV, COPY, RUN, USER switches) should precede the CMD instruction, which should be the final or near-final line. Reorganize the Dockerfile to consolidate lines 60–76 before line 59 (CMD).

Additionally, the multiple USER switches (appuser → node → root → node) are contradictory. Clarify the intended runtime user.

Suggested restructure (pseudocode):

- Lines 49–52: Create non-root user (appuser) ✓
- Before line 59 (CMD):
  - Set up NPM_CONFIG_PREFIX and PATH for global installs
  - Install global packages (axilo)
  - Copy firewall script and set permissions (as root)
  - Return to intended runtime user
- Line 59: CMD ["node", "dist/cli.js"]
  (no instructions after CMD)

Do you want me to propose a complete restructure?

🤖 Prompt for AI Agents
In axilo-cli/Dockerfile around lines 59 to 76, several build and permission
setup steps (ENV, PATH, npm global install, COPY of axilo.tgz, firewall script
copy/chmod and sudoers entry) appear after the CMD which should be the final
runtime instruction; also the multiple USER switches (appuser → node → root →
node) are inconsistent. Move all ENV, PATH, npm install, COPY and RUN (firewall
script + sudoers) steps to before the CMD, set NPM_CONFIG_PREFIX and update PATH
prior to running npm install, perform installations and file permission changes
as root (or escalate only for those commands), then switch to the intended
non-root runtime user once at the end before emitting CMD
["node","dist/cli.js"]; consolidate and remove unnecessary USER toggles and
document which non-root user is intended for runtime.

customDenyMessage?: string;
};

const alreadyProcessedResponses = new Set();
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Module-level Set may leak memory across sessions.

alreadyProcessedResponses is a module-scoped Set that accumulates processed response IDs but is never cleared. Over long-running CLI sessions or repeated invocations, this could grow unbounded and cause memory pressure.

Consider either:

  1. Moving the set into AgentLoop as an instance property and clearing it in terminate(), or
  2. Using a bounded LRU cache if cross-instance deduplication is truly needed.
🤖 Prompt for AI Agents
In axilo-cli/src/utils/agent/agent-loop.ts around line 31, the module-level Set
alreadyProcessedResponses currently lives for the process lifetime and can leak
memory; move this Set into the AgentLoop class as an instance property (e.g.,
this.alreadyProcessedResponses) so it is scoped per AgentLoop instance, replace
all uses of the module variable with the instance property, and clear it in the
AgentLoop.terminate() method (or reinitialize it) to release memory;
alternatively, if deduplication must span instances, replace the Set with a
bounded LRU cache implementation to cap growth.

Comment on lines +126 to +158
// If we have *not* seen any function_call IDs yet there is nothing that
// needs to be satisfied in a follow‑up request. In that case we clear
// the stored lastResponseId so a subsequent run starts a clean turn.
if (this.pendingAborts.size === 0) {
try {
this.onLastResponseId("");
} catch {
/* ignore */
}
}

// NOTE: We intentionally do *not* clear `lastResponseId` here. If the
// stream produced a `function_call` before the user cancelled, KhulnaSoft now
// expects a corresponding `function_call_output` that must reference that
// very same response ID. We therefore keep the ID around so the
// follow‑up request can still satisfy the contract.

// If we have *not* seen any function_call IDs yet there is nothing that
// needs to be satisfied in a follow‑up request. In that case we clear
// the stored lastResponseId so a subsequent run starts a clean turn.
if (this.pendingAborts.size === 0) {
try {
this.onLastResponseId("");
} catch {
/* ignore */
}
}

// NOTE: We intentionally do *not* clear `lastResponseId` here. If the
// stream produced a `function_call` before the user cancelled, KhulnaSoft now
// expects a corresponding `function_call_output` that must reference that
// very same response ID. We therefore keep the ID around so the
// follow‑up request can still satisfy the contract.
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Duplicated code block for clearing lastResponseId.

Lines 126-135 and 143-152 contain identical logic for checking pendingAborts.size === 0 and calling this.onLastResponseId(""). The associated comment blocks (lines 137-141 and 154-158) are also duplicated. This appears to be an accidental copy-paste.

Apply this diff to remove the duplicate:

     // If we have *not* seen any function_call IDs yet there is nothing that
     // needs to be satisfied in a follow‑up request.  In that case we clear
     // the stored lastResponseId so a subsequent run starts a clean turn.
     if (this.pendingAborts.size === 0) {
       try {
         this.onLastResponseId("");
       } catch {
         /* ignore */
       }
     }
 
     // NOTE: We intentionally do *not* clear `lastResponseId` here.  If the
     // stream produced a `function_call` before the user cancelled, KhulnaSoft now
     // expects a corresponding `function_call_output` that must reference that
     // very same response ID.  We therefore keep the ID around so the
     // follow‑up request can still satisfy the contract.
-
-    // If we have *not* seen any function_call IDs yet there is nothing that
-    // needs to be satisfied in a follow‑up request.  In that case we clear
-    // the stored lastResponseId so a subsequent run starts a clean turn.
-    if (this.pendingAborts.size === 0) {
-      try {
-        this.onLastResponseId("");
-      } catch {
-        /* ignore */
-      }
-    }
-
-    // NOTE: We intentionally do *not* clear `lastResponseId` here.  If the
-    // stream produced a `function_call` before the user cancelled, KhulnaSoft now
-    // expects a corresponding `function_call_output` that must reference that
-    // very same response ID.  We therefore keep the ID around so the
-    // follow‑up request can still satisfy the contract.
     this.onLoading(false);
📝 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
// If we have *not* seen any function_call IDs yet there is nothing that
// needs to be satisfied in a follow‑up request. In that case we clear
// the stored lastResponseId so a subsequent run starts a clean turn.
if (this.pendingAborts.size === 0) {
try {
this.onLastResponseId("");
} catch {
/* ignore */
}
}
// NOTE: We intentionally do *not* clear `lastResponseId` here. If the
// stream produced a `function_call` before the user cancelled, KhulnaSoft now
// expects a corresponding `function_call_output` that must reference that
// very same response ID. We therefore keep the ID around so the
// follow‑up request can still satisfy the contract.
// If we have *not* seen any function_call IDs yet there is nothing that
// needs to be satisfied in a follow‑up request. In that case we clear
// the stored lastResponseId so a subsequent run starts a clean turn.
if (this.pendingAborts.size === 0) {
try {
this.onLastResponseId("");
} catch {
/* ignore */
}
}
// NOTE: We intentionally do *not* clear `lastResponseId` here. If the
// stream produced a `function_call` before the user cancelled, KhulnaSoft now
// expects a corresponding `function_call_output` that must reference that
// very same response ID. We therefore keep the ID around so the
// follow‑up request can still satisfy the contract.
// If we have *not* seen any function_call IDs yet there is nothing that
// needs to be satisfied in a follow‑up request. In that case we clear
// the stored lastResponseId so a subsequent run starts a clean turn.
if (this.pendingAborts.size === 0) {
try {
this.onLastResponseId("");
} catch {
/* ignore */
}
}
// NOTE: We intentionally do *not* clear `lastResponseId` here. If the
// stream produced a `function_call` before the user cancelled, KhulnaSoft now
// expects a corresponding `function_call_output` that must reference that
// very same response ID. We therefore keep the ID around so the
// follow‑up request can still satisfy the contract.
this.onLoading(false);
🤖 Prompt for AI Agents
In axilo-cli/src/utils/agent/agent-loop.ts around lines 126 to 158 there is an
accidental duplicate of the same logic and comment that clears lastResponseId
when pendingAborts.size === 0; remove the second copy (the repeated if-block and
its duplicated NOTE comment — i.e. the duplicated lines starting at the second
if-check through the second NOTE comment) so only one instance of the check,
call to this.onLastResponseId(""), and explanatory NOTE remains.

setSessionId(this.sessionId);
setCurrentModel(this.model);

this.hardAbort = new AbortController();
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Reassignment of readonly property will throw at runtime.

hardAbort is declared as private readonly hardAbort = new AbortController(); on line 96, but line 264 attempts to reassign it in the constructor. This will cause a TypeError at runtime because you cannot assign to a readonly property.

Remove the redundant assignment since hardAbort is already initialized at declaration:

-    this.hardAbort = new AbortController();
-
     this.hardAbort.signal.addEventListener(
📝 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
this.hardAbort = new AbortController();
this.hardAbort.signal.addEventListener(
🤖 Prompt for AI Agents
In axilo-cli/src/utils/agent/agent-loop.ts around line 264, the constructor
reassigns the private readonly field hardAbort (this.hardAbort = new
AbortController()), which will throw at runtime; remove that redundant
assignment from the constructor so the field remains initialized only at
declaration (or if you need to recreate it, change the field declaration to
non-readonly and update usages accordingly).

Comment on lines +600 to +609
function write_file(p: string, content: string): void {
if (path.isAbsolute(p)) {
throw new DiffError("We do not support absolute paths.");
}
const parent = path.dirname(p);
if (parent !== ".") {
fs.mkdirSync(parent, { recursive: true });
}
fs.writeFileSync(p, content, "utf8");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

write_file lacks path traversal protection present in remove_file.

The remove_file function properly validates that the resolved path stays within process.cwd(), but write_file only checks for absolute paths. A relative path like ../../etc/malicious would bypass this check and write outside the intended directory.

Apply consistent security checks as done in remove_file:

 function write_file(p: string, content: string): void {
   if (path.isAbsolute(p)) {
     throw new DiffError("We do not support absolute paths.");
   }
+  
+  // Resolve the path to prevent directory traversal
+  const resolvedPath = path.resolve(p);
+  const currentDir = process.cwd();
+  
+  // Ensure the resolved path is within the current working directory
+  if (!resolvedPath.startsWith(currentDir + path.sep) && resolvedPath !== currentDir) {
+    throw new DiffError("Path traversal detected");
+  }
+  
   const parent = path.dirname(p);
   if (parent !== ".") {
     fs.mkdirSync(parent, { recursive: true });
   }
-  fs.writeFileSync(p, content, "utf8");
+  fs.writeFileSync(resolvedPath, content, "utf8");
 }
🤖 Prompt for AI Agents
In axilo-cli/src/utils/agent/apply-patch.ts around lines 600 to 609, write_file
currently only rejects absolute paths and thus allows path traversal via "../"
to write outside the repository; update write_file to mirror remove_file's
safety: resolve the target path against process.cwd(), verify the resolved path
starts with process.cwd() (or is equal), and throw a DiffError if it does not,
then create parent directories and write the file using the resolved path; keep
the original error message semantics and UTF-8 write.

Comment on lines +620 to +624

// Ensure the resolved path is within the current working directory
if (!resolvedPath.startsWith(currentDir)) {
throw new Error('Path traversal detected');
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Path traversal check has edge case with startsWith.

The check !resolvedPath.startsWith(currentDir) can have false positives. For example, if currentDir is /home/user and resolvedPath is /home/username/file, it would incorrectly pass. Append a path separator to the comparison.

-  if (!resolvedPath.startsWith(currentDir)) {
+  if (!resolvedPath.startsWith(currentDir + path.sep) && resolvedPath !== currentDir) {
     throw new Error('Path traversal detected');
   }
🤖 Prompt for AI Agents
In axilo-cli/src/utils/agent/apply-patch.ts around lines 620 to 624, the path
traversal check uses startsWith(currentDir) which can false-positive for
directories like /home/user vs /home/username; update the check to compare using
a canonicalized directory path with a trailing path separator (e.g., normalize
and resolve currentDir, ensure it ends with path.sep) and then verify
resolvedPath startsWith that directory-with-separator (or alternatively use
path.relative and confirm it does not begin with '..' or escape the directory).

Comment on lines +94 to +120
private checkRateLimit(): void {
const now = Date.now();

// Reset counter if we're in a new rate limit window
if (now - this.lastUsed > this.RATE_LIMIT_WINDOW) {
this.usageCount = 0;
this.lastUsed = now;
}

this.usageCount++;

if (this.usageCount > this.RATE_LIMIT) {
const timeLeft = Math.ceil((this.lastUsed + this.RATE_LIMIT_WINDOW - now) / 1000);
throw new Error(`API rate limit exceeded. Please try again in ${timeLeft} seconds.`);
}
}

public getApiKey(): string | null {
if (!this.isSet) {
return null;
}

this.checkRateLimit();
this.logKeyUsage('api_key_accessed');

return this.apiKey;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Rate limiting on getApiKey() is misplaced and will break API calls.

The rate limit check throws an exception when exceeded, but it's applied to getApiKey() calls rather than actual API requests. This means:

  1. If any code calls getApiKey() frequently (e.g., for logging, validation, or display), it will consume rate limit quota
  2. When the limit is exceeded, getApiKey() throws, likely crashing the calling code that may not expect an exception from a getter
  3. The rate limit doesn't correspond to actual API usage since getApiKey() may be called without making an API call

Either remove the rate limiting from getApiKey() (let the OpenAI SDK handle API rate limits) or make it a warning rather than an exception:

   public getApiKey(): string | null {
     if (!this.isSet) {
       return null;
     }
     
-    this.checkRateLimit();
+    // Track usage for metrics, but don't throw - let the API handle its own limits
+    this.trackUsage();
     this.logKeyUsage('api_key_accessed');
     
     return this.apiKey;
   }
+
+  private trackUsage(): void {
+    const now = Date.now();
+    if (now - this.lastUsed > this.RATE_LIMIT_WINDOW) {
+      this.usageCount = 0;
+      this.lastUsed = now;
+    }
+    this.usageCount++;
+  }
🤖 Prompt for AI Agents
In axilo-cli/src/utils/config.ts around lines 94 to 120, the rate-limit check is
incorrectly applied inside getApiKey(): this causes non-API operations that
merely access the key to consume quota and throw exceptions; remove the call to
checkRateLimit() (and optional logKeyUsage rate increment) from getApiKey() so
that returning the stored key is a simple, non-throwing getter, or alternatively
replace checkRateLimit() invocation with a non-throwing warning/log (no
exception) and do not increment usageCount here; move actual rate limiting to
the code paths that perform network/API requests (or rely on the OpenAI SDK) so
only actual API calls affect quota.

Comment on lines +461 to +497
export const saveConfig = (
config: AppConfig,
configPath = CONFIG_FILEPATH,
instructionsPath = INSTRUCTIONS_FILEPATH,
): void => {
// If the caller passed the default JSON path *and* a YAML config already
// exists on disk, save back to that YAML file instead to preserve the
// user's chosen format.
let targetPath = configPath;
if (
configPath === CONFIG_FILEPATH &&
!existsSync(configPath) &&
(existsSync(CONFIG_YAML_FILEPATH) || existsSync(CONFIG_YML_FILEPATH))
) {
targetPath = existsSync(CONFIG_YAML_FILEPATH)
? CONFIG_YAML_FILEPATH
: CONFIG_YML_FILEPATH;
}

const dir = dirname(targetPath);
if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true });
}

const ext = extname(targetPath).toLowerCase();
if (ext === ".yaml" || ext === ".yml") {
writeFileSync(targetPath, dumpYaml({ model: config.model }), "utf-8");
} else {
writeFileSync(
targetPath,
JSON.stringify({ model: config.model }, null, 2),
"utf-8",
);
}

writeFileSync(instructionsPath, config.instructions, "utf-8");
};
Copy link
Contributor

@coderabbitai coderabbitai bot Dec 4, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

saveConfig only persists model, discarding other config fields.

The saveConfig function only writes { model: config.model }, ignoring approvalMode, fullAutoErrorMode, and memory fields that are part of StoredConfig. This could cause data loss if users expect round-trip persistence.

Persist all relevant configuration fields:

   const ext = extname(targetPath).toLowerCase();
   if (ext === ".yaml" || ext === ".yml") {
-    writeFileSync(targetPath, dumpYaml({ model: config.model }), "utf-8");
+    const stored: StoredConfig = { model: config.model };
+    if (config.memory) stored.memory = config.memory;
+    if (config.fullAutoErrorMode) stored.fullAutoErrorMode = config.fullAutoErrorMode;
+    writeFileSync(targetPath, dumpYaml(stored), "utf-8");
   } else {
-    writeFileSync(
-      targetPath,
-      JSON.stringify({ model: config.model }, null, 2),
-      "utf-8",
-    );
+    const stored: StoredConfig = { model: config.model };
+    if (config.memory) stored.memory = config.memory;
+    if (config.fullAutoErrorMode) stored.fullAutoErrorMode = config.fullAutoErrorMode;
+    writeFileSync(targetPath, JSON.stringify(stored, null, 2), "utf-8");
   }
🤖 Prompt for AI Agents
In axilo-cli/src/utils/config.ts around lines 461 to 497, saveConfig currently
serializes only { model: config.model } which drops other StoredConfig fields
(approvalMode, fullAutoErrorMode, memory, etc.); change the serialization to
persist the complete stored configuration object (e.g., serialize the full
StoredConfig or spread the relevant fields) when writing YAML or JSON so all
config properties are round-tripped intact (use the same object for dumpYaml and
JSON.stringify and keep writing instructionsPath as-is).

Copy link
Contributor

Choose a reason for hiding this comment

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

In axilo-cli/src/utils/config.ts around lines 461 to 497, saveConfig currently
serializes only { model: config.model } which drops other StoredConfig fields
(approvalMode, fullAutoErrorMode, memory, etc.); change the serialization to
persist the complete stored configuration object (e.g., serialize the full
StoredConfig or spread the relevant fields) when writing YAML or JSON so all
config properties are round-tripped intact (use the same object for dumpYaml and
JSON.stringify and keep writing instructionsPath as-is).

Copy link
Contributor

Choose a reason for hiding this comment

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

@khulnasoft-bot, I'll provide the complete fix for the saveConfig function to persist all available StoredConfig fields:

export const saveConfig = (
  config: AppConfig,
  configPath = CONFIG_FILEPATH,
  instructionsPath = INSTRUCTIONS_FILEPATH,
): void => {
  // If the caller passed the default JSON path *and* a YAML config already
  // exists on disk, save back to that YAML file instead to preserve the
  // user's chosen format.
  let targetPath = configPath;
  if (
    configPath === CONFIG_FILEPATH &&
    !existsSync(configPath) &&
    (existsSync(CONFIG_YAML_FILEPATH) || existsSync(CONFIG_YML_FILEPATH))
  ) {
    targetPath = existsSync(CONFIG_YAML_FILEPATH)
      ? CONFIG_YAML_FILEPATH
      : CONFIG_YML_FILEPATH;
  }

  const dir = dirname(targetPath);
  if (!existsSync(dir)) {
    mkdirSync(dir, { recursive: true });
  }

  // Build the StoredConfig object with all available fields
  const stored: StoredConfig = { model: config.model };
  if (config.memory !== undefined) {
    stored.memory = config.memory;
  }
  if (config.fullAutoErrorMode !== undefined) {
    stored.fullAutoErrorMode = config.fullAutoErrorMode;
  }

  const ext = extname(targetPath).toLowerCase();
  if (ext === ".yaml" || ext === ".yml") {
    writeFileSync(targetPath, dumpYaml(stored), "utf-8");
  } else {
    writeFileSync(targetPath, JSON.stringify(stored, null, 2), "utf-8");
  }

  writeFileSync(instructionsPath, config.instructions, "utf-8");
};

Key changes:

  1. Created a stored object of type StoredConfig instead of an inline object literal
  2. Conditionally added memory and fullAutoErrorMode fields when they're present
  3. Used the same stored object for both YAML and JSON serialization
  4. Used !== undefined checks to preserve explicit false values for boolean fields

Note: The approvalMode field from StoredConfig cannot be persisted because it doesn't exist in the AppConfig type that saveConfig receives. If approval mode persistence is needed, the function signature would need to be updated to accept it as a parameter or AppConfig would need to include it.

Comment on lines +1 to +356
// NOTE: We intentionally point the TypeScript import at the source file
// (`./auto-approval-mode.ts`) instead of the emitted `.js` bundle. This makes
// the module resolvable when the project is executed via `ts-node`, which
// resolves *source* paths rather than built artefacts. During a production
// build the TypeScript compiler will automatically rewrite the path to
// `./auto-approval-mode.js`, so the change is completely transparent for the
// compiled `dist/` output used by the published CLI.

import type { FullAutoErrorMode } from "./auto-approval-mode.js";

import { log, isLoggingEnabled } from "./agent/log.js";
import { AutoApprovalMode } from "./auto-approval-mode.js";
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
import { load as loadYaml, dump as dumpYaml } from "js-yaml";
import { homedir } from "os";
import { dirname, join, extname, resolve as resolvePath } from "path";

export const DEFAULT_AGENTIC_MODEL = "o4-mini";
export const DEFAULT_FULL_CONTEXT_MODEL = "gpt-4.1";
export const DEFAULT_APPROVAL_MODE = AutoApprovalMode.SUGGEST;
export const DEFAULT_INSTRUCTIONS = "";

export const CONFIG_DIR = join(homedir(), ".axilo");
export const CONFIG_JSON_FILEPATH = join(CONFIG_DIR, "config.json");
export const CONFIG_YAML_FILEPATH = join(CONFIG_DIR, "config.yaml");
export const CONFIG_YML_FILEPATH = join(CONFIG_DIR, "config.yml");

// Keep the original constant name for backward compatibility, but point it at
// the default JSON path. Code that relies on this constant will continue to
// work unchanged.
export const CONFIG_FILEPATH = CONFIG_JSON_FILEPATH;
export const INSTRUCTIONS_FILEPATH = join(CONFIG_DIR, "instructions.md");

export const KHULNASOFT_TIMEOUT_MS =
parseInt(process.env["KHULNASOFT_TIMEOUT_MS"] || "0", 10) || undefined;
export const KHULNASOFT_BASE_URL = process.env["KHULNASOFT_BASE_URL"] || "";
export let KHULNASOFT_API_KEY = process.env["KHULNASOFT_API_KEY"] || "";

export function setApiKey(apiKey: string): void {
KHULNASOFT_API_KEY = apiKey;
}

// Formatting (quiet mode-only).
export const PRETTY_PRINT = Boolean(process.env["PRETTY_PRINT"] || "");

// Represents config as persisted in config.json.
export type StoredConfig = {
model?: string;
approvalMode?: AutoApprovalMode;
fullAutoErrorMode?: FullAutoErrorMode;
memory?: MemoryConfig;
};

// Minimal config written on first run. An *empty* model string ensures that
// we always fall back to DEFAULT_MODEL on load, so updates to the default keep
// propagating to existing users until they explicitly set a model.
export const EMPTY_STORED_CONFIG: StoredConfig = { model: "" };

// Pre‑stringified JSON variant so we don’t stringify repeatedly.
const EMPTY_CONFIG_JSON = JSON.stringify(EMPTY_STORED_CONFIG, null, 2) + "\n";

export type MemoryConfig = {
enabled: boolean;
};

// Represents full runtime config, including loaded instructions
export type AppConfig = {
apiKey?: string;
model: string;
instructions: string;
fullAutoErrorMode?: FullAutoErrorMode;
memory?: MemoryConfig;
};

// ---------------------------------------------------------------------------
// Project doc support (axilo.md)
// ---------------------------------------------------------------------------

export const PROJECT_DOC_MAX_BYTES = 32 * 1024; // 32 kB

const PROJECT_DOC_FILENAMES = ["axilo.md", ".axilo.md", "AXILO.md"];

export function discoverProjectDocPath(startDir: string): string | null {
const cwd = resolvePath(startDir);

// 1) Look in the explicit CWD first:
for (const name of PROJECT_DOC_FILENAMES) {
const direct = join(cwd, name);
if (existsSync(direct)) {
return direct;
}
}

// 2) Fallback: walk up to the Git root and look there
let dir = cwd;
// eslint-disable-next-line no-constant-condition
while (true) {
const gitPath = join(dir, ".git");
if (existsSync(gitPath)) {
// Once we hit the Git root, search its top‑level for the doc
for (const name of PROJECT_DOC_FILENAMES) {
const candidate = join(dir, name);
if (existsSync(candidate)) {
return candidate;
}
}
// If Git root but no doc, stop looking
return null;
}

const parent = dirname(dir);
if (parent === dir) {
// Reached filesystem root without finding Git
return null;
}
dir = parent;
}
}

/**
* Load the project documentation markdown (axilo.md) if present. If the file
* exceeds {@link PROJECT_DOC_MAX_BYTES} it will be truncated and a warning is
* logged.
*
* @param cwd The current working directory of the caller
* @param explicitPath If provided, skips discovery and loads the given path
*/
export function loadProjectDoc(cwd: string, explicitPath?: string): string {
let filepath: string | null = null;

if (explicitPath) {
filepath = resolvePath(cwd, explicitPath);
if (!existsSync(filepath)) {
// eslint-disable-next-line no-console
console.warn(`axilo: project doc not found at ${filepath}`);
filepath = null;
}
} else {
filepath = discoverProjectDocPath(cwd);
}

if (!filepath) {
return "";
}

try {
const buf = readFileSync(filepath);
if (buf.byteLength > PROJECT_DOC_MAX_BYTES) {
// eslint-disable-next-line no-console
console.warn(
`axilo: project doc '${filepath}' exceeds ${PROJECT_DOC_MAX_BYTES} bytes – truncating.`,
);
}
return buf.slice(0, PROJECT_DOC_MAX_BYTES).toString("utf-8");
} catch {
return "";
}
}

// (Receives params for testing)
export type LoadConfigOptions = {
/** Working directory used for project doc discovery */
cwd?: string;
/** Disable inclusion of the project doc */
disableProjectDoc?: boolean;
/** Explicit path to project doc (overrides discovery) */
projectDocPath?: string;
/** Whether we are in fullcontext mode. */
isFullContext?: boolean;
};

export const loadConfig = (
configPath: string | undefined = CONFIG_FILEPATH,
instructionsPath: string | undefined = INSTRUCTIONS_FILEPATH,
options: LoadConfigOptions = {},
): AppConfig => {
// Determine the actual path to load. If the provided path doesn't exist and
// the caller passed the default JSON path, automatically fall back to YAML
// variants.
let actualConfigPath = configPath;
if (!existsSync(actualConfigPath)) {
if (configPath === CONFIG_FILEPATH) {
if (existsSync(CONFIG_YAML_FILEPATH)) {
actualConfigPath = CONFIG_YAML_FILEPATH;
} else if (existsSync(CONFIG_YML_FILEPATH)) {
actualConfigPath = CONFIG_YML_FILEPATH;
}
}
}

let storedConfig: StoredConfig = {};
if (existsSync(actualConfigPath)) {
const raw = readFileSync(actualConfigPath, "utf-8");
const ext = extname(actualConfigPath).toLowerCase();
try {
if (ext === ".yaml" || ext === ".yml") {
storedConfig = loadYaml(raw) as unknown as StoredConfig;
} else {
storedConfig = JSON.parse(raw);
}
} catch {
// If parsing fails, fall back to empty config to avoid crashing.
storedConfig = {};
}
}

const instructionsFilePathResolved =
instructionsPath ?? INSTRUCTIONS_FILEPATH;
const userInstructions = existsSync(instructionsFilePathResolved)
? readFileSync(instructionsFilePathResolved, "utf-8")
: DEFAULT_INSTRUCTIONS;

// Project doc -----------------------------------------------------------
const shouldLoadProjectDoc =
!options.disableProjectDoc &&
process.env["AXILO_DISABLE_PROJECT_DOC"] !== "1";

let projectDoc = "";
let projectDocPath: string | null = null;
if (shouldLoadProjectDoc) {
const cwd = options.cwd ?? process.cwd();
projectDoc = loadProjectDoc(cwd, options.projectDocPath);
projectDocPath = options.projectDocPath
? resolvePath(cwd, options.projectDocPath)
: discoverProjectDocPath(cwd);
if (projectDocPath) {
if (isLoggingEnabled()) {
log(
`[axilo] Loaded project doc from ${projectDocPath} (${projectDoc.length} bytes)`,
);
}
} else {
if (isLoggingEnabled()) {
log(`[axilo] No project doc found in ${cwd}`);
}
}
}

const combinedInstructions = [userInstructions, projectDoc]
.filter((s) => s && s.trim() !== "")
.join("\n\n--- project-doc ---\n\n");

// Treat empty string ("" or whitespace) as absence so we can fall back to
// the latest DEFAULT_MODEL.
const storedModel =
storedConfig.model && storedConfig.model.trim() !== ""
? storedConfig.model.trim()
: undefined;

const config: AppConfig = {
model:
storedModel ??
(options.isFullContext
? DEFAULT_FULL_CONTEXT_MODEL
: DEFAULT_AGENTIC_MODEL),
instructions: combinedInstructions,
};

// -----------------------------------------------------------------------
// First‑run bootstrap: if the configuration file (and/or its containing
// directory) didn't exist we create them now so that users end up with a
// materialised ~/.axilo/config.json file on first execution. This mirrors
// what `saveConfig()` would do but without requiring callers to remember to
// invoke it separately.
//
// We intentionally perform this *after* we have computed the final
// `config` object so that we can just persist the resolved defaults. The
// write operations are guarded by `existsSync` checks so that subsequent
// runs that already have a config will remain read‑only here.
// -----------------------------------------------------------------------

try {
if (!existsSync(actualConfigPath)) {
// Ensure the directory exists first.
const dir = dirname(actualConfigPath);
if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true });
}

// Persist a minimal config – we include the `model` key but leave it as
// an empty string so that `loadConfig()` treats it as "unset" and falls
// back to whatever DEFAULT_MODEL is current at runtime. This prevents
// pinning users to an old default after upgrading Axilo.
const ext = extname(actualConfigPath).toLowerCase();
if (ext === ".yaml" || ext === ".yml") {
writeFileSync(actualConfigPath, dumpYaml(EMPTY_STORED_CONFIG), "utf-8");
} else {
writeFileSync(actualConfigPath, EMPTY_CONFIG_JSON, "utf-8");
}
}

// Always ensure the instructions file exists so users can edit it.
if (!existsSync(instructionsFilePathResolved)) {
const instrDir = dirname(instructionsFilePathResolved);
if (!existsSync(instrDir)) {
mkdirSync(instrDir, { recursive: true });
}
writeFileSync(instructionsFilePathResolved, userInstructions, "utf-8");
}
} catch {
// Silently ignore any errors – failure to persist the defaults shouldn't
// block the CLI from starting. A future explicit `axilo config` command
// or `saveConfig()` call can handle (re‑)writing later.
}

// Only include the "memory" key if it was explicitly set by the user. This
// preserves backward‑compatibility with older config files (and our test
// fixtures) that don't include a "memory" section.
if (storedConfig.memory !== undefined) {
config.memory = storedConfig.memory;
}

if (storedConfig.fullAutoErrorMode) {
config.fullAutoErrorMode = storedConfig.fullAutoErrorMode;
}

return config;
};

export const saveConfig = (
config: AppConfig,
configPath = CONFIG_FILEPATH,
instructionsPath = INSTRUCTIONS_FILEPATH,
): void => {
// If the caller passed the default JSON path *and* a YAML config already
// exists on disk, save back to that YAML file instead to preserve the
// user's chosen format.
let targetPath = configPath;
if (
configPath === CONFIG_FILEPATH &&
!existsSync(configPath) &&
(existsSync(CONFIG_YAML_FILEPATH) || existsSync(CONFIG_YML_FILEPATH))
) {
targetPath = existsSync(CONFIG_YAML_FILEPATH)
? CONFIG_YAML_FILEPATH
: CONFIG_YML_FILEPATH;
}

const dir = dirname(targetPath);
if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true });
}

const ext = extname(targetPath).toLowerCase();
if (ext === ".yaml" || ext === ".yml") {
writeFileSync(targetPath, dumpYaml({ model: config.model }), "utf-8");
} else {
writeFileSync(
targetPath,
JSON.stringify({ model: config.model }, null, 2),
"utf-8",
);
}

writeFileSync(instructionsPath, config.instructions, "utf-8");
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Backup file should not be committed.

The file config.ts.bak appears to be a backup version of config.ts. Backup files should not be committed to the repository as they create maintenance burden and confusion. If this is intentional for migration purposes, consider documenting it or removing after the migration is complete.

Remove this file from the repository:

git rm axilo-cli/src/utils/config.ts.bak

If you need to preserve history, consider using git history rather than keeping backup files in the tree.

🤖 Prompt for AI Agents
axilo-cli/src/utils/config.ts.bak lines 1-356: this is a backup file that should
not be committed; remove it from the repository with git rm
axilo-cli/src/utils/config.ts.bak (or git rm --cached ... if you must keep it
locally), commit the removal, and add an appropriate rule (e.g. *.bak or
axilo-cli/src/utils/*.bak) to .gitignore to prevent future backups from being
committed.

@khulnasoft-bot khulnasoft-bot merged commit ff1da57 into khulnasoft:dev Dec 4, 2025
2 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request Review effort 4/5

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants