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.
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/CHANGELOG.md +8 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/PKG-INFO +1 -1
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/__init__.py +1 -1
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/cli.py +100 -8
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/gem_synth/llm.py +44 -2
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/graph.py +2 -8
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/note_index.py +92 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/pyproject.toml +1 -1
- jfox_cli-1.2.2/tests/unit/test_add_backfill_links.py +345 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_gem_synth_llm.py +136 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_gem_synth_loop.py +3 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_note_index.py +203 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/uv.lock +1 -1
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/.claude/skills/ci/SKILL.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/.claude/skills/release/SKILL.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/.claude/skills/release/release_helper.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/.claude-plugin/marketplace.json +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/.githooks/pre-push +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/.github/ISSUE_TEMPLATE/docs_improvement.yml +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/.github/workflows/integration-test.yml +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/.github/workflows/publish.yml +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/.gitignore +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/.python-version +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/AGENTS.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/CLAUDE.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/DEVELOPMENT_PLAN.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/README.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/SESSION.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/SESSION_SUMMARY.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/claude-code-plugin-guide.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/installation.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-11-bulk-import-bm25-fix.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-11-edit-command.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-11-unify-format-option.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-12-ci-coverage-optimization.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-12-edit-content-file.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-12-fix-index-rebuild-clear.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-12-fix-index-verify-id-mismatch.md +0 -0
- {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
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-12-index-kb-param.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-12-lazy-import-perf.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-12-skill-redesign.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-13-add-content-file.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-13-bulk-import-vectorstore-init.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-13-ingest-log-command.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-13-ingest-skill-sync.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-13-suppress-third-party-logging.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-13-sync-skills-with-cli.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-14-sync-docs-daemon-show.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-15-gpu-embedding.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-15-make-daemon-deps-required.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-15-session-summary-confirmation.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-16-fix-windows-daemon-console-window.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-18-daemon-timeout-and-index-rebuild.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-22-release-skill.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-26-fix-daemon-deprecation-warnings.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-27-intranet-model-download.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-28-tag-filtering.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-04-29-daemon-stop-fix.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-05-03-jfox-check.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-05-03-list-notes-skip-summary.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-05-03-log-level-fix.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-05-04-list-notes-index.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-05-06-atomic-write.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-05-09-session-note-type.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-05-10-jfox-plugin.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-05-19-fix-last-used-never.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-05-20-modelscope-migration.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-06-02-todo-note-type.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-06-16-kimi-code-auto-summary.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-06-17-index-rebuild-backlinks.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-06-17-using-jfox-skill.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-06-18-self-update-command.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-06-21-fragment-capture.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-06-21-gem-synthesis.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/plans/2026-06-25-gem-synth-throttle-ledger.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-04-03-bugfixes-design.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-04-12-skill-redesign-design.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-04-13-pr-auto-code-review-design.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-04-14-show-command-design.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-04-15-gpu-embedding-design.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-04-15-session-summary-confirmation-design.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-04-21-release-skill-design.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-04-26-fix-daemon-deprecation-warnings-design.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-04-27-intranet-model-download-design.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-04-28-tag-filtering-design.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-04-29-daemon-stop-fix-design.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-04-30-jfox-kb-env-var-design.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-05-03-jfox-check-design.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-05-03-list-notes-skip-summary-design.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-05-03-log-level-fix-design.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-05-04-list-notes-index-design.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-05-06-atomic-write-design.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-05-09-jfox-plugin-design.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-05-09-session-note-type-design.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-05-20-modelscope-migration-design.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-06-02-todo-note-type-design.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-06-16-kimi-code-auto-summary-design.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-06-17-index-rebuild-backlinks-design.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-06-17-using-jfox-skill-design.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-06-18-archive-soft-delete-design.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-06-18-self-update-command-design.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-06-21-fragment-capture-design.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-06-21-gem-synthesis-design.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/superpowers/specs/2026-06-25-gem-synth-throttle-ledger-design.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/docs/troubleshooting.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jessica-jones-static-cable.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/__main__.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/auto_summary/__init__.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/auto_summary/cli.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/auto_summary/extractor.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/auto_summary/kimi_source.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/auto_summary/ledger.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/auto_summary/loop.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/auto_summary/runner.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/auto_summary/scanner.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/auto_summary/sources.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/bm25_index.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/config.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/daemon/__init__.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/daemon/__main__.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/daemon/client.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/daemon/process.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/daemon/server.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/embedding_backend.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/formatters.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/fragment/__init__.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/fragment/cli.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/fragment/detector.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/fragment/service.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/fragment/store.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/gem_synth/__init__.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/gem_synth/anchors.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/gem_synth/cli.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/gem_synth/grounding.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/gem_synth/loop.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/gem_synth/paths.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/gem_synth/store.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/gem_synth/synthesizer.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/gem_synth/transcript.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/git_extractor.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/global_config.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/indexer.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/kb_manager.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/model_downloader.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/models.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/note.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/performance.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/search_engine.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/template.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/template_cli.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/utils.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/jfox/vector_store.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/package.json +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/cc-plugin/.claude-plugin/plugin.json +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/cc-plugin/hooks/fragment-capture.sh +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/cc-plugin/hooks/hooks.json +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/cc-plugin/skills/ingest/SKILL.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/cc-plugin/skills/manage/SKILL.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/cc-plugin/skills/organize/SKILL.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/cc-plugin/skills/search/SKILL.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/cc-plugin/skills/session-summary/SKILL.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/cc-plugin/skills/using-jfox/SKILL.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/kimi-plugin/README.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/kimi-plugin/kimi.plugin.json +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/kimi-plugin/skills/jfox-ingest/SKILL.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/kimi-plugin/skills/jfox-manage/SKILL.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/kimi-plugin/skills/jfox-organize/SKILL.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/kimi-plugin/skills/jfox-search/SKILL.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/kimi-plugin/skills/jfox-session-summary/SKILL.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/packages/kimi-plugin/skills/using-jfox/SKILL.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/pytest.ini +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/run_full_test.ps1 +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/scripts/download-model-intranet.sh +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/skills-recommend/README.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/skills-recommend/kimi-cli/jfox-common/SKILL.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/skills-recommend/kimi-cli/jfox-ingest/SKILL.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/skills-recommend/kimi-cli/jfox-organize/SKILL.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/skills-recommend/kimi-cli/jfox-search/SKILL.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/skills-recommend/kimi-cli/jfox-session-summary/SKILL.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/skills-recommend/pi/jfox-ci/SKILL.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/skills-recommend/pi/jfox-common/SKILL.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/skills-recommend/pi/jfox-ingest/SKILL.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/skills-recommend/pi/jfox-organize/SKILL.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/skills-recommend/pi/jfox-release/SKILL.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/skills-recommend/pi/jfox-search/SKILL.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/skills-recommend/pi/jfox-session-summary/SKILL.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/COVERAGE_PLAN.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/MIGRATION.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/TESTS.md +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/conftest.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/integration/__init__.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/integration/test_auto_summary_kimi.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/integration/test_backlinks.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/integration/test_fragment_capture_flow.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/integration/test_gem_synth_flow.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/integration/test_index_rebuild_backlinks.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/integration/test_model_download.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/integration/test_tag_filter_cli.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/performance/__init__.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/performance/test_performance.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/test_advanced_features.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/test_cli_format.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/test_config_set_unit.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/test_config_unit.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/test_core_workflow.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/test_embedding_device.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/test_hybrid_search.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/test_integration.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/test_kb_current.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/test_suggest_links.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/__init__.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_archive.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_atomic_write.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_auto_summary_config_sources.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_auto_summary_defaults.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_auto_summary_extractor.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_auto_summary_ledger.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_auto_summary_runner.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_auto_summary_scanner.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_auto_summary_status.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_bm25_batch.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_check.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_content_file.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_daemon_cli.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_daemon_process.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_edit.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_embedding_local_load.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_format_unify.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_formatters.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_fragment_cli.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_fragment_config.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_fragment_detector.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_fragment_service.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_fragment_store.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_gem_synth_anchors.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_gem_synth_cli.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_gem_synth_config.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_gem_synth_grounding.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_gem_synth_store.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_gem_synth_synthesizer.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_gem_synth_transcript.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_git_extractor.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_global_config.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_index_kb_param.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_indexer_clear_before_rebuild.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_indexer_verify.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_kb_manager.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_kimi_extractor.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_kimi_scanner.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_lazy_import.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_ledger_migration.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_list_notes_skip.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_logging_config.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_model_downloader.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_note_candidate_fields.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_note_candidate_filename.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_note_type_candidate.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_rebuild_backlinks_impl.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_release_helper.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_runner_multi_source.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_scanner_list.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_session_file_source.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_session_note.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_session_source.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_show.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_system_prompt_sections.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_tag_filter.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_template.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_template_cli.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_update.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_use_kb_env_var.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_using_jfox_skill.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/unit/test_vector_store_clear.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/utils/__init__.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/utils/assertions.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/utils/jfox_cli.py +0 -0
- {jfox_cli-1.2.1 → jfox_cli-1.2.2}/tests/utils/note_generator.py +0 -0
- {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
|
|
@@ -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
|
|
258
|
+
from .note_index import extract_wiki_links_from_text
|
|
259
259
|
|
|
260
|
-
|
|
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
|
-
#
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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,
|