Skip to content

Prepare Release v0.7.0#23

Draft
StanAngeloff wants to merge 95 commits intomainfrom
develop
Draft

Prepare Release v0.7.0#23
StanAngeloff wants to merge 95 commits intomainfrom
develop

Conversation

@StanAngeloff
Copy link
Collaborator

No description provided.

StanAngeloff and others added 30 commits March 8, 2026 16:05
…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>
StanAngeloff and others added 30 commits March 10, 2026 14:56
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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant