memory: polish help and docs (#569)
- add /memory help and clearer invalid-subcommand guidance - register /memory in shared slash-command help - align memory docs with current behavior and config - add focused tests for help and discovery
This commit is contained in:
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.8.9] - Unreleased
|
||||
|
||||
### Changed
|
||||
- **User memory docs + help polish** (#497) — `/memory` is now listed in
|
||||
slash-command help, supports `/memory help`, and the README /
|
||||
configuration docs now point at the full `docs/MEMORY.md` guide and
|
||||
document both `[memory].enabled` and `DEEPSEEK_MEMORY`.
|
||||
|
||||
## [0.8.8] - 2026-05-03
|
||||
|
||||
### Added
|
||||
|
||||
@@ -231,7 +231,7 @@ A stabilization-focused release: a thick band of UX polish on top of the v0.8.6
|
||||
- **Every `HookEvent` now has a live producer** — `tool_call_before` / `tool_call_after` / `message_submit` / `on_error` fire from the runtime in addition to the existing session-lifecycle and mode-change events. Hooks remain read-only observers in v0.8.8 ([#455](https://github.com/Hmbown/DeepSeek-TUI/issues/455)).
|
||||
- **`instructions = [...]` config array** lets you stack additional system-prompt files; paths capped at 100 KiB each, project array replaces user array wholesale ([#454](https://github.com/Hmbown/DeepSeek-TUI/issues/454)).
|
||||
- **`deepseek pr <N>` subcommand** fetches a PR's title / body / diff via `gh` and launches the TUI with a review prompt already in the composer. Codepoint-safe diff cap at 200 KiB; optional `--repo` / `--checkout` ([#451](https://github.com/Hmbown/DeepSeek-TUI/issues/451)).
|
||||
- **User-memory MVP** (opt-in) — `~/.deepseek/memory.md` injected into the system prompt as a `<user_memory>` block; `# foo` typed in the composer appends a timestamped bullet without firing a turn; `/memory [show|path|clear|edit]` for inspection. Default off; enable with `[memory] enabled = true` or `DEEPSEEK_MEMORY=on` ([#489](https://github.com/Hmbown/DeepSeek-TUI/issues/489)–[#493](https://github.com/Hmbown/DeepSeek-TUI/issues/493)).
|
||||
- **User-memory MVP** (opt-in) — `~/.deepseek/memory.md` injected into the system prompt as a `<user_memory>` block; `# foo` typed in the composer appends a timestamped bullet without firing a turn; `/memory [show|path|clear|edit|help]` for inspection. Default off; enable with `[memory] enabled = true` or `DEEPSEEK_MEMORY=on`. See [docs/MEMORY.md](docs/MEMORY.md) for the full guide and examples ([#489](https://github.com/Hmbown/DeepSeek-TUI/issues/489)–[#493](https://github.com/Hmbown/DeepSeek-TUI/issues/493)).
|
||||
|
||||
### 🔒 Security
|
||||
|
||||
|
||||
@@ -346,6 +346,16 @@ mod tests {
|
||||
assert!(msg.contains("Aliases: dashboard, api"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_help_memory_topic_shows_usage_and_description() {
|
||||
let mut app = create_test_app();
|
||||
let result = help(&mut app, Some("memory"));
|
||||
let msg = result.message.expect("help topic should return message");
|
||||
assert!(msg.contains("memory"));
|
||||
assert!(msg.contains("persistent user-memory file"));
|
||||
assert!(msg.contains("Usage: /memory [show|path|clear|edit|help]"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_help_pushes_overlay() {
|
||||
let mut app = create_test_app();
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
//! - `/memory show` — alias for the no-arg form
|
||||
//! - `/memory clear` — replace the file contents with an empty marker
|
||||
//! - `/memory path` — show only the resolved path
|
||||
//! - `/memory help` — show command-specific help and the resolved path
|
||||
//!
|
||||
//! Editor integration (`/memory edit`) is intentionally minimal: the
|
||||
//! command prints a copy-pasteable shell line to open the file in the
|
||||
@@ -17,10 +18,31 @@
|
||||
//! doesn't have access to.
|
||||
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
use super::CommandResult;
|
||||
use crate::tui::app::App;
|
||||
|
||||
const MEMORY_USAGE: &str = "/memory [show|path|clear|edit|help]";
|
||||
|
||||
fn memory_help(path: &Path) -> String {
|
||||
format!(
|
||||
"Inspect or manage your persistent user-memory file.\n\n\
|
||||
Usage: {MEMORY_USAGE}\n\n\
|
||||
Current path: {}\n\n\
|
||||
Subcommands:\n\
|
||||
/memory Show the resolved path and current contents\n\
|
||||
/memory show Alias for the no-arg form\n\
|
||||
/memory path Print just the resolved path\n\
|
||||
/memory clear Replace the file contents with an empty marker\n\
|
||||
/memory edit Print the editor command for this file\n\
|
||||
/memory help Show this help\n\n\
|
||||
Quick capture: type `# foo` in the composer to append a timestamped\n\
|
||||
bullet without firing a turn.",
|
||||
path.display()
|
||||
)
|
||||
}
|
||||
|
||||
pub fn memory(app: &mut App, arg: Option<&str>) -> CommandResult {
|
||||
if !app.use_memory {
|
||||
return CommandResult::error(
|
||||
@@ -55,8 +77,76 @@ pub fn memory(app: &mut App, arg: Option<&str>) -> CommandResult {
|
||||
"to edit your memory file, run:\n\n ${{VISUAL:-${{EDITOR:-vi}}}} {}",
|
||||
path.display()
|
||||
)),
|
||||
"help" => CommandResult::message(memory_help(&path)),
|
||||
_ => CommandResult::error(format!(
|
||||
"unknown subcommand `{sub}`. usage: /memory [show|path|clear|edit]"
|
||||
"unknown subcommand `{sub}`. Try `/memory help`.\n\n{}",
|
||||
memory_help(&path)
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::config::Config;
|
||||
use crate::tui::app::{App, TuiOptions};
|
||||
use tempfile::TempDir;
|
||||
|
||||
fn create_test_app_with_memory(tmpdir: &TempDir, use_memory: bool) -> App {
|
||||
let options = TuiOptions {
|
||||
model: "deepseek-v4-pro".to_string(),
|
||||
workspace: tmpdir.path().to_path_buf(),
|
||||
config_path: None,
|
||||
config_profile: None,
|
||||
allow_shell: false,
|
||||
use_alt_screen: true,
|
||||
use_mouse_capture: false,
|
||||
use_bracketed_paste: true,
|
||||
max_subagents: 1,
|
||||
skills_dir: tmpdir.path().join("skills"),
|
||||
memory_path: tmpdir.path().join("memory.md"),
|
||||
notes_path: tmpdir.path().join("notes.txt"),
|
||||
mcp_config_path: tmpdir.path().join("mcp.json"),
|
||||
use_memory,
|
||||
start_in_agent_mode: false,
|
||||
skip_onboarding: true,
|
||||
yolo: false,
|
||||
resume_session_id: None,
|
||||
initial_input: None,
|
||||
};
|
||||
App::new(options, &Config::default())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn memory_help_lists_subcommands_and_resolved_path() {
|
||||
let tmpdir = TempDir::new().expect("tempdir");
|
||||
let mut app = create_test_app_with_memory(&tmpdir, true);
|
||||
let result = memory(&mut app, Some("help"));
|
||||
let msg = result.message.expect("help should return text");
|
||||
assert!(msg.contains("Usage: /memory [show|path|clear|edit|help]"));
|
||||
assert!(msg.contains("/memory edit"));
|
||||
assert!(msg.contains(app.memory_path.to_string_lossy().as_ref()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn memory_unknown_subcommand_points_to_help() {
|
||||
let tmpdir = TempDir::new().expect("tempdir");
|
||||
let mut app = create_test_app_with_memory(&tmpdir, true);
|
||||
let result = memory(&mut app, Some("wat"));
|
||||
let msg = result
|
||||
.message
|
||||
.expect("unknown subcommand should return text");
|
||||
assert!(msg.contains("Try `/memory help`"));
|
||||
assert!(msg.contains("/memory clear"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn memory_disabled_returns_enablement_hint() {
|
||||
let tmpdir = TempDir::new().expect("tempdir");
|
||||
let mut app = create_test_app_with_memory(&tmpdir, false);
|
||||
let result = memory(&mut app, None);
|
||||
let msg = result.message.expect("disabled memory should return text");
|
||||
assert!(msg.contains("user memory is disabled"));
|
||||
assert!(msg.contains("DEEPSEEK_MEMORY=on"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,6 +210,12 @@ pub const COMMANDS: &[CommandInfo] = &[
|
||||
usage: "/note <text>",
|
||||
description_id: MessageId::CmdNoteDescription,
|
||||
},
|
||||
CommandInfo {
|
||||
name: "memory",
|
||||
aliases: &[],
|
||||
usage: "/memory [show|path|clear|edit|help]",
|
||||
description_id: MessageId::CmdMemoryDescription,
|
||||
},
|
||||
CommandInfo {
|
||||
name: "attach",
|
||||
aliases: &["image", "media"],
|
||||
@@ -812,6 +818,7 @@ mod tests {
|
||||
fn command_registry_contains_config_and_links_but_not_set_or_deepseek() {
|
||||
assert!(COMMANDS.iter().any(|cmd| cmd.name == "config"));
|
||||
assert!(COMMANDS.iter().any(|cmd| cmd.name == "links"));
|
||||
assert!(COMMANDS.iter().any(|cmd| cmd.name == "memory"));
|
||||
assert!(!COMMANDS.iter().any(|cmd| cmd.name == "set"));
|
||||
assert!(!COMMANDS.iter().any(|cmd| cmd.name == "deepseek"));
|
||||
}
|
||||
|
||||
@@ -238,6 +238,7 @@ pub enum MessageId {
|
||||
CmdLoadDescription,
|
||||
CmdLogoutDescription,
|
||||
CmdMcpDescription,
|
||||
CmdMemoryDescription,
|
||||
CmdModelDescription,
|
||||
CmdModelsDescription,
|
||||
CmdNoteDescription,
|
||||
@@ -424,6 +425,7 @@ pub const ALL_MESSAGE_IDS: &[MessageId] = &[
|
||||
MessageId::CmdLoadDescription,
|
||||
MessageId::CmdLogoutDescription,
|
||||
MessageId::CmdMcpDescription,
|
||||
MessageId::CmdMemoryDescription,
|
||||
MessageId::CmdModelDescription,
|
||||
MessageId::CmdModelsDescription,
|
||||
MessageId::CmdNoteDescription,
|
||||
@@ -739,6 +741,7 @@ fn english(id: MessageId) -> &'static str {
|
||||
MessageId::CmdLoadDescription => "Load session from file",
|
||||
MessageId::CmdLogoutDescription => "Clear API key and return to setup",
|
||||
MessageId::CmdMcpDescription => "Open or manage MCP servers",
|
||||
MessageId::CmdMemoryDescription => "Inspect or manage the persistent user-memory file",
|
||||
MessageId::CmdModelDescription => "Switch or view current model",
|
||||
MessageId::CmdModelsDescription => "List available models from API",
|
||||
MessageId::CmdNoteDescription => {
|
||||
@@ -1020,6 +1023,7 @@ fn japanese(id: MessageId) -> Option<&'static str> {
|
||||
MessageId::CmdLoadDescription => "ファイルからセッションを読み込み",
|
||||
MessageId::CmdLogoutDescription => "API キーを消去してセットアップに戻る",
|
||||
MessageId::CmdMcpDescription => "MCP サーバを開く・管理する",
|
||||
MessageId::CmdMemoryDescription => "永続ユーザーメモリファイルを確認・管理",
|
||||
MessageId::CmdModelDescription => "現在のモデルを切り替え・確認",
|
||||
MessageId::CmdModelsDescription => "API から利用可能なモデルを一覧表示",
|
||||
MessageId::CmdNoteDescription => "永続ノートファイル(.deepseek/notes.md)に追記",
|
||||
@@ -1281,6 +1285,7 @@ fn chinese_simplified(id: MessageId) -> Option<&'static str> {
|
||||
MessageId::CmdLoadDescription => "从文件加载会话",
|
||||
MessageId::CmdLogoutDescription => "清除 API 密钥并返回设置",
|
||||
MessageId::CmdMcpDescription => "打开或管理 MCP 服务器",
|
||||
MessageId::CmdMemoryDescription => "查看或管理持久用户记忆文件",
|
||||
MessageId::CmdModelDescription => "切换或查看当前模型",
|
||||
MessageId::CmdModelsDescription => "列出 API 中可用的模型",
|
||||
MessageId::CmdNoteDescription => "将笔记追加到持久笔记文件(.deepseek/notes.md)",
|
||||
@@ -1530,6 +1535,9 @@ fn portuguese_brazil(id: MessageId) -> Option<&'static str> {
|
||||
MessageId::CmdLoadDescription => "Carregar a sessão de um arquivo",
|
||||
MessageId::CmdLogoutDescription => "Limpar a chave de API e voltar à configuração",
|
||||
MessageId::CmdMcpDescription => "Abrir ou gerenciar servidores MCP",
|
||||
MessageId::CmdMemoryDescription => {
|
||||
"Inspecionar ou gerenciar o arquivo persistente de memória do usuário"
|
||||
}
|
||||
MessageId::CmdModelDescription => "Trocar ou exibir o modelo atual",
|
||||
MessageId::CmdModelsDescription => "Listar os modelos disponíveis pela API",
|
||||
MessageId::CmdNoteDescription => {
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
//! prompt alongside the existing `<project_instructions>` block.
|
||||
//! - **`# foo`** typed in the composer appends `foo` to the memory
|
||||
//! file as a timestamped bullet — fast capture without leaving the TUI.
|
||||
//! - **`/memory`** opens the memory file in `$VISUAL` / `$EDITOR`.
|
||||
//! - **`/memory`** shows the resolved file path and current contents, and
|
||||
//! **`/memory edit`** prints a copy-pasteable `$VISUAL` / `$EDITOR`
|
||||
//! command for opening the file yourself.
|
||||
//! - **`remember` tool** lets the model itself append a bullet when it
|
||||
//! notices a durable preference or convention worth keeping across
|
||||
//! sessions.
|
||||
|
||||
@@ -128,6 +128,7 @@ These override config values:
|
||||
- `DEEPSEEK_SKILLS_DIR`
|
||||
- `DEEPSEEK_MCP_CONFIG`
|
||||
- `DEEPSEEK_NOTES_PATH`
|
||||
- `DEEPSEEK_MEMORY` (`1|on|true|yes|y|enabled` turns user memory on)
|
||||
- `DEEPSEEK_MEMORY_PATH`
|
||||
- `DEEPSEEK_ALLOW_SHELL` (`1`/`true` enables)
|
||||
- `DEEPSEEK_APPROVAL_POLICY` (`on-request|untrusted|never`)
|
||||
@@ -320,6 +321,11 @@ If you are upgrading from older releases:
|
||||
used immediately by `/mcp`, but rebuilding the model-visible MCP tool pool
|
||||
requires restarting the TUI.
|
||||
- `notes_path` (string, optional): defaults to `~/.deepseek/notes.txt` and is used by the `note` tool.
|
||||
- `[memory].enabled` (bool, optional): defaults to `false`. When `true`,
|
||||
the TUI loads the user memory file into a `<user_memory>` prompt block,
|
||||
enables `# foo` quick-capture in the composer, surfaces the `/memory`
|
||||
slash command, and registers the `remember` tool. The same toggle is
|
||||
available via `DEEPSEEK_MEMORY=on`.
|
||||
- `memory_path` (string, optional): defaults to `~/.deepseek/memory.md`.
|
||||
Used by the user-memory feature when enabled — see
|
||||
[`MEMORY.md`](MEMORY.md) for the full feature surface (`# foo`
|
||||
@@ -369,6 +375,31 @@ If you are upgrading from older releases:
|
||||
- `hooks` (optional): lifecycle hooks configuration (see `config.example.toml`).
|
||||
- `features.*` (optional): feature flag overrides (see below).
|
||||
|
||||
### User memory
|
||||
|
||||
User memory is split across one top-level path setting and one opt-in
|
||||
toggle table:
|
||||
|
||||
```toml
|
||||
memory_path = "~/.deepseek/memory.md"
|
||||
|
||||
[memory]
|
||||
enabled = true
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `memory_path` stays at the top level beside `notes_path` and
|
||||
`skills_dir`; it is not nested under `[memory]`.
|
||||
- `DEEPSEEK_MEMORY_PATH` overrides the file path from the environment.
|
||||
- `DEEPSEEK_MEMORY=on` (also `1`, `true`, `yes`, `y`, or `enabled`)
|
||||
flips the feature on without editing `config.toml`.
|
||||
- The feature is inert when disabled: no file is injected, `# foo`
|
||||
falls through to normal message submission, and the model does not
|
||||
see the `remember` tool.
|
||||
- See [`MEMORY.md`](MEMORY.md) for examples and the full `/memory`
|
||||
command surface.
|
||||
|
||||
### Parsed but currently unused (reserved for future versions)
|
||||
|
||||
These keys are accepted by the config loader but not currently used by the interactive TUI or built-in tools:
|
||||
|
||||
+45
-1
@@ -20,6 +20,9 @@ Either set the env var:
|
||||
export DEEPSEEK_MEMORY=on
|
||||
```
|
||||
|
||||
Accepted truthy values are `1`, `on`, `true`, `yes`, `y`, and
|
||||
`enabled`.
|
||||
|
||||
…or add to `~/.deepseek/config.toml`:
|
||||
|
||||
```toml
|
||||
@@ -31,7 +34,25 @@ Restart the TUI after toggling. Disabling is the same in reverse.
|
||||
|
||||
The memory file lives at `~/.deepseek/memory.md` by default; override
|
||||
with `memory_path` in `config.toml` or `DEEPSEEK_MEMORY_PATH` in
|
||||
the environment.
|
||||
the environment. `DEEPSEEK_MEMORY_PATH` wins over the config file when
|
||||
both are set.
|
||||
|
||||
## Quick examples
|
||||
|
||||
```text
|
||||
# remember that this repo prefers cargo fmt before commits
|
||||
/memory
|
||||
/memory path
|
||||
/memory edit
|
||||
/memory help
|
||||
```
|
||||
|
||||
- Type `# remember that this repo prefers cargo fmt before commits` in
|
||||
the composer to append a timestamped bullet without firing a turn.
|
||||
- Run `/memory` to confirm where the feature is writing and what is
|
||||
currently stored.
|
||||
- Run `/memory edit` when you want to groom the file manually in your
|
||||
editor.
|
||||
|
||||
## What gets injected
|
||||
|
||||
@@ -84,11 +105,18 @@ Inspect, clear, or get hints about editing the file:
|
||||
| `/memory path` | Print just the resolved path |
|
||||
| `/memory clear` | Replace the file with an empty marker |
|
||||
| `/memory edit` | Print the `${VISUAL:-${EDITOR:-vi}} <path>` shell line |
|
||||
| `/memory help` | Show command-specific help and the current path |
|
||||
|
||||
The `/memory edit` form intentionally just prints the command rather
|
||||
than spawning the editor in-process — that keeps the slash-command
|
||||
handler simple and consistent regardless of which editor you use.
|
||||
|
||||
You can also discover the feature from the general help surfaces:
|
||||
|
||||
- `/help memory` shows the slash-command summary and usage line.
|
||||
- `/memory help` prints the memory-specific subcommands plus the
|
||||
resolved path.
|
||||
|
||||
### 3. The `remember` tool (auto-update, #489)
|
||||
|
||||
When memory is enabled the model gets a `remember` tool with this
|
||||
@@ -134,6 +162,22 @@ about the timestamp format; it just reads the whole file as the
|
||||
memory block. The timestamp is convention so you can tell when each
|
||||
note was added when grooming the file.
|
||||
|
||||
## Hierarchy and imports
|
||||
|
||||
Memory is intentionally **user-scoped** rather than repo-scoped. It
|
||||
sits alongside — not inside — project instruction sources such as
|
||||
`AGENTS.md`, `.deepseek/instructions.md`, and `instructions = [...]`.
|
||||
|
||||
- Use **memory** for durable personal preferences that should follow
|
||||
you across repos and sessions.
|
||||
- Use **project instructions** for repo-specific conventions that
|
||||
should travel with the codebase.
|
||||
|
||||
The memory loader currently reads one resolved file path verbatim.
|
||||
`@path` imports / includes are **not** supported today; if you need a
|
||||
larger reusable instruction bundle, put it in a project instruction
|
||||
file or a skill instead.
|
||||
|
||||
## What stays out of memory
|
||||
|
||||
Memory is for **durable** signal. Things that should NOT live there:
|
||||
|
||||
Reference in New Issue
Block a user