Skip to content

feat(obsidian): finish configurable vault export workflow#348

Merged
jerry609 merged 1 commit into
devfrom
feat/obsidian-issue-169-close
Mar 11, 2026
Merged

feat(obsidian): finish configurable vault export workflow#348
jerry609 merged 1 commit into
devfrom
feat/obsidian-issue-169-close

Conversation

@jerry609

@jerry609 jerry609 commented Mar 11, 2026

Copy link
Copy Markdown
Owner

Summary

  • add first-class Obsidian settings and env overrides for vault/root/template/autosync behavior
  • support configurable paper templates plus related paper metadata/wiki-links in the filesystem exporter
  • wire automatic Obsidian snapshot export into research track create/update and save/unsave flows
  • add regression coverage for exporter, CLI defaults, settings loading, sync helpers, and route scheduling

Validation

  • PYTHONPATH=src pytest tests/unit/test_obsidian_exporter.py tests/unit/test_obsidian_cli.py tests/unit/test_obsidian_sync.py tests/unit/test_settings.py tests/unit/test_research_feedback_state.py tests/integration/test_research_track_routes.py -q
  • git diff --check

Closes #169

Copilot AI review requested due to automatic review settings March 11, 2026 13:37
@vercel

vercel Bot commented Mar 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
paper-bot Ready Ready Preview, Comment Mar 11, 2026 1:37pm

@coderabbitai

coderabbitai Bot commented Mar 11, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5f228e85-e68a-456c-ac36-d8eae3db8039

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/obsidian-issue-169-close

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.

@jerry609
jerry609 merged commit dafe03f into dev Mar 11, 2026
4 checks passed
@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the Obsidian integration by making it fully configurable and automating key export workflows. Users can now define their Obsidian vault settings, customize paper note templates, and have their research tracks and saved papers automatically exported and synchronized, streamlining knowledge management within Obsidian.

Highlights

  • Configurable Obsidian Settings: Introduced comprehensive Obsidian integration settings, allowing configuration of vault path, root directory, paper templates, and auto-sync behaviors via config.yaml and environment variables.
  • Custom Paper Templates and Wiki-Links: Implemented support for custom Jinja2 paper templates, enabling flexible note generation with dynamic content like paper metadata, track links, and wiki-links for related papers.
  • Automated Obsidian Exports: Automated Obsidian snapshot exports for research tracks, triggering on track creation/update and paper save/unsave actions.
  • Enhanced CLI Command: Expanded CLI obsidian export command to leverage new configuration settings and template options.
  • Comprehensive Test Coverage: Added extensive unit and integration tests to ensure robustness of Obsidian export, CLI, settings loading, and sync functionalities.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • config/config.yaml
    • Added a new obsidian section with enabled, vault_path, root_dir, paper_template_path, auto_export_on_save, auto_sync_tracks, and export_limit options.
  • env.example
    • Included new PAPERBOT_OBSIDIAN_ prefixed environment variables for Obsidian configuration.
  • src/paperbot/application/ports/vault_exporter_port.py
    • Updated the export_library_snapshot method signature to include an optional paper_template_path parameter.
  • src/paperbot/infrastructure/exporters/init.py
    • Exposed export_track_snapshot, get_obsidian_config, and obsidian_auto_export_enabled from the new obsidian_sync module.
  • src/paperbot/infrastructure/exporters/obsidian_exporter.py
    • Defined a DEFAULT_PAPER_TEMPLATE string for Jinja2 rendering.
    • Modified ObsidianFilesystemExporter to accept an optional paper_template_path during initialization.
    • Refactored the _write_paper_note method to use a Jinja2 template for note generation, supporting custom templates and dynamic content.
    • Added new helper methods _resolve_paper_template_path, _render_paper_note, _paper_related_links, _paper_related_titles, and _paper_related_entries to manage template rendering and related paper wiki-links.
  • src/paperbot/infrastructure/exporters/obsidian_sync.py
    • Added a new module containing functions to retrieve Obsidian configuration, check auto-export status, and perform track snapshot exports based on configured settings.
  • src/paperbot/presentation/cli/main.py
    • Imported create_settings to access application configurations.
    • Updated the obsidian export CLI command to allow vault, root-dir, and paper-template arguments to default to values from the Obsidian settings.
    • Modified _run_obsidian_export to retrieve Obsidian settings and pass them to the exporter.
  • tests/integration/test_research_track_routes.py
    • Added two new integration tests to verify that _schedule_obsidian_export is called when a research track is created or updated.
  • tests/unit/test_obsidian_cli.py
    • Added a new unit test to confirm that the obsidian export CLI command correctly utilizes default values from the Obsidian settings.
  • tests/unit/test_obsidian_exporter.py
    • Added a new test case to validate that the exporter correctly uses a custom Jinja2 template and generates related paper wiki-links.
  • tests/unit/test_obsidian_sync.py
    • Added a new file with unit tests for the obsidian_sync module, covering configuration loading and auto-export conditions.
  • tests/unit/test_research_feedback_state.py
    • Added a new test to ensure that the Obsidian export is scheduled when a paper is saved or unsaved via the feedback route.
  • tests/unit/test_settings.py
    • Added new unit tests to confirm that Obsidian-specific settings can be loaded correctly from dictionaries and overridden by environment variables.
