practicode
Advanced tools
| # Architecture | ||
| Practicode is local-first: user data stays under `.practicode/`, `problems/`, and `submissions/`. | ||
| ## Source Layout | ||
| - `src/core.rs` owns problem data, state loading/saving, judging, and file generation. | ||
| - `src/core/profile.rs` owns practice-profile defaults and normalization. | ||
| - `src/tui.rs` owns Ratatui rendering and interaction flow. | ||
| - `src/tui/commands.rs` owns the command palette catalog. | ||
| - `src/ai.rs` owns provider commands, daemon/model checks, and AI prompts. | ||
| - `src/update.rs` owns update checks. | ||
| - `src/text.rs` owns terminal text editing and markdown/plain rendering helpers. | ||
| ## Extension Rules | ||
| - Add domain logic under the owning module first; keep `tui.rs` as orchestration and rendering. | ||
| - Add user-visible commands in `src/tui/commands.rs`, then route behavior in `PracticodeApp::handle_command`. | ||
| - Add persisted profile settings to `Settings`, normalize them in `normalize_settings`, and cover old-state compatibility with tests. | ||
| - Keep provider-specific behavior in `src/ai.rs`; TUI should ask for status or start tasks, not know provider internals. | ||
| - Keep local user data backwards-compatible. Missing fields should default cleanly. | ||
| ## Release | ||
| See [MAINTAINING.md](MAINTAINING.md) for tag-based releases. |
| pub const DIFFICULTIES: &[&str] = &["auto", "easy", "medium", "hard"]; | ||
| pub fn default_difficulty() -> String { | ||
| "auto".to_string() | ||
| } | ||
| pub fn normalize_difficulty(difficulty: &str) -> String { | ||
| let difficulty = difficulty.trim().to_lowercase(); | ||
| if DIFFICULTIES.contains(&difficulty.as_str()) { | ||
| difficulty | ||
| } else { | ||
| default_difficulty() | ||
| } | ||
| } | ||
| pub fn parse_topic_list(value: &str) -> Vec<String> { | ||
| let mut topics = Vec::new(); | ||
| for topic in value.split(',') { | ||
| let topic = topic.trim().trim_start_matches('#').trim().to_lowercase(); | ||
| if !topic.is_empty() && !topics.contains(&topic) { | ||
| topics.push(topic); | ||
| } | ||
| } | ||
| topics | ||
| } | ||
| pub fn normalize_topic_list(topics: &[String]) -> Vec<String> { | ||
| parse_topic_list(&topics.join(",")) | ||
| } |
| #[derive(Clone, Copy)] | ||
| pub(super) struct CommandHint { | ||
| pub(super) insert: &'static str, | ||
| pub(super) display: &'static str, | ||
| pub(super) desc_key: &'static str, | ||
| pub(super) keep_open: bool, | ||
| pub(super) help: bool, | ||
| } | ||
| pub(super) const COMMAND_HINTS: &[CommandHint] = &[ | ||
| CommandHint { | ||
| insert: "run", | ||
| display: "/run", | ||
| desc_key: "cmd_run", | ||
| keep_open: false, | ||
| help: true, | ||
| }, | ||
| CommandHint { | ||
| insert: "code", | ||
| display: "/code", | ||
| desc_key: "cmd_code", | ||
| keep_open: false, | ||
| help: true, | ||
| }, | ||
| CommandHint { | ||
| insert: "next", | ||
| display: "/next", | ||
| desc_key: "cmd_next", | ||
| keep_open: false, | ||
| help: true, | ||
| }, | ||
| CommandHint { | ||
| insert: "back", | ||
| display: "/back", | ||
| desc_key: "cmd_prev", | ||
| keep_open: false, | ||
| help: true, | ||
| }, | ||
| CommandHint { | ||
| insert: "problems", | ||
| display: "/problems", | ||
| desc_key: "cmd_list", | ||
| keep_open: false, | ||
| help: true, | ||
| }, | ||
| CommandHint { | ||
| insert: "open ", | ||
| display: "/open <id>", | ||
| desc_key: "cmd_open", | ||
| keep_open: true, | ||
| help: true, | ||
| }, | ||
| CommandHint { | ||
| insert: "answer", | ||
| display: "/answer", | ||
| desc_key: "cmd_giveup", | ||
| keep_open: false, | ||
| help: true, | ||
| }, | ||
| CommandHint { | ||
| insert: "hint ", | ||
| display: "/hint <request>", | ||
| desc_key: "cmd_hint", | ||
| keep_open: true, | ||
| help: true, | ||
| }, | ||
| CommandHint { | ||
| insert: "profile", | ||
| display: "/profile", | ||
| desc_key: "cmd_profile", | ||
| keep_open: false, | ||
| help: true, | ||
| }, | ||
| CommandHint { | ||
| insert: "difficulty auto", | ||
| display: "/difficulty auto", | ||
| desc_key: "cmd_difficulty", | ||
| keep_open: false, | ||
| help: true, | ||
| }, | ||
| CommandHint { | ||
| insert: "difficulty easy", | ||
| display: "/difficulty easy", | ||
| desc_key: "cmd_difficulty", | ||
| keep_open: false, | ||
| help: false, | ||
| }, | ||
| CommandHint { | ||
| insert: "difficulty medium", | ||
| display: "/difficulty medium", | ||
| desc_key: "cmd_difficulty", | ||
| keep_open: false, | ||
| help: false, | ||
| }, | ||
| CommandHint { | ||
| insert: "difficulty hard", | ||
| display: "/difficulty hard", | ||
| desc_key: "cmd_difficulty", | ||
| keep_open: false, | ||
| help: false, | ||
| }, | ||
| CommandHint { | ||
| insert: "topics ", | ||
| display: "/topics <list>", | ||
| desc_key: "cmd_topics", | ||
| keep_open: true, | ||
| help: true, | ||
| }, | ||
| CommandHint { | ||
| insert: "avoid ", | ||
| display: "/avoid <list>", | ||
| desc_key: "cmd_avoid", | ||
| keep_open: true, | ||
| help: true, | ||
| }, | ||
| CommandHint { | ||
| insert: "provider codex", | ||
| display: "/provider codex", | ||
| desc_key: "cmd_provider", | ||
| keep_open: false, | ||
| help: true, | ||
| }, | ||
| CommandHint { | ||
| insert: "provider claude", | ||
| display: "/provider claude", | ||
| desc_key: "cmd_provider", | ||
| keep_open: false, | ||
| help: false, | ||
| }, | ||
| CommandHint { | ||
| insert: "model auto", | ||
| display: "/model auto", | ||
| desc_key: "cmd_model_auto", | ||
| keep_open: false, | ||
| help: true, | ||
| }, | ||
| CommandHint { | ||
| insert: "model ", | ||
| display: "/model <name>", | ||
| desc_key: "cmd_model_custom", | ||
| keep_open: true, | ||
| help: false, | ||
| }, | ||
| CommandHint { | ||
| insert: "language python", | ||
| display: "/language python", | ||
| desc_key: "cmd_lang", | ||
| keep_open: false, | ||
| help: true, | ||
| }, | ||
| CommandHint { | ||
| insert: "language ts", | ||
| display: "/language ts", | ||
| desc_key: "cmd_lang", | ||
| keep_open: false, | ||
| help: false, | ||
| }, | ||
| CommandHint { | ||
| insert: "language java", | ||
| display: "/language java", | ||
| desc_key: "cmd_lang", | ||
| keep_open: false, | ||
| help: false, | ||
| }, | ||
| CommandHint { | ||
| insert: "language rust", | ||
| display: "/language rust", | ||
| desc_key: "cmd_lang", | ||
| keep_open: false, | ||
| help: false, | ||
| }, | ||
| CommandHint { | ||
| insert: "ui en", | ||
| display: "/ui en", | ||
| desc_key: "cmd_ui", | ||
| keep_open: false, | ||
| help: true, | ||
| }, | ||
| CommandHint { | ||
| insert: "ui ko", | ||
| display: "/ui ko", | ||
| desc_key: "cmd_ui", | ||
| keep_open: false, | ||
| help: false, | ||
| }, | ||
| CommandHint { | ||
| insert: "ui ja", | ||
| display: "/ui ja", | ||
| desc_key: "cmd_ui", | ||
| keep_open: false, | ||
| help: false, | ||
| }, | ||
| CommandHint { | ||
| insert: "ui zh", | ||
| display: "/ui zh", | ||
| desc_key: "cmd_ui", | ||
| keep_open: false, | ||
| help: false, | ||
| }, | ||
| CommandHint { | ||
| insert: "ui es", | ||
| display: "/ui es", | ||
| desc_key: "cmd_ui", | ||
| keep_open: false, | ||
| help: false, | ||
| }, | ||
| CommandHint { | ||
| insert: "theme dark", | ||
| display: "/theme dark", | ||
| desc_key: "cmd_theme", | ||
| keep_open: false, | ||
| help: true, | ||
| }, | ||
| CommandHint { | ||
| insert: "theme light", | ||
| display: "/theme light", | ||
| desc_key: "cmd_theme", | ||
| keep_open: false, | ||
| help: false, | ||
| }, | ||
| CommandHint { | ||
| insert: "update", | ||
| display: "/update", | ||
| desc_key: "cmd_update", | ||
| keep_open: false, | ||
| help: true, | ||
| }, | ||
| CommandHint { | ||
| insert: "help", | ||
| display: "/help", | ||
| desc_key: "cmd_help", | ||
| keep_open: false, | ||
| help: true, | ||
| }, | ||
| CommandHint { | ||
| insert: "exit", | ||
| display: "/exit", | ||
| desc_key: "cmd_exit", | ||
| keep_open: false, | ||
| help: true, | ||
| }, | ||
| ]; |
@@ -35,2 +35,6 @@ { | ||
| "cmd_ai": "Ask AI about the current problem and code", | ||
| "cmd_profile": "Show practice profile", | ||
| "cmd_difficulty": "Set preferred difficulty", | ||
| "cmd_topics": "Set preferred topics", | ||
| "cmd_avoid": "Set topics to avoid", | ||
| "cmd_provider": "Set AI provider", | ||
@@ -37,0 +41,0 @@ "cmd_model": "Set AI model", |
@@ -35,2 +35,6 @@ { | ||
| "cmd_ai": "Preguntar a AI sobre el problema y codigo actuales", | ||
| "cmd_profile": "Mostrar perfil de practica", | ||
| "cmd_difficulty": "Configurar dificultad preferida", | ||
| "cmd_topics": "Configurar temas preferidos", | ||
| "cmd_avoid": "Configurar temas a evitar", | ||
| "cmd_provider": "Configurar AI provider", | ||
@@ -37,0 +41,0 @@ "cmd_model": "Configurar AI model", |
@@ -35,2 +35,6 @@ { | ||
| "cmd_ai": "現在の問題とコードについて AI に質問", | ||
| "cmd_profile": "練習プロファイルを表示", | ||
| "cmd_difficulty": "希望難易度を設定", | ||
| "cmd_topics": "希望トピックを設定", | ||
| "cmd_avoid": "避けるトピックを設定", | ||
| "cmd_provider": "AI provider を設定", | ||
@@ -37,0 +41,0 @@ "cmd_model": "AI model を設定", |
@@ -35,2 +35,6 @@ { | ||
| "cmd_ai": "현재 문제와 코드에 대해 AI에게 질문", | ||
| "cmd_profile": "연습 프로파일 보기", | ||
| "cmd_difficulty": "선호 난이도 설정", | ||
| "cmd_topics": "선호 주제 설정", | ||
| "cmd_avoid": "피할 주제 설정", | ||
| "cmd_provider": "AI provider 설정", | ||
@@ -37,0 +41,0 @@ "cmd_model": "AI model 설정", |
@@ -35,2 +35,6 @@ { | ||
| "cmd_ai": "向 AI 询问当前题目和代码", | ||
| "cmd_profile": "显示练习配置", | ||
| "cmd_difficulty": "设置偏好难度", | ||
| "cmd_topics": "设置偏好主题", | ||
| "cmd_avoid": "设置要避开的主题", | ||
| "cmd_provider": "设置 AI provider", | ||
@@ -37,0 +41,0 @@ "cmd_model": "设置 AI model", |
| <svg width="1200" height="720" viewBox="0 0 1200 720" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||
| <rect width="1200" height="720" fill="#070b12"/> | ||
| <rect x="64" y="52" width="1072" height="616" rx="10" fill="#0b111a" stroke="#1f6f7a" stroke-width="2"/> | ||
| <rect x="88" y="78" width="594" height="440" rx="4" fill="#0f1720" stroke="#00c2d1" stroke-width="2"/> | ||
| <rect x="704" y="78" width="408" height="440" rx="4" fill="#0d141c" stroke="#31536b" stroke-width="2"/> | ||
| <text x="112" y="112" fill="#d7e1ec" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="18" font-weight="700">Problem</text> | ||
| <text x="728" y="112" fill="#d7e1ec" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="18" font-weight="700">solution.py</text> | ||
| <text x="112" y="158" fill="#f8fafc" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="22" font-weight="700">001. Hello World</text> | ||
| <text x="112" y="204" fill="#c8d3f5" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="16">Difficulty: easy</text> | ||
| <text x="112" y="232" fill="#c8d3f5" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="16">Topics: io</text> | ||
| <text x="112" y="286" fill="#f8fafc" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="17">Print exactly Hello, World! to stdout.</text> | ||
| <text x="112" y="352" fill="#00a8ff" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="17" text-decoration="underline">Input</text> | ||
| <text x="112" y="386" fill="#f8fafc" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="17">No input.</text> | ||
| <text x="112" y="442" fill="#00a8ff" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="17" text-decoration="underline">Output</text> | ||
| <text x="112" y="476" fill="#f6c177" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="17">Hello, World!</text> | ||
| <text x="728" y="158" fill="#6b7280" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="17"> 1</text> | ||
| <text x="776" y="158" fill="#7dd3fc" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="17">print</text> | ||
| <text x="828" y="158" fill="#f8fafc" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="17">('Hello, World!')</text> | ||
| <rect x="64" y="536" width="1072" height="28" fill="#152033"/> | ||
| <text x="84" y="556" fill="#c8d3f5" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="15" font-weight="700">PRACTICODE | 001-hello-world | easy | idle | solved | code:written | python | next:ai | ai:codex/auto</text> | ||
| <rect x="64" y="586" width="1072" height="58" rx="2" fill="#0b1017" stroke="#008cff" stroke-width="2"/> | ||
| <text x="84" y="606" fill="#c8d3f5" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="15" font-weight="700">Command</text> | ||
| <text x="84" y="630" fill="#f8fafc" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="18">/ai hint about edge cases</text> | ||
| <rect x="54" y="42" width="1092" height="636" rx="10" fill="#0b111a" stroke="#214c5c" stroke-width="2"/> | ||
| <rect x="76" y="68" width="560" height="430" rx="4" fill="#0f1720" stroke="#00c2d1" stroke-width="2"/> | ||
| <rect x="664" y="68" width="458" height="430" rx="4" fill="#0d141c" stroke="#f8e71c" stroke-width="2"/> | ||
| <text x="92" y="91" fill="#16d6e8" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="15" font-weight="700">Problem</text> | ||
| <text x="680" y="91" fill="#f8e71c" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="15" font-weight="700">> solution.py</text> | ||
| <text x="96" y="128" fill="#f8fafc" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="22" font-weight="700">001. Hello World</text> | ||
| <text x="96" y="164" fill="#c8d3f5" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="16">Difficulty: easy Topics: io</text> | ||
| <text x="96" y="206" fill="#f8fafc" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="17">Print exactly Hello, World! to stdout.</text> | ||
| <text x="96" y="286" fill="#00d2e8" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="17" font-weight="700">Input</text> | ||
| <rect x="96" y="304" width="486" height="44" rx="3" fill="#101d28" stroke="#29495a"/> | ||
| <text x="112" y="332" fill="#f8fafc" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="16">No input.</text> | ||
| <text x="96" y="388" fill="#00d2e8" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="17" font-weight="700">Output</text> | ||
| <rect x="96" y="406" width="486" height="66" rx="3" fill="#101d28" stroke="#29495a"/> | ||
| <text x="112" y="446" fill="#f6c177" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="16">Hello, World!</text> | ||
| <text x="690" y="128" fill="#64748b" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="16"> 1</text> | ||
| <text x="738" y="128" fill="#7dd3fc" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="16">print</text> | ||
| <text x="790" y="128" fill="#f8fafc" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="16">('Hello, World!')</text> | ||
| <rect x="76" y="512" width="1046" height="30" fill="#152033"/> | ||
| <text x="94" y="533" fill="#c8d3f5" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="15" font-weight="700">PRACTICODE | 001-hello-world | easy | idle | assigned | code:written | python | Esc then / command</text> | ||
| <rect x="76" y="558" width="1046" height="48" rx="2" fill="#0b1017" stroke="#00c2d1" stroke-width="2"/> | ||
| <text x="94" y="579" fill="#16d6e8" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="15" font-weight="700">Command</text> | ||
| <text x="94" y="596" fill="#f8fafc" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="18">/</text> | ||
| <rect x="76" y="614" width="1046" height="46" rx="2" fill="#101923" stroke="#214c5c"/> | ||
| <text x="94" y="641" fill="#f8fafc" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="15">/run /next /hint <request> /profile /difficulty auto /topics arrays, strings</text> | ||
| </svg> |
+1
-1
@@ -360,3 +360,3 @@ # This file is automatically @generated by Cargo. | ||
| name = "practicode" | ||
| version = "0.1.5" | ||
| version = "0.1.6" | ||
| dependencies = [ | ||
@@ -363,0 +363,0 @@ "anyhow", |
+1
-1
| [package] | ||
| name = "practicode" | ||
| version = "0.1.5" | ||
| version = "0.1.6" | ||
| edition = "2024" | ||
@@ -5,0 +5,0 @@ description = "Local-first coding-test practice in a Rust terminal UI with optional AI help." |
@@ -12,2 +12,3 @@ # Contributing | ||
| - For larger UI, AI-generation, storage, or packaging changes, open an issue first so the scope is clear. | ||
| - Check [ARCHITECTURE.md](ARCHITECTURE.md) before adding commands, settings, provider behavior, or persisted state. | ||
| - Do not commit local practice data from `.practicode/`, `problems/`, or `submissions/`. | ||
@@ -14,0 +15,0 @@ - Do not include secrets, tokens, private prompts, or generated answer keys in docs or examples. |
+1
-1
| { | ||
| "name": "practicode", | ||
| "version": "0.1.5", | ||
| "version": "0.1.6", | ||
| "description": "Local-first coding-test practice in a Rust terminal UI with optional AI help.", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
+14
-6
@@ -69,13 +69,15 @@ # practicode | ||
| | `/next easy string problem` | Ask AI for a custom next problem | | ||
| | `/prev` | Go back through problem history | | ||
| | `/list` | Browse problems with `up/down` or `j/k`, open with `Enter` | | ||
| | `/back` | Go back through problem history | | ||
| | `/problems` | Browse problems with `up/down` or `j/k`, open with `Enter` | | ||
| | `/open 2` | Open by number, id, or slug | | ||
| | `/giveup` | Show the reference answer | | ||
| | `/answer` | Show the reference answer | | ||
| | `/hint` | Ask the selected AI for a concise hint | | ||
| | `/hint explain my bug` | Ask the selected AI about the current problem and submission | | ||
| | `/profile` | Show your current practice profile | | ||
| | `/difficulty auto` | Set difficulty preference: `auto`, `easy`, `medium`, `hard` | | ||
| | `/topics arrays, strings` | Set preferred topics for future problems | | ||
| | `/avoid dp, graph` | Set topics to avoid in future problems | | ||
| | `/provider codex` | Set AI provider and show local CLI/daemon status | | ||
| | `/model auto` | Use the provider default model for `/hint` and AI-backed `/next` | | ||
| | `/note prefer hashmap practice` | Append a standing note for future problem generation | | ||
| | `/notes` | Show your local next-problem notes | | ||
| | `/lang python` | Set code language: `python`, `ts`, `java`, `rust` | | ||
| | `/language python` | Set code language: `python`, `ts`, `java`, `rust` | | ||
| | `/ui en` | Set UI language: `en`, `ko`, `ja`, `zh`, `es` | | ||
@@ -86,4 +88,8 @@ | `/theme dark` | Set theme: `dark` or `light` | | ||
| Older command names such as `/prev`, `/list`, `/giveup`, and `/lang` still work as aliases. | ||
| The default UI language is English. Switch it any time with `/ui ko`, `/ui ja`, `/ui zh`, or `/ui es`. | ||
| Your practice profile is saved in `.practicode/problem-state.json`. It keeps UI language, code language, theme, preferred difficulty, preferred topics, and topics to avoid. `auto` difficulty follows gradual progression; a fixed difficulty asks local selection and AI generation to prefer that level. | ||
| ## AI Problems | ||
@@ -144,4 +150,6 @@ | ||
| Code layout and extension boundaries live in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). | ||
| ## License | ||
| practicode is MIT licensed. Third-party dependency license notes are in [THIRD_PARTY_LICENSES.md](THIRD_PARTY_LICENSES.md). |
+25
-4
@@ -126,4 +126,8 @@ use crate::{ | ||
| pub fn default_ai_next_prompt(request: &str) -> String { | ||
| default_ai_next_prompt_with_settings(&Settings::default(), request) | ||
| } | ||
| pub fn default_ai_next_prompt_with_settings(settings: &Settings, request: &str) -> String { | ||
| format!( | ||
| "Read AGENTS.md, docs/problem-authoring-notes.md if present, .practicode/problem_notes.md if present, problems/INDEX.md if present, .practicode/problem_bank.json if present, and .practicode/problem-state.json. Create exactly one new non-duplicate coding practice problem. The built-in 001-hello-world already exists, so do not duplicate it. User request: {}. Make the smallest valid edits: update .practicode/problem_bank.json, one problem directory, problems/INDEX.md, and .practicode/problem-state.json. Do not include the answer in the problem statement.", | ||
| "Read AGENTS.md, docs/problem-authoring-notes.md if present, .practicode/problem_notes.md if present, problems/INDEX.md if present, .practicode/problem_bank.json if present, and .practicode/problem-state.json. Create exactly one new non-duplicate coding practice problem. The built-in 001-hello-world already exists, so do not duplicate it. User request: {}. Practice profile: difficulty preference: {}; preferred topics: {}; avoid topics: {}; code language: {}; UI language: {}. Treat difficulty auto as gradual progression from state; otherwise prefer the requested difficulty unless the direct user request conflicts. Make the smallest valid edits: update .practicode/problem_bank.json, one problem directory, problems/INDEX.md, and .practicode/problem-state.json. Do not include the answer in the problem statement.", | ||
| if request.is_empty() { | ||
@@ -133,3 +137,8 @@ "(none)" | ||
| request | ||
| } | ||
| }, | ||
| settings.difficulty, | ||
| list_or_none(&settings.topics), | ||
| list_or_none(&settings.avoid_topics), | ||
| settings.language, | ||
| settings.ui_language | ||
| ) | ||
@@ -309,3 +318,5 @@ } | ||
| exec.push(' '); | ||
| exec.push_str(&sh_quote(&default_ai_next_prompt(request))); | ||
| exec.push_str(&sh_quote(&default_ai_next_prompt_with_settings( | ||
| settings, request, | ||
| ))); | ||
| format!("{start}; {exec}") | ||
@@ -328,3 +339,5 @@ } | ||
| claude.push_str(" -p "); | ||
| claude.push_str(&sh_quote(&default_ai_next_prompt(request))); | ||
| claude.push_str(&sh_quote(&default_ai_next_prompt_with_settings( | ||
| settings, request, | ||
| ))); | ||
| format!( | ||
@@ -345,2 +358,10 @@ "claude daemon status >/dev/null 2>&1 || true; cd {}; {}", | ||
| fn list_or_none(values: &[String]) -> String { | ||
| if values.is_empty() { | ||
| "(none)".to_string() | ||
| } else { | ||
| values.join(", ") | ||
| } | ||
| } | ||
| #[cfg(test)] | ||
@@ -347,0 +368,0 @@ mod tests { |
+25
-3
@@ -12,2 +12,7 @@ use crate::process::{CommandSpec, run_capture, which}; | ||
| mod profile; | ||
| pub use profile::{ | ||
| DIFFICULTIES, default_difficulty, normalize_difficulty, normalize_topic_list, parse_topic_list, | ||
| }; | ||
| pub const LANGUAGES: &[&str] = &["python", "ts", "java", "rust"]; | ||
@@ -29,2 +34,8 @@ pub use crate::i18n::{UI_LANGUAGES, normalize_ui_language, ui_text}; | ||
| pub theme: String, | ||
| #[serde(default = "default_difficulty")] | ||
| pub difficulty: String, | ||
| #[serde(default, skip_serializing_if = "Vec::is_empty")] | ||
| pub topics: Vec<String>, | ||
| #[serde(default, skip_serializing_if = "Vec::is_empty")] | ||
| pub avoid_topics: Vec<String>, | ||
| #[serde(default = "default_editor")] | ||
@@ -48,2 +59,5 @@ pub editor: String, | ||
| theme: default_theme(), | ||
| difficulty: default_difficulty(), | ||
| topics: Vec::new(), | ||
| avoid_topics: Vec::new(), | ||
| editor: default_editor(), | ||
@@ -370,2 +384,5 @@ next_source: default_next_source(), | ||
| } | ||
| settings.difficulty = normalize_difficulty(&settings.difficulty); | ||
| settings.topics = normalize_topic_list(&settings.topics); | ||
| settings.avoid_topics = normalize_topic_list(&settings.avoid_topics); | ||
| settings.next_source = normalize_next_source(&settings.next_source); | ||
@@ -383,4 +400,5 @@ settings.ai_provider = normalize_ai_provider(&settings.ai_provider); | ||
| pub fn normalize_language(language: &str) -> String { | ||
| if LANGUAGES.contains(&language) { | ||
| language.to_string() | ||
| let language = language.trim().to_lowercase(); | ||
| if LANGUAGES.contains(&language.as_str()) { | ||
| language | ||
| } else { | ||
@@ -764,3 +782,7 @@ "python".to_string() | ||
| .collect::<Vec<_>>(); | ||
| let preferred = &state.suggested_next_difficulty; | ||
| let preferred = if state.settings.difficulty == "auto" { | ||
| &state.suggested_next_difficulty | ||
| } else { | ||
| &state.settings.difficulty | ||
| }; | ||
| let problem = bank | ||
@@ -767,0 +789,0 @@ .iter() |
Sorry, the diff of this file is too big to display
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 2 instances
189612
6.08%30
11.11%355
5.97%153
5.52%3
-25%