Draft
Conversation
…evel mapping Flemma's canonical thinking levels (minimal/low/medium/high/max) are now silently mapped to valid provider API values via per-model metadata in models.lua, replacing scattered inline mapping logic in each provider. - models.lua: added thinking_effort_map to all effort-based models and supports_adaptive_thinking to Anthropic 4.6 models - base.lua: added map_effort() helper, effort-based resolve_thinking() path now applies the map (effort = API value, level = canonical name) - openai.lua: removed inline max→xhigh/high mapping, passes model_info to resolve_thinking which handles it - anthropic.lua: replaced inline effort_map, model name pattern matching for 4.6 detection, and Opus-only max guard with model metadata lookups - vertex.lua: removed THINKING_LEVEL_MAP/THINKING_LEVEL_MAP_PRO tables, replaced model name pattern matching with thinking_effort_map lookup - update-models command: added discovery-based instructions for effort maps including Pi source code as ground-truth reference Fixes the "Unsupported value: 'minimal'" API error when using thinking="minimal" with OpenAI models that don't accept it natively. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add _get_content_prefix, _emit_tool_use_block, _emit_thinking_block, _emit_redacted_thinking, _warn_truncated, _signal_blocked, _is_error_response, and _inject_orphan_results to base.lua. These will be used by providers in subsequent commits to eliminate duplicated formatting and lifecycle code. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…er to base base.process_response_line now owns the shared SSE parsing preamble and delegates to _process_data for provider-specific event dispatch. base.reset() and base.finalize_response() auto-destroy sinks in _response_buffer.extra via duck-typing (any value with :destroy()). _init_provider helper encapsulates the metatable chain setup. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Returns an alphabetically sorted array of tool definitions. Providers will use this instead of the manual get_for_prompt + pairs + sort pattern that's currently duplicated in all three providers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Constructor uses base._init_provider - Remove finalize_response override (base auto-destroys sinks) - Remove sink teardown guards in reset - Add _is_error_response override (data.type == 'error') - Rename process_response_line to _process_data - Use base._emit_tool_use_block, _emit_thinking_block, _emit_redacted_thinking, _warn_truncated, _signal_blocked - Use base._inject_orphan_results for orphaned tool calls - Use tools.get_sorted_for_prompt for tool list Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Constructor uses base._init_provider - Remove finalize_response override (base auto-destroys sinks) - Remove sink teardown guards in reset - Delete _emit_reasoning_block (replaced by base._emit_thinking_block) - Rename process_response_line to _process_data - Use base._emit_tool_use_block for tool formatting - Use base._inject_orphan_results for orphaned tool calls - Use tools.get_sorted_for_prompt for tool list Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Constructor uses base._init_provider - Remove finalize_response override (base auto-destroys sinks) - Remove sink teardown guards in reset - Rename process_response_line to _process_data - Use base._emit_tool_use_block, _emit_thinking_block, _warn_truncated, _signal_blocked - Use base._inject_orphan_results for orphaned tool calls - Use tools.get_sorted_for_prompt for tool list Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ption All providers now use base.process_response_line which always handles [DONE]. The allow_done parameter is no longer needed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ndency Session.get() now returns an internally-owned Session instance instead of reaching back into state.lua. state.get_session() forwards to session.get(). Both public APIs unchanged. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ons cycles Modules that state cannot require directly (executor, notifications) now register cleanup functions via state.register_cleanup(). Direct requires of client and writequeue are hoisted to top-level (no cycle). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace inline require("flemma.core") with core.callbacks to break the
circular dependency. All other inline requires hoisted to module top.
Register core callbacks (send_or_execute, cancel_request, update_ui) at
module init so they are available when autopilot dispatches via
vim.schedule.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace inline require("flemma.core") with core.callbacks. Register
cleanup hook with state instead of being required by state inline.
All other inline requires hoisted to module top.
Update sandbox_bwrap_integration_spec to also clear executor from
package.loaded when resetting sandbox, since executor now captures
sandbox_module at require-time.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace inline require("flemma.core") with core.callbacks for the
update_ui autocmd. All other inline requires hoisted to module top.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Register cleanup hook with state instead of being required by state inline. All other inline requires hoisted to module top. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All ~25 inline requires moved to module top. Dynamic require(provider_module) left inline as designed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Moved inline requires in tools/approval (loader, registry_utils) and tools/registry (registry_utils) to module top level. These modules are not cleared in test before_each blocks, so hoisting is safe. Other inline requires in the tools subsystem (parser, config, state, sandbox, tools.registry, tools.presets) must remain inline because tests clear their package.loaded entries for isolation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Moved inline requires across 15 production files to module top level. Dynamic requires and vim string-context requires left inline as designed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…h tool Hoisted 19 remaining static inline requires to module top level. Dynamic require(config.provider) left inline as designed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update test before_each blocks to clear provider implementation modules when clearing their dependencies (tools, registry, sink). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Shell script detects inline require("flemma.*") calls after the first
function definition, excluding dynamic requires and vim string-context.
Also hoists the last two inline requires found:
- core.lua: moved callbacks_registry require to top
- tools/injector.lua: consolidated all requires before first function
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rename the circular-dependency bridge module from flemma.core.callbacks to flemma.core.bridge — a more descriptive name for what it does. Convert commands.lua to lazy loading: all 15 top-level requires are now inline require() calls inside each command action/completion function. Commands are user-triggered entry points, so modules are only loaded when their command is actually invoked. The lint-inline-requires script exempts this file since the inline requires are intentional. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add guidance to CLAUDE.md that plan/design documents (docs/plans/) are working artifacts and must not be committed. Also updates AGENTS.md reference from core.callbacks to core.bridge. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace individual make targets (test, lint, check, lint-inline-requires) with a single `make qa` that runs all four gates silently. On success it prints "qa: OK". On failure it re-runs only the failed gate(s) with visible output, filtering test noise automatically. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Front-load critical rules (JSON, AST, requires, types) as a scannable checklist. Remove duplicated pitfall entries (naming, JSON) that restated conventions. Compress changesets section from 37 to 12 lines. Remove low-information "go read the code" subsections and fold useful bits into project structure. Tighten soft language to hard directives. Update stale make test reference to make qa. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the rigid `thinking_format` config with a composable `statusline.format`
using tmux-style `#{...}` syntax — supporting variable expansion, ternary
conditionals, string comparisons, boolean operators, and arbitrary nesting.
Variables are lazy-evaluated via __index metamethods so only referenced
variables trigger data lookups. The config snapshot is passed through to
resolvers to avoid redundant state.get_config() calls.
Available variables: #{model}, #{provider}, #{thinking}
Default format: #{model}#{?#{thinking}, (#{thinking}),}
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…n sandbox status The :Flemma status and :Flemma sandbox:status commands now display the fully resolved sandbox policy: writable paths (after variable expansion and deduplication), network access, and privilege policy. This makes it easy to verify what the sandbox actually allows at a glance. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…iness Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The default statusline format now shows ⏳ while async tool sources are loading. Also removes the redundant DEFAULT_FORMAT local from the lualine component — the single source of truth is config.lua. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The rebase onto develop introduced a policy field in sandbox status data that the booting indicator test fixtures were missing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Introduce `cursor.lua` as the single choke point for all cursor movement in `.chat` buffers. System-initiated moves are deferred until the user goes idle (tracked via a per-buffer uv timer reset on CursorMoved), then executed. User-initiated moves (force=true) execute immediately and clear any pending deferred move. An invisible extmark tracks deferred targets through buffer mutations. Adds buffer state fields (cursor_pending, cursor_idle_timer), setup() with autocmds and cleanup hooks, and full test coverage. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace direct nvim_win_set_cursor calls across 6 modules with cursor.request_move(). User-initiated sends (keymaps) pass user_initiated=true which becomes force=true on the spinner cursor move. System-initiated moves (response complete, tool phases, autopilot, executor) use deferred (non-forced) moves that coalesce and execute only after the user goes idle. Wire cursor.setup() into init.lua for autocmds and cleanup hooks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rsor engine Navigation (]m/[m) and role marker colon completion now use cursor.request_move(force=true) — user-initiated moves that execute immediately and clear any pending deferred move. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…er_cursor All production call sites now use cursor.request_move(). The old functions are preserved with @deprecated annotations for any external consumers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… sites Every cursor.request_move() call now includes a reason string that flows through the entire lifecycle — request, coalescing, idle timer reset, execution, cancellation, and skip. This makes "why did my cursor jump?" diagnosable with a single log grep. Reasons by call site: spinner/user-send, spinner/autopilot, response-complete, phase1/pending-placeholder, phase2/pending-block, tool-result/result, tool-result/next, autopilot/pending-tool, nav/next-message, nav/prev-message, role-marker-completion Engine internals now log at debug level for notable events (force moves, deferred executions, cancellations, buffer-not-visible skips) and trace level for routine operations (coalescing, timer resets, clamping). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove move_to_bottom, center_cursor, and set_cursor — all call sites were replaced by cursor.request_move() in the cursor engine refactor. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…xpressions
Enable gf / <C-w>f navigation in .chat buffers. Cursor on @./file or
{{ include('path') }} resolves the target path using the expression
evaluator with a capturing include() stub — no I/O, just path resolution.
Infrastructure changes:
- Add end_col to ast.Position and compute it in the parser for expression
and file-reference segments
- Add find_segment_at_position() to ast/query with a fallback pattern
that handles column-less text segments alongside column-aware expressions
- Wire includeexpr buffer option via v:lua in apply_chat_buffer_settings
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tion Instruments resolve_include_path with trace-level logging for cursor position, segment lookup, and include() capture flow. Debug-level logs for the resolved path and eval failures. Helps diagnose gf navigation issues without stepping through code. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When an include result is assigned to a frontmatter variable and
referenced in an expression (e.g., {{ mod }} where mod = include(file)),
gf navigation now resolves the path by inspecting the eval result for
a SOURCE_PATH symbol rather than relying solely on capturing the
include() call.
Introduces flemma.symbols — centralized well-known table keys (analogous
to JavaScript's Symbol) for cross-module metadata tagging. Emittable
include parts are tagged with symbols.SOURCE_PATH at construction time,
making the originating file path available to any downstream consumer.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Navigation was overriding env.include with a capturing stub that duplicated path resolution logic and crashed on nil arguments. Instead, evaluate the expression with the real include() and check the result for symbols.SOURCE_PATH — simpler, correct, and no behaviour divergence from normal evaluation. Also adds a type guard to eval.lua's include() so passing a non-string argument produces a clear diagnostic instead of crashing on :sub(). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add {, }, (, ) to buffer-local isfname so Neovim's gf extracts a
candidate covering the full {{ ... }} expression at any cursor
position — without these, gf on delimiters or whitespace within
the expression would not trigger includeexpr.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ssions (#18) ## Summary Adds `gf` / `<C-w>f` navigation support to `.chat` buffers. When the cursor is on an `@./file` reference or a `{{ include('path') }}` expression, pressing `gf` opens the referenced file, and `<C-w>f` opens it in a split. - Resolve `@./file` references to absolute paths via the existing include() machinery - Resolve `{{ include('path') }}` expressions by evaluating with a capturing stub - Frontmatter variables are available in expressions (e.g. `{{ include(my_var) }}`) - Falls back to Vim's default `v:fname` behavior when cursor is not on an include expression ## Architecture ### New infrastructure (shared with future LSP hover plan) **`end_col` on `flemma.ast.Position`** — The AST position type previously had `start_col` but not `end_col`. Both expression segments (`{{ expr }}`) and file reference segments (`@./file`) now track their ending column. For file references, `end_col` excludes trailing punctuation (e.g. the comma in `@./file.txt, more text`). This is needed for precise cursor-to-segment matching. **`find_segment_at_position(doc, lnum, col)`** in `ast/query.lua` — A new query function that finds the segment (and its parent message) at a given 1-indexed cursor position. The algorithm handles mixed segment types on the same line: column-aware segments (expressions, file refs) get precise matching, while column-less segments (plain text) serve as fallbacks when no column-specific match is found. This prevents text segments from shadowing expression segments on the same line. ### Navigation module additions **`resolve_include_path(bufnr)`** — The core resolution function. It: 1. Gets the cursor position and finds the segment under it via `find_segment_at_position` 2. Bails early if the segment isn't an expression 3. Evaluates frontmatter to populate the environment with user variables 4. Installs a capturing `include()` stub that records the resolved path without performing any I/O (no file reads, no MIME detection, no circular-include checks) 5. Evaluates the expression via `pcall(eval.eval_expression, ...)` — if it calls `include()`, we capture the path; if it doesn't (e.g. `{{ 2 + 2 }}`), we return nil 6. Returns the absolute resolved path, or nil **`resolve_include_path_expr()`** — A thin wrapper called by Vim's `includeexpr` option. Returns the resolved path or falls back to `vim.v.fname`. ### Wiring `apply_chat_buffer_settings` in `ui/init.lua` sets `includeexpr` to `v:lua.require("flemma.navigation").resolve_include_path_expr()`. The `require` happens lazily at `gf`-press time (not at buffer setup), so `navigation.lua` is not added to `ui/init.lua`'s top-level requires. ### Logging Debug and trace logging throughout `resolve_include_path` covering: cursor position, segment lookup, expression code, frontmatter diagnostics, include() capture details, eval failures, and resolved path. Follows the project convention of `"navigation: ..."` prefixed messages. ## Files changed | File | Change | |------|--------| | `lua/flemma/ast/nodes.lua` | Add `end_col? integer` to Position type | | `lua/flemma/ast/query.lua` | Add `find_segment_at_position()` with fallback pattern | | `lua/flemma/parser.lua` | Compute `end_col` for expression and file-reference segments | | `lua/flemma/navigation.lua` | Add `resolve_include_path`, `resolve_include_path_expr`, logging | | `lua/flemma/ui/init.lua` | Wire `includeexpr` buffer option | | `tests/flemma/ast_spec.lua` | 9 new specs for `end_col` and `find_segment_at_position` | | `tests/flemma/includeexpr_spec.lua` | 6 new integration specs for path resolution | | `tests/fixtures/include_target.txt` | Simple fixture for integration tests | | `.changeset/includeexpr-navigation.md` | Minor changeset | ## Test plan - [x] `make qa` passes (luacheck, LuaLS types, inline-requires lint, full test suite) - [x] Manual: open a `.chat` file, place cursor on `@./some-file.txt`, press `gf` — file opens - [x] Manual: place cursor on `{{ include('./some-file.txt') }}`, press `gf` — file opens - [x] Manual: place cursor on plain text, press `gf` — default Vim behavior (no error) - [x] Manual: frontmatter defines a variable, expression uses it in `include(var)`, `gf` resolves correctly 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.