jfox-cli 0.2.1__tar.gz → 0.2.2__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.
Files changed (96) hide show
  1. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/CLAUDE.md +1 -1
  2. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/PKG-INFO +1 -1
  3. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/jfox/__init__.py +1 -1
  4. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/jfox/cli.py +50 -12
  5. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/pyproject.toml +1 -1
  6. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/skills-recommend/claude-code/jfox-common/SKILL.md +91 -7
  7. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/skills-recommend/claude-code/jfox-ingest/SKILL.md +43 -31
  8. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/skills-recommend/claude-code/jfox-organize/SKILL.md +13 -3
  9. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/test_cli_format.py +49 -0
  10. jfox_cli-0.2.2/tests/unit/test_logging_config.py +34 -0
  11. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/uv.lock +1 -1
  12. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/.githooks/pre-push +0 -0
  13. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/.github/workflows/integration-test.yml +0 -0
  14. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/.github/workflows/publish.yml +0 -0
  15. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/.gitignore +0 -0
  16. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/.python-version +0 -0
  17. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/AGENTS.md +0 -0
  18. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/CHANGELOG.md +0 -0
  19. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/DEVELOPMENT_PLAN.md +0 -0
  20. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/README.md +0 -0
  21. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/SESSION.md +0 -0
  22. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/SESSION_SUMMARY.md +0 -0
  23. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/docs/superpowers/plans/2026-04-11-bulk-import-bm25-fix.md +0 -0
  24. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/docs/superpowers/plans/2026-04-11-edit-command.md +0 -0
  25. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/docs/superpowers/plans/2026-04-11-unify-format-option.md +0 -0
  26. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/docs/superpowers/plans/2026-04-12-ci-coverage-optimization.md +0 -0
  27. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/docs/superpowers/plans/2026-04-12-edit-content-file.md +0 -0
  28. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/docs/superpowers/plans/2026-04-12-fix-index-rebuild-clear.md +0 -0
  29. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/docs/superpowers/plans/2026-04-12-fix-index-verify-id-mismatch.md +0 -0
  30. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/docs/superpowers/plans/2026-04-12-fix-jfox-health-skill-kb-param.md +0 -0
  31. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/docs/superpowers/plans/2026-04-12-index-kb-param.md +0 -0
  32. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/docs/superpowers/plans/2026-04-12-lazy-import-perf.md +0 -0
  33. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/docs/superpowers/plans/2026-04-12-skill-redesign.md +0 -0
  34. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/docs/superpowers/specs/2026-04-03-bugfixes-design.md +0 -0
  35. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/docs/superpowers/specs/2026-04-12-skill-redesign-design.md +0 -0
  36. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/docs/superpowers/specs/2026-04-13-pr-auto-code-review-design.md +0 -0
  37. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/jessica-jones-static-cable.md +0 -0
  38. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/jfox/__main__.py +0 -0
  39. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/jfox/bm25_index.py +0 -0
  40. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/jfox/config.py +0 -0
  41. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/jfox/embedding_backend.py +0 -0
  42. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/jfox/formatters.py +0 -0
  43. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/jfox/git_extractor.py +0 -0
  44. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/jfox/global_config.py +0 -0
  45. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/jfox/graph.py +0 -0
  46. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/jfox/indexer.py +0 -0
  47. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/jfox/kb_manager.py +0 -0
  48. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/jfox/models.py +0 -0
  49. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/jfox/note.py +0 -0
  50. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/jfox/performance.py +0 -0
  51. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/jfox/search_engine.py +0 -0
  52. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/jfox/template.py +0 -0
  53. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/jfox/template_cli.py +0 -0
  54. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/jfox/vector_store.py +0 -0
  55. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/pytest.ini +0 -0
  56. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/run_full_test.ps1 +0 -0
  57. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/skill/evals/evals.json +0 -0
  58. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/skill/knowledge-base-notes/SKILL.md +0 -0
  59. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/skill/knowledge-base-workspace/SKILL.md +0 -0
  60. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/skills-recommend/README.md +0 -0
  61. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/skills-recommend/claude-code/jfox-search/SKILL.md +0 -0
  62. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/COVERAGE_PLAN.md +0 -0
  63. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/MIGRATION.md +0 -0
  64. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/TESTS.md +0 -0
  65. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/conftest.py +0 -0
  66. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/integration/__init__.py +0 -0
  67. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/integration/test_backlinks.py +0 -0
  68. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/performance/__init__.py +0 -0
  69. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/performance/test_performance.py +0 -0
  70. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/test_advanced_features.py +0 -0
  71. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/test_config_unit.py +0 -0
  72. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/test_core_workflow.py +0 -0
  73. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/test_hybrid_search.py +0 -0
  74. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/test_integration.py +0 -0
  75. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/test_kb_current.py +0 -0
  76. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/test_suggest_links.py +0 -0
  77. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/unit/__init__.py +0 -0
  78. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/unit/test_bm25_batch.py +0 -0
  79. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/unit/test_edit.py +0 -0
  80. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/unit/test_format_unify.py +0 -0
  81. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/unit/test_formatters.py +0 -0
  82. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/unit/test_git_extractor.py +0 -0
  83. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/unit/test_global_config.py +0 -0
  84. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/unit/test_index_kb_param.py +0 -0
  85. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/unit/test_indexer_clear_before_rebuild.py +0 -0
  86. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/unit/test_indexer_verify.py +0 -0
  87. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/unit/test_kb_manager.py +0 -0
  88. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/unit/test_lazy_import.py +0 -0
  89. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/unit/test_template.py +0 -0
  90. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/unit/test_template_cli.py +0 -0
  91. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/unit/test_vector_store_clear.py +0 -0
  92. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/utils/__init__.py +0 -0
  93. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/utils/assertions.py +0 -0
  94. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/utils/jfox_cli.py +0 -0
  95. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/utils/note_generator.py +0 -0
  96. {jfox_cli-0.2.1 → jfox_cli-0.2.2}/tests/utils/temp_kb.py +0 -0
