Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@

## Unreleased

## 2.4.3

**`prompt:` handles vague requests properly.** A request too thin to break into requirements now produces a prompt that names what is missing, instead of handing the request back reworded.

### Fixed

- **A vague request no longer returns itself.** Given *"develop a app that allows to perfrom a full schedule for my meeting a proper PM would"*, `prompt:` emitted that same sentence as the task, again as its only real constraint, and again as the output format, padded with two generic scope lines. It was structurally correct and invented nothing, so every automated check passed — but it told the reader nothing they had not just typed. When a request cannot be decomposed into distinct requirements, the output now names the open questions (platform, scope, data source, success criteria) and instructs the agent to settle them before building. It still adds no requirements: each line asserts only that something was *not* specified.
- **Typos and article agreement are corrected in the output.** Because the prompt is built from your own wording, "a app" and "perfrom" were repeated in every section. Surfaced text now fixes article agreement and a fixed list of common misspellings, without changing meaning. Well-known exceptions ("a user", "a unique", "a one-off") are preserved.

### Measurement

- **New rubric item R8 — no section may restate the task.** The eight-item rubric now fails any output whose constraints contain nothing beyond a restatement of the task plus boilerplate, or whose output format re-quotes the request. R8 is disqualifying on its own, so a structurally valid echo can no longer pass the release gate at any score. It is machine-checkable — no judge required.
- The reported request is now a permanent case in the fixed evaluation set, and R8 was validated against all 14 cases with **zero false verdicts** (it fails the old echo and clears every legitimate output, including multi-clause requests whose first bullet naturally restates the opening sentence).

## 2.4.2

**Clear failures instead of cryptic ones.** A benchmark that cannot fit in memory is now refused with an explanation, keep-warm no longer fights an in-progress benchmark, and errors the local server reports are passed through instead of being replaced by a generic message.
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ Flowkey is a Windows desktop assistant that adds local-LLM hotkeys for grammar f

