Skip to content

Add three stock prompt finders to the Harness - #18

Closed
vinniefalco wants to merge 2 commits into
cppalliance:masterfrom
vinniefalco:stock-prompt-finders
Closed

vinniefalco wants to merge 2 commits into
cppalliance:masterfrom
vinniefalco:stock-prompt-finders

Conversation

@vinniefalco

Copy link
Copy Markdown
Member

The Harness now has three ready-made PromptFinder implementations, so a Host no longer has to write its own closure to serve its prompts. They are in runvil::harness.

This branch sits on top of #17, which is still open. Until #17 merges, the commit list here shows its commit too. The change in this PR is the one commit 67d9d1b4.

Why

Every Host hand-wrote a finder, and the run-prompt example and the test suites each repeated the same one. A Host that keeps its prompts in one or more directories needs a little more than a closure: it has to pick up edits, notice new files, and say what it holds.

What is added

  • FindTextPrompt. Serves one prompt, by name, from text given at construction.
  • FindFilePrompt. Serves one prompt, by name, from a file. It reads the file with std::fs::read_to_string at each find, so an edit shows in the next find.
  • PromptSet. Holds the prompts a Host inserts as text (insert), as a file (insert_file), or as every prompt file in a directory (insert_dir). rescan lists the directories again, and names lists what the set serves.

How PromptSet behaves

  • A later insert wins. A find takes the newest insert that has the name. A Host that inserts its shipped directory and then its user's directory serves the user's file under a name both hold.
  • Files are read at each find, and nothing is cached. A rescan is only needed for added or removed files, never for edits.
  • The Host watches, the set does not. A Host fills the set, shares it behind an Arc, gives one handle to its Host as the finder, and keeps the other to call rescan when it sees a directory change.
  • rescan keeps going. A directory that cannot be listed holds no prompt until a later rescan lists it, the others are still listed, and rescan returns one failure for each directory it could not list.
  • insert_dir is strict. A directory that cannot be listed is an error and is not kept.
  • A prompt file is a regular file directly in the directory whose extension is exactly md and whose name is UTF-8. Its name is its file name, such as greet.md.
  • A removed file fails its find with the read's failure until the next rescan. The find does not fall back to an older insert.
  • Locking. The inserts take &mut self, so the list of inserts never changes once the set is shared. Each directory's listing has its own lock. A rescan holds it while it lists, and a find holds it only to look a name up and reads the file after.

Failures

Each is the universal Error:

  • An unknown name is NotFound, with the message no prompt named {name} and the Name fact.
  • A failed read is NotFound, NotUtf8, or Io, with the message prompt {name}: cannot read {path} then the system's reason, and the Name and Path facts.
  • A failed listing is NotFound or Io, with the message cannot list prompt directory {path} then the system's reason, and the Path fact.

What else changes

  • Facade. runvil::harness re-exports the three types. public-api.txt of runvil gains 20 lines and loses none. The runvil-engine listing does not change.
  • Example. run-prompt builds its Host with FindTextPrompt, and its hand-written find function is gone.
  • Docs. The crate docs of the runner and of runvil name the stock finders, and the runner has one new invariant: a stock finder reads a file only inside a find, and the Harness calls a finder only from Host::parse, before the run exists.
  • Engine. No file of the Engine crates changes, and there is no new dependency.

Tests

prompt_finders-tests.rs has 23 tests:

  • Each single finder serves its name, fails any other name, and a file finder fails as NotFound, NotUtf8, or Io.
  • The set: which files a directory gives, which insert wins a name, a file read fresh with no rescan, and a missing directory that is refused and not kept.
  • Rescan: an added file is found after it, a removed file is forgotten after it, a directory that cannot be listed is reported while the others are listed, and that directory serves its files again once it is back.
  • names lists every insert, sorted, each name once, and follows a rescan.
  • A Host parses a prompt that its application added to a shared set and rescanned.

Three rules of a listing have no test: a link to a regular file counts as that file, a file name that is not UTF-8 is skipped, and an entry that vanishes during the listing is skipped. The first two need a setup for each platform, and the third needs a test seam.

Checks

Every command in AGENTS.md passes on the final tree, with the run-prompt example, the doc build of runvil alone, and the private-items doc build of runvil-runtime. Nextest runs 3,818 tests, all passing, and skips 24. xtask api --check passes on the pinned nightly.

Plan and findings: plan/2026-11-14-3-stock-prompt-finders.plan.md (see "What the build found").

A run held the Packs that its prompt declared plus each `runvil` Pack whose Pack id no declaration took, so a prompt decided which Packs were in its run, and a registry held Packs of two vendors under one Pack id. `add` in `registry.rs` now refuses a Pack id that the registry holds, and `pick` in `packs.rs` takes every installed Pack. A `uses:` entry marks a Pack as required and installs its prelude, and it puts no Pack in a run and takes none out.

