1bcoder 0.1.8__tar.gz → 0.1.10__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.8 → 1bcoder-0.1.10}/1bcoder.egg-info/PKG-INFO +31 -4
  2. {1bcoder-0.1.8 → 1bcoder-0.1.10}/PKG-INFO +31 -4
  3. {1bcoder-0.1.8 → 1bcoder-0.1.10}/README.md +30 -3
  4. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/aliases.txt +4 -0
  5. {1bcoder-0.1.8 → 1bcoder-0.1.10}/chat.py +112 -10
  6. {1bcoder-0.1.8 → 1bcoder-0.1.10}/map_index.py +7 -4
  7. {1bcoder-0.1.8 → 1bcoder-0.1.10}/map_query.py +68 -0
  8. {1bcoder-0.1.8 → 1bcoder-0.1.10}/pyproject.toml +1 -1
  9. {1bcoder-0.1.8 → 1bcoder-0.1.10}/1bcoder.egg-info/SOURCES.txt +0 -0
  10. {1bcoder-0.1.8 → 1bcoder-0.1.10}/1bcoder.egg-info/dependency_links.txt +0 -0
  11. {1bcoder-0.1.8 → 1bcoder-0.1.10}/1bcoder.egg-info/entry_points.txt +0 -0
  12. {1bcoder-0.1.8 → 1bcoder-0.1.10}/1bcoder.egg-info/requires.txt +0 -0
  13. {1bcoder-0.1.8 → 1bcoder-0.1.10}/1bcoder.egg-info/top_level.txt +0 -0
  14. {1bcoder-0.1.8 → 1bcoder-0.1.10}/LICENSE +0 -0
  15. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/__init__.py +0 -0
  16. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/agents/advance.txt +0 -0
  17. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/agents/ask.txt +0 -0
  18. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/agents/compact.txt +0 -0
  19. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/agents/concepts.txt +0 -0
  20. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/agents/fill.txt +0 -0
  21. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/agents/planning.txt +0 -0
  22. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/agents/scan.txt +0 -0
  23. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/agents/sqlite.txt +0 -0
  24. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/agents/websearch.txt +0 -0
  25. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/doc/FLOWS.md +0 -0
  26. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/doc/MCP.md +0 -0
  27. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/doc/OLLAMA_SERVER_PARAM.md +0 -0
  28. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/doc/PARAM.md +0 -0
  29. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/doc/PROC.md +0 -0
  30. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/doc/TRANSLATE.md +0 -0
  31. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/flows/__pycache__/commit_message.cpython-311.pyc +0 -0
  32. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/flows/commit_message.py +0 -0
  33. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/flows/grounding.py +0 -0
  34. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/flows/py_error_trace.py +0 -0
  35. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/flows/simargl_files.py +0 -0
  36. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/flows/webask.py +0 -0
  37. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/map.txt +0 -0
  38. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/proc/action-required.py +0 -0
  39. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/proc/add-save.py +0 -0
  40. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/proc/assist.py +0 -0
  41. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/proc/collect-files.py +0 -0
  42. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/proc/ctx_cut.py +0 -0
  43. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/proc/extract-code.py +0 -0
  44. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/proc/extract-files.py +0 -0
  45. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/proc/extract-list.py +0 -0
  46. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/proc/grounding-check.py +0 -0
  47. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/proc/md.py +0 -0
  48. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/proc/mdx.py +0 -0
  49. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/proc/pattern-gate.py +0 -0
  50. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/proc/regexp-extract.py +0 -0
  51. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/proc/rude_words.py +0 -0
  52. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/proc/scan-save.py +0 -0
  53. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/proc/secret_check.py +0 -0
  54. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/proc/sql_readonly_guard.py +0 -0
  55. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/proc/tempctx-cut.py +0 -0
  56. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/profiles.txt +0 -0
  57. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/prompts/analysis.txt +0 -0
  58. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/prompts/sumarise.txt +0 -0
  59. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/prompts.txt +0 -0
  60. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/AddFunction.txt +0 -0
  61. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/AskProject.txt +0 -0
  62. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/CheckRequirements.txt +0 -0
  63. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/DockerMySQL.txt +0 -0
  64. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/DockerNginx.txt +0 -0
  65. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/DockerPython.txt +0 -0
  66. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/DockerStack.txt +0 -0
  67. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/DuckDuckGoInstant.txt +0 -0
  68. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/EnvTemplate.txt +0 -0
  69. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/Explain.txt +0 -0
  70. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/ExploreProjectStructure.txt +0 -0
  71. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/GitIgnorePython.txt +0 -0
  72. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/MySQLDump.txt +0 -0
  73. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/NewScript.txt +0 -0
  74. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/PipFreeze.txt +0 -0
  75. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/PyPI.txt +0 -0
  76. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/Refactor.txt +0 -0
  77. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/RunAndFix.txt +0 -0
  78. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/SQLiteSchema.txt +0 -0
  79. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/Translate.txt +0 -0
  80. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/WikiPage.txt +0 -0
  81. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/WikiSearch.txt +0 -0
  82. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/auto-bkup.txt +0 -0
  83. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/edit-control.txt +0 -0
  84. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/parallel_call.txt +0 -0
  85. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/personal/content/create-regular-content.txt +0 -0
  86. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/personal/content/plan.txt +0 -0
  87. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/personal/test/collect-data-from-test-environment.txt +0 -0
  88. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/plan.txt +0 -0
  89. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/remote/create-content-on-remote-server.txt +0 -0
  90. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/set_ctx.txt +0 -0
  91. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/simargl-cli_index_files.txt +0 -0
  92. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/simargl-cli_index_units.txt +0 -0
  93. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/simargl-cli_search.txt +0 -0
  94. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/team-map-worker.txt +0 -0
  95. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/team-search-worker.txt +0 -0
  96. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/team-summarize.txt +0 -0
  97. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/team-tree-worker.txt +0 -0
  98. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/scripts/test.txt +0 -0
  99. {1bcoder-0.1.8 → 1bcoder-0.1.10}/_bcoder_data/teams/code-analysis.yaml +0 -0
  100. {1bcoder-0.1.8 → 1bcoder-0.1.10}/setup.cfg +0 -0
  101. {1bcoder-0.1.8 → 1bcoder-0.1.10}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: 1bcoder
3
- Version: 0.1.8
3
+ Version: 0.1.10
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
@@ -59,7 +59,7 @@ Key design decisions:
59
59
  - **Short agent system prompts, at most 5 tools per agent, one function per agent** — `ask`, `edit`, `fill`, `scan`, `compact`. Not universal agents with bloated skill sets.
60
60
  - **Tolerant of long and malformed output** — post-processing is automatic; the programmer does not teach the model JSON syntax.
61
61
  - **`/parallel`** — send the same context to several models simultaneously and combine results; a 0.5b and a 1b model working together often outperform either alone; designed to coordinate small models running on multiple machines or phones.
62
- - **`/map`** — project structure index with structural diff; lets the model navigate a codebase without loading it into context.
62
+ - **`/map`** — project structure index with structural diff and keyword grounding; lets the model navigate a codebase without loading it into context. Keyword extract supports three modes: exact, like (`%token%`), and fuzzy (subword); `-o` resolves each keyword to the files it appears in.
63
63
  - **`/ctx`** — surgical context management: savepoints, selective compaction, named context library, multi-turn rollback. Small models cannot afford wasted tokens.
64
64
  - **`/scan`** — reads any large file chunk by chunk and builds a themed summary without overflowing context.
65
65
  - **`/proc`** — parameterized command scripts for repeatable preparation workflows.
@@ -538,8 +538,8 @@ The map command scans your project with language-agnostic regex, extracts defini
538
538
  /map trace <start> <end> [-y] — shortest dependency path between two points
539
539
  /map idiff [path] [depth] — re-index then show diff vs previous snapshot
540
540
  /map diff — show diff without re-indexing (safe to repeat)
541
- /map keyword index — build keyword vocabulary from map.txt
542
- /map keyword extract <text> [-f] [-a] [-n] [-c] — extract real identifiers from keyword.txt matching text/file
541
+ /map keyword index — build keyword vocabulary from map.txt
542
+ /map keyword extract <text> [-f] [-l] [-a] [-s] [-n] [-c] [-o] — extract real identifiers from keyword.txt matching text/file
543
543
  ```
544
544
 
545
545
  **Partial / incremental indexing** — for large codebases where a full re-scan is slow:
@@ -583,6 +583,33 @@ This lets you re-index a changed module in seconds instead of hours.
583
583
  /map find models -d 2 — filenames + defines/vars only
584
584
  ```
585
585
 
586
+ **`/map keyword`** — two-step identifier grounding:
587
+
588
+ ```
589
+ /map keyword index — scan map.txt → .1bcoder/keyword.txt (word, count, line refs)
590
+ /map keyword extract "fix rule search" -l -o — find identifiers, show which files they're in
591
+ ```
592
+
593
+ Extract flags:
594
+
595
+ | Flag | Mode | Effect |
596
+ |---|---|---|
597
+ | *(none)* | exact | query word must exactly match a keyword |
598
+ | `-f` | fuzzy | splits camelCase/snake_case into subwords; matches if all query subwords (≥5 chars) appear in the identifier |
599
+ | `-l` | like | substring match: any keyword containing the token (`%token%`) |
600
+ | `-n` | — | show frequency count: `RuleIndex(25)` |
601
+ | `-s` | — | sort by frequency descending |
602
+ | `-a` | — | sort alphabetically |
603
+ | `-c` | — | comma-separated output (default: one per line) |
604
+ | `-o` | — | show origin files: `parameter_rule -> lib/cop/rule.rb, config/default.yml` |
605
+
606
+ ```
607
+ /map keyword extract "add validation rule" -l -o
608
+ # parameter_rule -> lib/rubocop/cop/style/rule.rb, config/default.yml
609
+ # cop_rule -> lib/rubocop/cop/base.rb
610
+ # load_rules -> lib/rubocop/config_loader.rb
611
+ ```
612
+
586
613
  **`/map trace`** — three modes:
587
614
 
588
615
  **1. Backwards BFS** (who depends on this?):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: 1bcoder
3
- Version: 0.1.8
3
+ Version: 0.1.10
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
@@ -59,7 +59,7 @@ Key design decisions:
59
59
  - **Short agent system prompts, at most 5 tools per agent, one function per agent** — `ask`, `edit`, `fill`, `scan`, `compact`. Not universal agents with bloated skill sets.
60
60
  - **Tolerant of long and malformed output** — post-processing is automatic; the programmer does not teach the model JSON syntax.
61
61
  - **`/parallel`** — send the same context to several models simultaneously and combine results; a 0.5b and a 1b model working together often outperform either alone; designed to coordinate small models running on multiple machines or phones.
62
- - **`/map`** — project structure index with structural diff; lets the model navigate a codebase without loading it into context.
62
+ - **`/map`** — project structure index with structural diff and keyword grounding; lets the model navigate a codebase without loading it into context. Keyword extract supports three modes: exact, like (`%token%`), and fuzzy (subword); `-o` resolves each keyword to the files it appears in.
63
63
  - **`/ctx`** — surgical context management: savepoints, selective compaction, named context library, multi-turn rollback. Small models cannot afford wasted tokens.
64
64
  - **`/scan`** — reads any large file chunk by chunk and builds a themed summary without overflowing context.
65
65
  - **`/proc`** — parameterized command scripts for repeatable preparation workflows.
@@ -538,8 +538,8 @@ The map command scans your project with language-agnostic regex, extracts defini
538
538
  /map trace <start> <end> [-y] — shortest dependency path between two points
539
539
  /map idiff [path] [depth] — re-index then show diff vs previous snapshot
540
540
  /map diff — show diff without re-indexing (safe to repeat)
541
- /map keyword index — build keyword vocabulary from map.txt
542
- /map keyword extract <text> [-f] [-a] [-n] [-c] — extract real identifiers from keyword.txt matching text/file
541
+ /map keyword index — build keyword vocabulary from map.txt
542
+ /map keyword extract <text> [-f] [-l] [-a] [-s] [-n] [-c] [-o] — extract real identifiers from keyword.txt matching text/file
543
543
  ```
544
544
 
545
545
  **Partial / incremental indexing** — for large codebases where a full re-scan is slow:
@@ -583,6 +583,33 @@ This lets you re-index a changed module in seconds instead of hours.
583
583
  /map find models -d 2 — filenames + defines/vars only
584
584
  ```
585
585
 
586
+ **`/map keyword`** — two-step identifier grounding:
587
+
588
+ ```
589
+ /map keyword index — scan map.txt → .1bcoder/keyword.txt (word, count, line refs)
590
+ /map keyword extract "fix rule search" -l -o — find identifiers, show which files they're in
591
+ ```
592
+
593
+ Extract flags:
594
+
595
+ | Flag | Mode | Effect |
596
+ |---|---|---|
597
+ | *(none)* | exact | query word must exactly match a keyword |
598
+ | `-f` | fuzzy | splits camelCase/snake_case into subwords; matches if all query subwords (≥5 chars) appear in the identifier |
599
+ | `-l` | like | substring match: any keyword containing the token (`%token%`) |
600
+ | `-n` | — | show frequency count: `RuleIndex(25)` |
601
+ | `-s` | — | sort by frequency descending |
602
+ | `-a` | — | sort alphabetically |
603
+ | `-c` | — | comma-separated output (default: one per line) |
604
+ | `-o` | — | show origin files: `parameter_rule -> lib/cop/rule.rb, config/default.yml` |
605
+
606
+ ```
607
+ /map keyword extract "add validation rule" -l -o
608
+ # parameter_rule -> lib/rubocop/cop/style/rule.rb, config/default.yml
609
+ # cop_rule -> lib/rubocop/cop/base.rb
610
+ # load_rules -> lib/rubocop/config_loader.rb
611
+ ```
612
+
586
613
  **`/map trace`** — three modes:
587
614
 
588
615
  **1. Backwards BFS** (who depends on this?):
@@ -44,7 +44,7 @@ Key design decisions:
44
44
  - **Short agent system prompts, at most 5 tools per agent, one function per agent** — `ask`, `edit`, `fill`, `scan`, `compact`. Not universal agents with bloated skill sets.
45
45
  - **Tolerant of long and malformed output** — post-processing is automatic; the programmer does not teach the model JSON syntax.