Everything runs locally through [FastFlowLM](https://fastflowlm.com) (AMD Ryzen AI NPU) or, on machines without the NPU, through [Ollama](https://ollama.com) (CPU/GPU) as a secondary provider. No cloud service, analytics, or telemetry is used by the app.

Current version: `2.4.2`
Current version: `2.4.3`

## What's new in 2.4

- **`prompt:` handles vague requests properly.** If a request is too thin to break into concrete requirements, the generated prompt now names what's missing — platform, scope, data source, success criteria — and tells the coding agent to settle those before building, rather than handing your sentence back to you reworded. It still never invents requirements you didn't state. Typos and article agreement in your wording are cleaned up on the way out.
- **Benchmarks fail clearly, not cryptically.** Starting a benchmark on a model too large for your machine used to end in a raw graphics-driver error code. Flowkey now checks the model's memory footprint (plus room for the context sweep) first and tells you what it needs versus what's available. Background model warmups also pause while a benchmark runs, instead of competing with it for the same memory, and errors reported by the local server are passed through verbatim rather than replaced with a generic message.
- **The model picker shows every model, sized honestly.** Config → **Models** is now one card, styled like the rest of the dashboard (the old picker used a native browser dropdown that ignored the app's theme). Nothing is hidden from it: models too big for your machine are marked rather than silently dropped, and Mixture-of-Experts models are sized by the parameters actually active per token plus FastFlowLM's own measured memory footprint — so a 35B-total / 3B-active model is no longer mistaken for a 35B one. If a FastFlowLM upgrade invalidates your active model, the card now says so instead of letting the next hotkey fail with an opaque error.
- **Chat streams as it thinks.** The dashboard **Chat** tab now fills the reply in token-by-token over a live stream instead of waiting on a blank "Thinking…" until the whole answer is ready — the first words typically appear in ~1.6 s (warm) rather than after the full response. It works on both FastFlowLM and Ollama, and if anything interrupts the stream the partial reply is kept and the error is shown. Grammar, `prompt:`, and the other hotkeys are unchanged (they still paste the finished result).
Expand Down
7 changes: 7 additions & 0 deletions SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ Caveman-encoded (compression, not amputation). Paths / ids / action names / numb
- V41: `flm bench` start → refuse iff catalog `footprint_gb` + 32k-context headroom > usable mem, w/ plain-language reason; unknown footprint ∨ lookup failure → allow (⊥ block on a guess)
- V42: benchmark running → keep-warm tick ⊥ warms (`skipped_benchmark_running`); ∵ bench 10-20min > 15min keepalive ∴ collision is the norm
- V43: provider 200-response carrying `error` ∧ ⊥ `choices` → raise that message verbatim (⊥ generic "no usable text"); `error` + `choices` → ignore, return completion
- V44: source ⊥ decomposable (real clauses < 2) → clarify shape: unknowns named, ⊥ request echoed into constraints/output_format, ⊥ scope-guard padding; still ⊥ invention (V33 holds)
- V45: surfaced v2 text → article agreement + fixed typo map normalized (∵ render copies user wording verbatim); ⊥ meaning change
- V46: A/B rubric = 8 items; R8 = ⊥ section restates `<task>` (coverage-of-task ≥ 0.8), boilerplate-excluded set-wise on constraints; R8 false ⇒ disqualifying ∀ other scores; gate pass ≥ 7/8 ∧ R8 ∧ ⊥ invented
- V40: model picker + installed list = app-styled elements (⊥ native `<datalist>`/`<select size>`, ∵ browser chrome ignores page CSS); theme-aware + keyboard-navigable (↑↓/Enter/Esc)

## §T tasks
Expand Down Expand Up @@ -127,6 +130,7 @@ T24|x|prompt-v2 default + v1 rollback selector + 240/320/420 caps|V24,V27,V28,V3
T25|x|FastFlowLM startup+idle keep-warm + cold/warm measurement support|V31
T26|x|2.3.0 release evidence + version/docs rebaseline after A/B gate passes|V18,V29,V33,V34,V35,V36
T27|x|streaming Chat tab (SSE): `ffp_chat.stream_send` + daemon `_STREAM_ACTIONS`/`_sse_frame`/`_stream_action` + `app.js` fetch-reader w/ `chat_send` fallback; live FLM first-token 1.58s|V37
T30|x|clarify shape for underspecified requests + typo/article normalization + R8 echo gate + reported input in fixed set (2.4.3)|V44,V45,V46
T29|x|bench memory guard + keep-warm/bench mutual exclusion + provider-error surfacing (2.4.2)|V41,V42,V43
T28|x|model picker 2.4.1: footprint/MoE sizing + never-hide + active-model health + merged single Models card + app-styled combobox|V38,V39,V40
```
Expand Down Expand Up @@ -168,6 +172,9 @@ B30|2026-07-27|FLM 0.9.45 invalidated locally-pulled `qwen3.5:4b` (stamped 0.9.4
B31|2026-07-27|model picker ⊥ followed app styling ∵ native `<datalist>` popup drawn by browser chrome (page CSS inert) + native `<select size>`|V40; custom app-styled combobox + list
B32|2026-07-27|`app.js` set `models-title` = "Installed models — <provider>" ∴ overwrote merged card heading at runtime|V40 caught in browser; title → "Models — <provider>"
B33|2026-07-27|new CSS block referenced undefined `--muted`/`--card`/`--bad` (pre-existing pattern elsewhere in stylesheet: 6+2+3 refs, never defined ∴ inert)|use defined tokens `--text-muted`/`--surface`/`--warn`; pre-existing refs left for separate cleanup
B38|2026-07-28|vague 1-clause request ("develop a app ... a proper PM would") → deterministic render echoed it as task ∧ sole constraint ∧ output_format + 2 scope guards, typos kept; scored 5/7 machine (7/7 lenient judge), ⊥ hard-fail ∴ gate green on a pure echo. `is_weak_prompt_echo` ⊥ fires ∵ V32 exempts structured output, and V33 finalizer always emits structure|V44,V45,V46; clarify shape + normalization + R8
B39|2026-07-28|self-caught: first R8 metric = coverage-of-candidate ∴ flagged good CSV decomposition (bullets legitimately reuse task words)|invert → coverage-of-TASK
B40|2026-07-28|2nd R8 metric flagged long/debug cases ∵ task = sentence 1 ∴ one bullet legitimately restates it|judge constraints set-wise: fail iff ∅ remains after dropping restatements ∧ boilerplate
B35|2026-07-27|`flm bench qwen3.6-moe:35b-a3b` died 4s in w/ driver `0xc01e0200` (page-in failed): 24.3GB weights + 32k-sweep KV > 25.6GB usable; ⊥ preflight ∴ user saw only a hex code|V41; catalog-footprint preflight w/ explanation
B36|2026-07-27|keep-warm thread ⊥ aware of benchmarks: warms/reloads active model on 15min tick during a 10-20min bench ∴ NPU+mem contention mid-run|V42; `ffp_benchmark.is_running()` gate in `_warm_model_once`
B37|2026-07-27|FLM returns HTTP **200** + `{"error":"Failed to load <model> model!"}`; `_call_openai_compatible`/`ffp_chat` read only `choices` ∴ real cause discarded → "Local LLM returned no usable text"|V43; surface the error body
Expand Down
189 changes: 189 additions & 0 deletions config/grammar_hotkey.config.json.bak-20260728-083028
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
{
"enabled": true,
"llm": {
"provider": "fastflowlm",
"base_url": "http://127.0.0.1:52625",
"model": "qwen3.5:4b",
"auth_bearer": "flm",
"timeout_seconds": 96,
"auto_start": true
},
"providers": {
"fastflowlm": {
"base_url": "http://127.0.0.1:52625",
"model": "qwen3.5:4b",
"auth_bearer": "flm",
"timeout_seconds": 96,
"auto_start": true
},
"ollama": {
"base_url": "http://127.0.0.1:11434",
"model": "llama3.2:3b",
"auth_bearer": "ollama",
"timeout_seconds": 120,
"auto_start": false
}
},
"flm_base_url": "http://127.0.0.1:52625",
"flm_model": "qwen3.5:4b",
"flm_timeout_seconds": 96,
"history_filename": "grammar_fix_history.jsonl",
"history_store_text": true,
"server": {
"auto_start": true,
"warm_on_start": true,
"keep_warm_minutes": 15,
"performance_mode": "max",
"startup_timeout_seconds": 25,
"serve_extra_args": [],
"log_to_file": false,
"log_file": "flm_server.log"
},
"routing": {
"enabled": true,
"long_threshold_chars": 1400,
"chunk_size_chars": 1200,
"min_chunk_chars": 700
},
"prompt_builder": {
"prompt_version": "v2",
"target_agent": "claude_code",
"detail_level": "detailed",
"action_mode": "implement",
"structure": "agent_default",
"include_acceptance_criteria": true,
"include_verification": true,
"include_output_format": true,
"preserve_user_constraints": true,
"allow_user_suffix": true,
"user_suffix": ""
},
"dictionary": {
"protected_words": []
},
"notifications": {
"enabled": true,
"dedupe_seconds": 5.0,
"dnd": false,
"log_enabled": true,
"quiet_hours": {
"enabled": false,
"start": "22:00",
"end": "07:00"
},
"categories": {
"errors": {
"enabled": true
},
"clipboard_suggestions": {
"enabled": true
},
"updates": {
"enabled": true
},
"diagnostics": {
"enabled": true
},
"settings": {
"enabled": true
},
"lifecycle": {
"enabled": true
},
"action_result": {
"enabled": true
}
}
},
"meetings": {
"enabled": true,
"mcp_url": "http://127.0.0.1:19532/mcp",
"source": "auto",
"max_context_tokens": 6000,
"batch": {
"enabled": true,
"start": "17:00",
"end": "21:00",
"only_when_idle": true,
"idle_minutes": 10,
"max_per_run": 10
}
},
"modes": {
"grammar": {
"label": "Grammar fix",
"shortcut": "Ctrl+Shift+G",
"description": "Fix grammar and wording while preserving meaning.",
"system_prompt": "Fix grammar, spelling, punctuation, capitalization, and obvious wording mistakes. Preserve the meaning and leave everything else exactly as written. Return only the corrected text, with no preamble and no notes."
},
"prompt": {
"label": "Prompt fix (Claude)",
"description": "Rewrite rough text into a Claude-ready prompt (use prompt: prefix).",
"system_prompt": "Restate the user's requested coding deliverable as one concise imperative sentence. Use only details the user explicitly stated. Do not solve, infer, explain, or add requirements. Return only that sentence with no preamble, under 40 tokens."
},
"summarize": {
"label": "Summarize",
"description": "3-bullet summary of selected text (use summarize: prefix).",
"system_prompt": "Summarize the user text as exactly 3 bullet points. Each bullet is one sentence, factual, no preamble or sign-off. Return only the bullets."
},
"explain": {
"label": "Explain code/regex/SQL",
"description": "Plain-English explanation of selected code, regex, or query (use explain: prefix).",
"system_prompt": "Explain the selected code, regex, or SQL in 2-3 plain-English sentences. Call out one non-obvious edge case if any. No preamble. Return only the explanation."
},
"tone": {
"label": "Tone shift",
"description": "Rewrite in selected tone (use tone: prefix). Active preset cycles from the tray.",
"preset": "friendly",
"presets": {
"formal": {
"system_prompt": "Rewrite the user text in a formal, professional tone. Preserve the meaning. Return only the rewritten text."
},
"casual": {
"system_prompt": "Rewrite the user text in a casual, conversational tone. Preserve the meaning. Return only the rewritten text."
},
"friendly": {
"system_prompt": "Rewrite the user text in a warm, friendly tone. Preserve the meaning. Return only the rewritten text."
}
},
"system_prompt": "Rewrite the user text in a warm, friendly tone. Preserve the meaning. Return only the rewritten text."
}
},
"notes": {
"vault_dir": "%USERPROFILE%\\Documents\\FastFlowPrompt Notes",
"categories": [
"work/technical",
"work/managerial",
"work/career",
"research",
"personal",
"ideas"
],
"fetch_timeout_seconds": 8,
"max_extracted_chars": 2000,
"low_confidence_to_inbox": true,
"generate_title": true,
"generate_summary": true
},
"chat": {
"llm_auth_bearer": "flm",
"request_timeout_seconds": 240,
"temperature": 0.3,
"max_tokens": 1024,
"context_window_turns": 12,
"system_prompt": "You are a concise, helpful local assistant. Answer in Markdown. Keep replies short unless asked to elaborate.",
"window": {
"title": "Local LLM Chat",
"width": 520,
"height": 560,
"topmost": true,
"single_instance_port": 52640
}
},
"hotkeys": {
"grammar_fix": "^+g",
"open_chat": "^+1",
"capture_note": "^!n",
"ask_chat": "^+2"
}
}
2 changes: 1 addition & 1 deletion installer/installer.iss
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
#define AppURL "https://github.com/agr77one/Fastflow"
#define AppExeName "Flowkey.exe" ; symbolic — actual launchers below
; Keep in lockstep with scripts\_version.py.
#define AppVersion "2.4.2"
#define AppVersion "2.4.3"

[Setup]
AppId={{8A4F1E6C-9B3D-4E62-9F7A-FASTFLOW140}}
Expand Down
2 changes: 1 addition & 1 deletion installer/sign.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
.EXAMPLE
# Sign the installer
$env:FFP_SIGN_PFX_PASSWORD = "ChangeMe!"
.\sign.ps1 -FilePath ..\out\Flowkey-Setup-2.4.2.exe
.\sign.ps1 -FilePath ..\out\Flowkey-Setup-2.4.3.exe
#>

[CmdletBinding(DefaultParameterSetName = "Sign")]
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "fastflowprompt"
version = "2.4.2"
version = "2.4.3"
description = "Local-LLM-powered grammar fix, prompt rewrite, chat, and dashboard for Windows."
readme = "README.md"
requires-python = ">=3.11"
Expand Down
2 changes: 1 addition & 1 deletion scripts/_version.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Single source of truth for the app version. Read by grammar_fix.py."""

__version__ = "2.4.2"
__version__ = "2.4.3"
Loading