Skip to content

Inject mode-specific reminders on prompt switch - #20

Merged
yogthos merged 2 commits into
mainfrom
feature/agent-reminders
May 19, 2026
Merged

Inject mode-specific reminders on prompt switch#20
yogthos merged 2 commits into
mainfrom
feature/agent-reminders

Conversation

@yogthos

@yogthos yogthos commented May 19, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Appends a short, mode-specific reminder to the system preamble based on current_prompt_name:
    • plan → instructs the agent to write a plan, not code
    • review / review-security → reminds to flag issues with specifics
    • code (when PLAN.md exists) → reminds to execute the plan step by step
  • Keeps the reminder out of the prompt files themselves so prompt content stays purpose-only.

Stacked on #19.

Test plan

  • cargo build
  • Manual: /prompt plan → first agent turn carries the plan reminder; switch back to code and the reminder swaps appropriately

@yogthos
yogthos force-pushed the feature/glob-tool branch from f106da5 to 1800036 Compare May 19, 2026 16:48
@yogthos
yogthos force-pushed the feature/agent-reminders branch from a2c2280 to b0ff952 Compare May 19, 2026 16:49
@yogthos
yogthos changed the base branch from feature/glob-tool to main May 19, 2026 17:02
Yogthos added 2 commits May 19, 2026 13:36
- Plan mode: reminder to create a detailed plan in PLAN.md
- Code mode with existing PLAN.md: reminder to execute the plan step by step
- Review modes: reminder to review thoroughly and provide actionable feedback
- Code mode without PLAN.md: no additional context injected
format!() with no interpolation args is needless — replaced with push_str.
@yogthos
yogthos force-pushed the feature/agent-reminders branch from b0ff952 to 8ec159b Compare May 19, 2026 17:36
@yogthos
yogthos merged commit 14460fb into main May 19, 2026
@yogthos
yogthos deleted the feature/agent-reminders branch May 19, 2026 17:36
yogthos pushed a commit that referenced this pull request May 19, 2026
Adds targeted regression tests for each bug fix and behavioral guarantee
introduced across the eight feature PRs. Total: 205 → 254 tests (+49).

- background-tasks: 0 → 11 tests. Cover get()-evicts-on-read,
  truncation-by-chars on Completed/Failed, running tasks NOT evicted,
  shared state across clones (Arc<Mutex>), no-op on missing.
- apply-patch: 8 → 16 tests. Multi-op stop-on-failure with prior ops
  staying applied, ambiguous-update rejection, 1MB create cap (incl.
  off-by-one), nested-dir creation, PatchOp deserialization.
- glob-tool: 5 → 11 tests. Real-FS integration via TempTree: walks
  files, empty result returns empty string (not 'no files matched'),
  mtime sort with explicit path != CWD, respects .gitignore, regex
  metachars escaped, * doesn't cross directory boundaries.
- plan-tools: 5 → 8 tests. Source-level regression test that plan_exit
  has no fs::write / PLAN.md side-effects, channel-unavailable +
  reply-dropped error paths.
- web-tools: 7 → 12 / 4 → 7 tests. Wrap-width regression (long
  paragraph must wrap), input validation (empty/too many URLs),
  partial-field formatting in search results, 500-char snippet cap.
- task-status: 6 → 9 tests. Completed task evicts after one read,
  wait=true returns on Failed, wait=true on missing errors promptly
  (bounded timeout guard against infinite-loop regression).
- question-tool: 5 → 10 tests. Header → markdown ## heading, channel +
  reply error paths, default custom=true, multi-select comma-join.
- agent-reminders: 0 → 5 tests. Extracted append_mode_reminder() pure
  fn so the reminder injection is unit-testable. Covers plan/review/
  code modes, PLAN.md gate on code mode, unknown prompts pass through
  unchanged, section-separator prefix.

Each regression test has a comment explaining the bug it guards
against.
yogthos added a commit that referenced this pull request May 21, 2026
…aths (#111)

23 audit findings verified REAL via parallel agent verification +
cross-check against opencode/pi reference patterns. Shipping the
10 most concrete fixes here; the rest go in a follow-up docs/test
batch.

## Security

- **#9 bash quote_aware_split missed bare `|`** —
  `safe_cmd | rm -rf /` was treated as one segment; only the
  LHS got permission-checked. Pipe RHS rode in unchecked under
  the fallback (non-semantic-bash) path. Added single-byte `|`
  split after `||` is matched. The tree-sitter path was already
  correct.

- **#4 read.rs no binary detection** — feeding a PDF/ELF/.pyc
  into the LLM as lossy UTF-8 wasted tokens and confused the
  model. Ported opencode `read.ts:153-198`: reject by
  extension list (zip/exe/.o/.pdf/.png/etc.), then sniff the
  first 4 KiB — null byte = binary, >30% non-printable = binary.
  Clear error message tells the agent to use bash + xxd instead.

## Correctness

- **#2 skill override inverted** — README contract: "Project
  skills override global skills by name". Code used
  `map.entry(name).or_insert(skill)` which KEEPS the first
  (global) value and silently drops project overrides. Switch
  to `map.insert` (last-write-wins) since globals iterate
  first and project iterates second.

- **#37 skill empty name** — frontmatter `name:` with empty
  value parsed to "", which then matched any `skill ""` call
  silently. Fall back to directory name when frontmatter name
  is empty/whitespace-only.

- **#1 session_tree.janet hook never fired** — plugin defined
  `(defn on-message ...)` but `(def hooks [])` was empty AND
  the hook name doesn't exist (dirge uses `on-message-update`).
  `/label` was permanently broken ("no entry yet"). Fix:
  rename to `on-message-update` + register in hooks vector.

- **#7 workflow.janet hooks vector missing entries** — plugin
  defined `workflow-on-tool-end`, `-on-error`, `-on-complete`
  but only registered the first four hook names. Three hooks
  were dead. Added them.

- **#26 MCP malformed JSON silently empty args** —
  `serde_json::from_str(&args).unwrap_or_default()` turned bad
  JSON into None, sending the server an empty argument set.
  Server then errored with confusing "missing required field"
  instead of dirge surfacing the actual parse error. Now returns
  ToolError with the parse error message + first 200 chars of
  the offending JSON.

- **#22 /prompt default unreachable** — README documents
  `default` as a built-in prompt (prompts/default.md exists),
  but `/prompt default` was intercepted as a magic "clear"
  keyword. If `default` is registered in `context.prompts`,
  the new branch falls through to the normal name-lookup. Only
  acts as clear-keyword when no `default` prompt is present
  (legacy fallback).

- **#23 /allow add accepted invalid tools** — typo
  `/allow add bsah ...` silently created an inert rule the
  user couldn't debug. Added a known-tools whitelist matching
  PermissionConfig fields; unknown tools error with the valid
  list.

## Performance + correctness

- **#11 grep loaded whole files into memory** — no size cap
  meant a 9MB file got fully buffered. Added 10 MiB per-file
  cap via metadata pre-check.

- **#15 Python dunder methods marked non-exported** —
  `!name.starts_with('_')` treats `__init__`/`__call__`/etc.
  as private, even though they're Python's standard public
  protocol. Recognize `__x__` dunder pattern as exported.

## UI

- **#36 panel char-count truncation vs Unicode width** — panel
  truncation used `chars().count()` while wide emoji and CJK
  take 2 cells. A status line with an emoji overflowed the
  right border by one cell. Switched to
  `UnicodeWidthStr::width` for both truncation and padding.

## Tests

4 new regression tests:
- `test_is_binary_extension_known` — pdf/tgz/.so/.jpg/.pyc
- `test_is_binary_content_null_byte` — null byte trigger,
  UTF-8 Japanese stays clean, all-non-printable triggers
- `quote_aware_split_splits_on_bare_pipe` — pipe security
- `quote_aware_split_or_and_pipe_distinct` — `a || b | c`
  produces 3 segments, not 2

725 plugin / 599 default pass. All build profiles clean.

## Verified false positives (not fixed, audit was wrong)

- #3 cache.rs clear() race — generation counter gating in
  `get` makes stale entries invisible, no correctness impact.
- #17 DeepSeek auto-detect priority — auto-detect only fires
  when env vars present; default-default is still OpenRouter.
- #19 semantic tools in collision filter — semantic tools
  added separately, can't be shadowed by MCP.
- #20 glob global gitignore — intentionally disabled to match
  grep behavior.
- #28 nearest_root blocking std::fs — function doesn't exist
  in current code.
- #32 ReadArgs.path vs GrepArgs.path — semantically different
  by design (file vs dir), documented in schema.
- #33 install_plugin_providers dead-without-feature — gated
  with explicit `#[cfg_attr(not(feature), allow(dead_code))]`.
- #34 websearch double-gated — config + API key serve distinct
  purposes (enable + auth).

## Deferred to follow-up batches

Docs-only fixes (#6 CONFIG.md tools, #12 temperature, #13
--api-key, #14 acp_host/port), MCP/LSP architecture (#8, #25,
#27), test gaps (#38-40), and lower-priority polish — all in
a follow-up PR.

Co-authored-by: Yogthos <yogthos@gmail.com>
allen-munsch pushed a commit to allen-munsch/dirge that referenced this pull request Jun 3, 2026
Adds targeted regression tests for each bug fix and behavioral guarantee
introduced across the eight feature PRs. Total: 205 → 254 tests (+49).

- background-tasks: 0 → 11 tests. Cover get()-evicts-on-read,
  truncation-by-chars on Completed/Failed, running tasks NOT evicted,
  shared state across clones (Arc<Mutex>), no-op on missing.
- apply-patch: 8 → 16 tests. Multi-op stop-on-failure with prior ops
  staying applied, ambiguous-update rejection, 1MB create cap (incl.
  off-by-one), nested-dir creation, PatchOp deserialization.
- glob-tool: 5 → 11 tests. Real-FS integration via TempTree: walks
  files, empty result returns empty string (not 'no files matched'),
  mtime sort with explicit path != CWD, respects .gitignore, regex
  metachars escaped, * doesn't cross directory boundaries.
- plan-tools: 5 → 8 tests. Source-level regression test that plan_exit
  has no fs::write / PLAN.md side-effects, channel-unavailable +
  reply-dropped error paths.
- web-tools: 7 → 12 / 4 → 7 tests. Wrap-width regression (long
  paragraph must wrap), input validation (empty/too many URLs),
  partial-field formatting in search results, 500-char snippet cap.
- task-status: 6 → 9 tests. Completed task evicts after one read,
  wait=true returns on Failed, wait=true on missing errors promptly
  (bounded timeout guard against infinite-loop regression).
- question-tool: 5 → 10 tests. Header → markdown ## heading, channel +
  reply error paths, default custom=true, multi-select comma-join.
- agent-reminders: 0 → 5 tests. Extracted append_mode_reminder() pure
  fn so the reminder injection is unit-testable. Covers plan/review/
  code modes, PLAN.md gate on code mode, unknown prompts pass through
  unchanged, section-separator prefix.

Each regression test has a comment explaining the bug it guards
against.
allen-munsch pushed a commit to allen-munsch/dirge that referenced this pull request Jun 3, 2026
…aths (dirge-code#111)

23 audit findings verified REAL via parallel agent verification +
cross-check against opencode/pi reference patterns. Shipping the
10 most concrete fixes here; the rest go in a follow-up docs/test
batch.

## Security

- **dirge-code#9 bash quote_aware_split missed bare `|`** —
  `safe_cmd | rm -rf /` was treated as one segment; only the
  LHS got permission-checked. Pipe RHS rode in unchecked under
  the fallback (non-semantic-bash) path. Added single-byte `|`
  split after `||` is matched. The tree-sitter path was already
  correct.

- **#4 read.rs no binary detection** — feeding a PDF/ELF/.pyc
  into the LLM as lossy UTF-8 wasted tokens and confused the
  model. Ported opencode `read.ts:153-198`: reject by
  extension list (zip/exe/.o/.pdf/.png/etc.), then sniff the
  first 4 KiB — null byte = binary, >30% non-printable = binary.
  Clear error message tells the agent to use bash + xxd instead.

## Correctness

- **#2 skill override inverted** — README contract: "Project
  skills override global skills by name". Code used
  `map.entry(name).or_insert(skill)` which KEEPS the first
  (global) value and silently drops project overrides. Switch
  to `map.insert` (last-write-wins) since globals iterate
  first and project iterates second.

- **dirge-code#37 skill empty name** — frontmatter `name:` with empty
  value parsed to "", which then matched any `skill ""` call
  silently. Fall back to directory name when frontmatter name
  is empty/whitespace-only.

- **#1 session_tree.janet hook never fired** — plugin defined
  `(defn on-message ...)` but `(def hooks [])` was empty AND
  the hook name doesn't exist (dirge uses `on-message-update`).
  `/label` was permanently broken ("no entry yet"). Fix:
  rename to `on-message-update` + register in hooks vector.

- **dirge-code#7 workflow.janet hooks vector missing entries** — plugin
  defined `workflow-on-tool-end`, `-on-error`, `-on-complete`
  but only registered the first four hook names. Three hooks
  were dead. Added them.

- **dirge-code#26 MCP malformed JSON silently empty args** —
  `serde_json::from_str(&args).unwrap_or_default()` turned bad
  JSON into None, sending the server an empty argument set.
  Server then errored with confusing "missing required field"
  instead of dirge surfacing the actual parse error. Now returns
  ToolError with the parse error message + first 200 chars of
  the offending JSON.

- **dirge-code#22 /prompt default unreachable** — README documents
  `default` as a built-in prompt (prompts/default.md exists),
  but `/prompt default` was intercepted as a magic "clear"
  keyword. If `default` is registered in `context.prompts`,
  the new branch falls through to the normal name-lookup. Only
  acts as clear-keyword when no `default` prompt is present
  (legacy fallback).

- **dirge-code#23 /allow add accepted invalid tools** — typo
  `/allow add bsah ...` silently created an inert rule the
  user couldn't debug. Added a known-tools whitelist matching
  PermissionConfig fields; unknown tools error with the valid
  list.

## Performance + correctness

- **dirge-code#11 grep loaded whole files into memory** — no size cap
  meant a 9MB file got fully buffered. Added 10 MiB per-file
  cap via metadata pre-check.

- **dirge-code#15 Python dunder methods marked non-exported** —
  `!name.starts_with('_')` treats `__init__`/`__call__`/etc.
  as private, even though they're Python's standard public
  protocol. Recognize `__x__` dunder pattern as exported.

## UI

- **dirge-code#36 panel char-count truncation vs Unicode width** — panel
  truncation used `chars().count()` while wide emoji and CJK
  take 2 cells. A status line with an emoji overflowed the
  right border by one cell. Switched to
  `UnicodeWidthStr::width` for both truncation and padding.

## Tests

4 new regression tests:
- `test_is_binary_extension_known` — pdf/tgz/.so/.jpg/.pyc
- `test_is_binary_content_null_byte` — null byte trigger,
  UTF-8 Japanese stays clean, all-non-printable triggers
- `quote_aware_split_splits_on_bare_pipe` — pipe security
- `quote_aware_split_or_and_pipe_distinct` — `a || b | c`
  produces 3 segments, not 2

725 plugin / 599 default pass. All build profiles clean.

## Verified false positives (not fixed, audit was wrong)

- #3 cache.rs clear() race — generation counter gating in
  `get` makes stale entries invisible, no correctness impact.
- dirge-code#17 DeepSeek auto-detect priority — auto-detect only fires
  when env vars present; default-default is still OpenRouter.
- dirge-code#19 semantic tools in collision filter — semantic tools
  added separately, can't be shadowed by MCP.
- dirge-code#20 glob global gitignore — intentionally disabled to match
  grep behavior.
- dirge-code#28 nearest_root blocking std::fs — function doesn't exist
  in current code.
- dirge-code#32 ReadArgs.path vs GrepArgs.path — semantically different
  by design (file vs dir), documented in schema.
- dirge-code#33 install_plugin_providers dead-without-feature — gated
  with explicit `#[cfg_attr(not(feature), allow(dead_code))]`.
- dirge-code#34 websearch double-gated — config + API key serve distinct
  purposes (enable + auth).

## Deferred to follow-up batches

Docs-only fixes (dirge-code#6 CONFIG.md tools, dirge-code#12 temperature, dirge-code#13
--api-key, dirge-code#14 acp_host/port), MCP/LSP architecture (dirge-code#8, dirge-code#25,
dirge-code#27), test gaps (dirge-code#38-40), and lower-priority polish — all in
a follow-up PR.

Co-authored-by: Yogthos <yogthos@gmail.com>
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