@@ -91,7 +91,7 @@ Notes are Markdown files with YAML frontmatter stored under `~/.zettelkasten/<kb
91
91
 
92
92
  ## Conventions
93
93
 
94
- - **Version bump**: 发版时必须同时修改 `pyproject.toml` 和 `jfox/__init__.py` 两处版本号(曾有 #88 遗漏 `__init__.py` 的教训)
94
+ - **Version bump**: 发版时必须同时修改 `pyproject.toml`、`jfox/__init__.py` 和 `uv.lock` 三处版本号。先改前两个文件,再跑 `uv lock` 更新 lock 文件(曾有 #88 遗漏 `__init__.py` 的教训)
95
95
  - **Line length**: 100 chars (black + ruff configured in `pyproject.toml`)
96
96
  - **Comments/docs**: Chinese (中文)
97
97
  - **Adding a CLI command**: Add `@app.command()` in `cli.py`, implement `_xxx_impl()` helper, add `--kb` and `--format json` support
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jfox-cli
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: JFox - Zettelkasten 知识管理 CLI 工具
5
5
  Project-URL: Homepage, https://github.com/zhuxixi/jfox
6
6
  Project-URL: Repository, https://github.com/zhuxixi/jfox
@@ -1,5 +1,5 @@
1
1
  """JFox - Zettelkasten 知识管理工具"""
2
2
 
3
- __version__ = "0.2.1"
3
+ __version__ = "0.2.2"
4
4
  __author__ = "User"
5
5
  __email__ = "user@example.com"
@@ -37,6 +37,19 @@ from .template_cli import template_app
37
37
  logging.basicConfig(
38
38
  level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
39
39
  )
40
+
41
+ # 抑制第三方库的 INFO/DEBUG 日志,避免污染 CLI 输出
42
+ for _lib in (
43
+ "sentence_transformers",
44
+ "torch",
45
+ "chromadb",
46
+ "tqdm",
47
+ "urllib3",
48
+ "watchdog",
49
+ "PIL",
50
+ ):
51
+ logging.getLogger(_lib).setLevel(logging.WARNING)
52
+
40
53
  logger = logging.getLogger(__name__)
41
54
 
42
55
  # 创建应用
@@ -365,7 +378,7 @@ def _add_note_impl(
365
378
 
366
379
  @app.command()
367
380
  def add(
368
- content: str = typer.Argument(..., help="笔记内容(支持 [[笔记标题]] 格式链接)"),
381
+ content: Optional[str] = typer.Argument(None, help="笔记内容(支持 [[笔记标题]] 格式链接)"),
369
382
  title: Optional[str] = typer.Option(None, "--title", "-t", help="笔记标题"),
370
383
  note_type: str = typer.Option(
371
384
  "fleeting", "--type", help="笔记类型 (fleeting/literature/permanent)"
@@ -375,6 +388,9 @@ def add(
375
388
  template: Optional[str] = typer.Option(
376
389
  None, "--template", "-T", help="使用模板创建笔记 (quick/meeting/literature)"
377
390
  ),
391
+ content_file: Optional[str] = typer.Option(
392
+ None, "--content-file", help="从文件读取内容(用 - 表示 stdin)"
393
+ ),
378
394
  kb: Optional[str] = typer.Option(None, "--kb", "-k", help="目标知识库名称"),
379
395
  output_format: str = typer.Option("table", "--format", "-f", help="输出格式: json, table"),
380
396
  json_output: bool = typer.Option(
@@ -387,6 +403,18 @@ def add(
387
403
  if json_output:
388
404
  output_format = "json"
389
405
 
406
+ # content 和 --content-file 互斥
407
+ if content is not None and content_file is not None:
408
+ raise ValueError("不能同时指定内容参数和 --content-file,请选择其一")
409
+
410
+ # 从文件读取内容
411
+ if content_file is not None:
412
+ content = _read_content_file(content_file)
413
+
414
+ # 至少提供一种内容来源
415
+ if not content:
416
+ raise ValueError("请提供笔记内容(位置参数或 --content-file)")
417
+
390
418
  # 如果指定了知识库,临时切换
391
419
  if kb:
392
420
  from .config import use_kb
@@ -956,6 +984,26 @@ def delete(
956
984
  raise typer.Exit(1)
957
985
 
958
986
 
987
+ def _read_content_file(content_file: str) -> str:
988
+ """从文件或 stdin 读取内容(--content-file 共用逻辑)"""
989
+ if content_file == "-":
990
+ import sys
991
+
992
+ return sys.stdin.read()
993
+
994
+ p = Path(content_file)
995
+ if not p.exists():
996
+ raise ValueError(f"文件不存在: {content_file}")
997
+ if not p.is_file():
998
+ raise ValueError(f"路径不是文件: {content_file}")
999
+ try:
1000
+ return p.read_text(encoding="utf-8")
1001
+ except PermissionError:
1002
+ raise ValueError(f"无权限读取文件: {content_file}")
1003
+ except UnicodeDecodeError:
1004
+ raise ValueError(f"文件编码错误(需要 UTF-8): {content_file}")
1005
+
1006
+
959
1007
  def _edit_impl(
960
1008
  note_id: str,
961
1009
  content: Optional[str],
@@ -973,17 +1021,7 @@ def _edit_impl(
973
1021
 
974
1022
  # 从文件读取内容
975
1023
  if content_file is not None:
976
- p = Path(content_file)
977
- if not p.exists():
978
- raise ValueError(f"文件不存在: {content_file}")
979
- if not p.is_file():
980
- raise ValueError(f"路径不是文件: {content_file}")
981
- try:
982
- content = p.read_text(encoding="utf-8")
983
- except PermissionError:
984
- raise ValueError(f"无权限读取文件: {content_file}")
985
- except UnicodeDecodeError:
986
- raise ValueError(f"文件编码错误(需要 UTF-8): {content_file}")
1024
+ content = _read_content_file(content_file)
987
1025
 
988
1026
  # 验证:至少指定一个编辑字段
989
1027
  if all(v is None for v in [content, title, tags, note_type, source]):
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "jfox-cli"
7
- version = "0.2.1"
7
+ version = "0.2.2"
8
8
  description = "JFox - Zettelkasten 知识管理 CLI 工具"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -79,6 +79,67 @@ jfox kb current --format json # 当前知识库
79
79
  jfox kb rename <old> <new> # 重命名
80
80
  ```
81
81
 
82
+ ## 笔记 CRUD
83
+
84
+ ### 添加笔记
85
+
86
+ ```bash
87
+ # 快速添加(内容直接作为参数)
88
+ jfox add "笔记内容,支持 [[其他笔记标题]] 链接" --title "笔记标题"
89
+
90
+ # 指定类型和标签
91
+ jfox add "内容" --title "标题" --type permanent --tag design --tag backend
92
+
93
+ # 从文件读取内容(v0.2.1+,适合长文本)
94
+ jfox add --content-file notes/draft.md --title "标题" --type literature
95
+
96
+ # 从 stdin 读取
97
+ cat notes.txt | jfox add --content-file - --title "标题"
98
+
99
+ # 使用模板
100
+ jfox add --template meeting --title "周会记录"
101
+ ```
102
+
103
+ 笔记类型:
104
+ - `fleeting`(默认)— 快速捕获,稍后提炼
105
+ - `literature` — 阅读笔记
106
+ - `permanent` — 已提炼的知识
107
+
108
+ ### 编辑笔记
109
+
110
+ ```bash
111
+ # 编辑内容和标题
112
+ jfox edit <note_id> --content "新内容" --title "新标题"
113
+
114
+ # 从文件读取内容(v0.2.1+,适合长文本)
115
+ jfox edit <note_id> --content-file updated.md
116
+
117
+ # 修改标签和类型
118
+ jfox edit <note_id> --tag new-tag1 --tag new-tag2 --type permanent
119
+
120
+ # 在指定知识库中编辑
121
+ jfox edit <note_id> --kb work --content "新内容"
122
+ ```
123
+
124
+ 编辑会保留原始笔记 ID 和创建时间。
125
+
126
+ ### 删除笔记
127
+
128
+ ```bash
129
+ jfox delete <note_id> # 需确认
130
+ jfox delete <note_id> --force # 跳过确认
131
+ ```
132
+
133
+ ### 查看笔记
134
+
135
+ ```bash
136
+ jfox list --format json --limit 50 # 列出笔记
137
+ jfox list --type permanent --format json # 按类型筛选
138
+ jfox daily --json # 今天的笔记
139
+ jfox daily --date 2026-04-01 --json # 指定日期
140
+ jfox refs --search "<标题>" --format json # 查看反向链接
141
+ ```
142
+
82
143
  ### 删除知识库
