Skip to content

feat(prompt): add system prompt builder with context injection - #7

Merged
hakula139 merged 32 commits into
mainfrom
feat/system-prompt
Apr 5, 2026
Merged

feat(prompt): add system prompt builder with context injection#7
hakula139 merged 32 commits into
mainfrom
feat/system-prompt

Conversation

@hakula139

@hakula139hakula139 commented Apr 5, 2026

Copy link
Copy Markdown
Owner

Summary

Add a section-based system prompt builder with runtime environment detection, hierarchical instruction file discovery (CLAUDE.md / AGENTS.md), and static guidance sections. The prompt is rebuilt each turn so git status and branch info stay fresh.

The API client now sends the system prompt as an array of text blocks with the identity prefix in its own block, which is required for OAuth validation on non-Haiku models.

  • Section-based prompt assembly: identity, task guidance, caution, tool usage, tone / style, environment, user instructions.
  • CLAUDE.md / AGENTS.md discovery with root-to-CWD walk and .claude/ subdirectory support.
  • Runtime environment detection: working directory, platform, shell, git branch + clean / dirty status, local date, model name.
  • Per-turn prompt rebuild for fresh git state on every user message.
  • System prompt sent as array of TextBlock with identity prefix in its own block (OAuth requirement).
  • Research docs on system prompt architecture and API authentication (block format, attribution header, third-party restrictions).
  • User-facing guide: quickstart, configuration, instruction files.

Changes

FileDescription
crates/oxide-code/src/prompt.rsModule root: static guidance constants, build_system_prompt entry, assemble (testable core), find_git_root
crates/oxide-code/src/prompt/environment.rsEnvironment struct with detect / render, parallel git info via tokio::join!, current_date via time crate with local-offset fallback
crates/oxide-code/src/prompt/instructions.rsSlot-based instruction discovery: loadcandidate_slotswalk_root_to_cwdload_filesrender
crates/oxide-code/src/client/anthropic.rsSystemBlock type, system field changed from &str to Vec<SystemBlock>, prefix sent as separate block, SYSTEM_PROMPT_PREFIX moved here
crates/oxide-code/src/main.rsPer-turn build_system_prompt call, pass Some(&system_prompt) to client
crates/oxide-code/src/config/oauth.rs_ = style consistency
Cargo.tomlAdd time with local-offset feature
CLAUDE.mdAdd prompt.rs, prompt/environment.rs, prompt/instructions.rs to crate structure
docs/research/system-prompt.mdResearch: Claude Code and opencode prompt architecture, section caching, walk behavior
docs/research/anthropic-api.mdResearch: system block format, attribution header / fingerprint, third-party restrictions
docs/guide/quickstart.mdUser guide: install, credentials, first session, tools table
docs/guide/configuration.mdUser guide: API key, OAuth, environment variables
docs/guide/instructions.mdUser guide: CLAUDE.md / AGENTS.md discovery hierarchy, walk example
docs/guide/README.mdGuide index
docs/README.mdAdd User Guide section
docs/roadmap.mdMove system prompt to Working, update focus
README.mdSlim down, add Documentation table linking to guide

Test plan

  • cargo build compiles cleanly
  • cargo clippy --all-targets -- -D warnings — zero warnings
  • cargo test — 195 tests pass
  • cargo llvm-cov --ignore-filename-regex 'main\.rs' — 89% line coverage (prompt modules 98–99%)
  • Live test: ox responds correctly with full system prompt (CLAUDE.md injected, environment detected)
  • Live test: main branch works, feat/system-prompt works after block-format fix

