jfox-cli 0.4.3__tar.gz → 0.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/.github/workflows/integration-test.yml +1 -1
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/.gitignore +10 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/CHANGELOG.md +18 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/PKG-INFO +1 -1
- jfox_cli-0.6.0/docs/superpowers/plans/2026-04-13-add-content-file.md +301 -0
- jfox_cli-0.6.0/docs/superpowers/plans/2026-04-13-bulk-import-vectorstore-init.md +270 -0
- jfox_cli-0.6.0/docs/superpowers/plans/2026-04-13-ingest-log-command.md +809 -0
- jfox_cli-0.6.0/docs/superpowers/plans/2026-04-13-ingest-skill-sync.md +338 -0
- jfox_cli-0.6.0/docs/superpowers/plans/2026-04-13-suppress-third-party-logging.md +115 -0
- jfox_cli-0.6.0/docs/superpowers/plans/2026-04-13-sync-skills-with-cli.md +340 -0
- jfox_cli-0.6.0/docs/superpowers/plans/2026-04-15-gpu-embedding.md +905 -0
- jfox_cli-0.6.0/docs/superpowers/plans/2026-04-15-make-daemon-deps-required.md +358 -0
- jfox_cli-0.6.0/docs/superpowers/plans/2026-04-16-fix-windows-daemon-console-window.md +206 -0
- jfox_cli-0.6.0/docs/superpowers/plans/2026-04-18-daemon-timeout-and-index-rebuild.md +744 -0
- jfox_cli-0.6.0/docs/superpowers/plans/2026-04-22-release-skill.md +746 -0
- jfox_cli-0.6.0/docs/superpowers/plans/2026-04-26-fix-daemon-deprecation-warnings.md +185 -0
- jfox_cli-0.6.0/docs/superpowers/plans/2026-04-27-intranet-model-download.md +828 -0
- jfox_cli-0.6.0/docs/superpowers/plans/2026-04-28-tag-filtering.md +1087 -0
- jfox_cli-0.6.0/docs/superpowers/specs/2026-04-15-gpu-embedding-design.md +190 -0
- jfox_cli-0.6.0/docs/superpowers/specs/2026-04-21-release-skill-design.md +175 -0
- jfox_cli-0.6.0/docs/superpowers/specs/2026-04-26-fix-daemon-deprecation-warnings-design.md +56 -0
- jfox_cli-0.6.0/docs/superpowers/specs/2026-04-27-intranet-model-download-design.md +284 -0
- jfox_cli-0.6.0/docs/superpowers/specs/2026-04-28-tag-filtering-design.md +146 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/jfox/__init__.py +1 -1
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/jfox/cli.py +56 -112
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/jfox/config.py +23 -2
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/jfox/daemon/process.py +38 -5
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/jfox/daemon/server.py +19 -1
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/jfox/note.py +18 -3
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/jfox/search_engine.py +41 -10
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/jfox/template_cli.py +5 -23
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/jfox/vector_store.py +16 -1
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/pyproject.toml +1 -1
- jfox_cli-0.6.0/tests/integration/test_tag_filter_cli.py +81 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/performance/test_performance.py +1 -3
- jfox_cli-0.6.0/tests/unit/test_tag_filter.py +125 -0
- jfox_cli-0.6.0/tests/unit/test_use_kb_env_var.py +94 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/utils/jfox_cli.py +21 -2
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/utils/temp_kb.py +3 -3
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/uv.lock +1 -1
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/.claude/settings.local.json +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/.claude/skills/release/SKILL.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/.claude/skills/release/release_helper.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/.githooks/pre-push +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/.github/workflows/publish.yml +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/.python-version +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/AGENTS.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/CLAUDE.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/DEVELOPMENT_PLAN.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/README.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/SESSION.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/SESSION_SUMMARY.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/docs/installation.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/docs/superpowers/plans/2026-04-11-bulk-import-bm25-fix.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/docs/superpowers/plans/2026-04-11-edit-command.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/docs/superpowers/plans/2026-04-11-unify-format-option.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/docs/superpowers/plans/2026-04-12-ci-coverage-optimization.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/docs/superpowers/plans/2026-04-12-edit-content-file.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/docs/superpowers/plans/2026-04-12-fix-index-rebuild-clear.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/docs/superpowers/plans/2026-04-12-fix-index-verify-id-mismatch.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/docs/superpowers/plans/2026-04-12-fix-jfox-health-skill-kb-param.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/docs/superpowers/plans/2026-04-12-index-kb-param.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/docs/superpowers/plans/2026-04-12-lazy-import-perf.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/docs/superpowers/plans/2026-04-12-skill-redesign.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/docs/superpowers/plans/2026-04-14-sync-docs-daemon-show.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/docs/superpowers/plans/2026-04-15-session-summary-confirmation.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/docs/superpowers/specs/2026-04-03-bugfixes-design.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/docs/superpowers/specs/2026-04-12-skill-redesign-design.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/docs/superpowers/specs/2026-04-13-pr-auto-code-review-design.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/docs/superpowers/specs/2026-04-14-show-command-design.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/docs/superpowers/specs/2026-04-15-session-summary-confirmation-design.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/docs/troubleshooting.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/jessica-jones-static-cable.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/jfox/__main__.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/jfox/bm25_index.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/jfox/daemon/__init__.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/jfox/daemon/__main__.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/jfox/daemon/client.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/jfox/embedding_backend.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/jfox/formatters.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/jfox/git_extractor.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/jfox/global_config.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/jfox/graph.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/jfox/indexer.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/jfox/kb_manager.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/jfox/model_downloader.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/jfox/models.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/jfox/performance.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/jfox/template.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/pytest.ini +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/run_full_test.ps1 +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/scripts/download-model-intranet.sh +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/skills-recommend/README.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/skills-recommend/claude-code/jfox-common/SKILL.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/skills-recommend/claude-code/jfox-ingest/SKILL.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/skills-recommend/claude-code/jfox-organize/SKILL.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/skills-recommend/claude-code/jfox-search/SKILL.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/skills-recommend/claude-code/jfox-session-summary/SKILL.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/skills-recommend/kimi-cli/jfox-common/SKILL.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/skills-recommend/kimi-cli/jfox-ingest/SKILL.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/skills-recommend/kimi-cli/jfox-organize/SKILL.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/skills-recommend/kimi-cli/jfox-search/SKILL.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/skills-recommend/kimi-cli/jfox-session-summary/SKILL.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/COVERAGE_PLAN.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/MIGRATION.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/TESTS.md +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/conftest.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/integration/__init__.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/integration/test_backlinks.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/integration/test_model_download.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/performance/__init__.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/test_advanced_features.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/test_cli_format.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/test_config_set_unit.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/test_config_unit.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/test_core_workflow.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/test_embedding_device.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/test_hybrid_search.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/test_integration.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/test_kb_current.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/test_suggest_links.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/unit/__init__.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/unit/test_bm25_batch.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/unit/test_daemon_process.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/unit/test_edit.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/unit/test_format_unify.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/unit/test_formatters.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/unit/test_git_extractor.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/unit/test_global_config.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/unit/test_index_kb_param.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/unit/test_indexer_clear_before_rebuild.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/unit/test_indexer_verify.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/unit/test_kb_manager.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/unit/test_lazy_import.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/unit/test_logging_config.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/unit/test_model_downloader.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/unit/test_release_helper.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/unit/test_show.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/unit/test_template.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/unit/test_template_cli.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/unit/test_vector_store_clear.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/utils/__init__.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/utils/assertions.py +0 -0
- {jfox_cli-0.4.3 → jfox_cli-0.6.0}/tests/utils/note_generator.py +0 -0
|
@@ -245,7 +245,7 @@ jobs:
|
|
|
245
245
|
run: |
|
|
246
246
|
echo "lint: ${{ needs.lint.result }}"
|
|
247
247
|
echo "test-fast: ${{ needs.test-fast.result }}"
|
|
248
|
-
if [[ "${{ needs.lint.result }}" == "success" && "${{ needs.test-fast.result }}" == "success" ]]; then
|
|
248
|
+
if [[ "${{ needs.lint.result }}" == "success" && ("${{ needs.test-fast.result }}" == "success" || "${{ needs.test-fast.result }}" == "skipped") ]]; then
|
|
249
249
|
echo "Quality gate passed!"
|
|
250
250
|
exit 0
|
|
251
251
|
else
|
|
@@ -37,3 +37,13 @@ Thumbs.db
|
|
|
37
37
|
chroma_db/
|
|
38
38
|
*.sqlite3
|
|
39
39
|
cache/
|
|
40
|
+
|
|
41
|
+
# Claude Code runtime files
|
|
42
|
+
.claude/scheduled_tasks.json
|
|
43
|
+
.claude/scheduled_tasks.lock
|
|
44
|
+
.claude/worktrees/
|
|
45
|
+
|
|
46
|
+
# Temporary analysis files
|
|
47
|
+
analysis_report.md
|
|
48
|
+
kimi-export-*.md
|
|
49
|
+
review_body*.md
|
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to jfox-cli will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.5.0] - 2026-04-29
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
- 支持通过标签召回笔记 (#170) (#177)
|
|
9
|
+
- **cli**: show tags column in list table output
|
|
10
|
+
- **note**: add tags parameter to list_notes() and search_notes()
|
|
11
|
+
- **vector_store**: add tags parameter to search() for ChromaDB filtering
|
|
12
|
+
|
|
13
|
+
### Fixes
|
|
14
|
+
- **test**: resolve CI test-full failures (#175)
|
|
15
|
+
|
|
16
|
+
### Changes
|
|
17
|
+
- add superpowers plans and specs docs, update .gitignore (#178)
|
|
18
|
+
- add tag filtering implementation plan for #170
|
|
19
|
+
- add tag filtering design spec for #170
|
|
20
|
+
|
|
21
|
+
[0.5.0]: https://github.com/zhuxixi/jfox/compare/v0.4.3...v0.5.0
|
|
22
|
+
|
|
5
23
|
## [0.4.3] - 2026-04-28
|
|
6
24
|
|
|
7
25
|
### Features
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# add --content-file Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** `jfox add` 支持 `--content-file` 选项,从文件或 stdin 读取笔记内容。
|
|
6
|
+
|
|
7
|
+
**Architecture:** 在 `add` 命令中将 `content` 从必需位置参数改为可选参数,新增 `--content-file` 选项。文件读取逻辑复用 `edit` 命令已有的模式(`cli.py` L975-986),两者互斥校验。stdin 支持通过 `--content-file -` 实现。`_add_note_impl` 签名不变。
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Python, Typer, pathlib
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
### Task 1: 文件读取逻辑提取为公共函数
|
|
14
|
+
|
|
15
|
+
**Files:**
|
|
16
|
+
- Modify: `jfox/cli.py` (在 `_edit_impl` 之前添加公共函数)
|
|
17
|
+
- Modify: `jfox/cli.py:970-986` (`_edit_impl` 调用改为使用公共函数)
|
|
18
|
+
|
|
19
|
+
**为什么先做:** `add` 和 `edit` 都需要读取 `--content-file`,提取公共函数避免重复代码,也方便单独测试。
|
|
20
|
+
|
|
21
|
+
- [ ] **Step 1: 在 `_edit_impl` 之前添加 `_read_content_file()` 函数**
|
|
22
|
+
|
|
23
|
+
在 `jfox/cli.py` 约第 958 行(`_edit_impl` 定义之前)插入:
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
def _read_content_file(content_file: str) -> str:
|
|
27
|
+
"""从文件或 stdin 读取内容(--content-file 共用逻辑)"""
|
|
28
|
+
if content_file == "-":
|
|
29
|
+
import sys
|
|
30
|
+
|
|
31
|
+
return sys.stdin.read()
|
|
32
|
+
|
|
33
|
+
p = Path(content_file)
|
|
34
|
+
if not p.exists():
|
|
35
|
+
raise ValueError(f"文件不存在: {content_file}")
|
|
36
|
+
if not p.is_file():
|
|
37
|
+
raise ValueError(f"路径不是文件: {content_file}")
|
|
38
|
+
try:
|
|
39
|
+
return p.read_text(encoding="utf-8")
|
|
40
|
+
except PermissionError:
|
|
41
|
+
raise ValueError(f"无权限读取文件: {content_file}")
|
|
42
|
+
except UnicodeDecodeError:
|
|
43
|
+
raise ValueError(f"文件编码错误(需要 UTF-8): {content_file}")
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- [ ] **Step 2: 重构 `_edit_impl` 使用公共函数**
|
|
47
|
+
|
|
48
|
+
将 `jfox/cli.py` L974-986(`_edit_impl` 中的文件读取代码):
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
# 从文件读取内容
|
|
52
|
+
if content_file is not None:
|
|
53
|
+
p = Path(content_file)
|
|
54
|
+
if not p.exists():
|
|
55
|
+
raise ValueError(f"文件不存在: {content_file}")
|
|
56
|
+
if not p.is_file():
|
|
57
|
+
raise ValueError(f"路径不是文件: {content_file}")
|
|
58
|
+
try:
|
|
59
|
+
content = p.read_text(encoding="utf-8")
|
|
60
|
+
except PermissionError:
|
|
61
|
+
raise ValueError(f"无权限读取文件: {content_file}")
|
|
62
|
+
except UnicodeDecodeError:
|
|
63
|
+
raise ValueError(f"文件编码错误(需要 UTF-8): {content_file}")
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
替换为:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
# 从文件读取内容
|
|
70
|
+
if content_file is not None:
|
|
71
|
+
content = _read_content_file(content_file)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
- [ ] **Step 3: 运行快速回归测试,确认 edit 功能不变**
|
|
75
|
+
|
|
76
|
+
Run: `uv run pytest tests/test_cli_format.py::TestAddAndDeleteFormat -v`
|
|
77
|
+
Expected: 全部 PASS
|
|
78
|
+
|
|
79
|
+
- [ ] **Step 4: Commit**
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
git add jfox/cli.py
|
|
83
|
+
git commit -m "refactor: extract _read_content_file() for shared --content-file logic"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
### Task 2: add 命令支持 --content-file 选项
|
|
89
|
+
|
|
90
|
+
**Files:**
|
|
91
|
+
- Modify: `jfox/cli.py:366-383` (`add` 函数签名和调用)
|
|
92
|
+
|
|
93
|
+
- [ ] **Step 1: 修改 `add()` 函数签名**
|
|
94
|
+
|
|
95
|
+
将 `jfox/cli.py` L367-368:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
@app.command()
|
|
99
|
+
def add(
|
|
100
|
+
content: str = typer.Argument(..., help="笔记内容(支持 [[笔记标题]] 格式链接)"),
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
改为:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
@app.command()
|
|
107
|
+
def add(
|
|
108
|
+
content: Optional[str] = typer.Argument(None, help="笔记内容(支持 [[笔记标题]] 格式链接)"),
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
- [ ] **Step 2: 在 `--template` 选项之后添加 `--content-file` 参数**
|
|
112
|
+
|
|
113
|
+
在 `jfox/cli.py` L375-377(`template` 参数定义之后)插入:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
content_file: Optional[str] = typer.Option(
|
|
117
|
+
None, "--content-file", help="从文件读取内容(用 - 表示 stdin)"
|
|
118
|
+
),
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
- [ ] **Step 3: 在 `add()` 函数体中添加互斥校验和文件读取逻辑**
|
|
122
|
+
|
|
123
|
+
将 `jfox/cli.py` L384-397:
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
"""添加新笔记(内容中可用 [[笔记标题]] 引用其他笔记)"""
|
|
127
|
+
try:
|
|
128
|
+
# 向后兼容:--json 快捷方式
|
|
129
|
+
if json_output:
|
|
130
|
+
output_format = "json"
|
|
131
|
+
|
|
132
|
+
# 如果指定了知识库,临时切换
|
|
133
|
+
if kb:
|
|
134
|
+
from .config import use_kb
|
|
135
|
+
|
|
136
|
+
with use_kb(kb):
|
|
137
|
+
_add_note_impl(content, title, note_type, tags, source, output_format, template)
|
|
138
|
+
else:
|
|
139
|
+
_add_note_impl(content, title, note_type, tags, source, output_format, template)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
改为:
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
"""添加新笔记(内容中可用 [[笔记标题]] 引用其他笔记)"""
|
|
146
|
+
try:
|
|
147
|
+
# 向后兼容:--json 快捷方式
|
|
148
|
+
if json_output:
|
|
149
|
+
output_format = "json"
|
|
150
|
+
|
|
151
|
+
# content 和 --content-file 互斥
|
|
152
|
+
if content is not None and content_file is not None:
|
|
153
|
+
raise ValueError("不能同时指定内容参数和 --content-file,请选择其一")
|
|
154
|
+
|
|
155
|
+
# 从文件读取内容
|
|
156
|
+
if content_file is not None:
|
|
157
|
+
content = _read_content_file(content_file)
|
|
158
|
+
|
|
159
|
+
# 至少提供一种内容来源
|
|
160
|
+
if not content:
|
|
161
|
+
raise ValueError("请提供笔记内容(位置参数或 --content-file)")
|
|
162
|
+
|
|
163
|
+
# 如果指定了知识库,临时切换
|
|
164
|
+
if kb:
|
|
165
|
+
from .config import use_kb
|
|
166
|
+
|
|
167
|
+
with use_kb(kb):
|
|
168
|
+
_add_note_impl(content, title, note_type, tags, source, output_format, template)
|
|
169
|
+
else:
|
|
170
|
+
_add_note_impl(content, title, note_type, tags, source, output_format, template)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
- [ ] **Step 4: Commit**
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
git add jfox/cli.py
|
|
177
|
+
git commit -m "feat: add --content-file option to jfox add command"
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
### Task 3: 测试 --content-file 功能
|
|
183
|
+
|
|
184
|
+
**Files:**
|
|
185
|
+
- Modify: `tests/test_cli_format.py` (在 `TestAddAndDeleteFormat` 类中添加测试)
|
|
186
|
+
|
|
187
|
+
- [ ] **Step 1: 在 `test_add_format_json` 之前添加 --content-file 测试**
|
|
188
|
+
|
|
189
|
+
在 `tests/test_cli_format.py` 的 `TestAddAndDeleteFormat` 类中,`test_add_format_json` (L327) 之前插入:
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
def test_add_content_file(self, cli, tmp_path):
|
|
193
|
+
"""测试 add 命令 --content-file 从文件读取内容"""
|
|
194
|
+
content_file = tmp_path / "note.md"
|
|
195
|
+
content_file.write_text("从文件读取的笔记内容", encoding="utf-8")
|
|
196
|
+
|
|
197
|
+
result = cli.run("add", "--content-file", str(content_file), "--title", "FileContent")
|
|
198
|
+
|
|
199
|
+
assert result.success
|
|
200
|
+
data = result.data
|
|
201
|
+
assert data["success"] is True
|
|
202
|
+
assert data["note"]["title"] == "FileContent"
|
|
203
|
+
|
|
204
|
+
def test_add_content_file_mutual_exclusive(self, cli, tmp_path):
|
|
205
|
+
"""测试 add 命令 content 参数和 --content-file 不能同时指定"""
|
|
206
|
+
content_file = tmp_path / "note.md"
|
|
207
|
+
content_file.write_text("file content", encoding="utf-8")
|
|
208
|
+
|
|
209
|
+
result = cli.run("add", "直接内容", "--content-file", str(content_file))
|
|
210
|
+
|
|
211
|
+
assert not result.success
|
|
212
|
+
assert "不能同时指定" in result.stderr or "不能同时指定" in result.stdout
|
|
213
|
+
|
|
214
|
+
def test_add_content_file_not_found(self, cli):
|
|
215
|
+
"""测试 add 命令 --content-file 文件不存在时报错"""
|
|
216
|
+
result = cli.run("add", "--content-file", "/nonexistent/file.md")
|
|
217
|
+
|
|
218
|
+
assert not result.success
|
|
219
|
+
assert "文件不存在" in result.stderr or "文件不存在" in result.stdout
|
|
220
|
+
|
|
221
|
+
def test_add_content_file_empty(self, cli, tmp_path):
|
|
222
|
+
"""测试 add 命令不提供内容时报错"""
|
|
223
|
+
result = cli.run("add")
|
|
224
|
+
|
|
225
|
+
assert not result.success
|
|
226
|
+
|
|
227
|
+
def test_add_content_file_with_wiki_links(self, cli, tmp_path):
|
|
228
|
+
"""测试 add 命令 --content-file 内容中的 [[wiki链接]] 正常解析"""
|
|
229
|
+
# 先创建一个目标笔记
|
|
230
|
+
cli.add("目标笔记内容", title="目标笔记")
|
|
231
|
+
|
|
232
|
+
content_file = tmp_path / "linked.md"
|
|
233
|
+
content_file.write_text("引用 [[目标笔记]] 的内容", encoding="utf-8")
|
|
234
|
+
|
|
235
|
+
result = cli.run("add", "--content-file", str(content_file), "--title", "带链接的笔记")
|
|
236
|
+
|
|
237
|
+
assert result.success
|
|
238
|
+
data = result.data
|
|
239
|
+
assert data["note"]["links"]
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
- [ ] **Step 2: 运行测试确认全部通过**
|
|
243
|
+
|
|
244
|
+
Run: `uv run pytest tests/test_cli_format.py::TestAddAndDeleteFormat -v`
|
|
245
|
+
Expected: 全部 PASS(包括新增的 5 个测试)
|
|
246
|
+
|
|
247
|
+
- [ ] **Step 3: Commit**
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
git add tests/test_cli_format.py
|
|
251
|
+
git commit -m "test: add --content-file tests for jfox add command"
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
### Task 4: 验证 edit --content-file 回归
|
|
257
|
+
|
|
258
|
+
**Files:** 无修改
|
|
259
|
+
|
|
260
|
+
- [ ] **Step 1: 确认 edit 的 --content-file 仍然正常工作**
|
|
261
|
+
|
|
262
|
+
检查现有 edit 测试是否覆盖 --content-file:
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
uv run pytest tests/test_cli_format.py -v -k "edit"
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
如果 edit 的 --content-file 没有现有测试,手动验证:
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
uv run jfox add "原始内容" --title "编辑测试" --json
|
|
272
|
+
# 记录返回的 note_id
|
|
273
|
+
echo "新内容" > /tmp/test_edit.md
|
|
274
|
+
uv run jfox edit <note_id> --content-file /tmp/test_edit.md --json
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Expected: edit 成功,内容更新为 "新内容"
|
|
278
|
+
|
|
279
|
+
- [ ] **Step 2: 确认 add 的原有行为不受影响**
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
uv run jfox add "直接输入的内容" --title "位置参数测试" --json
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Expected: add 成功,内容为 "直接输入的内容"
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## Self-Review Checklist
|
|
290
|
+
|
|
291
|
+
| 检查项 | 状态 |
|
|
292
|
+
|--------|------|
|
|
293
|
+
| `_read_content_file` 覆盖文件不存在/非文件/权限/编码错误 | Done (Task 1) |
|
|
294
|
+
| stdin 支持 (`--content-file -`) | Done (Task 1, `content_file == "-"` 分支) |
|
|
295
|
+
| `content` 和 `--content-file` 互斥校验 | Done (Task 2) |
|
|
296
|
+
| 都不指定时报错提示 | Done (Task 2) |
|
|
297
|
+
| `--content-file` 与 `--template` 交互正确(内容作为 `{{content}}` 变量) | Done (Task 2, `_add_note_impl` 未修改,`content` 变量直接传入) |
|
|
298
|
+
| `-f` 短选项不被占用 | Done (Task 2, `--content-file` 无短选项) |
|
|
299
|
+
| `wiki_links` 提取仍正常工作 | Done (Task 3 测试覆盖) |
|
|
300
|
+
| `edit` 命令回归安全 | Done (Task 1 提取公共函数,Task 4 验证) |
|
|
301
|
+
| `_add_note_impl` 签名未修改 | Done (Task 2 只在 `add()` 中读取文件后传入 `content: str`) |
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# Bulk-Import VectorStore 初始化修复 实现计划
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** 修复 `bulk_import_notes()` 中 VectorStore.collection 未初始化导致批量索引静默失败的问题,同时让 `index rebuild` 命令同时重建 BM25 索引。
|
|
6
|
+
|
|
7
|
+
**Architecture:** 两处独立修复:(1) `performance.py` 在批量索引前确保 VectorStore 已初始化;(2) `cli.py` 的 `index rebuild` 命令增加 BM25 重建步骤。两处修复互不依赖。
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Python 3.10+, pytest, unittest.mock
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Task 1: 修复 bulk_import_notes 中 VectorStore 未初始化的 bug
|
|
14
|
+
|
|
15
|
+
**Files:**
|
|
16
|
+
- Modify: `jfox/performance.py:266`
|
|
17
|
+
- Test: `tests/unit/test_bm25_batch.py`
|
|
18
|
+
|
|
19
|
+
- [ ] **Step 1: 写一个能复现 bug 的失败测试**
|
|
20
|
+
|
|
21
|
+
在 `tests/unit/test_bm25_batch.py` 的 `TestBulkImportBM25Integration` 类中,添加一个测试:模拟 `VectorStore` 的 `collection` 为 `None`(即真实场景),验证 `bulk_import_notes` 不会抛异常。
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
@patch("jfox.bm25_index.get_bm25_index")
|
|
25
|
+
@patch("jfox.embedding_backend.get_backend")
|
|
26
|
+
@patch("jfox.note.create_note")
|
|
27
|
+
def test_bulk_import_with_uninitialized_vectorstore(
|
|
28
|
+
self, mock_create_note, mock_get_backend, mock_get_bm25, tmp_path
|
|
29
|
+
):
|
|
30
|
+
"""bulk_import_notes 应在 collection.add() 前自动初始化 VectorStore"""
|
|
31
|
+
import numpy as np
|
|
32
|
+
|
|
33
|
+
from jfox.models import Note, NoteType
|
|
34
|
+
from jfox.performance import bulk_import_notes
|
|
35
|
+
|
|
36
|
+
# 准备 mock note
|
|
37
|
+
mock_note = MagicMock(spec=Note)
|
|
38
|
+
mock_note.id = "20260413120000"
|
|
39
|
+
mock_note.title = "测试初始化"
|
|
40
|
+
mock_note.content = "内容"
|
|
41
|
+
mock_note.type = NoteType.PERMANENT
|
|
42
|
+
mock_note.tags = []
|
|
43
|
+
mock_note.filepath = tmp_path / "notes" / "permanent" / "test.md"
|
|
44
|
+
mock_note.to_markdown.return_value = "# 测试初始化\n内容"
|
|
45
|
+
mock_create_note.return_value = mock_note
|
|
46
|
+
|
|
47
|
+
# mock embedding backend
|
|
48
|
+
mock_backend = MagicMock()
|
|
49
|
+
mock_backend.model = MagicMock()
|
|
50
|
+
mock_backend.encode.return_value = np.array([[0.1] * 384])
|
|
51
|
+
mock_get_backend.return_value = mock_backend
|
|
52
|
+
|
|
53
|
+
# mock BM25
|
|
54
|
+
mock_bm25 = MagicMock()
|
|
55
|
+
mock_bm25.add_documents_batch.return_value = True
|
|
56
|
+
mock_get_bm25.return_value = mock_bm25
|
|
57
|
+
|
|
58
|
+
# 不 mock get_vector_store,让真实的 VectorStore 被创建
|
|
59
|
+
# 但 patch 掉 VectorStore.init 来验证它被调用了
|
|
60
|
+
with patch("jfox.performance.get_vector_store") as mock_get_vs:
|
|
61
|
+
mock_vs = MagicMock()
|
|
62
|
+
# 关键:collection 初始为 None,模拟真实场景
|
|
63
|
+
mock_vs.collection = None
|
|
64
|
+
|
|
65
|
+
# init() 会设置 collection
|
|
66
|
+
def fake_init():
|
|
67
|
+
mock_vs.collection = MagicMock()
|
|
68
|
+
|
|
69
|
+
mock_vs.init.side_effect = fake_init
|
|
70
|
+
mock_get_vs.return_value = mock_vs
|
|
71
|
+
|
|
72
|
+
notes_data = [{"title": "测试初始化", "content": "内容"}]
|
|
73
|
+
result = bulk_import_notes(notes_data, show_progress=False)
|
|
74
|
+
|
|
75
|
+
# 验证 init 被调用了
|
|
76
|
+
mock_vs.init.assert_called()
|
|
77
|
+
# 导入成功
|
|
78
|
+
assert result["imported"] == 1
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
- [ ] **Step 2: 运行测试确认它失败**
|
|
82
|
+
|
|
83
|
+
Run: `uv run pytest tests/unit/test_bm25_batch.py::TestBulkImportBM25Integration::test_bulk_import_with_uninitialized_vectorstore -v`
|
|
84
|
+
Expected: FAIL — 当前代码直接访问 `vector_store.collection.add()`,不会调用 `init()`,mock 的 `collection` 仍为 None,会抛 `AttributeError`。
|
|
85
|
+
|
|
86
|
+
- [ ] **Step 3: 修复 performance.py — 在 collection.add() 前添加初始化**
|
|
87
|
+
|
|
88
|
+
在 `jfox/performance.py` 第 266 行之前,添加初始化检查。修改 `# 批量添加到 ChromaDB` 注释后的代码块:
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
# 确保 VectorStore 已初始化
|
|
92
|
+
if vector_store.collection is None:
|
|
93
|
+
vector_store.init()
|
|
94
|
+
|
|
95
|
+
# 批量添加到 ChromaDB
|
|
96
|
+
vector_store.collection.add(
|
|
97
|
+
ids=ids, documents=documents, embeddings=embeddings, metadatas=metadatas
|
|
98
|
+
)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
完整上下文(`performance.py` 约第 254-268 行)修改后为:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
# 批量添加到 ChromaDB
|
|
105
|
+
ids = [n.id for n in notes]
|
|
106
|
+
metadatas = [
|
|
107
|
+
{
|
|
108
|
+
"title": n.title,
|
|
109
|
+
"type": n.type.value,
|
|
110
|
+
"filepath": str(n.filepath),
|
|
111
|
+
"tags": ",".join(n.tags),
|
|
112
|
+
}
|
|
113
|
+
for n in notes
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
# 确保 VectorStore 已初始化
|
|
117
|
+
if vector_store.collection is None:
|
|
118
|
+
vector_store.init()
|
|
119
|
+
|
|
120
|
+
vector_store.collection.add(
|
|
121
|
+
ids=ids, documents=documents, embeddings=embeddings, metadatas=metadatas
|
|
122
|
+
)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
- [ ] **Step 4: 运行测试确认通过**
|
|
126
|
+
|
|
127
|
+
Run: `uv run pytest tests/unit/test_bm25_batch.py -v`
|
|
128
|
+
Expected: ALL PASS(包括新测试和原有测试)
|
|
129
|
+
|
|
130
|
+
- [ ] **Step 5: Commit**
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
git add jfox/performance.py tests/unit/test_bm25_batch.py
|
|
134
|
+
git commit -m "fix: bulk_import_notes 中 VectorStore.collection 未初始化导致索引失败
|
|
135
|
+
|
|
136
|
+
performance.py 的 bulk_import_notes() 直接访问 vector_store.collection.add(),
|
|
137
|
+
绕过了 add_note() 中的 init() 保护。在新会话中直接执行 bulk-import 时,
|
|
138
|
+
collection 为 None 导致 'NoneType' object has no attribute 'add' 错误。
|
|
139
|
+
|
|
140
|
+
在 collection.add() 前添加 if vector_store.collection is None: vector_store.init()。
|
|
141
|
+
|
|
142
|
+
Closes #126 (修复 1/2)"
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Task 2: 让 index rebuild 同时重建 BM25 索引
|
|
148
|
+
|
|
149
|
+
**Files:**
|
|
150
|
+
- Modify: `jfox/cli.py:1701-1713`
|
|
151
|
+
- Test: `tests/unit/test_index_kb_param.py`(在已有文件中添加测试)
|
|
152
|
+
|
|
153
|
+
- [ ] **Step 1: 写失败测试 — 验证 rebuild 时也调用了 BM25 重建**
|
|
154
|
+
|
|
155
|
+
在 `tests/unit/test_index_kb_param.py` 中添加测试:
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
class TestIndexRebuildIncludesBM25:
|
|
159
|
+
"""验证 index rebuild 同时重建 BM25 索引"""
|
|
160
|
+
|
|
161
|
+
@patch("jfox.cli.note")
|
|
162
|
+
@patch("jfox.cli.get_bm25_index")
|
|
163
|
+
@patch("jfox.cli.Indexer")
|
|
164
|
+
@patch("jfox.cli.get_vector_store")
|
|
165
|
+
def test_rebuild_calls_bm25_rebuild(
|
|
166
|
+
self, mock_get_vs, mock_indexer_cls, mock_get_bm25, mock_note, tmp_path
|
|
167
|
+
):
|
|
168
|
+
"""index rebuild 应在 ChromaDB 重建后调用 BM25 rebuild_from_notes"""
|
|
169
|
+
from jfox.cli import _index_impl
|
|
170
|
+
|
|
171
|
+
# mock vector store
|
|
172
|
+
mock_vs = MagicMock()
|
|
173
|
+
mock_get_vs.return_value = mock_vs
|
|
174
|
+
|
|
175
|
+
# mock indexer
|
|
176
|
+
mock_indexer = MagicMock()
|
|
177
|
+
mock_indexer.index_all.return_value = 10
|
|
178
|
+
mock_indexer_cls.return_value = mock_indexer
|
|
179
|
+
|
|
180
|
+
# mock note.list_notes
|
|
181
|
+
mock_notes = [MagicMock() for _ in range(3)]
|
|
182
|
+
mock_note.list_notes.return_value = mock_notes
|
|
183
|
+
|
|
184
|
+
# mock BM25
|
|
185
|
+
mock_bm25 = MagicMock()
|
|
186
|
+
mock_bm25.rebuild_from_notes.return_value = True
|
|
187
|
+
mock_get_bm25.return_value = mock_bm25
|
|
188
|
+
|
|
189
|
+
_index_impl("rebuild", "text")
|
|
190
|
+
|
|
191
|
+
# 验证 ChromaDB 重建被调用
|
|
192
|
+
mock_indexer.index_all.assert_called_once()
|
|
193
|
+
# 验证 BM25 重建也被调用
|
|
194
|
+
mock_get_bm25.assert_called_once()
|
|
195
|
+
mock_bm25.rebuild_from_notes.assert_called_once_with(mock_notes)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
- [ ] **Step 2: 运行测试确认它失败**
|
|
199
|
+
|
|
200
|
+
Run: `uv run pytest tests/unit/test_index_kb_param.py::TestIndexRebuildIncludesBM25 -v`
|
|
201
|
+
Expected: FAIL — 当前 `rebuild` 分支不调用 BM25 重建。
|
|
202
|
+
|
|
203
|
+
- [ ] **Step 3: 修改 cli.py 的 rebuild 分支,增加 BM25 重建**
|
|
204
|
+
|
|
205
|
+
修改 `jfox/cli.py` 第 1701-1713 行的 `rebuild` 分支。修改后:
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
elif action == "rebuild":
|
|
209
|
+
console.print("[yellow]Rebuilding index...[/yellow]")
|
|
210
|
+
count = indexer.index_all()
|
|
211
|
+
|
|
212
|
+
# 同时重建 BM25 索引
|
|
213
|
+
from . import note as note_module
|
|
214
|
+
from .bm25_index import get_bm25_index
|
|
215
|
+
|
|
216
|
+
bm25_index = get_bm25_index()
|
|
217
|
+
notes = note_module.list_notes(limit=10000)
|
|
218
|
+
bm25_success = bm25_index.rebuild_from_notes(notes)
|
|
219
|
+
|
|
220
|
+
result = {
|
|
221
|
+
"success": True,
|
|
222
|
+
"indexed": count,
|
|
223
|
+
"bm25_rebuilt": bm25_success,
|
|
224
|
+
"bm25_indexed": len(notes),
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if output_format == "json":
|
|
228
|
+
print(output_json(result))
|
|
229
|
+
else:
|
|
230
|
+
console.print(f"[green]✓[/green] Indexed {count} notes")
|
|
231
|
+
if bm25_success:
|
|
232
|
+
console.print(f"[green]✓[/green] BM25 index rebuilt: {len(notes)} notes")
|
|
233
|
+
else:
|
|
234
|
+
console.print("[yellow]⚠[/yellow] ChromaDB rebuilt, but BM25 rebuild failed")
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
- [ ] **Step 4: 运行测试确认通过**
|
|
238
|
+
|
|
239
|
+
Run: `uv run pytest tests/unit/test_index_kb_param.py -v`
|
|
240
|
+
Expected: ALL PASS
|
|
241
|
+
|
|
242
|
+
- [ ] **Step 5: 验证现有 rebuild-bm25 单独命令仍正常**
|
|
243
|
+
|
|
244
|
+
Run: `uv run pytest tests/unit/test_index_kb_param.py -v -k "not RebuildIncludesBM25"`
|
|
245
|
+
Expected: ALL PASS(原有测试不受影响)
|
|
246
|
+
|
|
247
|
+
- [ ] **Step 6: Commit**
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
git add jfox/cli.py tests/unit/test_index_kb_param.py
|
|
251
|
+
git commit -m "fix: index rebuild 同时重建 BM25 索引
|
|
252
|
+
|
|
253
|
+
之前 index rebuild 只重建 ChromaDB,用户需额外手动执行 rebuild-bm25。
|
|
254
|
+
现在 rebuild 操作自动同时重建两个索引,输出中显示各自的重建状态。
|
|
255
|
+
|
|
256
|
+
Refs #126 (修复 2/2)"
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## 自查清单
|
|
262
|
+
|
|
263
|
+
| 检查项 | 状态 |
|
|
264
|
+
|--------|------|
|
|
265
|
+
| 修复 1: performance.py VectorStore 初始化 | Task 1 覆盖 |
|
|
266
|
+
| 修复 2: cli.py rebuild 含 BM25 | Task 2 覆盖 |
|
|
267
|
+
| 现有测试不受影响 | Task 1/2 的 Step 4 验证 |
|
|
268
|
+
| 无 placeholder / TBD | 已检查,每步都有完整代码 |
|
|
269
|
+
| 类型/方法签名一致 | `rebuild_from_notes(notes)` 在 bm25_index.py:369 和 cli.py 一致 |
|
|
270
|
+
| `rebuild-bm25` 独立命令仍可用 | Task 2 Step 5 验证,独立命令逻辑未被修改 |
|