83
144
 
84
145
  ```bash
@@ -228,7 +289,9 @@ Score = 100
228
289
 
229
290
  ## 命令参考
230
291
 
231
- 以下仅列出知识库管理和健康检查相关命令。所有命令支持 `--kb <name>` 指定知识库,省略时使用当前默认知识库。
292
+ 以下仅列出知识库管理、笔记 CRUD 和健康检查相关命令。所有命令支持 `--kb <name>` 指定知识库,省略时使用当前默认知识库。
293
+
294
+ **约定**:所有命令均支持 `--format json` 输出 JSON,也可使用快捷方式 `--json`(两者等价)。下文示例统一使用 `--json`。
232
295
 
233
296
  ### 知识库管理
234
297
 
@@ -244,15 +307,35 @@ jfox kb remove <name> --force # 删除(含文件,不可恢复
244
307
  jfox status --format json # 知识库状态
245
308
  ```
246
309
 
310
+ ### 笔记 CRUD
311
+
312
+ ```bash
313
+ jfox add "<content>" --title "<title>" --type <type> --tag <tags> # 添加笔记
314
+ jfox add --content-file <path> --title "<title>" # 从文件添加
315
+ jfox edit <id> --content "<new>" --title "<title>" # 编辑笔记
316
+ jfox edit <id> --content-file <path> # 从文件编辑
317
+ jfox delete <id> --force # 删除笔记
318
+ jfox list --format json --limit <N> # 列出笔记
319
+ jfox daily --json # 今天的笔记
320
+ jfox daily --date YYYY-MM-DD --json # 指定日期
321
+ jfox refs --search "<title>" --format json # 反向链接
322
+ ```
323
+
324
+ ### 数据导入
325
+
326
+ ```bash
327
+ jfox ingest-log <repo-path> --limit <N> --type fleeting --kb <name> # Git 仓库导入
328
+ jfox bulk-import <file.json> --type fleeting --kb <name> # 批量导入
329
+ ```
330
+
247
331
  ### 健康检查
248
332
 