46
46
  - **`/parallel`** — send the same context to several models simultaneously and combine results; a 0.5b and a 1b model working together often outperform either alone; designed to coordinate small models running on multiple machines or phones.
47
- - **`/map`** — project structure index with structural diff; lets the model navigate a codebase without loading it into context.
47
+ - **`/map`** — project structure index with structural diff and keyword grounding; lets the model navigate a codebase without loading it into context. Keyword extract supports three modes: exact, like (`%token%`), and fuzzy (subword); `-o` resolves each keyword to the files it appears in.
48
48
  - **`/ctx`** — surgical context management: savepoints, selective compaction, named context library, multi-turn rollback. Small models cannot afford wasted tokens.
49
49
  - **`/scan`** — reads any large file chunk by chunk and builds a themed summary without overflowing context.
50
50
  - **`/proc`** — parameterized command scripts for repeatable preparation workflows.
@@ -523,8 +523,8 @@ The map command scans your project with language-agnostic regex, extracts defini
523
523
  /map trace <start> <end> [-y] — shortest dependency path between two points
524
524
  /map idiff [path] [depth] — re-index then show diff vs previous snapshot
525
525
  /map diff — show diff without re-indexing (safe to repeat)
526
- /map keyword index — build keyword vocabulary from map.txt
527
- /map keyword extract <text> [-f] [-a] [-n] [-c] — extract real identifiers from keyword.txt matching text/file
526
+ /map keyword index — build keyword vocabulary from map.txt
527
+ /map keyword extract <text> [-f] [-l] [-a] [-s] [-n] [-c] [-o] — extract real identifiers from keyword.txt matching text/file
528
528
  ```
529
529
 
530
530
  **Partial / incremental indexing** — for large codebases where a full re-scan is slow:
@@ -568,6 +568,33 @@ This lets you re-index a changed module in seconds instead of hours.
568
568
  /map find models -d 2 — filenames + defines/vars only
569
569
  ```
570
570
 
571
+ **`/map keyword`** — two-step identifier grounding:
572
+
573
+ ```
574
+ /map keyword index — scan map.txt → .1bcoder/keyword.txt (word, count, line refs)
575
+ /map keyword extract "fix rule search" -l -o — find identifiers, show which files they're in
576
+ ```
577
+
578
+ Extract flags:
579
+
580
+ | Flag | Mode | Effect |
581
+ |---|---|---|
582
+ | *(none)* | exact | query word must exactly match a keyword |
583
+ | `-f` | fuzzy | splits camelCase/snake_case into subwords; matches if all query subwords (≥5 chars) appear in the identifier |
584
+ | `-l` | like | substring match: any keyword containing the token (`%token%`) |
585
+ | `-n` | — | show frequency count: `RuleIndex(25)` |
586
+ | `-s` | — | sort by frequency descending |
587
+ | `-a` | — | sort alphabetically |
588
+ | `-c` | — | comma-separated output (default: one per line) |
589
+ | `-o` | — | show origin files: `parameter_rule -> lib/cop/rule.rb, config/default.yml` |
590
+
591
+ ```
592
+ /map keyword extract "add validation rule" -l -o
593
+ # parameter_rule -> lib/rubocop/cop/style/rule.rb, config/default.yml
594
+ # cop_rule -> lib/rubocop/cop/base.rb
595
+ # load_rules -> lib/rubocop/config_loader.rb
596
+ ```
597
+
571
598
  **`/map trace`** — three modes:
572
599
 
573
600
  **1. Backwards BFS** (who depends on this?):
@@ -11,6 +11,10 @@
11
11
  /compact = /agent compact {{args}}
12
12
  /websearch = /agent websearch {{args}}
13
13
  /webask = /flow webask {{args}}
14
+ /simargl = /run simargl {{args}}
15
+ /sim = /run simargl {{args}}
16
+ /svitovyd = /run svitovyd {{args}}
17
+ /svi = /run svitovyd {{args}}
14
18
 
15
19
  /small = /parallel {{args}} profile: small
16
20
  /explain = /parallel {{args}} profile: explain
@@ -916,7 +916,7 @@ Output capture operators (work with any command — LLM reply, tool, proc):
916
916
  CSV format: word, count, semicolon-separated list of line numbers in map.txt.
917
917
  Sorted alphabetically. Run once after /map index (or whenever map changes).
918
918
  e.g. /map keyword index
919
- /map keyword extract <text or file> [-a] [-f] [-n] [-c]
919
+ /map keyword extract <text or file> [-a] [-s] [-f] [-l] [-n] [-c] [-o]
920
920
  Extract real identifiers from keyword.txt matching words in the given text or file.
921
921
  Output is always real identifiers from keyword.txt — never synthetic splits.
922
922
  Default (exact): query word must exactly match a keyword.txt entry.
@@ -928,16 +928,20 @@ Output capture operators (work with any command — LLM reply, tool, proc):
928
928
  "RuleIndex" → matches RuleIndex only (needs both 'rule' AND 'index')
929
929
  "coverage" → matches CoverageMetric, LineCoverage, BranchCoverage
930
930
  "RuleIndex" → does NOT match Rule (missing 'index') or Index (missing 'rule')
931
+ -l like match (%token%): any keyword containing the query token as substring
932
+ "rule" → RuleIndex, rule_validator, parseRule, getRuleSet
931
933
  -a alphabetical order
932
934
  -s sort by codebase count descending (most frequent first)
933
935
  -n show codebase count next to each word: RuleIndex(25) RuleName(12)
934
936
  (-n implies -s)
935
937
  -c comma-separated output instead of one per line
938
+ -o show origin: which files each keyword appears in: keyword -> file1, file2
936
939
  e.g. /map keyword extract notes.txt
937
940
  /map keyword extract notes.txt -f
941
+ /map keyword extract notes.txt -l
938
942
  /map keyword extract notes.txt -f -n -c
939
943
  /map keyword extract "add isbn field to the Book class" -f -a
940
- /map keyword extract "fix rule search" -f -c
944
+ /map keyword extract "fix rule search" -l -o
941
945
 
942
946
  /hook before|after <cmd> <script>
943
947
  Run a script before or after a command (edit, patch, fix, insert).
@@ -1175,8 +1179,11 @@ def _fmt_ctx(n: int) -> str:
1175
1179
 
1176
1180
 
1177
1181
  def read_file(path, start=None, end=None, line_numbers=True):
1178
- with open(path, "r", encoding="utf-8") as f:
1179
- lines = f.readlines()
1182
+ try:
1183
+ with open(path, "r", encoding="utf-8") as f:
1184
+ lines = f.readlines()
1185
+ except UnicodeDecodeError:
1186
+ raise ValueError(f"[read] {path}: binary file, cannot read as text")
1180
1187
  total = len(lines)
1181
1188
  if start is not None:
1182
1189
  start = max(1, start)
@@ -2243,6 +2250,30 @@ class CoderCLI:
2243
2250
  right = right[:5]
2244
2251
  return f"{left}:{right}" if right else left
2245
2252
 
2253
+ def _autosave_prompt(self) -> None:
2254
+ """Ask user whether to save context; save to .1bcoder/autosave/ if confirmed."""
2255
+ if not self.messages:
2256
+ return
2257
+ try:
2258
+ ans = input("Save context before action? [Y/n] ").strip().lower()
2259
+ except (EOFError, KeyboardInterrupt):
2260
+ print()
2261
+ return
2262
+ if ans == "n":
2263
+ return
2264
+ import datetime as _dt
2265
+ stamp = _dt.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
2266
+ save_dir = os.path.join(
2267
+ BCODER_DIR if os.path.isdir(BCODER_DIR) else HOME_BCODER_DIR,
2268
+ "autosave"
2269
+ )
2270
+ os.makedirs(save_dir, exist_ok=True)
2271
+ fpath = os.path.join(save_dir, f"{stamp}.txt")
2272
+ with open(fpath, "w", encoding="utf-8") as f:
2273
+ for msg in self.messages:
2274
+ f.write(f"=== {msg['role']} ===\n{msg['content']}\n\n")
2275
+ print(f"[autosave] context saved → {fpath}")
2276
+
2246
2277
  def _print_status(self) -> None:
2247
2278
  """Print a single status line showing model, size, quant, native ctx, and usage."""
2248
2279
  est_tokens = sum(len(m["content"]) for m in self.messages) // 4
@@ -2253,6 +2284,7 @@ class CoderCLI:
2253
2284
  parts.append(_fmt_ctx(self._meta_ctx))
2254
2285
  meta = f" [{' '.join(parts)}]" if parts else ""
2255
2286
  print(f"\033[2m {model_str}{meta} │ ctx {est_tokens} / {self.num_ctx} ({pct}%)\033[0m")
2287
+ print()
2256
2288
 
2257
2289
  def __init__(self, base_url, model, models, provider="ollama"):
2258
2290
  self.base_url = base_url
@@ -2350,6 +2382,8 @@ class CoderCLI:
2350
2382
  ) as resp:
2351
2383
  resp.raise_for_status()
2352
2384
  in_think = False
2385
+ _had_thinking = False
2386
+ _sep_done = False
2353
2387
  for line in resp.iter_lines():
2354
2388
  if not line:
2355
2389
  continue
@@ -2367,6 +2401,7 @@ class CoderCLI:
2367
2401
  reasoning = delta.get("reasoning_content") or ""
2368
2402
  if reasoning and self.think_show:
2369
2403
  _print(f"\033[90m{reasoning}\033[0m")
2404
+ _had_thinking = True
2370
2405
  chunk = delta.get("content") or ""
2371
2406
  if chunk:
2372
2407
  # apply <think>...</think> state machine (same as Ollama path)