- `holder` is in the place of `reserves`, and the reason `pack_id_taken` in the place of `reserved_name`. The refusal names the Pack that holds the id, a built-in or any other, and a Pack that `insert_unavailable` added holds its id too.
- `pick` puts the declared Packs first, in declaration order, then every other installed Pack. It leaves out a Pack whose Pack id an `unresolved` entry names, because the roster lists that id as unavailable. The first step of such a run refuses it.
- `find` names the one other Pack in `write {other} to use that Pack`, where it had `others.join(" or ")`.
- A run makes its instance of every installed Pack and waits on the `ready` of each one, whether or not the prompt declares the Pack. A Pack whose `ready` never resolves holds each run of its Host until a cancel.
- A Pack of another vendor that the prompt does not declare is in the run: its tools are in the catalog, its roster entry has its vendor, and its prelude is not installed.
- The docs of `PackRegistry`, `insert`, `BUILTIN`, `Picked`, and `RUNVIL`, the module doc of `packs.rs`, and two invariants in the runner's `lib.rs` state the rule. The crate docs of `runvil-plugin` and `plugin-web` change by one sentence each.
- `packs-tests-pick.rs` is written over a Host that holds `runvil/web` and `acme/poop`, with `the_pick_is_every_installed_pack_with_the_declared_ones_first` among its tests. `a_pack_inserted_unavailable_holds_its_pack_id` is a test of the registry.
- `an_undeclared_third_party_pack_that_never_gets_ready_holds_no_run_up` is removed. In its place, one test waits for an undeclared Pack of another vendor that starts late, and one cancels a run during that wait.
- The diff changes no file of the Engine crates and neither `public-api.txt` listing.

Plan: /plan/2026-11-14-2-every-pack-in-every-run.plan.md
The change gives a Host three implementations of `PromptFinder`, so that it does not write its own. `prompt_finders.rs` in the runner adds `FindTextPrompt`, `FindFilePrompt`, and `PromptSet`, and the `harness` module of the `runvil` facade re-exports them. A file is read with `std::fs::read_to_string` at each find and no text is kept, and a `PromptSet` lists a directory at `insert_dir` and again at each `rescan`.

- `PromptSet` keeps one `Entry` for each insert in a `Vec`, and its `find` takes the newest entry that has the name. The three inserts take `&mut self`, and `rescan` and `names` take `&self`. The only state that changes in a shared set is the `names` of an `Entry::Dir`, behind a `Mutex`.
- `rescan` holds that lock while it lists the directory. A find holds it for the lookup only, and reads the file after it releases the lock.
- `list_prompts` takes the regular files of the directory whose extension is `md` and whose name is UTF-8. It does not enter a subdirectory, and it skips an entry whose `metadata` fails as `NotFound`. Any other failure of an entry fails the listing.
- `make_io_error` takes the code of a failed read or listing from the `io::ErrorKind`: `NotFound`, `NotUtf8` for `InvalidData`, and `Io` for the rest. The message names the prompt or the directory, then the path, then the cause.
- A find for a name that the finder does not have fails as `NotFound` with the message `no prompt named {name}` and the prose `No prompt has that name.`.
- `insert_dir` returns the failure of the listing and does not keep the directory. `rescan` returns one failure for each directory that it cannot list, clears the names of that directory, and lists the others.
- A find for a file that is removed from a listed directory fails with the failure of the read until the next `rescan`. It does not go to an older entry.
- The `run-prompt` example builds its `Host` with `FindTextPrompt::new(GREET_NAME, GREET)`, and its `find` function is removed.
- `public-api.txt` of `runvil` gains 20 lines: three structs, three `new` functions, five methods of `PromptSet`, and three impls of `PromptFinder`.
- `prompt_finders-tests.rs` has 23 tests. No test covers a link to a file, a file name that is not UTF-8, or an entry that is removed during a listing.
- The diff changes no manifest and no file of the Engine crates.

Plan: /plan/2026-11-14-3-stock-prompt-finders.plan.md
@coderabbitai

coderabbitai Bot commented Nov 14, 2026 •

Copy link
Copy Markdown

Explain Change →

Caution

Review failed

The pull request is closed.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

@vinniefalco

Copy link
Copy Markdown
Member Author

Closing: this was opened from a topical branch by mistake. The same commit, 67d9d1b, is now on vinniefalco:master, so it is part of #17.

@vinniefalco
vinniefalco deleted the stock-prompt-finders branch November 14, 2026 19:55
@greptile-apps

greptile-apps Bot commented Nov 14, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[High impact] The PR appears safe to merge.

Summary

Adds FindTextPrompt, FindFilePrompt, and PromptSet to runvil::harness.

  • Runs hold every Pack installed by their Host.
  • Each Pack id can belong to only one registered Pack.
  • Hosts can serve prompts from text, files, or directories.

No actionable issues found. vinniefalco explicitly documents the broader Pack access and readiness wait as intentional. vinniefalco also acknowledges missing tests for links, non-UTF-8 names, and entries removed during listing.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  App[Application] -->|insert text, files, directories| Set[PromptSet]
  Watcher[Directory watcher] -->|rescan| Set
  Picker[Prompt picker] -->|names| Set
  Host[Host::parse] -->|find| Set
  Set -->|read at each find| Files[Prompt files]
  Host --> Parsed[Parsed prompt]
  Parsed --> Run[Harness run]
Loading

Reviews (1) · Last reviewed commit: "Add three stock prompt finders to the Ha..." · Reviewed by Greptile

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