249
333
  ```bash
250
- jfox graph --stats --json # 图谱指标(与 --orphans 互斥,分开运行)
251
- jfox graph --orphans --json # 孤立笔记列表
252
- jfox index verify # 索引完整性验证
253
- jfox index rebuild # 重建索引
254
- jfox list --format json --limit <N> # 笔记列表
255
- jfox inbox --json --limit <N> # 未处理笔记
334
+ jfox graph --stats --json # 图谱指标(与 --orphans 互斥,分开运行)
335
+ jfox graph --orphans --json # 孤立笔记列表
336
+ jfox index verify # 索引完整性验证
337
+ jfox index rebuild # 重建索引
338
+ jfox inbox --json --limit <N> # 未处理笔记
256
339
  ```
257
340
 
258
341
  > 搜索、导入、整理等高频操作命令见对应技能文档(jfox-search、jfox-ingest、jfox-organize)。
@@ -265,3 +348,4 @@ jfox inbox --json --limit <N> # 未处理笔记
265
348
  | "Path is outside managed directory" | 所有知识库必须在 `~/.zettelkasten/` 下 |
266
349
  | `jfox: command not found` | 安装:`uv tool install jfox-cli` |
267
350
  | 索引过时或 `jfox index verify` 异常 | 运行 `jfox index rebuild` 重建搜索索引 |
351
+ | `ingest-log` 报 "Not a git repository" | 提供正确的 Git 仓库路径 |
@@ -57,17 +57,19 @@ git -C <path> remote get-url origin
57
57
 
58
58
  ### Step 3: 采集 git log
59
59
 
60
+ 使用 `jfox ingest-log` 命令(基于 `jfox/git_extractor.py` 模块),一行完成提取 + 转换 + 导入:
61
+
60
62
  ```bash
61
- git -C <path> log --format="%H|%s|%b|%an|%ad" --date=short -50
63
+ jfox ingest-log path/to/repo --limit 50 --kb name --type fleeting
62
64
  ```
63
65
 
64
- 解析每条 commit,转化为笔记结构:
65
-
66
- - **title**: commit subject(第一行 `%s`)
67
- - **content**: 包含 commit hash、完整 body、作者、日期
68
- - **tags**: `source:<repo-name>`, `source:git-log`
66
+ 该命令会:
67
+ - 调用 `git log` 提取 commit 历史
68
+ - 自动解析为结构化数据(hash, subject, author, date, body)
69
+ - 转换为 fleeting 笔记并批量导入知识库
70
+ - 自动添加标签:`source:repo-name`, `source:git-log`
69
71
 
70
- 示例笔记内容:
72
+ 生成笔记示例:
71
73
  ```
72
74
  Commit: a1b2c3d
73
75
  Author: 张三
@@ -78,6 +80,8 @@ feat: add user authentication module
78
80
  实现了 JWT 认证,支持 refresh token 机制。
79
81
  ```
80
82
 
83
+ > **约定**:`jfox ingest-log` 支持 `--format json` 输出 JSON,也可使用快捷方式 `--json`(两者等价)。下文示例统一使用 `--json`。
84
+
81
85
  ### Step 4: 采集 GitHub PRs
82
86
 
83
87
  仅当 Step 1 检测到 GitHub 仓库时执行。
@@ -139,38 +143,40 @@ Comments:
139
143
  - @zhangsan: 已修复,请验证
140
144
  ```
141
145
 
142
- ### Step 6: 去重与导入
146
+ ### Step 6: 导入 GitHub 数据(git-log 已在 Step 3 完成)
147
+
148
+ git-log 数据已通过 `jfox ingest-log` 完成导入,此步骤仅处理 GitHub PR/Issues 数据。
143
149
 
144
150
  **去重检查**:导入前检查知识库中是否已有该仓库的数据:
145
151
  ```bash
146
- jfox search "<repo-name>" --format json
152
+ jfox search "repo-name" --format json
147
153
  ```
148
154
 
149
- 如果已有记录,只导入新增的条目(通过 commit hash、PR 编号、Issue 编号判断)。
155
+ 如果已有记录,只导入新增的条目(通过 PR 编号、Issue 编号判断)。
150
156
 
151
- **生成临时 JSON 文件**:将所有待导入记录组装为 JSON 数组:
157
+ **生成临时 JSON 文件**:将 PR/Issues 数据组装为 JSON 数组(仅 GitHub 数据):
152
158
 
153
159
  ```json