Activity
  • The author, jerry609, has implemented a new feature for configurable Obsidian vault export.
  • Regression coverage has been added for the exporter, CLI defaults, settings loading, sync helpers, and route scheduling.
  • The pull request addresses and closes issue [P0] Obsidian Vault 适配层 — 论文笔记自动生成 #169.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copilot AI 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.

Pull request overview

Adds a configurable Obsidian vault export workflow to PaperBot, including settings/env overrides, templated paper-note generation, and automatic snapshot exports triggered by research track and save/unsave operations.

Changes:

  • Introduces first-class obsidian settings (YAML + env var overrides) and documents them in env.example.
  • Extends the Obsidian filesystem exporter to support custom Jinja2 paper templates and related-paper wiki-links/frontmatter.
  • Wires background-task Obsidian exports into track create/update and paper save/unsave routes, with new unit/integration coverage.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/unit/test_settings.py Adds coverage for loading obsidian config from dict + env overrides.
tests/unit/test_research_feedback_state.py Verifies save/unsave feedback schedules the Obsidian export hook.
tests/unit/test_obsidian_sync.py Adds unit tests for settings-driven auto-export gating + snapshot export wiring.
tests/unit/test_obsidian_exporter.py Adds coverage for custom template rendering + related paper links/frontmatter.
tests/unit/test_obsidian_cli.py Ensures CLI obsidian export can fall back to settings defaults.
tests/integration/test_research_track_routes.py Validates track create/update scheduling of Obsidian exports.
src/paperbot/presentation/cli/main.py Makes --vault/--root-dir/--paper-template optional with settings defaults and validates vault presence.
src/paperbot/infrastructure/exporters/obsidian_sync.py New helper module for auto-export enablement checks and snapshot export function.
src/paperbot/infrastructure/exporters/obsidian_exporter.py Adds template-based note rendering and related-paper link/title extraction.
src/paperbot/infrastructure/exporters/init.py Re-exports obsidian sync helpers from the exporters package.
src/paperbot/application/ports/vault_exporter_port.py Extends exporter port to accept an optional paper_template_path.
src/paperbot/api/routes/research.py Adds background-task scheduling for Obsidian exports on track create/update and save/unsave feedback.
env.example Documents Obsidian-related environment variables.
config/settings.py Introduces ObsidianConfig and env var override handling.
config/config.yaml Adds an obsidian: section with defaults.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +13 to +24
def get_obsidian_config() -> ObsidianConfig:
return create_settings().obsidian


def obsidian_auto_export_enabled(*, for_tracks: bool = False) -> bool:
config = get_obsidian_config()
if not config.enabled:
return False
if not str(config.vault_path or "").strip():
return False
return config.auto_sync_tracks if for_tracks else config.auto_export_on_save

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

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

get_obsidian_config() calls create_settings() on every invocation, which re-reads config.yaml and env vars. This function is used on request paths (via obsidian_auto_export_enabled), so it can introduce unnecessary file I/O and config parsing overhead. Consider caching the loaded settings/config (e.g., module-level cached settings/config with explicit refresh if needed) and reusing it here.

Copilot uses AI. Check for mistakes.
Comment on lines +76 to +81
except Exception as exc:
Logger.warning(
f"Obsidian export failed for track {track_id}: {exc}",
file=LogFiles.HARVEST,
)
return None

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

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

