jfox-cli 1.2.1__tar.gz → 1.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 (283) hide show
  1. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/CHANGELOG.md +8 -0
  2. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/PKG-INFO +1 -1
  3. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/__init__.py +1 -1
  4. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/cli.py +100 -8
  5. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/gem_synth/llm.py +44 -2
  6. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/graph.py +2 -8
  7. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/note_index.py +92 -0
  8. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/pyproject.toml +1 -1
  9. jfox_cli-1.2.2/tests/unit/test_add_backfill_links.py +345 -0
  10. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_gem_synth_llm.py +136 -0
  11. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_gem_synth_loop.py +3 -0
  12. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_note_index.py +203 -0
  13. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/uv.lock +1 -1
  14. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/.claude/skills/ci/SKILL.md +0 -0
  15. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/.claude/skills/release/SKILL.md +0 -0
  16. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/.claude/skills/release/release_helper.py +0 -0
  17. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/.claude-plugin/marketplace.json +0 -0
  18. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/.githooks/pre-push +0 -0
  19. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  20. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  21. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/.github/ISSUE_TEMPLATE/docs_improvement.yml +0 -0
  22. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  23. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  24. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/.github/workflows/integration-test.yml +0 -0
  25. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/.github/workflows/publish.yml +0 -0
  26. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/.gitignore +0 -0
  27. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/.python-version +0 -0
  28. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/AGENTS.md +0 -0
  29. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/CLAUDE.md +0 -0
  30. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/DEVELOPMENT_PLAN.md +0 -0
  31. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/README.md +0 -0
  32. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/SESSION.md +0 -0
  33. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/SESSION_SUMMARY.md +0 -0
  34. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/claude-code-plugin-guide.md +0 -0
  35. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/installation.md +0 -0
  36. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-11-bulk-import-bm25-fix.md +0 -0
  37. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-11-edit-command.md +0 -0
  38. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-11-unify-format-option.md +0 -0
  39. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-12-ci-coverage-optimization.md +0 -0
  40. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-12-edit-content-file.md +0 -0
  41. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-12-fix-index-rebuild-clear.md +0 -0
  42. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-12-fix-index-verify-id-mismatch.md +0 -0
  43. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-12-fix-jfox-health-skill-kb-param.md +0 -0
  44. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-12-index-kb-param.md +0 -0
  45. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-12-lazy-import-perf.md +0 -0
  46. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-12-skill-redesign.md +0 -0
  47. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-13-add-content-file.md +0 -0
  48. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-13-bulk-import-vectorstore-init.md +0 -0
  49. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-13-ingest-log-command.md +0 -0
  50. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-13-ingest-skill-sync.md +0 -0
  51. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-13-suppress-third-party-logging.md +0 -0
  52. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-13-sync-skills-with-cli.md +0 -0
  53. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-14-sync-docs-daemon-show.md +0 -0
  54. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-15-gpu-embedding.md +0 -0
  55. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-15-make-daemon-deps-required.md +0 -0
  56. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-15-session-summary-confirmation.md +0 -0
  57. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-16-fix-windows-daemon-console-window.md +0 -0
  58. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-18-daemon-timeout-and-index-rebuild.md +0 -0
  59. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-22-release-skill.md +0 -0
  60. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-26-fix-daemon-deprecation-warnings.md +0 -0
  61. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-27-intranet-model-download.md +0 -0
  62. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-28-tag-filtering.md +0 -0
  63. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-29-daemon-stop-fix.md +0 -0
  64. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-05-03-jfox-check.md +0 -0
  65. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-05-03-list-notes-skip-summary.md +0 -0
  66. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-05-03-log-level-fix.md +0 -0
  67. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-05-04-list-notes-index.md +0 -0
  68. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-05-06-atomic-write.md +0 -0
  69. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-05-09-session-note-type.md +0 -0
  70. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-05-10-jfox-plugin.md +0 -0
  71. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-05-19-fix-last-used-never.md +0 -0
  72. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-05-20-modelscope-migration.md +0 -0
  73. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-06-02-todo-note-type.md +0 -0
  74. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-06-16-kimi-code-auto-summary.md +0 -0
  75. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-06-17-index-rebuild-backlinks.md +0 -0
  76. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-06-17-using-jfox-skill.md +0 -0
  77. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-06-18-self-update-command.md +0 -0
  78. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-06-21-fragment-capture.md +0 -0
  79. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-06-21-gem-synthesis.md +0 -0
  80. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-06-25-gem-synth-throttle-ledger.md +0 -0
  81. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-04-03-bugfixes-design.md +0 -0
  82. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-04-12-skill-redesign-design.md +0 -0
  83. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-04-13-pr-auto-code-review-design.md +0 -0
  84. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-04-14-show-command-design.md +0 -0
  85. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-04-15-gpu-embedding-design.md +0 -0
  86. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-04-15-session-summary-confirmation-design.md +0 -0
  87. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-04-21-release-skill-design.md +0 -0
  88. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-04-26-fix-daemon-deprecation-warnings-design.md +0 -0
  89. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-04-27-intranet-model-download-design.md +0 -0
  90. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-04-28-tag-filtering-design.md +0 -0
  91. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-04-29-daemon-stop-fix-design.md +0 -0
  92. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-04-30-jfox-kb-env-var-design.md +0 -0
  93. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-05-03-jfox-check-design.md +0 -0
  94. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-05-03-list-notes-skip-summary-design.md +0 -0
  95. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-05-03-log-level-fix-design.md +0 -0
  96. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-05-04-list-notes-index-design.md +0 -0
  97. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-05-06-atomic-write-design.md +0 -0
  98. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-05-09-jfox-plugin-design.md +0 -0
  99. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-05-09-session-note-type-design.md +0 -0
  100. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-05-20-modelscope-migration-design.md +0 -0
  101. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-06-02-todo-note-type-design.md +0 -0
  102. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-06-16-kimi-code-auto-summary-design.md +0 -0
  103. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-06-17-index-rebuild-backlinks-design.md +0 -0
  104. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-06-17-using-jfox-skill-design.md +0 -0
  105. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-06-18-archive-soft-delete-design.md +0 -0
  106. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-06-18-self-update-command-design.md +0 -0
  107. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-06-21-fragment-capture-design.md +0 -0
  108. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-06-21-gem-synthesis-design.md +0 -0
  109. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-06-25-gem-synth-throttle-ledger-design.md +0 -0
  110. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/troubleshooting.md +0 -0
  111. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jessica-jones-static-cable.md +0 -0
  112. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/__main__.py +0 -0
  113. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/auto_summary/__init__.py +0 -0
  114. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/auto_summary/cli.py +0 -0
  115. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/auto_summary/extractor.py +0 -0
  116. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/auto_summary/kimi_source.py +0 -0
  117. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/auto_summary/ledger.py +0 -0
  118. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/auto_summary/loop.py +0 -0
  119. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/auto_summary/runner.py +0 -0
  120. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/auto_summary/scanner.py +0 -0
  121. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/auto_summary/sources.py +0 -0
  122. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/bm25_index.py +0 -0
  123. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/config.py +0 -0
  124. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/daemon/__init__.py +0 -0
  125. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/daemon/__main__.py +0 -0
  126. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/daemon/client.py +0 -0
  127. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/daemon/process.py +0 -0
  128. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/daemon/server.py +0 -0
  129. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/embedding_backend.py +0 -0
  130. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/formatters.py +0 -0
  131. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/fragment/__init__.py +0 -0
  132. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/fragment/cli.py +0 -0
  133. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/fragment/detector.py +0 -0
  134. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/fragment/service.py +0 -0
  135. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/fragment/store.py +0 -0
  136. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/gem_synth/__init__.py +0 -0
  137. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/gem_synth/anchors.py +0 -0
  138. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/gem_synth/cli.py +0 -0
  139. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/gem_synth/grounding.py +0 -0
  140. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/gem_synth/loop.py +0 -0
  141. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/gem_synth/paths.py +0 -0
  142. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/gem_synth/store.py +0 -0
  143. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/gem_synth/synthesizer.py +0 -0
  144. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/gem_synth/transcript.py +0 -0
  145. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/git_extractor.py +0 -0
  146. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/global_config.py +0 -0
  147. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/indexer.py +0 -0
  148. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/kb_manager.py +0 -0
  149. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/model_downloader.py +0 -0
  150. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/models.py +0 -0
  151. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/note.py +0 -0
  152. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/performance.py +0 -0
  153. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/search_engine.py +0 -0
  154. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/template.py +0 -0
  155. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/template_cli.py +0 -0
  156. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/utils.py +0 -0
  157. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/vector_store.py +0 -0
  158. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/package.json +0 -0
  159. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/cc-plugin/.claude-plugin/plugin.json +0 -0
  160. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/cc-plugin/hooks/fragment-capture.sh +0 -0
  161. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/cc-plugin/hooks/hooks.json +0 -0
  162. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/cc-plugin/skills/ingest/SKILL.md +0 -0
  163. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/cc-plugin/skills/manage/SKILL.md +0 -0
  164. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/cc-plugin/skills/organize/SKILL.md +0 -0
  165. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/cc-plugin/skills/search/SKILL.md +0 -0
  166. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/cc-plugin/skills/session-summary/SKILL.md +0 -0
  167. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/cc-plugin/skills/using-jfox/SKILL.md +0 -0
  168. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/kimi-plugin/README.md +0 -0
  169. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/kimi-plugin/kimi.plugin.json +0 -0
  170. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/kimi-plugin/skills/jfox-ingest/SKILL.md +0 -0
  171. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/kimi-plugin/skills/jfox-manage/SKILL.md +0 -0
  172. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/kimi-plugin/skills/jfox-organize/SKILL.md +0 -0
  173. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/kimi-plugin/skills/jfox-search/SKILL.md +0 -0
  174. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/kimi-plugin/skills/jfox-session-summary/SKILL.md +0 -0
  175. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/kimi-plugin/skills/using-jfox/SKILL.md +0 -0
  176. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/pytest.ini +0 -0
  177. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/run_full_test.ps1 +0 -0
  178. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/scripts/download-model-intranet.sh +0 -0
  179. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/skills-recommend/README.md +0 -0
  180. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/skills-recommend/kimi-cli/jfox-common/SKILL.md +0 -0
  181. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/skills-recommend/kimi-cli/jfox-ingest/SKILL.md +0 -0
  182. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/skills-recommend/kimi-cli/jfox-organize/SKILL.md +0 -0
  183. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/skills-recommend/kimi-cli/jfox-search/SKILL.md +0 -0
  184. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/skills-recommend/kimi-cli/jfox-session-summary/SKILL.md +0 -0
  185. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/skills-recommend/pi/jfox-ci/SKILL.md +0 -0
  186. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/skills-recommend/pi/jfox-common/SKILL.md +0 -0
  187. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/skills-recommend/pi/jfox-ingest/SKILL.md +0 -0
  188. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/skills-recommend/pi/jfox-organize/SKILL.md +0 -0
  189. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/skills-recommend/pi/jfox-release/SKILL.md +0 -0
  190. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/skills-recommend/pi/jfox-search/SKILL.md +0 -0
  191. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/skills-recommend/pi/jfox-session-summary/SKILL.md +0 -0
  192. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/COVERAGE_PLAN.md +0 -0
  193. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/MIGRATION.md +0 -0
  194. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/TESTS.md +0 -0
  195. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/conftest.py +0 -0
  196. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/integration/__init__.py +0 -0
  197. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/integration/test_auto_summary_kimi.py +0 -0
  198. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/integration/test_backlinks.py +0 -0
  199. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/integration/test_fragment_capture_flow.py +0 -0
  200. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/integration/test_gem_synth_flow.py +0 -0
  201. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/integration/test_index_rebuild_backlinks.py +0 -0
  202. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/integration/test_model_download.py +0 -0
  203. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/integration/test_tag_filter_cli.py +0 -0
  204. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/performance/__init__.py +0 -0
  205. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/performance/test_performance.py +0 -0
  206. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/test_advanced_features.py +0 -0
  207. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/test_cli_format.py +0 -0
  208. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/test_config_set_unit.py +0 -0
  209. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/test_config_unit.py +0 -0
  210. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/test_core_workflow.py +0 -0
  211. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/test_embedding_device.py +0 -0
  212. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/test_hybrid_search.py +0 -0
  213. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/test_integration.py +0 -0
  214. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/test_kb_current.py +0 -0
  215. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/test_suggest_links.py +0 -0
  216. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/__init__.py +0 -0
  217. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_archive.py +0 -0
  218. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_atomic_write.py +0 -0
  219. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_auto_summary_config_sources.py +0 -0
  220. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_auto_summary_defaults.py +0 -0
  221. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_auto_summary_extractor.py +0 -0
  222. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_auto_summary_ledger.py +0 -0
  223. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_auto_summary_runner.py +0 -0
  224. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_auto_summary_scanner.py +0 -0
  225. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_auto_summary_status.py +0 -0
  226. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_bm25_batch.py +0 -0
  227. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_check.py +0 -0
  228. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_content_file.py +0 -0
  229. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_daemon_cli.py +0 -0
  230. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_daemon_process.py +0 -0
  231. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_edit.py +0 -0
  232. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_embedding_local_load.py +0 -0
  233. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_format_unify.py +0 -0
  234. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_formatters.py +0 -0
  235. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_fragment_cli.py +0 -0
  236. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_fragment_config.py +0 -0
  237. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_fragment_detector.py +0 -0
  238. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_fragment_service.py +0 -0
  239. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_fragment_store.py +0 -0
  240. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_gem_synth_anchors.py +0 -0
  241. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_gem_synth_cli.py +0 -0
  242. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_gem_synth_config.py +0 -0
  243. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_gem_synth_grounding.py +0 -0
  244. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_gem_synth_store.py +0 -0
  245. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_gem_synth_synthesizer.py +0 -0
  246. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_gem_synth_transcript.py +0 -0
  247. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_git_extractor.py +0 -0
  248. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_global_config.py +0 -0
  249. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_index_kb_param.py +0 -0
  250. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_indexer_clear_before_rebuild.py +0 -0
  251. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_indexer_verify.py +0 -0
  252. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_kb_manager.py +0 -0
  253. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_kimi_extractor.py +0 -0
  254. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_kimi_scanner.py +0 -0
  255. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_lazy_import.py +0 -0
  256. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_ledger_migration.py +0 -0
  257. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_list_notes_skip.py +0 -0
  258. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_logging_config.py +0 -0
  259. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_model_downloader.py +0 -0
  260. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_note_candidate_fields.py +0 -0
  261. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_note_candidate_filename.py +0 -0
  262. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_note_type_candidate.py +0 -0
  263. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_rebuild_backlinks_impl.py +0 -0
  264. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_release_helper.py +0 -0
  265. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_runner_multi_source.py +0 -0
  266. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_scanner_list.py +0 -0
  267. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_session_file_source.py +0 -0
  268. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_session_note.py +0 -0
  269. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_session_source.py +0 -0
  270. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_show.py +0 -0
  271. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_system_prompt_sections.py +0 -0
  272. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_tag_filter.py +0 -0
  273. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_template.py +0 -0
  274. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_template_cli.py +0 -0
  275. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_update.py +0 -0
  276. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_use_kb_env_var.py +0 -0
  277. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_using_jfox_skill.py +0 -0
  278. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_vector_store_clear.py +0 -0
  279. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/utils/__init__.py +0 -0
  280. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/utils/assertions.py +0 -0
  281. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/utils/jfox_cli.py +0 -0
  282. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/utils/note_generator.py +0 -0
  283. {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/utils/temp_kb.py +0 -0
@@ -2,6 +2,14 @@
2
2
 
3
3
  All notable changes to jfox-cli will be documented in this file.
4
4
 
5
+ ## [1.2.2] - 2026-06-28
6
+
7
+ ### Fixes
8
+ - **gem-synth**: 剥 claude markdown 代码围栏,修合成全失败 (#283) (#288)
9
+ - **add**: 创建目标笔记后回填引用方的正向 links (#276)
10
+
11
+ [1.2.2]: https://github.com/zhuxixi/jfox/compare/v1.2.1...v1.2.2
12
+
5
13
  ## [1.2.1] - 2026-06-26
6
14
 
7
15
  ### Features
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jfox-cli
3
- Version: 1.2.1
3
+ Version: 1.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__ = "1.2.1"
3
+ __version__ = "1.2.2"
4
4
  __author__ = "User"
5
5
  __email__ = "user@example.com"
@@ -255,11 +255,9 @@ def _print_action_table(action: str, fields: dict):
255
255
 
256
256
  def extract_wiki_links(content: str) -> List[str]:
257
257
  """从内容中提取 [[...]] 格式的维基链接"""
258
- import re
258
+ from .note_index import extract_wiki_links_from_text
259
259
 
260
- pattern = r"\[\[(.*?)\]\]"
261
- matches = re.findall(pattern, content)
262
- return [m.strip() for m in matches]
260
+ return extract_wiki_links_from_text(content)
263
261
 
264
262
 
265
263
  def find_note_id_by_title_or_id(
@@ -380,8 +378,8 @@ def _rebuild_backlinks_impl(output_format: str = "table") -> Dict[str, Any]:
380
378
  changed_note_ids: List[str] = []
381
379
 
382
380
  for n in notes:
383
- # 注意:cli.py 模块级存在名为 list 的命令函数,会遮蔽 built-in list()
384
- # 因此这里使用切片复制列表。
381
+ # list 命令函数已重命名为 list_notes,不再遮蔽 built-in list()
382
+ # 保留切片复制以避免原地修改遍历中的列表。
385
383
  old_links = n.links[:]
386
384
  old_backlinks = n.backlinks[:]
387
385
  new_links_sorted = merged_links[n.id]
@@ -528,6 +526,74 @@ def _add_note_impl(
528
526
  note.save_note(target_note, add_to_index=False)
529
527
  backlink_updated += 1
530
528
 
529
+ # Backward 回填:正文中引用了新笔记标题但 links 未包含新笔记 ID 的笔记
530
+ # 把新笔记 ID 回填进它们的 links,并同步更新新笔记的 backlinks
531
+ from .note_index import get_note_index
532
+
533
+ idx = get_note_index()
534
+ idx.update_note_meta(new_note)
535
+
536
+ original_backlinks_count = len(new_note.backlinks)
537
+ forward_backfilled = 0
538
+ failed_refs: List[str] = []
539
+ rollback_failures: List[str] = []
540
+ backfilled_ref_ids: List[str] = []
541
+ for ref_meta in idx.find_notes_referencing_title(new_note.title):
542
+ if ref_meta.id == new_note.id:
543
+ continue
544
+ ref_note = note.load_note_by_id(ref_meta.id)
545
+ if not ref_note:
546
+ continue
547
+
548
+ ref_changed = new_note.id not in ref_note.links
549
+ backlink_changed = ref_note.id not in new_note.backlinks
550
+
551
+ if ref_changed:
552
+ ref_note.links.append(new_note.id)
553
+ if backlink_changed:
554
+ new_note.backlinks.append(ref_note.id)
555
+
556
+ if not ref_changed:
557
+ # ref_note.links 已包含新笔记,只需补齐 new_note.backlinks
558
+ continue
559
+
560
+ if note.save_note(ref_note, add_to_index=False):
561
+ forward_backfilled += 1
562
+ idx.update_note_meta(ref_note)
563
+ backfilled_ref_ids.append(ref_note.id)
564
+ else:
565
+ # save_note 内部已吞掉所有异常并返回 False;回滚内存修改保持双向一致
566
+ if new_note.id in ref_note.links:
567
+ ref_note.links.remove(new_note.id)
568
+ if backlink_changed and ref_note.id in new_note.backlinks:
569
+ new_note.backlinks.remove(ref_note.id)
570
+ failed_refs.append(ref_note.id)
571
+ logger.warning(f"回填正向链接时保存笔记 {ref_note.id} 失败")
572
+
573
+ new_note_backfill_save_failed = False
574
+ if len(new_note.backlinks) != original_backlinks_count:
575
+ if note.save_note(new_note, add_to_index=False):
576
+ idx.update_note_meta(new_note)
577
+ else:
578
+ new_note_backfill_save_failed = True
579
+ logger.warning("回填后保存新笔记 backlinks 失败")
580
+ # 回滚已持久化的 ref_note 正向链接,避免 links/backlinks 不一致
581
+ for ref_id in backfilled_ref_ids:
582
+ rb_note = note.load_note_by_id(ref_id)
583
+ if not rb_note:
584
+ rollback_failures.append(ref_id)
585
+ logger.warning(f"回滚时无法加载 ref_note {ref_id}")
586
+ continue
587
+ if new_note.id in rb_note.links:
588
+ rb_note.links.remove(new_note.id)
589
+ if note.save_note(rb_note, add_to_index=False):
590
+ idx.update_note_meta(rb_note)
591
+ else:
592
+ rollback_failures.append(ref_id)
593
+ logger.warning(f"回滚 ref_note {ref_id} 正向链接失败")
594
+ # 回滚内存中的 new_note.backlinks
595
+ new_note.backlinks = new_note.backlinks[:original_backlinks_count]
596
+
531
597
  result = {
532
598
  "success": True,
533
599
  "note": {
@@ -541,6 +607,12 @@ def _add_note_impl(
541
607
 
542
608
  if unresolved:
543
609
  result["warnings"] = f"Unresolved links: {', '.join(unresolved)}"
610
+ if failed_refs:
611
+ result["backfill_failures"] = failed_refs
612
+ if rollback_failures:
613
+ result["rollback_failures"] = rollback_failures
614
+ if new_note_backfill_save_failed:
615
+ result["backfill_note_save_failed"] = True
544
616
 
545
617
  if output_format == "json":
546
618
  print(output_json(result))
@@ -556,6 +628,26 @@ def _add_note_impl(
556
628
  )
557
629
  if backlink_updated > 0:
558
630
  console.print(f"[dim] Backlinks updated: {backlink_updated} note(s)[/dim]")
631
+ if forward_backfilled > 0:
632
+ console.print(
633
+ f"[dim] Forward links backfilled: {forward_backfilled} note(s)[/dim]"
634
+ )
635
+ if new_note_backfill_save_failed:
636
+ console.print(
637
+ " [yellow]Warning: Failed to save new note backlinks; "
638
+ "forward backfill has been rolled back[/yellow]"
639
+ )
640
+ if failed_refs:
641
+ console.print(
642
+ f" [yellow]Warning: Forward link backfill failed for "
643
+ f"{len(failed_refs)} note(s): {', '.join(failed_refs)}[/yellow]"
644
+ )
645
+ if rollback_failures:
646
+ console.print(
647
+ f" [red]Warning: Forward link rollback failed for "
648
+ f"{len(rollback_failures)} note(s): {', '.join(rollback_failures)}. "
649
+ f"Run 'jfox index rebuild --backlinks' to repair.[/red]"
650
+ )
559
651
  if unresolved:
560
652
  console.print(
561
653
  f" [yellow]Warning: Unresolved links - {', '.join(unresolved)}[/yellow]"
@@ -1028,8 +1120,8 @@ def _list_impl(
1028
1120
  raise ValueError(f"Unsupported format: {output_format}")
1029
1121
 
1030
1122
 
1031
- @app.command()
1032
- def list(
1123
+ @app.command(name="list")
1124
+ def list_notes(
1033
1125
  note_type: Optional[str] = typer.Option(None, "--type", "-t", help="筛选笔记类型"),
1034
1126
  tags: Optional[List[str]] = typer.Option(
1035
1127
  None, "--tag", help="按标签筛选(可多次使用,AND 逻辑)"
@@ -28,7 +28,8 @@ SYSTEM_PROMPT = """你是知识合成器。给定一段对话上下文和若干
28
28
  "knowledge_type": "factual|procedural|preference|constraint",
29
29
  "grounded_by": ["引用的永久笔记标题列表"]
30
30
  }
31
- 若上下文不足以合成有效知识,confidence 给低分(<0.3)并简述原因。不要编造基准里没有的事实。"""
31
+ 若上下文不足以合成有效知识,confidence 给低分(<0.3)并简述原因。不要编造基准里没有的事实。
32
+ 直接输出 JSON 对象本身,不要用 markdown 代码围栏包裹(不要 ```json ... ```)。"""
32
33
 
33
34
 
34
35
  def _resolve_claude_binary(cfg: GemSynthesisConfig) -> str:
@@ -204,6 +205,46 @@ def _invoke_claude(
204
205
  pass
205
206
 
206
207
 
208
+ def _parse_json_lenient(inner: Any) -> Optional[Dict[str, Any]]:
209
+ """从模型输出解析 JSON 对象,容忍 markdown 围栏 / 前导解释文本 / 前置代码块 / 尾部噪声。
210
+
211
+ 模型常把 JSON 包在 ```json ... ``` 里,或前后加解释文本/代码示例。早期用 fence-strip
212
+ 正则提取,但当 JSON 的 content 字段内部含 ``` 代码示例(代码宝石常见)时,正则会把
213
+ 内部 ``` 当外层围栏终点、截断 JSON(kimi R3/R4 issue-4/5)。正则路径本质脆弱。
214
+
215
+ 改解析式(JSON 解析器尊重字符串字面量,content 内的 ``` / { 干扰不了它):
216
+ 1. 直接 json.loads(裸 JSON:含 content 内代码围栏也安全,因 ``` 在字符串字面量内)
217
+ 2. 失败则扫描所有 { 位置,各自 raw_decode,取**跨度最大**的有效 JSON 对象——目标 gem
218
+ 比前导文本/代码块里的零散小对象(如 python 字典示例)更长,故胜出;schema 无关,
219
+ 且 content 内的 { 其片段跨度也小于整个 gem(kimi R5 issue-6)
220
+ 3. 都失败返回 None(调用方 mark_failed)
221
+ """
222
+ if isinstance(inner, dict):
223
+ return inner
224
+ if not isinstance(inner, str):
225
+ return None
226
+ s = inner.strip()
227
+ try:
228
+ obj = json.loads(s)
229
+ return obj if isinstance(obj, dict) else None
230
+ except json.JSONDecodeError:
231
+ pass
232
+ # 扫描所有 {,raw_decode 各自尝试,取跨度最大(字符最多)的有效 JSON 对象
233
+ decoder = json.JSONDecoder()
234
+ best: Optional[Dict[str, Any]] = None
235
+ best_end = -1
236
+ for i, ch in enumerate(s):
237
+ if ch != "{":
238
+ continue
239
+ try:
240
+ obj, end = decoder.raw_decode(s[i:])
241
+ except json.JSONDecodeError:
242
+ continue
243
+ if isinstance(obj, dict) and end > best_end:
244
+ best, best_end = obj, end
245
+ return best
246
+
247
+
207
248
  def synthesize_with_llm(
208
249
  turn_context: str,
209
250
  grounding: List[Dict[str, Any]],
@@ -223,7 +264,8 @@ def synthesize_with_llm(
223
264
  raw = _invoke_claude(prompt, cfg, stop_event)
224
265
  wrapper = json.loads(raw)
225
266
  inner = wrapper.get("result", raw) if isinstance(wrapper, dict) else raw
226
- parsed = json.loads(inner) if isinstance(inner, str) else inner
267
+ # 容忍 markdown 围栏/前导文本:解析式提取 JSON(content 内代码围栏也安全)
268
+ parsed = _parse_json_lenient(inner)
227
269
  if not isinstance(parsed, dict) or "title" not in parsed:
228
270
  logger.warning("LLM 输出缺 title: %r", parsed)
229
271
  logger.debug("LLM raw output: %r", raw)
@@ -7,7 +7,6 @@ Provides:
7
7
  - Hub and authority analysis
8
8
  """
9
9
 
10
- import re
11
10
  from dataclasses import dataclass
12
11
  from pathlib import Path
13
12
  from typing import Dict, List, Optional, Set, Tuple
@@ -17,6 +16,7 @@ from rich.console import Console
17
16
 
18
17
  from .config import ZKConfig
19
18
  from .models import Note
19
+ from .note_index import extract_wiki_links_from_text
20
20
 
21
21
  console = Console()
22
22
 
@@ -94,7 +94,7 @@ class KnowledgeGraph:
94
94
 
95
95
  # Third pass: extract and add wiki links from content [[...]]
96
96
  for note_id, note in self._note_cache.items():
97
- wiki_links = self._extract_wiki_links(note.content)
97
+ wiki_links = extract_wiki_links_from_text(note.content)
98
98
  for link_text in wiki_links:
99
99
  linked_id = self._resolve_link(link_text)
100
100
  if linked_id and linked_id in self._note_cache:
@@ -104,12 +104,6 @@ class KnowledgeGraph:
104
104
 
105
105
  return self
106
106
 
107
- def _extract_wiki_links(self, content: str) -> List[str]:
108
- """Extract [[...]] wiki links from content."""
109
- pattern = r"\[\[(.*?)\]\]"
110
- matches = re.findall(pattern, content)
111
- return [m.strip() for m in matches]
112
-
113
107
  def _resolve_link(self, link_text: str) -> Optional[str]:
114
108
  """Resolve a link text to a note ID."""
115
109
  # First try exact ID match
@@ -1,6 +1,7 @@
1
1
  """轻量级元数据索引,只解析 frontmatter 不读正文"""
2
2
 
3
3
  import logging
4
+ import re
4
5
  import time
5
6
  from dataclasses import dataclass, field
6
7
  from datetime import datetime
@@ -14,6 +15,41 @@ from .models import Note, NoteType, _to_bool
14
15
 
15
16
  logger = logging.getLogger(__name__)
16
17
 
18
+ # 维基链接正则:匹配 [[标题]] 语法(非贪婪,不支持跨行)
19
+ _WIKI_LINK_RE = re.compile(r"\[\[(.*?)\]\]")
20
+
21
+
22
+ def extract_wiki_links_from_text(text: str) -> List[str]:
23
+ """从文本中提取 [[...]] 格式的维基链接。
24
+
25
+ 支持常见变体:
26
+ - [[标题]]
27
+ - [[标题|别名]] -> 取标题部分
28
+ - [[标题#锚点]] -> 取标题部分
29
+ """
30
+ raw_links = [m.group(1).strip() for m in _WIKI_LINK_RE.finditer(text)]
31
+ return [_normalize_wiki_link_title(link) for link in raw_links]
32
+
33
+
34
+ def _normalize_wiki_link_title(link_text: str) -> str:
35
+ """把 [[标题|别名]] / [[标题#锚点]] 归一化为纯标题。"""
36
+ # 先取管道符左侧的显示标题/目标
37
+ target = link_text.split("|", 1)[0]
38
+ # 再取锚点左侧的标题主体
39
+ return target.split("#", 1)[0].strip()
40
+
41
+
42
+ def _strip_wiki_link_exclusions(text: str) -> str:
43
+ """移除不应参与 wiki-link 匹配的 Markdown 区域(fenced code block、HTML 注释)。
44
+
45
+ 这是轻量级处理,覆盖最常见的误匹配场景;不保证解析所有 Markdown 边界情况。
46
+ """
47
+ # fenced code block(支持可选语言标识)
48
+ text = re.sub(r"```[\s\S]*?```", "", text)
49
+ # HTML 注释
50
+ text = re.sub(r"<!--[\s\S]*?-->", "", text)
51
+ return text
52
+
17
53
 
18
54
  @dataclass
19
55
  class NoteMeta:
@@ -68,6 +104,29 @@ def _parse_frontmatter_only(filepath: Path) -> Optional[dict]:
68
104
  return None
69
105
 
70
106
 
107
+ def _read_body_after_frontmatter(filepath: Path) -> str:
108
+ """读取文件正文部分(跳过 frontmatter)。
109
+
110
+ 若文件没有 frontmatter,则返回全文。
111
+ 读取失败时返回空字符串。
112
+ """
113
+ try:
114
+ with open(filepath, "r", encoding="utf-8-sig") as f:
115
+ first_line = f.readline()
116
+ if first_line.strip() != "---":
117
+ # 没有 frontmatter,返回剩余全部内容
118
+ return first_line + f.read()
119
+
120
+ for line in f:
121
+ if line.strip() == "---":
122
+ return f.read()
123
+
124
+ # 有开始标记但没有结束标记,视为无效 frontmatter
125
+ return ""
126
+ except (OSError, UnicodeDecodeError):
127
+ return ""
128
+
129
+
71
130
  class NoteIndex:
72
131
  """轻量级元数据索引,CLI 模式每次启动时重建"""
73
132
 
@@ -164,6 +223,39 @@ class NoteIndex:
164
223
  prefix_lower = prefix.lower()
165
224
  return [m for m in self._by_id.values() if m.title.lower().startswith(prefix_lower)]
166
225
 
226
+ def find_notes_referencing_title(self, title: str) -> List[NoteMeta]:
227
+ """查找正文中引用了指定标题的笔记([[标题]] 语法)。
228
+
229
+ 基于索引中已缓存的文件路径直接读取正文,避免全量加载 Note 对象。
230
+ 匹配规则为大小写不敏感的精确标题匹配;frontmatter、 fenced code block、
231
+ HTML 注释中的 [[...]] 不参与匹配。支持 [[标题|别名]] / [[标题#锚点]] 变体。
232
+
233
+ 注意:与正向解析 find_note_id_by_title_or_id 的“标题包含”子串 fallback 不同,
234
+ 反向回填只回填精确标题引用,避免把 [[Foo]] 错误回填到标题为 "Foo Bar" 的笔记。
235
+
236
+ Args:
237
+ title: 被引用的笔记标题
238
+
239
+ Returns:
240
+ 引用了该标题的 NoteMeta 列表
241
+ """
242
+ title_lower = title.lower()
243
+ results: List[NoteMeta] = []
244
+
245
+ for meta in self._by_id.values():
246
+ filepath = Path(meta.filepath)
247
+ if not filepath.exists():
248
+ continue
249
+
250
+ body = _read_body_after_frontmatter(filepath)
251
+ body = _strip_wiki_link_exclusions(body)
252
+ for link_text in extract_wiki_links_from_text(body):
253
+ if link_text.lower() == title_lower:
254
+ results.append(meta)
255
+ break
256
+
257
+ return results
258
+
167
259
  def list_meta(
168
260
  self,
169
261
  note_type: Optional[NoteType] = None,
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "jfox-cli"
7
- version = "1.2.1"
7
+ version = "1.2.2"
8
8
  description = "JFox - Zettelkasten 知识管理 CLI 工具"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}