Move system prompt ownership out of the transport layer into a dedicated
prompt module. The prompt assembles static guidance sections (identity,
task, tool, style), runtime environment detection (platform, git, date),
and discovered CLAUDE.md files (global + project).
- Fix global CLAUDE.md skipped when cwd is None (candidate_paths now
takes Option<&Path> and always includes the global path).
- Handle git command failures independently — default to empty branch
and assume dirty rather than discarding all git info.
- Run environment detection and CLAUDE.md loading concurrently.
- Tighten test assertions (identity prefix boundary, candidate_paths
length).
At each project location, check CLAUDE.md first, then AGENTS.md as a
fallback. The first file found at each location wins. The global
~/.claude/ directory stays CLAUDE.md only.
@hakula139hakula139 added the enhancement New feature or request label Apr 5, 2026
@hakula139hakula139 self-assigned this Apr 5, 2026
Discover instruction files at every directory level from the project root
down to the working directory, not just at the root. Subdirectory-specific
instructions appear later in the prompt (higher priority), matching Claude
Code's hierarchical discovery behavior.
- Return GitInfo directly from detect_git_info instead of wrapping in
Option — the function never returns None due to fallback logic, so the
Option was misleading. The caller already gates on git_root.is_some().
- Replace date subprocess (date +%Y-%m-%d) with the time crate for
portability and no subprocess overhead. Uses local time with UTC
fallback when local offset detection fails.
- Improve render tests to verify exact line ordering and structure
instead of loose contains() checks.
- Add tests for Environment::detect with various cwd/git_root
combinations and strengthen current_date year validation.
Build the system prompt at the start of each user interaction instead
of once at startup. This keeps dynamic data (git branch, dirty status,
working directory, date) fresh during long sessions.
The rebuild happens per user message, not per tool round — one set of
git subprocess calls per interaction is acceptable overhead.
Replace Vec<(Vec<PathBuf>, &'static str)> with a named Slot struct
for readability — slot.candidates and slot.label are self-documenting
compared to tuple field access.
Add comment in walk_root_to_cwd explaining the strip_prefix behavior
when cwd equals root (empty relative path, loop body never executes).
Add tests for load_files: empty results, first-candidate preference,
AGENTS.md fallback, whitespace-only skip, multi-slot collection.
Extend candidate_slots test to verify .claude/ companion slots.
Add render_single_file test.
Add find_git_root tests: success inside a git repo (verifies .git
exists), returns None in a temp dir outside any repo.
Add build_system_prompt tests: user instructions branch (verifies
project CLAUDE.md injection), section join boundary (double newline
separator between sections).
…opencode findings
Add root-to-CWD walk pattern to CLAUDE.md Loading Hierarchy section
with a concrete example showing intermediate directory discovery.
Fix opencode section: TypeScript / Bun (not Go), correct the patterns
based on actual source investigation — provider-specific templates,
AGENTS.md / CLAUDE.md / CONTEXT.md hierarchy with walk-up semantics,
per-turn rebuild, 4-level config (not 8), two-phase compaction.
Add opencode source paths to the Sources section.
…ance
Add "Executing actions with care" section — guidance on reversibility,
blast radius, and confirmation for destructive/shared-state operations.
Inspired by Claude Code's approach but kept concise for token efficiency.
Expand task guidance: stronger "read before modify" language, specific
security vulnerability classes (command injection, path traversal,
OWASP top 10), scope discipline, and "diagnose before retry" detail.
Expand style section: output focus guidance and emoji restriction.
…uctions
Create docs/guide/ with three focused pages covering what actually
ships today:
- quickstart.md — install, first session, tool overview, CLAUDE.md teaser
- configuration.md — API key and OAuth auth, env vars, model selection
- instructions.md — CLAUDE.md / AGENTS.md discovery hierarchy with walk
example, writing tips
Slim down the main README: replace inline config details with a
documentation table linking to the guide pages. Update docs/README.md
with a user guide section above the internal docs index.
The Anthropic API requires the identity prefix to be a separate text
block in the system array for OAuth validation. Concatenating it into
the prompt body as a single string causes 429 for non-Haiku models.
Move the prefix constant and block construction into the client, and
change stream_message to accept Option<&str> for the prompt body.
@hakula139
hakula139 merged commit 57d1b7b into mainApr 5, 2026
1 check passed
@hakula139
hakula139 deleted the feat/system-prompt branch April 5, 2026 16:27
hakula139 added a commit that referenced this pull request May 5, 2026
PR #64 (modal infrastructure) shipped Option C: bare /model opens the combined
picker, bare /effort errors with a usage hint pointing at /model. The user
guide, design notes, and roadmap still described the older "both bare forms
open the picker with different initial focus" shape. Updated:
- docs/guide/slash-commands.md — table description, mid-turn classification
paragraph, and the "Switching the Effort" / "Switching the Model" sections.
- docs/design/slash/commands.md — design decision #5, /effort and /model
per-command notes, source list (`agent_loop_task` → `agent_turn`).
- docs/design/slash/modals.md — design decisions #4 (`SessionInfo` →
`LiveSessionInfo`) and #7 (typed-arg-only contract).
- docs/roadmap.md — moved the combined picker out of "Current Focus" (shipped
in PR #64) into Working Today; replaced with the deferred /effort slider.
- CLAUDE.md — `slash/effort.rs` description updated to match the typed-arg
contract.
hakula139 added a commit that referenced this pull request Jun 26, 2026
## Summary
Refresh dependencies across both ecosystems and clear the two open
Dependabot DoS advisories (#7, #8).
- **Rust:** `cargo update` (77 crates, semver-compatible) plus two
direct major bumps — `nix` 0.30 → 0.31 and `toml` 0.8 → 1. Both have
narrow usage (`nix` only `killpg`/`Pid`/`Signal` in `bash.rs`; `toml`
only `from_str` deserialization), so the migrations are no-ops at the
call sites.
- **Node:** `pnpm update --latest` (cspell 10.0.1, markdownlint-cli2
0.22.1) plus pnpm `overrides` forcing the patched `js-yaml` and
`markdown-it`.
## Design decisions
- **DoS fix needs an override, not a direct bump.** Both `js-yaml` and
`markdown-it` reach the tree only transitively through
`markdownlint-cli2@0.22.1`, which still pins the vulnerable versions, so
`overrides` is the only way to force the patched releases.
- **`js-yaml` capped at `^4.2.0`, not `>=4.2.0`.** The latest is 5.1.0,
but 5.x drops the `default` ESM export `markdownlint-cli2` imports,
which breaks `pnpm lint`. The advisory is patched in 4.2.0, so the 4.x
line fixes the alert without breakage.
- **`ratatui` / `crossterm` / `syntect` left on their current majors.**
They are already at their newest major; only `nix` and `toml` had a
clean newer major among our direct deps.
## Changes
| File | Description |
| ---------------- |
------------------------------------------------------------------------------
|
| `Cargo.toml` | Bump `nix` 0.30 → 0.31, `toml` 0.8 → 1 |
| `Cargo.lock` | Relock; `cargo update` across 77 crates |
| `package.json` | cspell `^10.0.1`, markdownlint-cli2 `^0.22.1`, pnpm
overrides for the DoS deps |
| `pnpm-lock.yaml` | Relock onto patched + latest versions |
## Test plan
- [x] `cargo fmt --all --check` — clean
- [x] `cargo clippy --all-targets -- -D warnings` — zero warnings
- [x] `cargo test` — 2094 pass
- [x] `pnpm lint` — 48 files, 0 errors
- [x] `pnpm spellcheck` — 184 files, 0 issues
- [x] No `js-yaml@4.1.1` / `markdown-it@14.1.1` left in the lock
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancementNew feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@hakula139