This except Exception as exc: is intentionally swallowing errors and degrading gracefully, but the repo appears to use # noqa: BLE001 for these cases. Add the # noqa: BLE001 annotation (and ideally a short reason) to keep linting consistent with the rest of the codebase.

Copilot uses AI. Check for mistakes.
Comment on lines +378 to +387
if template_path:
resolved = template_path.expanduser().resolve()
environment = Environment(
loader=FileSystemLoader(str(resolved.parent)),
autoescape=False,
keep_trailing_newline=True,
trim_blocks=False,
lstrip_blocks=False,
)
template = environment.get_template(resolved.name)

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

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

_render_paper_note() creates a new Jinja Environment and (re)loads/compiles the template for every paper note. For snapshot exports with many saved papers this adds avoidable overhead. Consider constructing/caching the Environment/compiled template once per export_library_snapshot() call (or once per exporter instance) and reusing it for each note render.

Copilot uses AI. Check for mistakes.
Comment on lines +378 to +399
if template_path:
resolved = template_path.expanduser().resolve()
environment = Environment(
loader=FileSystemLoader(str(resolved.parent)),
autoescape=False,
keep_trailing_newline=True,
trim_blocks=False,
lstrip_blocks=False,
)
template = environment.get_template(resolved.name)
return template.render(
frontmatter=frontmatter,
title=title,
abstract=abstract,
metadata_rows=metadata_rows,
track_link=track_link,
external_links=external_links,
related_links=related_links,
paper=paper,
track=track,
related_titles=related_titles,
)

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

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

When template_path is provided, a missing/unreadable template currently bubbles up as a Jinja TemplateNotFound (or similar) without a clear, user-oriented message. Since CLI/API callers treat some errors as ValueError, consider validating that the template file exists/is a file and raising a clearer exception describing the bad path.