154
160
  [
155
- {
156
- "title": "feat: add user authentication module",
157
- "content": "Commit: a1b2c3d\nAuthor: 张三\nDate: 2026-04-10\n\n实现了 JWT 认证,支持 refresh token 机制。",
158
- "tags": ["source:my-app", "source:git-log"]
159
- },
160
161
  {
161
162
  "title": "Add user authentication",
162
163
  "content": "PR #42: Add user authentication\nState: merged\nAuthor: zhangsan\n...",
163
164
  "tags": ["source:my-app", "source:pr"]
165
+ },
166
+ {
167
+ "title": "Login page crashes on mobile",
168
+ "content": "Issue #15: Login page crashes on mobile\nState: closed\n...",
169
+ "tags": ["source:my-app", "source:issue"]
164
170
  }
165
171
  ]
166
172
  ```
167
173
 
168
174
  保存到临时文件(使用跨平台路径),然后执行导入:
169
175
  ```bash
170
- jfox bulk-import <temp-file.json> --type fleeting [--kb <name>]
176
+ jfox bulk-import temp-file.json --type fleeting --kb name
171
177
  ```
172
178
 
173
- > **注意**: `jfox bulk-import` 使用 `--json`/`--no-json`(默认开启),不要使用 `--format json`。
179
+ > **约定**:`jfox bulk-import` 默认输出 JSON。使用 `--no-json` 切换为 table 格式,或 `--format table` 显式指定。
174
180
 
175
181
  ### Step 7: 确认报告
176
182
 
@@ -191,10 +197,12 @@ jfox bulk-import <temp-file.json> --type fleeting [--kb <name>]
191
197
  jfox add "<content>" --title "<title>" --type fleeting --tag <tags> [--kb <name>]
192
198
  ```
193
199
 
194
- > **注意**: `jfox add` 使用 `--json`/`--no-json`(默认开启),不要使用 `--format json`。
200
+ > **约定**:`jfox add` 支持 `--format json` 输出 JSON,也可使用快捷方式 `--json`(两者等价)。
195
201
 
196
202
  ## 笔记格式规范
197
203
 
204
+ > git-log 格式由 `jfox ingest-log` 自动处理,以下规范主要供理解输出结构参考,以及手动处理 GitHub PR/Issues 数据时使用。
205
+
198
206
  | 数据源 | title 来源 | content 内容 | 额外标签 |
199
207
  |--------|-----------|-------------|---------|
200
208
  | git-log | commit subject | hash, author, date, body | `source:<repo-name>`, `source:git-log` |
