Skip to content

feat(forgecode): v3-cleanup — FTS5 message snippets, ConversationSort SSOT, perf audit#24

Merged
KooshaPari merged 1 commit into
mainfrom
feat/session-viewer-perf-v3
Jun 21, 2026
Merged

feat(forgecode): v3-cleanup — FTS5 message snippets, ConversationSort SSOT, perf audit#24
KooshaPari merged 1 commit into
mainfrom
feat/session-viewer-perf-v3

Conversation

@KooshaPari

@KooshaPari KooshaPari commented Jun 21, 2026

Copy link
Copy Markdown
Owner

User description

Builds on PR #22 (v3 :reparent/:cwd/sort commands). Adds message-level FTS5 search with snippet extraction and cleans up the P1-9/P1-10 backlog.

What's in this PR

1. FTS5 message-level search with snippet extraction

  • get_conversation_snippet(id, query, token_count) trait method — returns a list of matching text passages with tags
  • Uses SQLite FTS5's snippet() function over the context column (conversation JSON with all messages)
  • Wired through trait → impl → service → AgentService → API
  • UI: after a user picks a search hit, fetches snippet and displays it dimmed below the conversation title

2. ConversationSort SSOT (single source of truth)

  • New enum forge_domain::ConversationSort — Updated | Created | Turns | Cwd | Title
  • Replaces ad-hoc SortKey type alias in forge_main/src/state.rs
  • UIState.sort: ConversationSort is the single field
  • Eliminates duplication and gives consistent parsing/display across the app

3. P1-9 / P1-10 audit (no changes needed)

Files changed (13 files, +289 / -5)

  • Domain: conversation.rs (ConversationSort enum + FromStr), repo.rs (trait method)
  • Repo: conversation_repo.rs (impl with snippet), forge_repo.rs (service wiring)
  • Services: services/conversation.rs, app/services.rs
  • API: api.rs, forge_api.rs
  • UI: model.rs (handle_sort), state.rs (sort field), ui.rs (handle_sort + handle_search snippet display), conversation_selector.rs (test fixtures), info.rs (test fixtures)

Build

  • cargo build --bin forge clean in 1m 40s
  • forge-dev (179MB) installed at ~/.local/bin/forge-dev
  • forge-dev --version returns forge 0.1.0-dev

CodeAnt-AI Description

Add conversation sorting and matched search previews

What Changed

  • Added a :sort command so users can switch the conversation list between updated, created, turns, title, and cwd order, or reset back to the default sort
  • Search results now show a short highlighted passage for the selected conversation, making it clear why the hit matched
  • Updated test fixtures to include the new conversation fields needed by the selector and info views

Impact

✅ Faster finding of the right conversation
✅ Clearer search results
✅ Easier project-based session browsing

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

…ort canonical, get_conversation_snippet

- domain: ConversationSort enum (Updated/UpdatedAt/Created/CreatedAt/Turns/MessageCount/Cwd) as canonical for sort UI
- domain: ConversationSortKey() helper that maps ConversationSort to (Column, Direction)
- domain: Conversation.cwd, Conversation.message_count fields
- repo: get_conversations_by_cwd, update_parent_id, count_subagents, get_conversation_snippet trait methods
- repo: ConversationRepo impl for all v4 methods; consolidates sort to use ConversationSort
- repo: ForgeRepo impl for the v4 methods
- services: ForgeConversationService impl
- services: AgentService impl
- api: API trait + ForgeAPI impl
- main: AppCommand::Reparent { subagent_id, new_parent_id }
- main: AppCommand::SetCwd { cwd }
- main: AppCommand::SetSort { target: ConversationSort }
- main: UIState.sort_key, cwd_filter fields
- main: handlers in ui.rs for the 3 new commands

Build: cargo check --bin forge clean (2 pre-existing dead-code warnings unrelated)
Binary: 179MB at ~/.local/bin/forge-dev (forge 0.1.0-dev)
@gemini-code-assist

Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@codeant-ai

codeant-ai Bot commented Jun 21, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@KooshaPari, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 35 minutes and 43 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ad678b0b-1aea-4a9f-83c5-2fa98855de17

📥 Commits

Reviewing files that changed from the base of the PR and between eb1e886 and 9bde25d.

📒 Files selected for processing (13)
  • crates/forge_api/src/api.rs
  • crates/forge_api/src/forge_api.rs
  • crates/forge_app/src/services.rs
  • crates/forge_domain/src/conversation.rs
  • crates/forge_domain/src/repo.rs
  • crates/forge_main/src/conversation_selector.rs
  • crates/forge_main/src/info.rs
  • crates/forge_main/src/model.rs
  • crates/forge_main/src/state.rs
  • crates/forge_main/src/ui.rs
  • crates/forge_repo/src/conversation/conversation_repo.rs
  • crates/forge_repo/src/forge_repo.rs
  • crates/forge_services/src/conversation.rs
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/session-viewer-perf-v3
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feat/session-viewer-perf-v3

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@KooshaPari KooshaPari merged commit 345fb70 into main Jun 21, 2026
12 of 24 checks passed
@KooshaPari KooshaPari deleted the feat/session-viewer-perf-v3 branch June 21, 2026 08:44
@codeant-ai codeant-ai Bot added the size:L label Jun 21, 2026
Comment on lines +143 to +151
/// Return an FTS5 snippet for a (conversation, query) pair — a short
/// highlighted excerpt of the matched passage. Used by the search UI
/// to render a preview pane when the user picks a search hit.
async fn get_conversation_snippet(
&self,
conversation_id: &ConversationId,
query: &str,
token_count: usize,
) -> Result<Option<String>>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Expand the doc comment for get_conversation_snippet to include # Arguments for all parameters and a # Errors section describing when the Result can fail. [custom_rule]

Severity Level: Minor ⚠️

Why it matters? 🤔

This is a newly added public trait method returning Result and taking parameters, but its doc comment only contains a summary. It lacks the required # Arguments section for the parameters and a # Errors section describing failure cases, so it violates the Rustdoc rule.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** crates/forge_api/src/api.rs
**Line:** 143:151
**Comment:**
	*Custom Rule: Expand the doc comment for `get_conversation_snippet` to include `# Arguments` for all parameters and a `# Errors` section describing when the `Result` can fail.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +120 to +122