Copilot uses AI. Check for mistakes.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a feature for exporting data to Obsidian, including configuration, background tasks, and CLI integration. However, it's critical to address a significant security vulnerability: the lack of authentication and authorization for user-specific actions, which leads to Insecure Direct Object Reference (IDOR) vulnerabilities due to unverified user_id values. It is strongly recommended to implement an authentication layer before deploying this API to a multi-user or public environment. Additionally, there are suggestions to improve maintainability, error handling, and security best practices, such as making the ObsidianFilesystemExporter stateless, improving configuration error handling, and enabling auto-escaping in Jinja2 templates.

)
_schedule_obsidian_export(
background_tasks,
user_id=req.user_id,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security-high high

The research API endpoints for creating tracks, updating tracks, and adding paper feedback use a user-supplied user_id (either from query parameters or the request body) without any authentication or authorization checks. This allows an attacker to access, modify, or trigger actions (like Obsidian exports) for any other user's data by simply providing their user_id.

This pull request adds automatic Obsidian snapshot exports to these endpoints, which also use the unverified user_id, extending the impact of this vulnerability.

Remediation:

  1. Implement a robust authentication system (e.g., JWT, OAuth2).
  2. Replace the user-supplied user_id in API requests with the identity of the currently authenticated user.
  3. Verify that the authenticated user has the necessary permissions to access or modify the requested resource.

_schedule_embedding_precompute(background_tasks, user_id=user_id, track_ids=[track_id])
_schedule_obsidian_export(
background_tasks,
user_id=user_id,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security-high high

The user_id parameter is taken directly from the query string and used to schedule an Obsidian export without verifying if the requester is authorized to act on behalf of that user. This is a classic IDOR vulnerability.

if normalized_action in {"save", "unsave"}:
_schedule_obsidian_export(
background_tasks,
user_id=req.user_id,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security-high high

The req.user_id from the request body is used to trigger an Obsidian export without any authentication check. An attacker can provide any user_id to trigger exports for other users.

Comment thread config/settings.py
Comment on lines +388 to +389
except ValueError:
pass

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Silently ignoring a ValueError here can hide configuration issues from the user. If PAPERBOT_OBSIDIAN_EXPORT_LIMIT is set to a non-integer value, it will be ignored, and the default value will be used without any warning. It would be better to log a warning to inform the user about the invalid configuration value.

Comment on lines +88 to +89
def __init__(self, *, paper_template_path: Optional[Path] = None):
self._paper_template_path = Path(paper_template_path).expanduser() if paper_template_path else None

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The introduction of state (self._paper_template_path) in ObsidianFilesystemExporter via the __init__ method makes the class stateful. However, the VaultExporterPort protocol and the export_library_snapshot method signature already support passing paper_template_path as an argument. To improve clarity and make the exporter stateless (which is generally preferable for this kind of utility), consider removing the __init__ method and relying solely on the paper_template_path argument of export_library_snapshot. This would simplify the class and align it better with its usage. The callers in obsidian_sync.py and cli/main.py would need to be updated accordingly.

resolved = template_path.expanduser().resolve()
environment = Environment(
loader=FileSystemLoader(str(resolved.parent)),
autoescape=False,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

In _render_paper_note, jinja2.Environment is initialized with autoescape=False (here and on line 401). This is generally discouraged as it can lead to cross-site scripting (XSS) vulnerabilities if any of the template variables contain untrusted user input. While the risk might be low for an Obsidian export, it's a best practice to enable auto-escaping by default (autoescape=True) and explicitly mark safe content with |safe in the template if needed. This provides a safer default.

Comment on lines +59 to +70
if config.paper_template_path:
exporter = ObsidianFilesystemExporter(
paper_template_path=Path(config.paper_template_path).expanduser()
)
else:
exporter = ObsidianFilesystemExporter()
result = exporter.export_library_snapshot(
vault_path=vault_path,
saved_items=saved_items,
track=track,
root_dir=config.root_dir,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

To make ObsidianFilesystemExporter stateless and align with the protocol, it's better to pass paper_template_path to the export_library_snapshot method instead of the constructor. This change would be part of the refactoring suggested in my comment on obsidian_exporter.py.

        exporter = ObsidianFilesystemExporter()
        template_path = (
            Path(config.paper_template_path).expanduser() if config.paper_template_path else None
        )
        result = exporter.export_library_snapshot(
            vault_path=vault_path,
            saved_items=saved_items,
            track=track,
            root_dir=config.root_dir,
            paper_template_path=template_path,
        )

Comment on lines +630 to 641
if paper_template_path:
exporter = ObsidianFilesystemExporter(
paper_template_path=Path(paper_template_path).expanduser()
)
else:
exporter = ObsidianFilesystemExporter()
result = exporter.export_library_snapshot(
vault_path=Path(parsed.vault),
vault_path=Path(vault_value),
saved_items=saved_items,
track=track,
root_dir=parsed.root_dir,
root_dir=root_dir,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Similar to my other comment, to make ObsidianFilesystemExporter stateless, it's better to pass paper_template_path to the export_library_snapshot method instead of the constructor. This change would be part of the refactoring suggested in my comment on obsidian_exporter.py.

        exporter = ObsidianFilesystemExporter()
        template_path = (
            Path(paper_template_path).expanduser() if paper_template_path else None
        )
        result = exporter.export_library_snapshot(
            vault_path=Path(vault_value),
            saved_items=saved_items,
            track=track,
            root_dir=root_dir,
            paper_template_path=template_path,
        )

@jerry609
jerry609 deleted the feat/obsidian-issue-169-close branch March 12, 2026 03:08
jerry609 added a commit that referenced this pull request Mar 13, 2026
* fix: validate studio output dirs and add p2c module design docs

:wq

* feat(PaperToContext): M3
#140
Closes #140

* feat(p2c): implement ContextEngineBridge to inject user context into extraction Related to #157

* activate paper-scope memory read/write path. Raletd to #158

* fix(p2c): address Gemini code review issues from PR. Related to #157

* fix(p2c): address Gemini code review issues. Related to #158

* fix(p2c): sanitize XML tag content to prevent tag-escape prompt injection

* feat(memory): add FTS5 full-text search and sqlite-vec hybrid search. Related to #161

* feat(p2c): persist CodeMemory experiences to SQLite. Related to #162

* address Gemini code review issues from PR #224 and #225

* fix(memory): address Gemini code review issues from #153 epic audit

P0 fixes:
- Replace unbounded daemon threads with ThreadPoolExecutor(max_workers=2)
  for embedding writes; atexit + close() ensure graceful shutdown so
  in-flight embeddings are not lost on process exit
- Restore apprise>=1.9.0 and feedgen>=1.0.0 removed in epic branch,
  which would have broken Epic #179 push/RSS features on merge

P1 fixes:
- _escape_fts: replace double-quote-only escaping with a whitelist regex
  [A-Za-z0-9_+-] so FTS5 operators (*, NEAR, NOT, ^) cannot alter query
  semantics; empty queries now short-circuit to []
- _hybrid_merge: skip items with None/invalid id instead of defaulting
  to id=0, preventing silent score collisions across unrelated records
- ReproExperienceStore: add application-level dedup check + UNIQUE
  constraint (paper_id, pattern_type, content) + IntegrityError fallback
  to prevent duplicate experiences from accumulating across retries

Migration:
- 0022_repro_experience_dedup: adds uq_repro_exp_paper_type_content
  unique constraint to existing repro_code_experience table

Tests: 47 unit tests pass (+3 new tests covering the fixes above)

* fix: harden repro experience isolation and wire persistence into repro pipeline

Add user-scoped isolation for repro_code_experience and enforce dedup semantics with migration 0022_repro_experience_dedup.

Inject ReproExperienceStore through ReproAgent/Orchestrator/CodingAgent/GenerationNode and propagate user_id/pack_id in generation, verification, and debugging persistence paths.

Update /api/gen-code to accept user_id and extend unit tests for user isolation and persistence behavior.

* feat(memory): introduce memory decay mechanism (#163)

Add decay-aware scoring that combines relevance (confidence), recency
(exponential decay with 90-day half-life), and usage frequency to
re-rank search results. New memories default to expires_at = created_at
+ 365 days. search_memories() now auto-touches usage on hits.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(memory): add cross-track batch search (#164)

Add search_memories_batch() that queries multiple scope_ids in a single
SQL call, eliminating the N+1 loop in build_context_pack(). Engine now
uses this batch method for cross-track memory retrieval.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(context): implement layered context loading (#165)

Refactor build_context_pack() into 4 layer methods:
- Layer 0: user profile (cached with 5-min TTL, ~200 tokens)
- Layer 1: track context — tasks/milestones (~500 tokens)
- Layer 2: query-relevant + cross-track memories (~1000 tokens)
- Layer 3: paper-scoped memories (on-demand)

Return value adds context_layers metadata while remaining fully
backward-compatible.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(memory): align decay with OpenClaw patterns

- Use standard ln(2)/halfLifeDays lambda formula (matches OpenClaw
  temporal-decay.ts:toDecayLambda)
- Default half-life lowered to 30 days (was 90, now matches OpenClaw)
- Evergreen memories (global scope, preference kind) are immune to
  recency decay (inspired by OpenClaw isEvergreenMemoryPath)
- Add _to_decay_lambda() and _is_evergreen_memory() helpers
- Expand tests for lambda math, half-life precision, and evergreen logic

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(context): isolate layer0 cache and touch batch hits (#236)

* feat(memory): upgrade batch retrieval with hybrid and MMR options (#237)

* feat(context): add embedding fallback chain and token guard config (#238)

* fix(memory): bound batch hybrid candidates by scope_ids

* docs: update README with macOS python3 tips

* docs: update README with more badges

* docs: update README with deleting extra badges

* feat(search): add offline retrieval benchmark harness

Add a deterministic retrieval benchmark fixture, scorer, CLI, docs, and CI smoke gate for PaperSearchService.\n\nCloses #284\nRefs #283

* feat(context): add offline context-engine benchmark

Add deterministic fixtures, scoring, CLI, and smoke coverage for layered assembly, token guard, and advisory routing in ContextEngine.\n\nCloses #286\nRefs #283

* feat(memory): add scope isolation acceptance bench

Add an offline scope-isolation benchmark for memory retrieval paths, extend the metric collector with cross-user and cross-scope leak rates, and wire the new check into CI.\n\nCloses #285\nRefs #283

* feat(memory): add offline injection robustness detector

Add a deterministic prompt-injection pattern detector, labeled offline fixtures, an acceptance benchmark, and CI coverage for Injection Robustness L1.\n\nCloses #287\nRefs #283

* feat(memory): add offline performance benchmark harness

Add a deterministic synthetic benchmark for memory search latency baselines across 10k/100k/1M scales, plus docs and smokeable unit coverage.\n\nCloses #288\nRefs #283

* feat: add ROI benchmark for repro memory

Closes #289

Refs #283

* docs: add MemoryBench epic completion report

Refs #283

* docs: add runtime memory benchmark report

Refs #283

* Docs: update README with our new name (#290)

* docs: update README with macOS python3 tips

* docs: update README with more badges

* docs: update README with deleting extra badges

* docs: update README with new god name

---------

Co-authored-by: 林杰 <linjie@linjiedeMacBook-Air.local>
Co-authored-by: 林杰 <linjie@v8d1ef64c.dip.tu-dresden.de>
Co-authored-by: 林杰 <linjie@v8d1ef43e.dip.tu-dresden.de>
Co-authored-by: 林杰 <linjie@v8d1ef6c5.dip.tu-dresden.de>

* Fix: show friendly error when backend is unreachable (#291)

* docs: update README with macOS python3 tips

* docs: update README with more badges

* docs: update README with deleting extra badges

* docs: update README with new god name

---------

Co-authored-by: 林杰 <linjie@linjiedeMacBook-Air.local>
Co-authored-by: 林杰 <linjie@v8d1ef64c.dip.tu-dresden.de>
Co-authored-by: 林杰 <linjie@v8d1ef43e.dip.tu-dresden.de>
Co-authored-by: 林杰 <linjie@v8d1ef6c5.dip.tu-dresden.de>

* feat: improve memory ROI and effectiveness benchmarks

* fix: avoid importing missing data template in main

* fix: stabilize live memory roi benchmark

* feat: expand multi-session memory effectiveness benchmark

* feat: implement MemoryBench evaluation suite with 4 bench suites

Add comprehensive memory module evaluation aligned with LongMemEval
(ICLR 2025), LoCoMo (ACL 2024), Mem0, and Letta benchmarks.

- Retrieval Bench v2: IR metrics (Recall@5=0.873, MRR@10=0.731, nDCG@10=0.747)
  with 40 annotated queries across 5 question types and 5 memory dimensions
- Scope Isolation + CRUD: zero-leak verification across user x scope matrix,
  Mem0-aligned CRUD lifecycle (add/update/delete/dedup)
- Context Extraction: L0-L3 layer completeness, precision, token budget guard,
  TrackRouter accuracy (100%), graceful degradation
- Injection Robustness L1: offline pattern detection (0% pollution, 0% FP)
- Fixture dataset: 45 memories (2 users), 12 injection patterns
- Testing documentation with methodology, validity analysis, and results

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: add MemoryBench quantitative results to README

Add evaluation results section with metrics from 4 bench suites:
- Retrieval quality (Recall@5=0.873, MRR@10=0.731, nDCG@10=0.747)
- Scope isolation + CRUD lifecycle (zero leaks, all CRUD pass)
- Context extraction (100% precision, 100% router accuracy)
- Injection robustness (0% pollution, 0% false positive)

Includes LoCoMo question-type breakdown and run instructions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(AgentSwarm): add Claude/Codex workflow, workspace persistence, and human review UX (#296)

* feat(AgentSwarm): add Claude/Codex workflow, workspace persistence, and human review UX

- 添加 /api/agent-board 路由和 Swarm Commander/Dispatcher 基础架构

- 将生成的任务文件持久化到已配置的工作区

- 在工作区中为每个任务生成用户审核文档(reviews/<任务>-user-review.md)

- 添加代理看板 UI,包含看板、任务详细日志和人工审核操作

- 要求“全部运行”任务设置工作区,并添加 VS Code 打开操作

- 实现 Studio 存储和 SSE 任务的更新/插入/同步行为

- 为路由流程、持久化和超时处理添加单元/功能测试

- 更新 Studio 布局、PostCSS 配置、后端 URL 助手和锁定文件

Closes #197

* (fix)修改AI review问题

* refactor: share research fetch helpers (#295)

Co-authored-by: 林杰 <linjie@v8d1ef6c5.dip.tu-dresden.de>

* feat: add research/yearCombobox. Realted to #297

* refactor(research): dedupe fetch helpers across Research pages; fix Radix popover import; add @radix-ui/react-popover dep

* fix(web): move @radix-ui/react-popover to dependencies

* fix(research): keep track list order stable across activation

* refactor(research): share stable track merge helper

* fix(research): make paper feedback togglable

* feat(research): hide 'Open Discovery Workspace' behind feature flag

* feat(research): gate track memory button behind feature flag (#333)

* feat(research): gate track memory button behind feature flag

* fix(research): avoid reactivating already active track

* ci(vercel): add auto deploy workflow for dev branch

* feat: calm dashboard workspace (#334)

* feat: harden search and connector infrastructure (#339)

* refactor(infra): unify async request layer for connectors

Implements issue #262 by moving Arxiv/OpenAlex/PapersCool connectors onto the shared async transport with retry support, updating async call sites, and adding focused tests.

* perf(infra): batch OpenAlex ID lookups

Implements issue #267 by replacing per-ID OpenAlex work fetches with batched filter queries and adds focused coverage for the batched path.

* refactor(infra): unify Semantic Scholar client stack

Implements issue #266 by routing both the shared mixin and the scholar-tracking agent through the same SemanticScholarClient surface, removing the duplicated legacy API client path, and adding focused unification tests.

* feat(infra): harden SSE transport handling

Implements issue #263 by moving heartbeat, timeout, cancellation cleanup, and X-Accel-Buffering handling into the shared SSE wrapper and switching the streaming routes onto the common response helper with regression coverage.

* fix(infra): bound ARQ jobs and reuse event log

Implements issue #268 by adding timeout/max_tries metadata to worker functions, reusing the SqlAlchemyEventLog singleton inside the worker module, and covering both behaviors with focused tests.

* feat(search): add three-tier paper deduplicator (DOI/arxiv_id/rapidfuzz)

Replaces the identity-key-only dedup in PaperSearchService._fuse_with_rrf()
with a dedicated PaperDeduplicator that matches across DOI, arxiv_id (version-
stripped), and fuzzy title similarity via rapidfuzz. Merged papers accumulate
the best metadata (highest citations, longest abstract, union of identities).

Closes #317

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(search): prevent conflicting identity dedup merges

Refs #317

* fix(embeddings): add CJK character support to hash embedding tokenizer

Extends HashEmbeddingProvider regex to include CJK Unified Ideographs
(U+4E00–U+9FFF) and Extension A (U+3400–U+4DBF), enabling proper
embedding of Chinese text.

Closes #276

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(embeddings): extend hash tokenizer beyond Han-only CJK

Refs #276

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix(research): restore feedback track ux on current dev (#341)

* refactor(research): dedupe fetch helpers across Research pages; fix Radix popover import; add @radix-ui/react-popover dep

* fix(web): preserve item order in research tracks selection

Closes #304

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(web): remove unused components

Closes #305

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(web): keep active research track visible

Refs #304

* fix(research): align paper feedback toggles with persisted state

Refs #324

* fix(web): add missing @radix-ui/react-popover dependency

Research page crashed with "Module not found: Can't resolve
'@radix-ui/react-popover'" because SearchBox.tsx imports popover.tsx
which depends on this package.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(research): restore feedback track ux on current dev

---------

Co-authored-by: 林杰 <linjie@v8d1ef6a5.dip.tu-dresden.de>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix(web): resolve DeepCode Studio page issues

* fix(repro): improve memory and workflow correctness

* feat: add intelligence radar backend

* fix(api): block cross-user intelligence feed access

* fix(intelligence): avoid thread-unsafe refresh races

* refactor: enforce alembic-only store initialization

* fix(db): bootstrap sqlite schemas for lazy stores

* fix(stores): align ports with store interfaces

* fix(stores): align remaining port contracts

* fix(api): harden request boundaries and settings handling

* fix(api): stop trusting spoofable forwarded hosts

* fix(api): gate studio code execution by default

* fix(api): stream request-size checks safely

* feat(export): add Obsidian filesystem exporter

* feat(cli): add Obsidian export command

* feat(obsidian): finish configurable vault export workflow (#348)

* refactor(web): move workflow workbench out of dashboard

* ci(vercel): use --preview flag for dev deploy

* fix(ci): remove invalid Vercel preview flag

* fix(ci): keep Vercel secrets free of inline comments

* refactor: share candidate search boundary and simplify dashboard

* refactor(api): split paperscool search curate and ingest

* refactor(web): flatten dashboard brief snapshot

* refactor(web): reduce dashboard brief visual noise

* refactor(web): feature top dashboard signals

* fix(obsidian): harden export follow-ups

* fix(obsidian): tighten review follow-ups

* feat(web): add Obsidian handoff to Research workspace (#352)

* feat(web): add Obsidian handoff to Research workspace

* fix(web): address obsidian workspace review feedback

* feat: ship track-centric research context stack (#356)

* feat(api): add track context read model service

* feat(api): expose consolidated track context endpoint

* refactor(web): migrate research page to track context endpoint

* refactor(memory): wrap track-scoped memory access

* chore: document track-centric research model and stabilize tests

* docs: refresh readme screenshots

* fix(deps): align package manifests with runtime imports

* docs: refresh email push screenshot

* feat(research): close citation graph and obsidian export gaps (#360)

* feat(obsidian): add bidirectional vault sync

* fix(paper): improve saved papers table layout

* feat(openclaw): add paperbot plugin bridge

* fix(obsidian): address sync review feedback

* feat(web): flatten dashboard action bands

* refactor(web): condense dashboard next-up panel

* refactor(web): simplify dashboard around recommendations

* refactor(web): simplify dashboard surface and workflow copy

* feat(web): add dashboard queue actions

* fix(web): harden dashboard queue links and brief parsing

* fix(paper): unify unsave with feedback API

* fix(paper): scope saved papers per track

* fix: unblock dashboard build and e2e

* ci: add vercel pr preview automation

* fix: repair vercel preview workflow setup

* fix: run vercel build from repo root

* fix: skip preview smoke without bypass secret

* feat(auth): add multi-user authentication foundation. Related to #151

- Add User domain model and SQLAlchemy UserModel with soft-delete support
- Add SqlAlchemyUserStore with email/GitHub user CRUD, password auth, reset tokens
- Add JWT signing/verification (python-jose), bcrypt password hashing
- Add FastAPI auth dependencies: get_user_id (optional fallback) and get_current_user (strict)
- Add /api/auth routes: register, login, github/exchange, me, forgot/reset-password
- Add Alembic migrations for users and password_reset_tokens tables
- Add AUTH_OPTIONAL env var for gradual migration from legacy 'default' user
- Fix account lifecycle bugs: reactivate on OAuth re-login, reject inactive on password login
- Add auth API tests

* fix(auth): address code review feedback on PR #365

* feat(document): add explicit evidence indexing pipeline

* docs(benchmark): define document evidence eval contract

* feat(benchmark): add document evidence eval scaffold

* fix(api): restore py39 auth compatibility after dev rebase

* chore(logging): surface cleanup and FTS5 failures

* fix(paper): refine saved filters and cleanup code

* fix(paper): refine paper context cleanup and year filter

* feat(eval): support dedicated embedding benchmark providers

* feat(settings): add embedding endpoint configuration

* refactor(settings): align embedding endpoint ux with cc-switch

* refactor(settings): simplify embedding endpoint panel

* refactor(settings): tighten embedding layout on wide screens

* feat(studio): refine paper gallery icon animation and context
  workspace layout

* ci: disable native vercel git deploys

* docs: restore master demo gallery assets

* fix: unblock ci for merge-dev-into-master

* fix: address codeql alerts

* fix: harden agent board workspace path validation

---------

Co-authored-by: boyu <oor2020@163.com>
Co-authored-by: WenjingWang <jingnvx@outlook.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: 林杰 <linjie@linjiedeMacBook-Air.local>
Co-authored-by: 林杰 <linjie@v8d1ef64c.dip.tu-dresden.de>
Co-authored-by: Linjie-top <linjie666z@gmail.com>
Co-authored-by: 林杰 <linjie@v8d1ef43e.dip.tu-dresden.de>
Co-authored-by: 林杰 <linjie@v8d1ef6c5.dip.tu-dresden.de>
Co-authored-by: 林杰 <linjie@v8d1ef6a5.dip.tu-dresden.de>
Co-authored-by: 林杰 <linjie@v8d1ef4ea.dip.tu-dresden.de>
Co-authored-by: 林杰 <linjie@v8d1ef41f.dip.tu-dresden.de>
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.

2 participants