Skip to content

feat(session): record active prompt name and source - #228

Merged
gi-dellav merged 1 commit into
gi-dellav:mainfrom
xavierforge:feat/session-prompt-provenance
Aug 1, 2026
Merged

gi-dellav merged 1 commit into
gi-dellav:mainfrom
xavierforge:feat/session-prompt-provenance

Conversation

@xavierforge

Copy link
Copy Markdown
Collaborator

Closes #227

Current behavior

The prompt shaping a session lives only in memory, on ContextFiles::current_prompt_name. It is set at startup from default_prompt or --load-prompt, and updated at runtime through apply_prompt_mode plus a few direct assignment sites. It surfaces in exactly one place, the status bar. The session file has no field for it, so a saved session cannot say whether it ran under code, plan, review, or a user prompt file, even though a prompt's %%mode= directive now drives permission enforcement.

What changes

  • Session gains #[serde(default)] pub prompt: Option<PromptRef>, where PromptRef is { name, source } and source is BuiltIn or UserFile.
  • context::prompts gains source_of(name), a small helper that checks the same override directories load() already walks (global prompts dir, the project's data/prompts/, .zerostack/prompts/) to classify a resolved prompt's origin, without changing load()'s HashMap<String, String> return shape.
  • Startup prompt resolution writes the field in both the default_prompt block and the --load-prompt block.
  • apply_prompt_mode now takes the session and writes the field, so its five call sites (/prompt <name>, /review, chain phase transitions, both dot-prefix branches) pick it up by construction. The three sites that assign current_prompt_name directly instead of going through apply_prompt_mode (/prompt default, and the two dot-prefix restore paths) get the same treatment.
  • No switch history is kept; the last prompt active wins, matching what the status bar already shows.
  • #[serde(default)] keeps existing session files loading with prompt: None.

Tests

  • src/session/mod.rs schema plus a save/load round trip test and a test that a session file written before this field existed still loads, with prompt: None.
  • src/context/prompts.rs: source_of unit tests for a built-in prompt, a user file shadowing a built-in name, and a prompt that only exists as a user file.
  • src/tests/prompt_mode_tests.rs: apply_prompt_mode now asserts the session's recorded prompt name and source, including that an unknown prompt name is a no-op that leaves the previous record untouched, and that switching twice leaves the record reflecting the prompt active last.

Verified with cargo fmt --check and cargo clippy --all-targets --all-features -- -D warnings, both clean.

@gi-dellav

Copy link
Copy Markdown
Owner

Please fix conflicts; for the rest, it's ready to be merged

@xavierforge
xavierforge force-pushed the feat/session-prompt-provenance branch from 532c16f to 272345c Compare July 27, 2026 23:58
The session file never recorded which prompt shaped it, even though a
prompt's %%mode= directive now drives permission enforcement. Add
Session.prompt: Option<PromptRef> (name plus built-in vs user-file
source), written at startup prompt resolution and kept in sync on
every runtime prompt switch, with the last one winning. Old session
files load unchanged via serde(default).

The source is decided by content, not by file existence: ensure_global()
seeds every embedded prompt into the global prompts dir on first run, so
code.md being present there says nothing about whether the user touched
it. source_of() reads the highest-precedence on-disk copy, mirroring
load()'s last-writer-wins order, and reports UserFile only when that text
differs from the compiled-in one or has no compiled-in counterpart.
@xavierforge
xavierforge force-pushed the feat/session-prompt-provenance branch from 272345c to 0b2d2bb Compare July 28, 2026 00:09
@xavierforge

Copy link
Copy Markdown
Collaborator Author

Conflicts fixed (rebased onto current main; the only overlap was Session gaining fields from both #230 and this PR).

While rebasing I found a defect in this PR worth fixing before merge, so the force-push also changes behavior. source_of() decided built-in vs user-file by testing whether <name>.md existed in any of the three prompt dirs. But context::load() calls prompts::ensure_global(), which seeds every embedded prompt into the global prompts dir on first run, so code.md always exists there and the field reported user_file for every prompt: BuiltIn was unreachable in production. The unit tests missed it because they point ZS_DATA_DIR at an empty temp dir and never call ensure_global().

It now compares content instead: read the highest-precedence on-disk copy (mirroring load()'s override order) and report UserFile only when that text differs from the compiled-in one, or has no compiled-in counterpart. Added three tests, one of which calls ensure_global() first and pins code as BuiltIn (it fails against the old logic).

@gi-dellav
gi-dellav merged commit c6314ad into gi-dellav:main Aug 1, 2026
12 checks passed
@xavierforge
xavierforge deleted the feat/session-prompt-provenance branch August 9, 2026 06:56
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.

Session files do not record which prompt was loaded

2 participants