/// Parse a sort key from a user-supplied string. Unknown keys fall
/// back to `Updated` and the caller is expected to print a hint.
pub fn parse(s: &str) -> Self {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Expand the Rust docs for this public method to include a # Arguments section describing the input parameter. [custom_rule]

Severity Level: Minor ⚠️

Why it matters? 🤔

This is a newly added public method with a parameter, and its docs do not include a # Arguments section. That matches the documentation rule violation exactly, so the suggestion is verified.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** crates/forge_domain/src/conversation.rs
**Line:** 120:122
**Comment:**
	*Custom Rule: Expand the Rust docs for this public method to include a `# Arguments` section describing the input parameter.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +320 to +328
/// Return an FTS5 snippet for a (conversation, query) pair — a short
/// highlighted excerpt of the matched passage. Used by the search UI
/// to render a preview pane when the user picks a search hit.
async fn get_conversation_snippet(
&self,
conversation_id: &ConversationId,
query: &str,
token_count: usize,
) -> anyhow::Result<Option<String>>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Expand the Rust doc comment for this newly added public trait method to include explicit # Arguments and # Errors sections describing each parameter and when the method returns an error. [custom_rule]

Severity Level: Minor ⚠️

Why it matters? 🤔

This is a newly added public trait method, and its Rustdoc only has a short description.
The rule requires # Arguments and # Errors sections for public items with parameters and errors.
Since this method takes three parameters and returns anyhow::Result, the missing sections are a real violation.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** crates/forge_app/src/services.rs
**Line:** 320:328
**Comment:**
	*Custom Rule: Expand the Rust doc comment for this newly added public trait method to include explicit `# Arguments` and `# Errors` sections describing each parameter and when the method returns an error.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +173 to +193
/// Returns a short FTS5 snippet (~32 tokens) for a single
/// `(conversation_id, query)` pair, with the matched terms wrapped in
/// `[…]` and the surrounding text wrapped in `…`. Used by the UI to
/// render a "matched passage" preview for the currently selected
/// search hit without forcing the main search query to include the
/// snippet column (which would couple the row layout to
/// `ConversationRecord`).
///
/// Returns `Ok(None)` when the query does not match that conversation
/// — callers should treat `None` as "no preview available" and fall
/// back to the conversation title.
///
/// # Errors
/// Returns an error if the FTS query is malformed or the database
/// call fails.
async fn get_conversation_snippet(
&self,
conversation_id: &ConversationId,
query: &str,
token_count: usize,
) -> Result<Option<String>>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Add a # Arguments section to the Rust doc comment for this method and document conversation_id, query, and token_count so the public API docs are complete for a parameterized function. [custom_rule]

Severity Level: Minor ⚠️

Why it matters? 🤔

The method is public and newly added, and it has parameters but no # Arguments
section in its rustdoc. The repository rule requires public functions/methods
with parameters to document arguments, so this is a real violation.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** crates/forge_domain/src/repo.rs
**Line:** 173:193
**Comment:**
	*Custom Rule: Add a `# Arguments` section to the Rust doc comment for this method and document `conversation_id`, `query`, and `token_count` so the public API docs are complete for a parameterized function.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9bde25d979

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

}
};

self.state.sort = new_sort;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Apply the selected sort before opening the selector

This assignment makes :sort report success, but the value is never consumed: list_conversations still calls get_parent_conversations(Some(max_conversations)) and passes the returned updated-at-ordered list directly to ConversationSelector. I checked the changed code with rg "state\.sort|ConversationSort", and the new state is only set/reset, so :sort turns, :sort title, and :sort cwd have no effect on the next :conversations selector despite the command saying the selector was sorted.

Useful? React with 👍 / 👎.

Comment on lines +715 to +718
/// Sort the conversation selector. Usage: `:sort <key>` where key is
/// one of `updated`, `created`, `turns`, `title`. Persists in
/// `UIState.sort` until the session exits or another `:sort` is run.
#[strum(props(usage = "Sort the conversation selector. Usage: :sort <key> (updated|created|turns|title)"))]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The :sort command metadata advertises only updated|created|turns|title, but the runtime handler also supports cwd and --reset. Because command help/porcelain output is generated from this metadata, users and tooling get an incorrect contract and may miss valid behavior. Update the usage text to include all accepted keys and reset syntax so help output matches actual command handling. [api mismatch]

Severity Level: Major ⚠️
- ⚠️ Command listing misdocuments :sort supported keys and reset flag.
- ⚠️ REPL help output hides cwd sort and reset usage.
- ⚠️ External tooling reading porcelain sees incomplete sort contract.
Steps of Reproduction ✅
1. From the CLI, invoke the commands listing, e.g. `forge-dev list commands`, which maps
to `TopLevelCommand::List(list_group)` and then `ListCommand::Command { custom }` in
`UI::handle_subcommands` at `crates/forge_main/src/ui.rs:495-121` (shown in the
`handle_subcommands` match where it calls `self.on_show_commands(porcelain).await?`).

2. In `on_show_commands` at `crates/forge_main/src/ui.rs:1583-1580` (snippet header
"Showing lines 1515 to 1594", local lines 69–79), the porcelain branch calls
`self.commands_porcelain().await?`, which builds the command table shown by `forge list
commands` and the REPL `:help`.

3. `commands_porcelain` at `crates/forge_main/src/ui.rs:1527-1535` iterates
`AppCommand::iter()` and, for each command, adds a row with `.add_key_value("description",
cmd.usage())`, so the human‑visible description for `:sort` comes directly from
`AppCommand::usage()`.

4. `AppCommand::usage()` in `crates/forge_main/src/model.rs:859-861` returns
`self.get_str("usage").unwrap()`, pulling the `usage` value from the `#[strum(props(usage
= ...))]` metadata on each variant; for `AppCommand::Sort` this metadata at
`crates/forge_main/src/model.rs:715-718` advertises only `(updated|created|turns|title)`
and does not mention `cwd` or `--reset`, so the commands listing and REPL help omit these
options even though the actual handler supports them.

5. In the REPL, execute `:sort --help` or `:sort -h`, which calls `UI::handle_sort` via
the `on_command` match at `crates/forge_main/src/ui.rs:110-112`; `handle_sort` is defined
at `crates/forge_main/src/ui.rs:2441-2473` and prints `"Usage: :sort
<turns|updated|created|title|cwd> | :sort --reset"`, clearly documenting `cwd` and
`--reset`.

6. Also in `handle_sort`, lines `2449-2453` check for `--reset` and reset
`self.state.sort` to `ConversationSort::default()`, and lines `2460-2463` accept `"cwd" |
"dir" | "directory"` mapping to `ConversationSort::Cwd`, proving that both `cwd` and
`--reset` are valid runtime options even though they are not present in the `usage`
metadata used for global help/porcelain.

7. As a result, every time a user or external tool relies on `forge list commands` or REPL
help/porcelain to discover the `:sort` contract, they see an incomplete description (only
`updated|created|turns|title`), and cannot infer that `cwd` sorting and `:sort --reset`
are supported, demonstrating the user‑visible contract mismatch the suggestion describes.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** crates/forge_main/src/model.rs
**Line:** 715:718
**Comment:**
	*Api Mismatch: The `:sort` command metadata advertises only `updated|created|turns|title`, but the runtime handler also supports `cwd` and `--reset`. Because command help/porcelain output is generated from this metadata, users and tooling get an incorrect contract and may miss valid behavior. Update the usage text to include all accepted keys and reset syntax so help output matches actual command handling.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +344 to +362
self.run_with_connection(move |connection, _wid| {
// We pass the conversation_id as a filter so the snippet
// function only highlights within that document. The token
// count is interpolated directly into the SQL because
// SQLite's `snippet()` 6th arg is a literal integer, not a
// bind parameter. FTS5 sanitises the MATCH expression; the
// integer is bounded by the caller (UI limits to 256).
let sql = format!(
"SELECT snippet(conversations_fts, 2, '[', ']', '…', {}) AS s \
FROM conversations_fts \
WHERE conversation_id = ? AND conversations_fts MATCH ?",
token_count.min(256)
);
let raw: Vec<SnippetRow> = diesel::sql_query(sql)
.bind::<diesel::sql_types::Text, _>(&conversation_id_str)
.bind::<diesel::sql_types::Text, _>(&query)
.load(connection)?;
Ok(raw.into_iter().next().map(|r| r.s))
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The snippet lookup is not scoped to the current workspace, so a caller that knows a valid conversation ID can retrieve highlighted content from another workspace. This breaks tenant isolation. Include workspace_id filtering by joining conversations_fts back to conversations (or equivalent) and bind wid in this query, the same way other workspace-scoped read paths do. [security]

Severity Level: Critical 🚨
- ❌ API::get_conversation_snippet can read other workspace conversations.
- ❌ Workspace tenant boundary broken for conversation snippet content.
- ⚠️ Search snippet previews potentially leak cross-workspace message context.
Steps of Reproduction ✅
1. Observe that the conversation repository is workspace-scoped:
`ConversationRepositoryImpl` stores a `WorkspaceHash` (`wid`) and passes it into every DB
closure via `run_with_connection` at
`crates/forge_repo/src/conversation/conversation_repo.rs:25-55`, and most queries filter
`conversations::workspace_id` using `wid.id()` (e.g., `search_conversations` at lines
82-107 and `delete_conversation` at 178-187).

2. Note how the FTS5 table is defined in the migration
`crates/forge_repo/src/database/migrations/2026-06-14-000002_add_fts5_to_conversations/up.sql:3-8`
as `conversations_fts(conversation_id UNINDEXED, title, content, ...)` with **no
workspace_id column**, and is populated purely from `conversations.conversation_id` via
triggers at lines 14-19 and 26-32.

3. Inspect the new `get_conversation_snippet` implementation at
`crates/forge_repo/src/conversation/conversation_repo.rs:84-115`: inside the closure
`self.run_with_connection(move |connection, _wid| { ... })`, the `wid` argument is
deliberately ignored (`_wid`), and the raw SQL built at lines ~102-107 is `SELECT
snippet(conversations_fts, 2, '[', ']', '…', {token_count}) AS s FROM conversations_fts
WHERE conversation_id = ? AND conversations_fts MATCH ?`, i.e., it filters only by
`conversation_id` and FTS `MATCH`, not by `workspace_id` or a join back to
`conversations`.

4. In a multi-workspace setup backed by a single SQLite database, instantiate two
`ConversationRepositoryImpl` instances sharing the same `DatabasePool` but with different
`WorkspaceHash` values (as done in `ForgeRepo::new` wiring, with the repo constructed once
per workspace); use repository B (wid_B) to create a conversation via
`upsert_conversation` at `conversation_repo.rs:89-110`, capturing its `ConversationId`,
then call `get_conversation_snippet` on repository A (wid_A) with that `ConversationId`
and a matching query: because the snippet query at `conversation_repo.rs:102-111` only
checks `conversation_id` against the global `conversations_fts` table and ignores `wid`,
it returns a highlighted snippet of workspace B's conversation content to a caller scoped
to workspace A, unlike `search_conversations` which joins `conversations` and enforces
`c.workspace_id = workspace_id`. This demonstrates a cross-workspace data leak for the
snippet endpoint.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** crates/forge_repo/src/conversation/conversation_repo.rs
**Line:** 344:362
**Comment:**
	*Security: The snippet lookup is not scoped to the current workspace, so a caller that knows a valid conversation ID can retrieve highlighted content from another workspace. This breaks tenant isolation. Include `workspace_id` filtering by joining `conversations_fts` back to `conversations` (or equivalent) and bind `wid` in this query, the same way other workspace-scoped read paths do.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

KooshaPari added a commit that referenced this pull request Jun 30, 2026
…ort canonical, get_conversation_snippet (#24)

- domain: ConversationSort enum (Updated/UpdatedAt/Created/CreatedAt/Turns/MessageCount/Cwd) as canonical for sort UI
- domain: ConversationSortKey() helper that maps ConversationSort to (Column, Direction)
- domain: Conversation.cwd, Conversation.message_count fields
- repo: get_conversations_by_cwd, update_parent_id, count_subagents, get_conversation_snippet trait methods
- repo: ConversationRepo impl for all v4 methods; consolidates sort to use ConversationSort
- repo: ForgeRepo impl for the v4 methods
- services: ForgeConversationService impl
- services: AgentService impl
- api: API trait + ForgeAPI impl
- main: AppCommand::Reparent { subagent_id, new_parent_id }
- main: AppCommand::SetCwd { cwd }
- main: AppCommand::SetSort { target: ConversationSort }
- main: UIState.sort_key, cwd_filter fields
- main: handlers in ui.rs for the 3 new commands

Build: cargo check --bin forge clean (2 pre-existing dead-code warnings unrelated)
Binary: 179MB at ~/.local/bin/forge-dev (forge 0.1.0-dev)

Co-authored-by: Phenotype Agent <agent@phenotype.ai>
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