@@ -2385,6 +2420,9 @@ class CoderCLI:
2385
2420
  else:
2386
2421
  start = chunk.find('<think>')
2387
2422
  if start == -1:
2423
+ if _had_thinking and not _sep_done:
2424
+ _print("\n")
2425
+ _sep_done = True
2388
2426
  _print(chunk)
2389
2427
  chunks.append(chunk)
2390
2428
  chunk = ""
@@ -2393,6 +2431,7 @@ class CoderCLI:
2393
2431
  _print(chunk[:start])
2394
2432
  chunks.append(chunk[:start])
2395
2433
  in_think = True
2434
+ _had_thinking = True
2396
2435
  chunk = chunk[start + 7:]
2397
2436
  else:
2398
2437
  opts = {"num_ctx": self.num_ctx}
@@ -2405,6 +2444,8 @@ class CoderCLI:
2405
2444
  ) as resp:
2406
2445
  resp.raise_for_status()
2407
2446
  in_think = False
2447
+ _had_thinking = False
2448
+ _sep_done = False
2408
2449
  for line in resp.iter_lines():
2409
2450
  if not line:
2410
2451
  continue
@@ -2414,6 +2455,7 @@ class CoderCLI:
2414
2455
  thinking = msg.get("thinking", "")
2415
2456
  if thinking and self.think_show:
2416
2457
  _print(f"\033[90m{thinking}\033[0m")
2458
+ _had_thinking = True
2417
2459
  chunk = msg.get("content", "")
2418
2460
  if chunk:
2419
2461
  # State machine: track <think>...</think> across tokens
@@ -2432,6 +2474,9 @@ class CoderCLI:
2432
2474
  else:
2433
2475
  start = chunk.find('<think>')
2434
2476
  if start == -1:
2477
+ if _had_thinking and not _sep_done:
2478
+ _print("\n")
2479
+ _sep_done = True
2435
2480
  _print(chunk)
2436
2481
  chunks.append(chunk)
2437
2482
  chunk = ""
@@ -2440,6 +2485,7 @@ class CoderCLI:
2440
2485
  _print(chunk[:start])
2441
2486
  chunks.append(chunk[:start])
2442
2487
  in_think = True
2488
+ _had_thinking = True
2443
2489
  chunk = chunk[start + len('<think>'):]
2444
2490
  if data.get("done"):
2445
2491
  break
@@ -2484,9 +2530,9 @@ class CoderCLI:
2484
2530
  self._apply_config(_session_cfg)
2485
2531
  print()
2486
2532
  self._translate_autoload()
2533
+ self._print_status()
2487
2534
  while True:
2488
2535
  try:
2489
- self._print_status()
2490
2536
  user_input = input("> ").strip()
2491
2537
  except (EOFError, KeyboardInterrupt):
2492
2538
  print()
@@ -2496,6 +2542,7 @@ class CoderCLI:
2496
2542
  if user_input not in self._history or (self._history and self._history[-1] != user_input):
2497
2543
  self._history.append(user_input)
2498
2544
  self._route(user_input)
2545
+ self._print_status()
2499
2546
  if user_input.startswith("/"):
2500
2547
  import shlex as _shlex
2501
2548
  _args = user_input.split(None, 1)[1] if " " in user_input else ""
@@ -2569,6 +2616,7 @@ class CoderCLI:
2569
2616
  return
2570
2617
 
2571
2618
  if user_input == "/exit":
2619
+ self._autosave_prompt()
2572
2620
  sys.exit(0)
2573
2621
  elif user_input == "/about":
2574
2622
  self._cmd_about()
@@ -2940,6 +2988,10 @@ advanced_tools =
2940
2988
  print(f"[no messages found in {load_path}]")
2941
2989
  return
2942
2990
  self.messages.extend(loaded)
2991
+ last_a = next((m["content"] for m in reversed(loaded) if m["role"] == "assistant"), "")
2992
+ if last_a:
2993
+ self.last_reply = last_a
2994
+ self._last_output = last_a
2943
2995
  print(f"[loaded {len(loaded)} messages from {load_path}]")
2944
2996
  except FileNotFoundError:
2945
2997
  print(f"file not found: {load_path}")
@@ -3086,6 +3138,7 @@ advanced_tools =
3086
3138
  print("[ctx compact] failed — context unchanged")
3087
3139
  return
3088
3140
 
3141
+ self._autosave_prompt()
3089
3142
  if use_savepoint:
3090
3143
  del self.messages[self._savepoint:]
3091
3144
  self.messages.append({"role": "user", "content": f"[summary since savepoint]\n{summary}"})
@@ -3782,7 +3835,7 @@ advanced_tools =
3782
3835
  _ok(f"context: injected {label}")
3783
3836
  except FileNotFoundError:
3784
3837
  print(f"file not found: {path}")
3785
- except OSError as e:
3838
+ except (OSError, ValueError) as e:
3786
3839
  _err(e)
3787
3840
 
3788
3841
  def _cmd_edit(self, user_input: str):
@@ -4984,7 +5037,21 @@ advanced_tools =
4984
5037
  try:
4985
5038
  if mode == "online":
4986
5039
  from deep_translator import GoogleTranslator as _GT
4987
- return _GT(source=from_lang, target=to_lang).translate(text)
5040
+ _MAX = 4500
5041
+ if len(text) <= _MAX:
5042
+ return _GT(source=from_lang, target=to_lang).translate(text) or text
5043
+ # chunk by paragraphs to stay under Google's limit
5044
+ _parts, _buf, _results = text.split("\n\n"), "", []
5045
+ for _p in _parts:
5046
+ if len(_buf) + len(_p) + 2 <= _MAX:
5047
+ _buf = (_buf + "\n\n" + _p) if _buf else _p
5048
+ else:
5049
+ if _buf:
5050
+ _results.append(_GT(source=from_lang, target=to_lang).translate(_buf) or _buf)
5051
+ _buf = _p
5052
+ if _buf:
5053
+ _results.append(_GT(source=from_lang, target=to_lang).translate(_buf) or _buf)
5054
+ return "\n\n".join(_results)
4988
5055
  elif mode == "lm":
4989
5056
  cfg = self._translate_load_cfg()
4990
5057
  lm_timeout = int(cfg.get("lm_timeout", 120))
@@ -5567,7 +5634,10 @@ Config stored in ~/.1bcoder/translate.json
5567
5634
  translated = self._translate_run(self.last_reply, "en", lang, mode=mode)
5568
5635
  self.last_translated_reply = translated
5569
5636
  print(f"\n{_LBLUE}─── {lang.upper()} ───{_R}")
5570
- print(translated)
5637
+ if translated:
5638
+ print(translated)
5639
+ else:
5640
+ print("[translate] translation returned empty result — check language code or network")
5571
5641
  return
5572
5642
 
5573
5643
  print("usage: /translate setup [lang:uk] [mode:lm] [host:<url>] [model:<name>] [profile:<name>]")
@@ -7668,11 +7738,13 @@ Config stored in ~/.1bcoder/translate.json
7668
7738
  sort_alpha = "-a" in args
7669
7739
  sort_count = "-s" in args
7670
7740
  fuzzy = "-f" in args
7741
+ like = "-l" in args
7671
7742
  show_counts = "-n" in args
7672
7743
  csv_out = "-c" in args
7673
- src_tokens = [a for a in args if a not in ("-a", "-s", "-f", "-n", "-c")]
7744
+ show_origin = "-o" in args
7745
+ src_tokens = [a for a in args if a not in ("-a", "-s", "-f", "-l", "-n", "-c", "-o")]
7674
7746
  if not src_tokens:
7675
- print("usage: /map keyword extract <text or file> [-a] [-s] [-f] [-n] [-c]")
7747
+ print("usage: /map keyword extract <text or file> [-a] [-s] [-f] [-l] [-n] [-c] [-o]")
7676
7748
  return
7677
7749
  # load keyword vocab: word → count
7678
7750
  _csv.field_size_limit(10_000_000) # lines field can be large for common words
@@ -7714,6 +7786,12 @@ Config stored in ~/.1bcoder/translate.json
7714
7786
  # keyword matches if ALL query subwords are present in keyword's subwords
7715
7787
  if query_parts <= kp and kw not in seen:
7716
7788
  seen[kw] = i
7789
+ elif like:
7790
+ for i, m in enumerate(token_re.finditer(text)):
7791
+ token = m.group().lower()
7792
+ for j, kw in enumerate(kw_freq):
7793
+ if token in kw.lower() and kw not in seen:
7794
+ seen[kw] = i * 100000 + j
7717
7795
  else:
7718
7796
  # default: exact identifier match
7719
7797
  kw_set = set(kw_freq)
@@ -7730,6 +7808,30 @@ Config stored in ~/.1bcoder/translate.json
7730
7808
  result = sorted(seen, key=lambda w: (-kw_freq[w], w.lower()))
7731
7809
  else:
7732
7810
  result = sorted(seen, key=lambda w: (seen[w], w.lower()))
7811
+ if show_origin:
7812
+ map_path = os.path.join(BCODER_DIR, "map.txt")
7813
+ line_to_file = map_query._build_line_to_file(map_path)
7814
+ # load line numbers column from keyword.txt
7815
+ kw_lines: dict = {}
7816
+ with open(kw_path, encoding="utf-8", newline="") as f:
7817
+ reader2 = _csv.reader(f)
7818
+ next(reader2, None)
7819
+ for row in reader2:
7820
+ if len(row) >= 3 and row[0] in seen:
7821
+ kw_lines[row[0]] = [int(x) for x in row[2].split(";") if x]
7822
+ lines_out = []
7823
+ for w in result:
7824
+ label = f"{w}({kw_freq[w]})" if show_counts else w
7825
+ files: list = []
7826
+ seen_files: set = set()
7827
+ for ln in kw_lines.get(w, []):
7828
+ f = line_to_file.get(ln)
7829
+ if f and f not in seen_files:
7830
+ seen_files.add(f)
7831
+ files.append(f)
7832
+ lines_out.append(f"{label} -> {', '.join(files)}" if files else label)
7833
+ print("\n".join(lines_out))
7834
+ return
7733
7835
  if show_counts:
7734
7836
  items = [f"{w}({kw_freq[w]})" for w in result]
7735
7837
  else:
@@ -292,15 +292,18 @@ def build_map(root: str, depth: int = 2, map_path: str = None) -> str:
292
292
  global_index[name] = rel
293
293
 
294
294
  # ── link phase (changed files only) ──────────────────────────────────────────
295
+ global_set = set(global_index.keys())
295
296
  file_links = {} # rel → { target_rel → { name → kind } }
296
297
  for rel, text in tqdm(file_content.items(), desc="linking", unit="file", file=sys.stderr):
298
+ tokens_in_file = set(_WORD_RE.findall(text))
299
+ candidates = tokens_in_file & global_set
297
300
  by_target = {}
298
- for name, target_rel in global_index.items():
301
+ for name in candidates:
302
+ target_rel = global_index[name]
299
303
  if target_rel == rel:
300
304
  continue
301
- if re.search(r'\b' + re.escape(name) + r'\b', text):
302
- kind = classify_ref(name, text)
303
- by_target.setdefault(target_rel, {})[name] = kind
305
+ kind = classify_ref(name, text)
306
+ by_target.setdefault(target_rel, {})[name] = kind
304
307
  file_links[rel] = by_target
305
308
 
306
309
  # ── format output ────────────────────────────────────────────────────────────
@@ -538,6 +538,74 @@ def idiff_report(map_prev: str, map_curr: str) -> str:
538
538
  return '\n'.join(lines)
539
539
 
540
540
 
541
+ # ── keywords ────────────────────────────────────────────────────────────────────
542
+
543
+ def _split_identifier(name: str) -> list:
544
+ """Split camelCase / snake_case / PascalCase into lowercase subwords."""
545
+ parts = re.split(r'[_\-]+', name)
546
+ result = []
547
+ for part in parts:
548
+ if not part:
549
+ continue
550
+ s = re.sub(r'([A-Z]+)([A-Z][a-z])', r'\1_\2', part)
551
+ s = re.sub(r'([a-z\d])([A-Z])', r'\1_\2', s)
552
+ result.extend(w.lower() for w in s.split('_') if len(w) >= 2)
553
+ seen: dict = {}
554
+ for w in result:
555
+ seen.setdefault(w, None)
556
+ return list(seen)
557
+
558
+
559
+ def _build_line_to_file(map_path: str) -> dict:
560
+ """Return {line_number: file_path} for every line in map.txt.
561
+
562
+ File ownership: nearest non-indented, non-comment line above.
563
+ """
564
+ with open(map_path, encoding='utf-8', errors='replace') as f:
565
+ lines = f.readlines()
566
+ result = {}
567
+ current_file = None
568
+ for i, line in enumerate(lines, 1):
569
+ stripped = line.rstrip('\n')
570
+ if stripped and not stripped[0].isspace() and not stripped.startswith('#'):
571
+ current_file = stripped.strip()
572
+ if current_file:
573
+ result[i] = current_file
574
+ return result
575
+
576
+
577
+ def keyword_to_files(map_path: str, word: str) -> list:
578
+ """Return list of files where *word* appears, in order of first occurrence."""
579
+ import csv as _csv
580
+
581
+ kw_path = os.path.join(os.path.dirname(os.path.abspath(map_path)), 'keyword.txt')
582
+ if not os.path.exists(kw_path):
583
+ return []
584
+
585
+ line_nums = []
586
+ _csv.field_size_limit(10_000_000)
587
+ with open(kw_path, encoding='utf-8', newline='') as f:
588
+ reader = _csv.reader(f)
589
+ next(reader, None)
590
+ for row in reader:
591
+ if len(row) >= 3 and row[0] == word:
592
+ line_nums = [int(x) for x in row[2].split(';') if x]
593
+ break
594
+
595
+ if not line_nums:
596
+ return []
597
+
598
+ line_to_file = _build_line_to_file(map_path)
599
+ seen: set = set()
600
+ files = []
601
+ for ln in line_nums:
602
+ f = line_to_file.get(ln)
603
+ if f and f not in seen:
604
+ seen.add(f)
605
+ files.append(f)
606
+ return files
607
+
608
+
541
609
  # ── CLI entry point ─────────────────────────────────────────────────────────────
542
610
 
543
611
  def main():
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "1bcoder"
7
- version = "0.1.8"
7
+ version = "0.1.10"
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