@@ -212,26 +220,30 @@ jfox add "<content>" --title "<title>" --type fleeting --tag <tags> [--kb <name>
212
220
 
213
221
  ```bash
214
222
  # 检测仓库类型
215
- git -C <path> remote get-url origin
223
+ git -C path/to/repo remote get-url origin
216
224
  gh auth status
217
225
 
218
- # 采集数据
219
- git -C <path> log --format="%H|%s|%b|%an|%ad" --date=short -50
220
- gh pr list --repo <owner/repo> --state all --limit 20 --json number,title,body,state,author,createdAt,updatedAt,labels
221
- gh pr view <number> --repo <owner/repo> --json comments
222
- gh issue list --repo <owner/repo> --state all --limit 30 --json number,title,body,state,author,createdAt,labels,comments
226
+ # 采集 git log(一行完成提取+导入)
227
+ jfox ingest-log path/to/repo --limit 50 --kb name --type fleeting
228
+
229
+ # 采集 GitHub 数据
230
+ gh pr list --repo owner/repo --state all --limit 20 --json number,title,body,state,author,createdAt,updatedAt,labels
231
+ gh pr view number --repo owner/repo --json comments
232
+ gh issue list --repo owner/repo --state all --limit 30 --json number,title,body,state,author,createdAt,labels,comments
223
233
 
224
234
  # 去重检查
225
- jfox search "<repo-name>" --format json
235
+ jfox search "repo-name" --format json
236
+
237
+ # 导入 GitHub 数据
238
+ jfox bulk-import file.json --type fleeting --kb name
226
239
 
227
- # 导入
228
- jfox bulk-import <file.json> --type fleeting [--kb <name>]
229
- jfox add "<content>" --title "<title>" --type fleeting [--kb <name>]
240
+ # 手动添加单条笔记
241
+ jfox add "content" --title "title" --type fleeting --kb name
230
242
  ```
231
243
 
232
244
  ## 错误处理
233
245
 
234
- - **"Not a git repository"**: 提示用户提供正确的仓库路径
235
- - **`gh: not found`** 或 `gh auth status` 失败: 跳过 GitHub PR/Issues 导入,仅导入 git log
246
+ - **"Not a git repository"**: `jfox ingest-log` 会报错,提示用户提供正确的仓库路径
247
+ - **`gh: not found`** 或 `gh auth status` 失败: 跳过 GitHub PR/Issues 导入,仅用 `jfox ingest-log` 导入 git log
236
248
  - **"Knowledge base not found"**: 提示用户先运行 `/jfox-common` 创建知识库
237
249
  - **Bulk import 部分失败**: 报告成功/失败数量,失败记录不重试
@@ -62,7 +62,7 @@ jfox inbox --json --limit 50
62
62
  jfox delete <原始-id> --force
63
63
  ```
64
64
 
65
- > **注意**:`jfox add` `jfox delete` 使用 `--json`/`--no-json`(默认开启),不要用 `--format json`。
65
+ > **约定**:所有命令均支持 `--format json` 输出 JSON,也可使用快捷方式 `--json`(两者等价)。下文示例统一使用 `--json`。
66
66
 
67
67
  ### 提炼策略表
68
68
 
@@ -91,9 +91,12 @@ jfox graph --orphans --json
91
91
  3. 如果有匹配度 >= 0.6 的结果,建议添加链接:
92
92
  ```bash
93
93
  jfox edit <孤立笔记_id> --content "原内容... [[相关笔记标题]]"
94
+
95
+ # 使用文件内容编辑(适合长文本)
96
+ jfox edit <孤立笔记_id> --content-file updated.md
94
97
  ```
95
98
 
96
- > **注意**:`jfox edit` 使用 `--json`/`--no-json`(默认开启),不要用 `--format json`。
99
+ > **约定**:`jfox edit` 支持 `--format json`,也可使用快捷方式 `--json`(两者等价)。
97
100
 
98
101
  ### 确认改善
99
102
 
@@ -117,6 +120,12 @@ jfox graph --stats --json
117
120
  **创建笔记**:
118
121
  ```bash
119
122
  jfox add "<内容>" --title "<标题>" --type <fleeting|permanent> --tag <tags> [--kb <name>]
123
+
124
+ # 从文件读取内容(适合长文本,避免 shell 转义问题)
125
+ jfox add --content-file notes/draft.md --title "<标题>" --type permanent --tag <tags> [--kb <name>]
126
+
127
+ # 从 stdin 读取
128
+ echo "内容" | jfox add --content-file - --title "<标题>" --type fleeting
120
129
  ```
121
130
 
122
131
  对于 permanent 笔记,先运行 `jfox suggest-links` 查找 `[[wiki links]]` 再插入。
@@ -151,6 +160,7 @@ jfox graph --orphans --json # 查找孤立笔记
151
160
  jfox graph --stats --json # 图谱统计指标
152
161
  jfox list --format json --limit <N> # 列出笔记
153
162
  jfox daily --json # 查看今天的笔记
163
+ jfox daily --date 2026-04-01 --json # 查看指定日期的笔记
154
164
  ```
155
165
 
156
166
  ## 错误处理
@@ -158,7 +168,7 @@ jfox daily --json # 查看今天的笔记
158
168
  - **收件箱为空** → 告知用户 "收件箱为空,无需整理",可跳到 Step 3 图谱优化
159
169
  - **`jfox suggest-links` 返回低匹配度**(score < 0.6)→ 跳过链接推荐,不强制添加
160
170
  - **`jfox delete` 目标 ID 不存在** → 报告错误,跳过继续处理其他笔记
161
- - **`jfox add` / `jfox edit` / `jfox delete` 使用 `--json`/`--no-json`**,不要用 `--format json`
171
+ - **`jfox add` / `jfox edit` / `jfox delete`** 支持 `--format json`,也可使用快捷方式 `--json`
162
172
 
163
173
  ## 使用建议
164
174
 
@@ -324,6 +324,55 @@ class TestCLIFormat:
324
324
  # ==========================================================================
325
325
  # Add 命令测试
326
326
  # ==========================================================================
327
+ def test_add_content_file(self, cli, tmp_path):
328
+ """测试 add 命令 --content-file 从文件读取内容"""
329
+ content_file = tmp_path / "note.md"
330
+ content_file.write_text("从文件读取的笔记内容", encoding="utf-8")
331
+
332
+ result = cli.run("add", "--content-file", str(content_file), "--title", "FileContent")
333
+
334
+ assert result.success
335
+ data = result.data
336
+ assert data["success"] is True
337
+ assert data["note"]["title"] == "FileContent"
338
+
339
+ def test_add_content_file_mutual_exclusive(self, cli, tmp_path):
340
+ """测试 add 命令 content 参数和 --content-file 不能同时指定"""
341
+ content_file = tmp_path / "note.md"
342
+ content_file.write_text("file content", encoding="utf-8")
343
+
344
+ result = cli.run("add", "直接内容", "--content-file", str(content_file))
345
+
346
+ assert not result.success
347
+ assert "不能同时指定" in result.stderr or "不能同时指定" in result.stdout
348
+
349
+ def test_add_content_file_not_found(self, cli):
350
+ """测试 add 命令 --content-file 文件不存在时报错"""
351
+ result = cli.run("add", "--content-file", "/nonexistent/file.md")
352
+
353
+ assert not result.success
354
+ assert "文件不存在" in result.stderr or "文件不存在" in result.stdout
355
+
356
+ def test_add_content_file_empty(self, cli):
357
+ """测试 add 命令不提供内容时报错"""
358
+ result = cli.run("add")
359
+
360
+ assert not result.success
361
+
362
+ def test_add_content_file_with_wiki_links(self, cli, tmp_path):
363
+ """测试 add 命令 --content-file 内容中的 [[wiki链接]] 正常解析"""
364
+ # 先创建一个目标笔记
365
+ cli.add("目标笔记内容", title="目标笔记")
366
+
367
+ content_file = tmp_path / "linked.md"
368
+ content_file.write_text("引用 [[目标笔记]] 的内容", encoding="utf-8")
369
+
370
+ result = cli.run("add", "--content-file", str(content_file), "--title", "带链接的笔记")
371
+
372
+ assert result.success
373
+ data = result.data
374
+ assert data["note"]["links"]
375
+
327
376
  def test_add_format_json(self, cli):
328
377
  """测试 add 命令 --format json"""
329
378
  result = cli.run("add", "test content", "--title", "FormatTest", "--format", "json")
@@ -0,0 +1,34 @@
1
+ """测试日志配置:第三方库日志级别应被抑制为 WARNING"""
2
+
3
+ import logging
4
+
5
+
6
+ def test_third_party_loggers_suppressed():
7
+ """导入 jfox.cli 后,第三方库的日志级别应为 WARNING 或更高"""
8
+ import jfox.cli # noqa: F401
9
+
10
+ noisy_libs = [
11
+ "sentence_transformers",
12
+ "torch",
13
+ "chromadb",
14
+ "tqdm",
15
+ "urllib3",
16
+ "watchdog",
17
+ "PIL",
18
+ ]
19
+ for lib in noisy_libs:
20
+ lib_logger = logging.getLogger(lib)
21
+ assert (
22
+ lib_logger.level >= logging.WARNING
23
+ ), f"{lib} logger level is {lib_logger.level}, expected >= {logging.WARNING}"
24
+
25
+
26
+ def test_jfox_own_logger_unchanged():
27
+ """jfox 自身的日志不应被抑制(保持继承 root 的 INFO)"""
28
+ import jfox.cli # noqa: F401
29
+
30
+ jfox_logger = logging.getLogger("jfox")
31
+ # jfox logger 未被显式设为 WARNING,保持 NOTSET 并继承 root 的 INFO
32
+ assert (
33
+ jfox_logger.level < logging.WARNING
34
+ ), f"jfox logger level is {jfox_logger.level}, should NOT be suppressed to WARNING"
@@ -851,7 +851,7 @@ wheels = [
851
851
 
852
852
  [[package]]
853
853
  name = "jfox-cli"
854
- version = "0.2.1"
854
+ version = "0.2.2"
855
855
  source = { editable = "." }
856
856
  dependencies = [
857
857
  { name = "chromadb" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes