1bcoder 0.1.10__tar.gz → 0.1.11__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. {1bcoder-0.1.10 → 1bcoder-0.1.11}/1bcoder.egg-info/PKG-INFO +31 -4
  2. {1bcoder-0.1.10 → 1bcoder-0.1.11}/PKG-INFO +31 -4
  3. {1bcoder-0.1.10 → 1bcoder-0.1.11}/README.md +30 -3
  4. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/agents/advance.txt +3 -2
  5. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/doc/PARAM.md +2 -0
  6. {1bcoder-0.1.10 → 1bcoder-0.1.11}/chat.py +192 -38
  7. {1bcoder-0.1.10 → 1bcoder-0.1.11}/map_query.py +17 -8
  8. {1bcoder-0.1.10 → 1bcoder-0.1.11}/pyproject.toml +1 -1
  9. {1bcoder-0.1.10 → 1bcoder-0.1.11}/1bcoder.egg-info/SOURCES.txt +0 -0
  10. {1bcoder-0.1.10 → 1bcoder-0.1.11}/1bcoder.egg-info/dependency_links.txt +0 -0
  11. {1bcoder-0.1.10 → 1bcoder-0.1.11}/1bcoder.egg-info/entry_points.txt +0 -0
  12. {1bcoder-0.1.10 → 1bcoder-0.1.11}/1bcoder.egg-info/requires.txt +0 -0
  13. {1bcoder-0.1.10 → 1bcoder-0.1.11}/1bcoder.egg-info/top_level.txt +0 -0
  14. {1bcoder-0.1.10 → 1bcoder-0.1.11}/LICENSE +0 -0
  15. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/__init__.py +0 -0
  16. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/agents/ask.txt +0 -0
  17. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/agents/compact.txt +0 -0
  18. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/agents/concepts.txt +0 -0
  19. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/agents/fill.txt +0 -0
  20. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/agents/planning.txt +0 -0
  21. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/agents/scan.txt +0 -0
  22. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/agents/sqlite.txt +0 -0
  23. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/agents/websearch.txt +0 -0
  24. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/aliases.txt +0 -0
  25. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/doc/FLOWS.md +0 -0
  26. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/doc/MCP.md +0 -0
  27. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/doc/OLLAMA_SERVER_PARAM.md +0 -0
  28. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/doc/PROC.md +0 -0
  29. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/doc/TRANSLATE.md +0 -0
  30. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/flows/__pycache__/commit_message.cpython-311.pyc +0 -0
  31. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/flows/commit_message.py +0 -0
  32. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/flows/grounding.py +0 -0
  33. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/flows/py_error_trace.py +0 -0
  34. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/flows/simargl_files.py +0 -0
  35. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/flows/webask.py +0 -0
  36. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/map.txt +0 -0
  37. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/proc/action-required.py +0 -0
  38. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/proc/add-save.py +0 -0
  39. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/proc/assist.py +0 -0
  40. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/proc/collect-files.py +0 -0
  41. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/proc/ctx_cut.py +0 -0
  42. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/proc/extract-code.py +0 -0
  43. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/proc/extract-files.py +0 -0
  44. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/proc/extract-list.py +0 -0
  45. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/proc/grounding-check.py +0 -0
  46. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/proc/md.py +0 -0
  47. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/proc/mdx.py +0 -0
  48. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/proc/pattern-gate.py +0 -0
  49. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/proc/regexp-extract.py +0 -0
  50. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/proc/rude_words.py +0 -0
  51. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/proc/scan-save.py +0 -0
  52. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/proc/secret_check.py +0 -0
  53. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/proc/sql_readonly_guard.py +0 -0
  54. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/proc/tempctx-cut.py +0 -0
  55. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/profiles.txt +0 -0
  56. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/prompts/analysis.txt +0 -0
  57. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/prompts/sumarise.txt +0 -0
  58. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/prompts.txt +0 -0
  59. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/AddFunction.txt +0 -0
  60. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/AskProject.txt +0 -0
  61. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/CheckRequirements.txt +0 -0
  62. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/DockerMySQL.txt +0 -0
  63. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/DockerNginx.txt +0 -0
  64. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/DockerPython.txt +0 -0
  65. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/DockerStack.txt +0 -0
  66. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/DuckDuckGoInstant.txt +0 -0
  67. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/EnvTemplate.txt +0 -0
  68. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/Explain.txt +0 -0
  69. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/ExploreProjectStructure.txt +0 -0
  70. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/GitIgnorePython.txt +0 -0
  71. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/MySQLDump.txt +0 -0
  72. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/NewScript.txt +0 -0
  73. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/PipFreeze.txt +0 -0
  74. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/PyPI.txt +0 -0
  75. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/Refactor.txt +0 -0
  76. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/RunAndFix.txt +0 -0
  77. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/SQLiteSchema.txt +0 -0
  78. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/Translate.txt +0 -0
  79. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/WikiPage.txt +0 -0
  80. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/WikiSearch.txt +0 -0
  81. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/auto-bkup.txt +0 -0
  82. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/edit-control.txt +0 -0
  83. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/parallel_call.txt +0 -0
  84. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/personal/content/create-regular-content.txt +0 -0
  85. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/personal/content/plan.txt +0 -0
  86. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/personal/test/collect-data-from-test-environment.txt +0 -0
  87. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/plan.txt +0 -0
  88. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/remote/create-content-on-remote-server.txt +0 -0
  89. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/set_ctx.txt +0 -0
  90. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/simargl-cli_index_files.txt +0 -0
  91. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/simargl-cli_index_units.txt +0 -0
  92. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/simargl-cli_search.txt +0 -0
  93. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/team-map-worker.txt +0 -0
  94. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/team-search-worker.txt +0 -0
  95. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/team-summarize.txt +0 -0
  96. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/team-tree-worker.txt +0 -0
  97. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/scripts/test.txt +0 -0
  98. {1bcoder-0.1.10 → 1bcoder-0.1.11}/_bcoder_data/teams/code-analysis.yaml +0 -0
  99. {1bcoder-0.1.10 → 1bcoder-0.1.11}/map_index.py +0 -0
  100. {1bcoder-0.1.10 → 1bcoder-0.1.11}/setup.cfg +0 -0
  101. {1bcoder-0.1.10 → 1bcoder-0.1.11}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: 1bcoder
3
- Version: 0.1.10
3
+ Version: 0.1.11
4
4
  Summary: AI coding assistant agent for 1B–7B local models (Ollama, LMStudio, llama.cpp). Terminal REPL with file editing, project map, agents, scripts, and parallel multi-model queries.
5
5
  Project-URL: Homepage, https://github.com/szholobetsky/1bcoder
6
6
  Project-URL: Repository, https://github.com/szholobetsky/1bcoder
@@ -13,6 +13,8 @@ Requires-Dist: tqdm>=4.64
13
13
  Requires-Dist: rich>=13.0
14
14
  Dynamic: license-file
15
15
 
16
+ ![1bcoder](images/1bcoder.png)
17
+
16
18
  # 1bcoder
17
19
 
18
20
  AI coding assistant for small local models (0.5B–4B) — runs via [Ollama](https://ollama.com), [LMStudio](https://lmstudio.ai), or any OpenAI-compatible backend.
@@ -128,7 +130,7 @@ Tasks that require the model to decide *what to look at* — refactoring across
128
130
  - **Command autocorrection** — typos in command names, file paths, and keywords are detected and fixed automatically before execution, for both human input and agent actions
129
131
  - **`/tree [path]`** — display directory tree of the whole project or any subtree; ask to inject into context (or pass `ctx` to skip the prompt)
130
132
  - **`/find <pattern>`** — search filenames and file content with regex; supports `-f`/`-c`/`-i`/`--ext` flags; highlights matches, asks to inject results into context; sets `{{find_files}}` after every search; **`/find <terms> -r`** ranked BM25 mode returns top-10 files by relevance; hidden directories (`.git`, `.venv`, etc.) excluded automatically
131
- - AI proposes a **one-line fix** (`/fix`) or a **SEARCH/REPLACE patch** (`/patch`) — always shows a diff before applying
133
+ - AI proposes a **one-line fix** (`/fix`), a **SEARCH/REPLACE patch** (`/patch`), or a **FIM-based fix** (`/fim`) — always shows a unified diff before applying
132
134
  - **Apply AI code blocks directly** with `/edit <file> code` (new/full file) or `/patch <file> code` (SEARCH/REPLACE from reply, no line numbers needed) — preferred for agent mode
133
135
  - **`<think>` tag support** — reasoning blocks shown in terminal by default; `/think hide` suppresses terminal display; `/think include` keeps reasoning in context for chained turns
134
136
  - Run shell commands and inject their output with `/run`
@@ -453,10 +455,12 @@ Then configure `/translate` to use it:
453
455
  | Command | Description |
454
456
  |---|---|
455
457
  | `/fix <file> [start-end] [hint]` | AI proposes one-line fix, shows diff, asks to apply |
458
+ | `/fim <file> <line or start-end> [-w N] [hint]` | FIM-based fix — model rewrites the marked section, shows unified diff |
456
459
  | `/patch <file> [start-end] [hint]` | AI proposes SEARCH/REPLACE block, shows unified diff |
457
460
  | `/patch <file> code` | Apply SEARCH/REPLACE block from last AI reply (no new LLM call) |
458
461
 
459
- `/fix` is designed for 1B modelsoutput is strictly constrained to `LINE N: content`.
462
+ `/fix` asks the model to output `LINE N: content` simple but small models often lose indentation.
463
+ `/fim` (Fill-In-the-Middle) marks the target line(s) with `<<<...>>>` inside the full file, asks the model to return the corrected file, then diffs the result — no output format to learn, grammar constraints come from the surrounding code. Use `-w N` to limit context to N lines around the target when the file is larger than the context window.
460
464
  `/patch` works better with larger models (7B+) and can replace multiple consecutive lines.
461
465
  `/patch <file> code` is the preferred agent mode edit — the agent writes the SEARCH/REPLACE block in its reply, then calls `/patch <file> code` to apply it without needing line numbers.
462
466
 
@@ -465,6 +469,9 @@ When `/patch` fails to find the SEARCH text it shows a diagnostic diff — the S
465
469
  ```
466
470
  /fix main.py
467
471
  /fix main.py 2-2 wrong operator
472
+ /fim main.py 3 replace != with ==
473
+ /fim main.py 3-5 fix the logic
474
+ /fim huge_file.py 14567 -w 20 fix indentation
468
475
  /patch main.py 10-40 fix the loop logic
469
476
  /patch main.py code
470
477
  ```
@@ -1739,7 +1746,7 @@ On Windows: hold `Shift` and drag with the left mouse button to select and copy
1739
1746
  ## Tips for 1B models
1740
1747
 
1741
1748
  - **Start small.** Use `/read file.py 10-25` instead of loading the whole file. Short context = better focus.
1742
- - **Use `/fix` not `/patch`.** The `LINE N: content` format is much more reliable at 1B scale than free-form generation.
1749
+ - **Try `/fim` for tricky fixes.** FIM-based editing activates the model's grammar knowledge through surrounding code context — more reliable than `/fix` for indentation and multi-line logic. Use `-w 20` for large files.
1743
1750
  - **Build a map first.** Run `/map index .` at the start of a session, then use `/map find` to load only the relevant parts into context.
1744
1751
  - **Use scripts.** Scripts make multi-step work reproducible — the model only needs to handle one step at a time.
1745
1752
  - **Capture workflows.** After solving a task manually, run `/script create ctx` to save the exact steps as a reusable script.
@@ -1796,6 +1803,26 @@ For human input, the corrected command is shown with `[fix?]` and you are asked
1796
1803
 
1797
1804
  ---
1798
1805
 
1806
+ ## Part of the SIMARGL toolkit
1807
+
1808
+ 1bcoder is one of four tools that together form an **intellectual development support system**:
1809
+
1810
+ | Tool | Role |
1811
+ |---|---|
1812
+ | **[simargl](https://github.com/szholobetsky/simargl)** | Task-to-code retrieval — given a task description, finds which files and modules are likely affected, using semantic similarity over git history |
1813
+ | **[svitovyd](https://github.com/szholobetsky/svitovyd)** | Project map — scans any codebase and produces a structural map of definitions and cross-file dependencies; exposes it as an MCP server |
1814
+ | **[1bcoder](https://github.com/szholobetsky/1bcoder)** | AI coding assistant for small local models — surgical context management, agents, parallel inference, proc scripts |
1815
+ | **[yasna](https://github.com/szholobetsky/yasna)** | Session memory — indexes conversations from all AI agents so you can find what was discussed, when, and where |
1816
+
1817
+ - **simargl** answers: *what code is related to this task?*
1818
+ - **svitovyd** answers: *how is the code structured and what depends on what?*
1819
+ - **1bcoder** answers: *how do I work with local models efficiently?*
1820
+ - **yasna** answers: *where did I already discuss this?*
1821
+
1822
+ Together they cover the full development loop: understand the codebase, find relevant history, work with AI locally, remember what was decided.
1823
+
1824
+ ---
1825
+
1799
1826
  **(c) 2026 Stanislav Zholobetskyi**
1800
1827
  Institute for Information Recording, National Academy of Sciences of Ukraine, Kyiv
1801
1828
  *PhD research: «Intelligent Technology for Software Development and Maintenance Support»*
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: 1bcoder
3
- Version: 0.1.10
3
+ Version: 0.1.11
4
4
  Summary: AI coding assistant agent for 1B–7B local models (Ollama, LMStudio, llama.cpp). Terminal REPL with file editing, project map, agents, scripts, and parallel multi-model queries.
5
5
  Project-URL: Homepage, https://github.com/szholobetsky/1bcoder
6
6
  Project-URL: Repository, https://github.com/szholobetsky/1bcoder
@@ -13,6 +13,8 @@ Requires-Dist: tqdm>=4.64
13
13
  Requires-Dist: rich>=13.0
14
14
  Dynamic: license-file
15
15
 
16
+ ![1bcoder](images/1bcoder.png)
17
+
16
18
  # 1bcoder
17
19
 
18
20
  AI coding assistant for small local models (0.5B–4B) — runs via [Ollama](https://ollama.com), [LMStudio](https://lmstudio.ai), or any OpenAI-compatible backend.
@@ -128,7 +130,7 @@ Tasks that require the model to decide *what to look at* — refactoring across
128
130
  - **Command autocorrection** — typos in command names, file paths, and keywords are detected and fixed automatically before execution, for both human input and agent actions
129
131
  - **`/tree [path]`** — display directory tree of the whole project or any subtree; ask to inject into context (or pass `ctx` to skip the prompt)
130
132
  - **`/find <pattern>`** — search filenames and file content with regex; supports `-f`/`-c`/`-i`/`--ext` flags; highlights matches, asks to inject results into context; sets `{{find_files}}` after every search; **`/find <terms> -r`** ranked BM25 mode returns top-10 files by relevance; hidden directories (`.git`, `.venv`, etc.) excluded automatically
131
- - AI proposes a **one-line fix** (`/fix`) or a **SEARCH/REPLACE patch** (`/patch`) — always shows a diff before applying
133
+ - AI proposes a **one-line fix** (`/fix`), a **SEARCH/REPLACE patch** (`/patch`), or a **FIM-based fix** (`/fim`) — always shows a unified diff before applying
132
134
  - **Apply AI code blocks directly** with `/edit <file> code` (new/full file) or `/patch <file> code` (SEARCH/REPLACE from reply, no line numbers needed) — preferred for agent mode
133
135
  - **`<think>` tag support** — reasoning blocks shown in terminal by default; `/think hide` suppresses terminal display; `/think include` keeps reasoning in context for chained turns
134
136
  - Run shell commands and inject their output with `/run`
@@ -453,10 +455,12 @@ Then configure `/translate` to use it:
453
455
  | Command | Description |
454
456
  |---|---|
455
457
  | `/fix <file> [start-end] [hint]` | AI proposes one-line fix, shows diff, asks to apply |
458
+ | `/fim <file> <line or start-end> [-w N] [hint]` | FIM-based fix — model rewrites the marked section, shows unified diff |
456
459
  | `/patch <file> [start-end] [hint]` | AI proposes SEARCH/REPLACE block, shows unified diff |
457
460
  | `/patch <file> code` | Apply SEARCH/REPLACE block from last AI reply (no new LLM call) |
458
461
 
459
- `/fix` is designed for 1B modelsoutput is strictly constrained to `LINE N: content`.
462
+ `/fix` asks the model to output `LINE N: content` simple but small models often lose indentation.
463
+ `/fim` (Fill-In-the-Middle) marks the target line(s) with `<<<...>>>` inside the full file, asks the model to return the corrected file, then diffs the result — no output format to learn, grammar constraints come from the surrounding code. Use `-w N` to limit context to N lines around the target when the file is larger than the context window.
460
464
  `/patch` works better with larger models (7B+) and can replace multiple consecutive lines.
461
465
  `/patch <file> code` is the preferred agent mode edit — the agent writes the SEARCH/REPLACE block in its reply, then calls `/patch <file> code` to apply it without needing line numbers.
462
466
 
@@ -465,6 +469,9 @@ When `/patch` fails to find the SEARCH text it shows a diagnostic diff — the S
465
469
  ```
466
470
  /fix main.py
467
471
  /fix main.py 2-2 wrong operator
472
+ /fim main.py 3 replace != with ==
473
+ /fim main.py 3-5 fix the logic
474
+ /fim huge_file.py 14567 -w 20 fix indentation
468
475
  /patch main.py 10-40 fix the loop logic
469
476
  /patch main.py code
470
477
  ```
@@ -1739,7 +1746,7 @@ On Windows: hold `Shift` and drag with the left mouse button to select and copy
1739
1746
  ## Tips for 1B models
1740
1747
 
1741
1748
  - **Start small.** Use `/read file.py 10-25` instead of loading the whole file. Short context = better focus.
1742
- - **Use `/fix` not `/patch`.** The `LINE N: content` format is much more reliable at 1B scale than free-form generation.
1749
+ - **Try `/fim` for tricky fixes.** FIM-based editing activates the model's grammar knowledge through surrounding code context — more reliable than `/fix` for indentation and multi-line logic. Use `-w 20` for large files.
1743
1750
  - **Build a map first.** Run `/map index .` at the start of a session, then use `/map find` to load only the relevant parts into context.
1744
1751
  - **Use scripts.** Scripts make multi-step work reproducible — the model only needs to handle one step at a time.
1745
1752
  - **Capture workflows.** After solving a task manually, run `/script create ctx` to save the exact steps as a reusable script.
@@ -1796,6 +1803,26 @@ For human input, the corrected command is shown with `[fix?]` and you are asked
1796
1803
 
1797
1804
  ---
1798
1805
 
1806
+ ## Part of the SIMARGL toolkit
1807
+
1808
+ 1bcoder is one of four tools that together form an **intellectual development support system**:
1809
+
1810
+ | Tool | Role |
1811
+ |---|---|
1812
+ | **[simargl](https://github.com/szholobetsky/simargl)** | Task-to-code retrieval — given a task description, finds which files and modules are likely affected, using semantic similarity over git history |
1813
+ | **[svitovyd](https://github.com/szholobetsky/svitovyd)** | Project map — scans any codebase and produces a structural map of definitions and cross-file dependencies; exposes it as an MCP server |
1814
+ | **[1bcoder](https://github.com/szholobetsky/1bcoder)** | AI coding assistant for small local models — surgical context management, agents, parallel inference, proc scripts |
1815
+ | **[yasna](https://github.com/szholobetsky/yasna)** | Session memory — indexes conversations from all AI agents so you can find what was discussed, when, and where |
1816
+
1817
+ - **simargl** answers: *what code is related to this task?*
1818
+ - **svitovyd** answers: *how is the code structured and what depends on what?*
1819
+ - **1bcoder** answers: *how do I work with local models efficiently?*
1820
+ - **yasna** answers: *where did I already discuss this?*
1821
+
1822
+ Together they cover the full development loop: understand the codebase, find relevant history, work with AI locally, remember what was decided.
1823
+
1824
+ ---
1825
+
1799
1826
  **(c) 2026 Stanislav Zholobetskyi**
1800
1827
  Institute for Information Recording, National Academy of Sciences of Ukraine, Kyiv
1801
1828
  *PhD research: «Intelligent Technology for Software Development and Maintenance Support»*
@@ -1,3 +1,5 @@
1
+ ![1bcoder](images/1bcoder.png)
2
+
1
3
  # 1bcoder
2
4
 
3
5
  AI coding assistant for small local models (0.5B–4B) — runs via [Ollama](https://ollama.com), [LMStudio](https://lmstudio.ai), or any OpenAI-compatible backend.
@@ -113,7 +115,7 @@ Tasks that require the model to decide *what to look at* — refactoring across
113
115
  - **Command autocorrection** — typos in command names, file paths, and keywords are detected and fixed automatically before execution, for both human input and agent actions
114
116
  - **`/tree [path]`** — display directory tree of the whole project or any subtree; ask to inject into context (or pass `ctx` to skip the prompt)
115
117
  - **`/find <pattern>`** — search filenames and file content with regex; supports `-f`/`-c`/`-i`/`--ext` flags; highlights matches, asks to inject results into context; sets `{{find_files}}` after every search; **`/find <terms> -r`** ranked BM25 mode returns top-10 files by relevance; hidden directories (`.git`, `.venv`, etc.) excluded automatically
116
- - AI proposes a **one-line fix** (`/fix`) or a **SEARCH/REPLACE patch** (`/patch`) — always shows a diff before applying
118
+ - AI proposes a **one-line fix** (`/fix`), a **SEARCH/REPLACE patch** (`/patch`), or a **FIM-based fix** (`/fim`) — always shows a unified diff before applying
117
119
  - **Apply AI code blocks directly** with `/edit <file> code` (new/full file) or `/patch <file> code` (SEARCH/REPLACE from reply, no line numbers needed) — preferred for agent mode
118
120
  - **`<think>` tag support** — reasoning blocks shown in terminal by default; `/think hide` suppresses terminal display; `/think include` keeps reasoning in context for chained turns
119
121
  - Run shell commands and inject their output with `/run`
@@ -438,10 +440,12 @@ Then configure `/translate` to use it:
438
440
  | Command | Description |
439
441
  |---|---|
440
442
  | `/fix <file> [start-end] [hint]` | AI proposes one-line fix, shows diff, asks to apply |
443
+ | `/fim <file> <line or start-end> [-w N] [hint]` | FIM-based fix — model rewrites the marked section, shows unified diff |
441
444
  | `/patch <file> [start-end] [hint]` | AI proposes SEARCH/REPLACE block, shows unified diff |
442
445
  | `/patch <file> code` | Apply SEARCH/REPLACE block from last AI reply (no new LLM call) |
443
446
 
444
- `/fix` is designed for 1B modelsoutput is strictly constrained to `LINE N: content`.
447
+ `/fix` asks the model to output `LINE N: content` simple but small models often lose indentation.
448
+ `/fim` (Fill-In-the-Middle) marks the target line(s) with `<<<...>>>` inside the full file, asks the model to return the corrected file, then diffs the result — no output format to learn, grammar constraints come from the surrounding code. Use `-w N` to limit context to N lines around the target when the file is larger than the context window.
445
449
  `/patch` works better with larger models (7B+) and can replace multiple consecutive lines.
446
450
  `/patch <file> code` is the preferred agent mode edit — the agent writes the SEARCH/REPLACE block in its reply, then calls `/patch <file> code` to apply it without needing line numbers.
447
451
 
@@ -450,6 +454,9 @@ When `/patch` fails to find the SEARCH text it shows a diagnostic diff — the S
450
454
  ```
451
455
  /fix main.py
452
456
  /fix main.py 2-2 wrong operator
457
+ /fim main.py 3 replace != with ==
458
+ /fim main.py 3-5 fix the logic
459
+ /fim huge_file.py 14567 -w 20 fix indentation
453
460
  /patch main.py 10-40 fix the loop logic
454
461
  /patch main.py code
455
462
  ```
@@ -1724,7 +1731,7 @@ On Windows: hold `Shift` and drag with the left mouse button to select and copy
1724
1731
  ## Tips for 1B models
1725
1732
 
1726
1733
  - **Start small.** Use `/read file.py 10-25` instead of loading the whole file. Short context = better focus.
1727
- - **Use `/fix` not `/patch`.** The `LINE N: content` format is much more reliable at 1B scale than free-form generation.
1734
+ - **Try `/fim` for tricky fixes.** FIM-based editing activates the model's grammar knowledge through surrounding code context — more reliable than `/fix` for indentation and multi-line logic. Use `-w 20` for large files.
1728
1735
  - **Build a map first.** Run `/map index .` at the start of a session, then use `/map find` to load only the relevant parts into context.
1729
1736
  - **Use scripts.** Scripts make multi-step work reproducible — the model only needs to handle one step at a time.
1730
1737
  - **Capture workflows.** After solving a task manually, run `/script create ctx` to save the exact steps as a reusable script.
@@ -1781,6 +1788,26 @@ For human input, the corrected command is shown with `[fix?]` and you are asked
1781
1788
 
1782
1789
  ---
1783
1790
 
1791
+ ## Part of the SIMARGL toolkit
1792
+
1793
+ 1bcoder is one of four tools that together form an **intellectual development support system**:
1794
+
1795
+ | Tool | Role |
1796
+ |---|---|
1797
+ | **[simargl](https://github.com/szholobetsky/simargl)** | Task-to-code retrieval — given a task description, finds which files and modules are likely affected, using semantic similarity over git history |
1798
+ | **[svitovyd](https://github.com/szholobetsky/svitovyd)** | Project map — scans any codebase and produces a structural map of definitions and cross-file dependencies; exposes it as an MCP server |
1799
+ | **[1bcoder](https://github.com/szholobetsky/1bcoder)** | AI coding assistant for small local models — surgical context management, agents, parallel inference, proc scripts |
1800
+ | **[yasna](https://github.com/szholobetsky/yasna)** | Session memory — indexes conversations from all AI agents so you can find what was discussed, when, and where |
1801
+
1802
+ - **simargl** answers: *what code is related to this task?*
1803
+ - **svitovyd** answers: *how is the code structured and what depends on what?*
1804
+ - **1bcoder** answers: *how do I work with local models efficiently?*
1805
+ - **yasna** answers: *where did I already discuss this?*
1806
+
1807
+ Together they cover the full development loop: understand the codebase, find relevant history, work with AI locally, remember what was decided.
1808
+
1809
+ ---
1810
+
1784
1811
  **(c) 2026 Stanislav Zholobetskyi**
1785
1812
  Institute for Information Recording, National Academy of Sciences of Ukraine, Kyiv
1786
1813
  *PhD research: «Intelligent Technology for Software Development and Maintenance Support»*
@@ -13,6 +13,7 @@ system =
13
13
 
14
14
  How to write files:
15
15
  - To MODIFY an existing file: write a SEARCH/REPLACE block, then ACTION: /patch <file> code
16
+ Or use FIM for targeted fixes: ACTION: /fim <file> <line or start-end> <hint>
16
17
  - To INSERT new code at a line: write the code block, then ACTION: /insert <file> <line> code
17
18
  - To CREATE or fully REPLACE a file: write the full code block, then ACTION: /save <file> code
18
19
 
@@ -24,7 +25,7 @@ system =
24
25
  >>>>>>> REPLACE
25
26
 
26
27
  Rules:
27
- - /read a file before editing it
28
+ - /readln a file before editing it
28
29
  - /bkup save <file> before modifying important files
29
30
  - /run to test after applying a fix
30
31
 
@@ -40,7 +41,7 @@ tools =
40
41
  bkup
41
42
  diff
42
43
  patch
43
- fix
44
+ fim
44
45
  tree
45
46
  find
46
47
  map
@@ -17,6 +17,8 @@ These are handled by 1bcoder directly and are not forwarded to the model API.
17
17
  | `think_exclude` | `true` | Strip `<think>…</think>` blocks from context (blocks are shown in terminal only). Set `false` to keep reasoning in context for multi-turn chains. |
18
18
  | `ask_limit` | 8000 | Max characters of `/ask` tool output kept in context. |
19
19
  | `ask_show` | 500 | Characters shown in terminal when output is truncated. |
20
+ | `run_timeout` | 30 | Timeout in seconds for `/run` shell commands. Set to `0` to disable (no timeout). Useful for long-running CLI tools like `simargl search`. |
21
+ | `log` | `false` | Print full request details before each LLM call: URL, model, message count, options. Also prints HTTP response headers on error. Useful for debugging 500 errors or unexpected model behaviour. |
20
22
 
21
23
  ---
22
24
 
@@ -225,7 +225,7 @@ PROFILES_FILE = os.path.join(BCODER_DIR, "profiles.txt")
225
225
  GLOBAL_PROFILES_FILE = os.path.join(HOME_BCODER_DIR, "profiles.txt")
226
226
 
227
227
  DEFAULT_AGENT_TOOLS = [
228
- "read", "tree", "find", "insert", "save", "patch",
228
+ "read", "readln", "tree", "find", "insert", "save", "fim",
229
229
  ]
230
230
 
231
231
  DEFAULT_AGENT_TOOLS_ADVANCED = [
@@ -247,33 +247,19 @@ AGENT_SYSTEM_BASIC = """\
247
247
  You are a coding assistant. Complete the task using the available tools.
248
248
 
249
249
  To call a tool, write ACTION: on its own line. Wait for [tool result].
250
- Run actions in a loop until the task is complete.
251
- When all actions are done, write a short summary with no ACTION and end with: Task is complete.
250
+ Run one action at a time until the task is complete.
251
+ When done, write a short summary with no ACTION and end with: Task is complete.
252
252
 
253
- Available actions:
254
- ACTION: /read <file> read whole file
255
- ACTION: /read <file> 35-55 ← read lines 35–55
253
+ To fix or edit a file:
254
+ ACTION: /fim <file> <description of what to fix>
255
+ Example: ACTION: /fim calc.py fix the division by zero bug
256
+ Example: ACTION: /fim main.py fix the indentation on line 5
256
257
 
257
- To modify a file:
258
- 1. Write the code block (```...```)
259
- 2. Then call:
260
- ACTION: /insert <file> <line> code ← insert code block before line N
261
- ACTION: /insert <file> <line> <text> ← insert literal text before line N
262
- ACTION: /patch <file> code ← apply SEARCH/REPLACE block
263
- ACTION: /save <file> code ← save or overwrite whole file
258
+ To read a file:
259
+ ACTION: /read <file>
264
260
 
265
- The keyword "code" in the action means: use the last code block (``` ```) written above as the content.
266
-
267
- SEARCH/REPLACE format for /patch:
268
- <<<<<<< SEARCH
269
- exact lines to replace
270
- =======
271
- new lines
272
- >>>>>>> REPLACE
273
-
274
- Rules:
275
- - If you don't know the project structure, start with ACTION: /tree
276
- - Always /read a file before inserting or patching it.
261
+ To create or fully replace a file: write the full code block, then:
262
+ ACTION: /save <file> code
277
263
 
278
264
  Available tools:
279
265
  {tool_list}
@@ -287,6 +273,7 @@ When the task is complete, write a short summary and end with: Task is complete.
287
273
 
288
274
  How to write files (the keyword "code" means: use the last ``` ``` block written above):
289
275
  - To MODIFY an existing file: write a SEARCH/REPLACE block, then ACTION: /patch <file> code
276
+ Or use FIM for targeted fixes: ACTION: /fim <file> <line or start-end> <hint>
290
277
  - To INSERT new code at a line: write the code block, then ACTION: /insert <file> <line> code
291
278
  - To CREATE or fully REPLACE a file: write the full code block, then ACTION: /save <file> code
292
279
 
@@ -298,7 +285,7 @@ new lines
298
285
  >>>>>>> REPLACE
299
286
 
300
287
  Rules:
301
- - /read a file before editing it
288
+ - /readln a file before editing it
302
289
  - /bkup save <file> before modifying important files
303
290
  - /run to test after applying a fix
304
291
 
@@ -339,7 +326,7 @@ FIX_SYSTEM = (
339
326
  "You are a code repair tool. "
340
327
  "Respond with ONLY the single most important fix in this exact format:\n"
341
328
  "LINE <number>: <corrected line content>\n"
342
- "One fix only. No explanation. No other text. Preserve indentation."
329
+ "One fix only. No explanation. No other text."
343
330
  )
344
331
 
345
332
  PATCH_SYSTEM = (
@@ -450,6 +437,16 @@ Commands
450
437
  /fix main.py 2-2
451
438
  /fix main.py 2-2 wrong operator
452
439
 
440
+ /fim <file> [line or start-end] [-w N] [hint]
441
+ FIM-based fix. Marks the line(s), passes file to model, diffs the result.
442
+ Without a line number: passes the whole file with hint only (agent fallback mode).
443
+ -w N pass only N lines of context around the marked section (for large files).
444
+ e.g. /fim main.py 3
445
+ /fim main.py 3 replace != with ==
446
+ /fim main.py 3-5 fix the logic
447
+ /fim huge.py 14567 -w 20 fix indentation
448
+ /fim main.py fix the division by zero bug
449
+
453
450
  /patch <file> [start-end] [hint]
454
451
  AI proposes a multi-line SEARCH/REPLACE edit. Shows unified diff before applying.
455
452
  Better for 7B+ models. Use /fix for 1B models.
@@ -874,6 +871,7 @@ Output capture operators (work with any command — LLM reply, tool, proc):
874
871
  \\!term exclude entire block if any child contains term
875
872
  -term show ONLY child lines containing term
876
873
  -!term hide child lines containing term
874
+ +term like -term but also hides files with no matching children
877
875
  e.g. /map find register
878
876
  /map find \\register !mock
879
877
  /map find auth \\UserService -!deprecated -y
@@ -1271,6 +1269,54 @@ def ai_fix(base_url, model, content, label, hint="", on_chunk=None, provider="ol
1271
1269
  return None, raw
1272
1270
 
1273
1271
 
1272
+ def ai_fill(base_url, model, file_lines, start, end, hint="", on_chunk=None, provider="ollama"):
1273
+ """FIM-based fix: marks line range in the whole file, asks model for corrected file.
1274
+ If start is None: no marker — whole file passed with hint only (agent fallback mode)."""
1275
+ if start is None:
1276
+ file_content = "".join(file_lines)
1277
+ user_msg = file_content + "\nFix the issue. Output the complete corrected file."
1278
+ else:
1279
+ marked = list(file_lines)
1280
+ if start == end:
1281
+ marked[start - 1] = f"<<<{file_lines[start - 1].rstrip()}>>>\n"
1282
+ else:
1283
+ marked[start - 1] = "<<<\n" + file_lines[start - 1]
1284
+ marked[end - 1] = file_lines[end - 1].rstrip() + "\n>>>\n"
1285
+ file_content = "".join(marked)
1286
+ user_msg = file_content + "\nFix the <<<...>>> section. Output the complete corrected file."
1287
+ if hint:
1288
+ user_msg = f"{hint}\n\n{user_msg}"
1289
+ msgs = [{"role": "user", "content": user_msg}]
1290
+ chunks = []
1291
+ if provider == "openai":
1292
+ with requests.post(
1293
+ f"{base_url}/v1/chat/completions",
1294
+ json={"model": model, "messages": msgs, "stream": True},
1295
+ stream=True, timeout=120,
1296
+ ) as resp:
1297
+ resp.raise_for_status()
1298
+ _parse_openai_stream(resp, on_chunk, chunks)
1299
+ else:
1300
+ with requests.post(
1301
+ f"{base_url}/api/chat",
1302
+ json={"model": model, "messages": msgs, "stream": True},
1303
+ stream=True, timeout=120,
1304
+ ) as resp:
1305
+ resp.raise_for_status()
1306
+ for line in resp.iter_lines():
1307
+ if not line:
1308
+ continue
1309
+ data = json.loads(line)
1310
+ chunk = data.get("message", {}).get("content", "")
1311
+ if chunk:
1312
+ if on_chunk:
1313
+ on_chunk(chunk)
1314
+ chunks.append(chunk)
1315
+ if data.get("done"):
1316
+ break
1317
+ return "".join(chunks)
1318
+
1319
+
1274
1320
  def _parse_patch(text):
1275
1321
  """Extract (search_text, replace_text) from a SEARCH/REPLACE block, or (None, None)."""
1276
1322
  m = re.search(
@@ -1984,7 +2030,7 @@ def _map_patch_remove(map_path: str, rel_prefix: str) -> int:
1984
2030
 
1985
2031
  _KNOWN_CMDS = [
1986
2032
  "/read", "/readln", "/insert", "/edit", "/save", "/run", "/script", "/mcp",
1987
- "/parallel", "/patch", "/fix", "/bkup", "/diff", "/agent", "/tree",
2033
+ "/parallel", "/patch", "/fix", "/fim", "/bkup", "/diff", "/agent", "/tree",
1988
2034
  "/find", "/map", "/ctx", "/think", "/format", "/param", "/model",
1989
2035
  "/host", "/help", "/init", "/clear", "/exit",
1990
2036
  "/prompt", "/proc", "/team", "/var", "/config", "/alias",
@@ -2326,6 +2372,7 @@ class CoderCLI:
2326
2372
  self._last_find_results: list = [] # numbered results from last /proj find
2327
2373
  self._history: list[str] = []
2328
2374
  self.cmd_history: list[str] = [] # all /commands typed this session
2375
+ self.log_requests: bool = False # /param log true — print request body + error detail
2329
2376
  # enable readline history if available
2330
2377
  try:
2331
2378
  import readline
@@ -2376,6 +2423,9 @@ class CoderCLI:
2376
2423
  if self.provider == "openai":
2377
2424
  body = {"model": self.model, "messages": messages, "stream": True}
2378
2425
  body.update(self.params)
2426
+ if self.log_requests:
2427
+ print(f" [log] POST {self.base_url}/v1/chat/completions")
2428
+ print(f" [log] model={self.model} messages={len(messages)} params={self.params}")
2379
2429
  with requests.post(
2380
2430
  f"{self.base_url}/v1/chat/completions",
2381
2431
  json=body, stream=True, timeout=self.timeout,
@@ -2436,6 +2486,9 @@ class CoderCLI:
2436
2486
  else:
2437
2487
  opts = {"num_ctx": self.num_ctx}
2438
2488
  opts.update(self.params)
2489
+ if self.log_requests:
2490
+ print(f" [log] POST {self.base_url}/api/chat")
2491
+ print(f" [log] model={self.model} messages={len(messages)} options={opts}")
2439
2492
  with requests.post(
2440
2493
  f"{self.base_url}/api/chat",
2441
2494
  json={"model": self.model, "messages": messages, "stream": True,
@@ -2494,6 +2547,13 @@ class CoderCLI:
2494
2547
  return None # sentinel: interrupted (vs "" which means empty reply)
2495
2548
  except requests.exceptions.RequestException as e:
2496
2549
  print(f"\nerror: {e}")
2550
+ if hasattr(e, "response") and e.response is not None:
2551
+ body = e.response.text.strip()
2552
+ if body:
2553
+ print(f" detail: {body[:500]}")
2554
+ if self.log_requests:
2555
+ print(f" [log] status: {e.response.status_code}")
2556
+ print(f" [log] headers: {dict(e.response.headers)}")
2497
2557
  return ""
2498
2558
  print()
2499
2559
  reply = "".join(chunks)
@@ -2706,6 +2766,8 @@ class CoderCLI:
2706
2766
  if self._run_hook("before_patch", _ha):
2707
2767
  self._cmd_patch(user_input)
2708
2768
  self._run_hook("after_patch", _ha)
2769
+ elif user_input.startswith("/fim"):
2770
+ self._cmd_fill(user_input)
2709
2771
  elif user_input.startswith("/fix"):
2710
2772
  _ha = self._extract_hook_args(user_input)
2711
2773
  if self._run_hook("before_fix", _ha):
@@ -2882,6 +2944,8 @@ advanced_tools =
2882
2944
  print(f" num_ctx = {self.num_ctx}")
2883
2945
  print(f" think_exclude = {not self.think_in_ctx} (strip <think> blocks from context)")
2884
2946
  print(f" think_show = {self.think_show} (show <think> blocks in terminal)")
2947
+ print(f" log = {self.log_requests} (print request body + error detail)")
2948
+ print(f" run_timeout = {self.params.get('run_timeout', 30)}s (0 = no timeout, for /run commands)")
2885
2949
  print(f" ask_limit = {self.params.get('ask_limit', ASK_RESULT_LIMIT_CHARS)} (chars, /ask truncation limit)")
2886
2950
  print(f" ask_show = {self.params.get('ask_show', ASK_RESULT_SHOW_CHARS)} (chars shown when truncated)")
2887
2951
  model_params = {k: v for k, v in self.params.items() if k not in ("ask_limit", "ask_show")}
@@ -2927,6 +2991,10 @@ advanced_tools =
2927
2991
  self.think_show = bool(val)
2928
2992
  _ok(f"[param] think_show = {self.think_show}")
2929
2993
  return
2994
+ if key == "log":
2995
+ self.log_requests = bool(val)
2996
+ _ok(f"[param] log = {self.log_requests}")
2997
+ return
2930
2998
  self.params[key] = val
2931
2999
  _ok(f"[param] {key} = {val}")
2932
3000
 
@@ -4061,7 +4129,7 @@ advanced_tools =
4061
4129
  return
4062
4130
  try:
4063
4131
  current_text, _ = read_file(path, lineno, lineno)
4064
- current_text = current_text.split(":", 1)[1].rstrip("\n") if ":" in current_text else current_text.rstrip()
4132
+ current_text = current_text.split(":", 1)[1][1:].rstrip("\n") if ":" in current_text else current_text.rstrip()
4065
4133
  print(f" current [{lineno}]: {current_text}")
4066
4134
  print(f" new [{lineno}]: {new_content}")
4067
4135
  except (FileNotFoundError, OSError):
@@ -4075,6 +4143,95 @@ advanced_tools =
4075
4143
  else:
4076
4144
  print("[skipped]")
4077
4145
 
4146
+ def _cmd_fill(self, user_input: str):
4147
+ parts = user_input[4:].strip().split(None, 2)
4148
+ path = parts[0] if parts else ""
4149
+ start = end = None
4150
+ hint = ""
4151
+ if not path:
4152
+ print("usage: /fim <file> <line or start-end> [-w N] [hint]")
4153
+ return
4154
+ if len(parts) >= 2:
4155
+ m = re.match(r'^(\d+)(?:-(\d+))?$', parts[1])
4156
+ if m:
4157
+ start = int(m.group(1))
4158
+ end = int(m.group(2)) if m.group(2) else start
4159
+ hint = parts[2] if len(parts) >= 3 else ""
4160
+ else:
4161
+ hint = " ".join(parts[1:])
4162
+ if start is None and not hint:
4163
+ print("usage: /fim <file> [line or start-end] [-w N] [hint]")
4164
+ return
4165
+ # extract -w <window> from hint
4166
+ window = None
4167
+ wm = re.search(r'(?:^|\s)-w\s+(\d+)', hint)
4168
+ if wm:
4169
+ window = int(wm.group(1))
4170
+ hint = (hint[:wm.start()] + hint[wm.end():]).strip()
4171
+ try:
4172
+ with open(path, "r", encoding="utf-8") as f:
4173
+ file_lines = f.readlines()
4174
+ except (FileNotFoundError, OSError) as e:
4175
+ _err(e)
4176
+ return
4177
+ total = len(file_lines)
4178
+ if start is not None and (start < 1 or end > total):
4179
+ _err(f"line {start}-{end} out of range (file has {total} lines)")
4180
+ return
4181
+ # compute slice for windowed mode (only when line range is given)
4182
+ if start is not None and window is not None:
4183
+ w_start = max(0, start - 1 - window) # 0-indexed inclusive
4184
+ w_end = min(total, end + window) # 0-indexed exclusive
4185
+ slice_lines = file_lines[w_start:w_end]
4186
+ rel_start = start - w_start # 1-indexed within slice
4187
+ rel_end = end - w_start
4188
+ else:
4189
+ slice_lines = file_lines
4190
+ rel_start, rel_end = start, end
4191
+ w_start, w_end = 0, total
4192
+ if hint:
4193
+ print(f"hint: {hint}")
4194
+ if window is not None:
4195
+ print(f"[fim] window: lines {w_start+1}–{w_end} of {total}")
4196
+ self._sep("AI")
4197
+ def on_chunk(c):
4198
+ sys.stdout.write(c)
4199
+ sys.stdout.flush()
4200
+ try:
4201
+ raw = ai_fill(self.base_url, self.model, slice_lines, rel_start, rel_end, hint, on_chunk, self.provider)
4202
+ except KeyboardInterrupt:
4203
+ print("\n[interrupted]")
4204
+ return
4205
+ except requests.exceptions.RequestException as e:
4206
+ print(f"\nerror: {e}")
4207
+ if hasattr(e, "response") and e.response is not None:
4208
+ body = e.response.text.strip()
4209
+ if body:
4210
+ print(f" detail: {body[:500]}")
4211
+ return
4212
+ print()
4213
+ m = re.search(r'```(?:\w+)?\n(.*?)\n```', raw, re.DOTALL)
4214
+ new_content = m.group(1) if m else raw.strip()
4215
+ new_slice = [l if l.endswith('\n') else l + '\n' for l in new_content.splitlines()]
4216
+ # reconstruct full file: unchanged prefix + new slice + unchanged suffix
4217
+ full_new = file_lines[:w_start] + new_slice + file_lines[w_end:]
4218
+ diff = list(difflib.unified_diff(file_lines, full_new, fromfile=path, tofile=path + " (fixed)", lineterm=""))
4219
+ if not diff:
4220
+ print("[fim] no changes detected — try a different hint or model")
4221
+ return
4222
+ for dline in diff:
4223
+ print(_cdiff(dline))
4224
+ if self._confirm(" apply? [Y/n]:"):
4225
+ try:
4226
+ with open(path, "w", encoding="utf-8") as f:
4227
+ f.writelines(full_new)
4228
+ changed = sum(1 for d in diff if d.startswith('+') and not d.startswith('+++'))
4229
+ print(f"[{changed} line(s) updated in {path}]")
4230
+ except OSError as e:
4231
+ _err(e)
4232
+ else:
4233
+ print("[skipped]")
4234
+
4078
4235
  # ── hook support ──────────────────────────────────────────────────────────
4079
4236
 
4080
4237
  def _extract_hook_args(self, user_input: str) -> dict:
@@ -4303,7 +4460,7 @@ advanced_tools =
4303
4460
  if search_text is None:
4304
4461
  print("could not parse SEARCH/REPLACE block — try a more capable model")
4305
4462
  return
4306
- if search_text.strip() == replace_text.strip():
4463
+ if search_text == replace_text:
4307
4464
  _warn("[patch] SEARCH and REPLACE are identical — model included the new code in both blocks (no-op)")
4308
4465
  return
4309
4466
  try:
@@ -4454,9 +4611,11 @@ advanced_tools =
4454
4611
 
4455
4612
  def _cmd_run(self, shell_cmd: str):
4456
4613
  print(f"$ {shell_cmd}")
4614
+ run_timeout = self.params.get("run_timeout", 30)
4457
4615
  try:
4458
4616
  proc = subprocess.run(
4459
- shell_cmd, shell=True, capture_output=True, timeout=30,
4617
+ shell_cmd, shell=True, capture_output=True,
4618
+ timeout=run_timeout if run_timeout else None,
4460
4619
  encoding="utf-8", errors="replace",
4461
4620
  )
4462
4621
  output = proc.stdout + proc.stderr
@@ -7646,6 +7805,7 @@ Config stored in ~/.1bcoder/translate.json
7646
7805
  print(" /map find \\!term — exclude block if any child contains term")
7647
7806
  print(" /map find -term — show ONLY child lines containing term")
7648
7807
  print(" /map find -!term — hide child lines containing term")
7808
+ print(" /map find +term — like -term but also hides files with no matching children")
7649
7809
  print(" combine freely: auth \\register !mock -!deprecated -y -d 2")
7650
7810
  print(" /map trace <identifier> [-d N] [-y] — follow call chain backwards from identifier")
7651
7811
  print(" /map diff — diff map.txt vs map.prev.txt (no re-index)")
@@ -8275,13 +8435,7 @@ Config stored in ~/.1bcoder/translate.json
8275
8435
  if template is None:
8276
8436
  return user_input
8277
8437
  if "{{args}}" in template:
8278
- # Extract --flags separately; pass remaining args verbatim to preserve
8279
- # quoted segments and keywords like file: / plan: intact.
8280
- flags = re.findall(r'--\S+', args)
8281
- text = re.sub(r'\s*--\S+', '', args).strip()
8282
- expanded = template.replace("{{args}}", text)
8283
- if flags:
8284
- expanded = expanded + " " + " ".join(flags)
8438
+ expanded = template.replace("{{args}}", args)
8285
8439
  else:
8286
8440
  expanded = (template + (" " + args if args else "")).strip()
8287
8441
  return self._expand_alias(expanded, depth + 1)
@@ -22,6 +22,7 @@ Filter token syntax for find:
22
22
  \\!term exclude entire block if any child contains term
23
23
  -term show ONLY child lines containing term
24
24
  -!term hide child lines containing term
25
+ +term show ONLY child lines containing term AND hide file if none match
25
26
 
26
27
  (no tokens → print full map)
27
28
 
@@ -154,13 +155,14 @@ def find_map(map_path: str, query: str) -> tuple:
154
155
  with open(map_path, encoding='utf-8') as f:
155
156
  content = f.read()
156
157
 
157
- tokens = query.split()
158
- pos_file = [] # term — filename must contain
159
- neg_file = [] # !term — filename must NOT contain
160
- pos_child = [] # \term — include block if any child line contains (all terms same line)
161
- neg_block = [] # \!term — exclude block if any child line contains
162
- show_lines = [] # -term — show ONLY child lines containing term (whitelist)
163
- hide_lines = [] # -!term — hide child lines containing term
158
+ tokens = query.split()
159
+ pos_file = [] # term — filename must contain
160
+ neg_file = [] # !term — filename must NOT contain
161
+ pos_child = [] # \term — include block if any child line contains (all terms same line)
162
+ neg_block = [] # \!term — exclude block if any child line contains
163
+ show_lines = [] # -term — show ONLY child lines containing term (keep file even if empty)
164
+ hide_lines = [] # -!term — hide child lines containing term
165
+ must_show_lines = [] # +term — show ONLY child lines containing term AND hide file if none match
164
166
 
165
167
  for t in tokens:
166
168
  if t.startswith('\\!') and len(t) > 2:
@@ -171,13 +173,15 @@ def find_map(map_path: str, query: str) -> tuple:
171
173
  hide_lines.append(t[2:].lower())
172
174
  elif t.startswith('-') and len(t) > 1:
173
175
  show_lines.append(t[1:].lower())
176
+ elif t.startswith('+') and len(t) > 1:
177
+ must_show_lines.append(t[1:].lower())
174
178
  elif t.startswith('!') and len(t) > 1:
175
179
  neg_file.append(t[1:].lower())
176
180
  else:
177
181
  pos_file.append(t.lower())
178
182
 
179
183
  # no criteria → return full map
180
- if not any([pos_file, neg_file, pos_child, neg_block, show_lines, hide_lines]):
184
+ if not any([pos_file, neg_file, pos_child, neg_block, show_lines, hide_lines, must_show_lines]):
181
185
  return [], content
182
186
 
183
187
  blocks = re.split(r'\n(?=\S)', content)
@@ -201,6 +205,11 @@ def find_map(map_path: str, query: str) -> tuple:
201
205
  if show_lines:
202
206
  child_lines = [l for l in child_lines
203
207
  if any(t in l.lower() for t in show_lines)]
208
+ if must_show_lines:
209
+ child_lines = [l for l in child_lines
210
+ if any(t in l.lower() for t in must_show_lines)]
211
+ if not child_lines:
212
+ return None
204
213
  if hide_lines:
205
214
  child_lines = [l for l in child_lines
206
215
  if not any(t in l.lower() for t in hide_lines)]
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "1bcoder"
7
- version = "0.1.10"
7
+ version = "0.1.11"
8
8
  description = "AI coding assistant agent for 1B–7B local models (Ollama, LMStudio, llama.cpp). Terminal REPL with file editing, project map, agents, scripts, and parallel multi-model queries."
9
9
  requires-python = ">=3.10"
10
10
  readme = {file = "README.md", content-type = "text/markdown"}
File without changes
File without changes
File without changes
File without changes
File without changes