jfox-cli 0.4.0__tar.gz → 0.4.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-0.4.2/.claude/skills/release/SKILL.md +169 -0
- jfox_cli-0.4.2/.claude/skills/release/release_helper.py +337 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/CHANGELOG.md +20 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/PKG-INFO +1 -1
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/jfox/__init__.py +1 -1
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/jfox/cli.py +5 -1
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/jfox/daemon/__init__.py +8 -1
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/jfox/daemon/process.py +88 -21
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/jfox/indexer.py +2 -2
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/jfox/vector_store.py +52 -1
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/pyproject.toml +1 -1
- jfox_cli-0.4.2/skills-recommend/README.md +93 -0
- jfox_cli-0.4.2/skills-recommend/kimi-cli/jfox-common/SKILL.md +314 -0
- jfox_cli-0.4.2/skills-recommend/kimi-cli/jfox-ingest/SKILL.md +217 -0
- jfox_cli-0.4.2/skills-recommend/kimi-cli/jfox-organize/SKILL.md +169 -0
- jfox_cli-0.4.2/skills-recommend/kimi-cli/jfox-search/SKILL.md +111 -0
- jfox_cli-0.4.2/skills-recommend/kimi-cli/jfox-session-summary/SKILL.md +122 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/unit/test_daemon_process.py +75 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/unit/test_indexer_clear_before_rebuild.py +13 -13
- jfox_cli-0.4.2/tests/unit/test_release_helper.py +128 -0
- jfox_cli-0.4.2/tests/unit/test_vector_store_clear.py +213 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/uv.lock +1 -1
- jfox_cli-0.4.0/skills-recommend/README.md +0 -66
- jfox_cli-0.4.0/tests/unit/test_vector_store_clear.py +0 -76
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/.claude/settings.local.json +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/.githooks/pre-push +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/.github/workflows/integration-test.yml +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/.github/workflows/publish.yml +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/.gitignore +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/.python-version +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/AGENTS.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/CLAUDE.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/DEVELOPMENT_PLAN.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/README.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/SESSION.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/SESSION_SUMMARY.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/docs/installation.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/docs/superpowers/plans/2026-04-11-bulk-import-bm25-fix.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/docs/superpowers/plans/2026-04-11-edit-command.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/docs/superpowers/plans/2026-04-11-unify-format-option.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/docs/superpowers/plans/2026-04-12-ci-coverage-optimization.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/docs/superpowers/plans/2026-04-12-edit-content-file.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/docs/superpowers/plans/2026-04-12-fix-index-rebuild-clear.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/docs/superpowers/plans/2026-04-12-fix-index-verify-id-mismatch.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/docs/superpowers/plans/2026-04-12-fix-jfox-health-skill-kb-param.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/docs/superpowers/plans/2026-04-12-index-kb-param.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/docs/superpowers/plans/2026-04-12-lazy-import-perf.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/docs/superpowers/plans/2026-04-12-skill-redesign.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/docs/superpowers/plans/2026-04-14-sync-docs-daemon-show.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/docs/superpowers/plans/2026-04-15-session-summary-confirmation.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/docs/superpowers/specs/2026-04-03-bugfixes-design.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/docs/superpowers/specs/2026-04-12-skill-redesign-design.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/docs/superpowers/specs/2026-04-13-pr-auto-code-review-design.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/docs/superpowers/specs/2026-04-14-show-command-design.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/docs/superpowers/specs/2026-04-15-session-summary-confirmation-design.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/docs/troubleshooting.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/jessica-jones-static-cable.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/jfox/__main__.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/jfox/bm25_index.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/jfox/config.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/jfox/daemon/__main__.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/jfox/daemon/client.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/jfox/daemon/server.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/jfox/embedding_backend.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/jfox/formatters.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/jfox/git_extractor.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/jfox/global_config.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/jfox/graph.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/jfox/kb_manager.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/jfox/models.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/jfox/note.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/jfox/performance.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/jfox/search_engine.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/jfox/template.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/jfox/template_cli.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/pytest.ini +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/run_full_test.ps1 +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/skills-recommend/claude-code/jfox-common/SKILL.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/skills-recommend/claude-code/jfox-ingest/SKILL.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/skills-recommend/claude-code/jfox-organize/SKILL.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/skills-recommend/claude-code/jfox-search/SKILL.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/skills-recommend/claude-code/jfox-session-summary/SKILL.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/COVERAGE_PLAN.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/MIGRATION.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/TESTS.md +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/conftest.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/integration/__init__.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/integration/test_backlinks.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/performance/__init__.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/performance/test_performance.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/test_advanced_features.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/test_cli_format.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/test_config_set_unit.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/test_config_unit.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/test_core_workflow.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/test_embedding_device.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/test_hybrid_search.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/test_integration.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/test_kb_current.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/test_suggest_links.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/unit/__init__.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/unit/test_bm25_batch.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/unit/test_edit.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/unit/test_format_unify.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/unit/test_formatters.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/unit/test_git_extractor.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/unit/test_global_config.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/unit/test_index_kb_param.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/unit/test_indexer_verify.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/unit/test_kb_manager.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/unit/test_lazy_import.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/unit/test_logging_config.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/unit/test_show.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/unit/test_template.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/unit/test_template_cli.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/utils/__init__.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/utils/assertions.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/utils/jfox_cli.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/utils/note_generator.py +0 -0
- {jfox_cli-0.4.0 → jfox_cli-0.4.2}/tests/utils/temp_kb.py +0 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: release
|
|
3
|
+
description: Release a new version of jfox. Bumps version, generates CHANGELOG, creates PR and GitHub Release. Triggers on "发版", "release", "bump version", "发布版本".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Release Skill
|
|
7
|
+
|
|
8
|
+
将 jfox 发版流程从多步手动操作简化为一条命令。覆盖版本号 bump → CHANGELOG → commit → PR → GitHub Release 全流程。
|
|
9
|
+
|
|
10
|
+
## 用法
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
/release 0.5.0 # 指定具体版本号
|
|
14
|
+
/release patch # bump patch: 0.4.1 → 0.4.2
|
|
15
|
+
/release minor # bump minor: 0.4.1 → 0.5.0
|
|
16
|
+
/release major # bump major: 0.4.1 → 1.0.0
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 执行流程
|
|
20
|
+
|
|
21
|
+
严格按照以下步骤执行。每一步必须完成后再进入下一步。
|
|
22
|
+
|
|
23
|
+
### Step 1: 前置校验
|
|
24
|
+
|
|
25
|
+
运行以下检查,任何一项失败则立即停止并告知用户原因:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# 1. 当前分支必须是 main
|
|
29
|
+
git branch --show-current
|
|
30
|
+
# 期望输出: main
|
|
31
|
+
|
|
32
|
+
# 2. 工作区必须干净
|
|
33
|
+
git status --porcelain
|
|
34
|
+
# 期望输出: 空
|
|
35
|
+
|
|
36
|
+
# 3. 不存在未合并的 bump 分支
|
|
37
|
+
git branch --list 'chore/bump-*'
|
|
38
|
+
# 期望输出: 空
|
|
39
|
+
|
|
40
|
+
# 4. 没有未合并的 bump PR
|
|
41
|
+
gh pr list --state open --head "chore/bump-*"
|
|
42
|
+
# 期望输出: 空
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Step 2: 运行辅助脚本(预览模式)
|
|
46
|
+
|
|
47
|
+
用 `--dry-run` 先预览计算结果:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
uv run python .claude/skills/release/release_helper.py <version> --dry-run
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
解析 JSON 输出,提取 `current_version`、`new_version`、`changelog_preview`、`changelog_summary`。
|
|
54
|
+
|
|
55
|
+
### Step 3: 展示变更摘要并等待确认
|
|
56
|
+
|
|
57
|
+
向用户展示:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
📦 Release 预览:
|
|
61
|
+
当前版本: {current_version}
|
|
62
|
+
新版本号: {new_version}
|
|
63
|
+
变更摘要: {changelog_summary}
|
|
64
|
+
|
|
65
|
+
CHANGELOG 预览:
|
|
66
|
+
{changelog_preview}
|
|
67
|
+
|
|
68
|
+
将修改的文件:
|
|
69
|
+
- pyproject.toml
|
|
70
|
+
- jfox/__init__.py
|
|
71
|
+
- uv.lock
|
|
72
|
+
- CHANGELOG.md
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**必须等待用户明确确认后才继续。** 如果用户拒绝或要求修改,停止流程。
|
|
76
|
+
|
|
77
|
+
### Step 4: 运行辅助脚本(正式模式)
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
uv run python .claude/skills/release/release_helper.py <version>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
确认脚本退出码为 0。如果非 0,读取错误信息并告知用户,停止流程。
|
|
84
|
+
|
|
85
|
+
### Step 5: Git 操作
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# 创建分支
|
|
89
|
+
git checkout -b chore/bump-version-{new_version}
|
|
90
|
+
|
|
91
|
+
# 暂存文件
|
|
92
|
+
git add pyproject.toml jfox/__init__.py uv.lock CHANGELOG.md
|
|
93
|
+
|
|
94
|
+
# 提交
|
|
95
|
+
git commit -m "chore: bump version to {new_version}"
|
|
96
|
+
|
|
97
|
+
# 推送
|
|
98
|
+
git push -u origin chore/bump-version-{new_version}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Step 6: 创建 PR
|
|
102
|
+
|
|
103
|
+
使用 CHANGELOG 内容作为 PR body:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
gh pr create \
|
|
107
|
+
--title "chore: bump version to {new_version}" \
|
|
108
|
+
--body "$(cat <<'EOF'
|
|
109
|
+
## Summary
|
|
110
|
+
Bump version from {current_version} to {new_version}
|
|
111
|
+
|
|
112
|
+
{changelog_preview}
|
|
113
|
+
|
|
114
|
+
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
|
115
|
+
EOF
|
|
116
|
+
)"
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
记录返回的 PR URL。
|
|
120
|
+
|
|
121
|
+
### Step 7: 等待合并
|
|
122
|
+
|
|
123
|
+
告知用户:
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
PR 已创建: {PR_URL}
|
|
127
|
+
请合并此 PR 后告知我,我将继续创建 GitHub Release。
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
等待用户确认 PR 已合并。
|
|
131
|
+
|
|
132
|
+
### Step 8: 切回 main 并拉取最新代码
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
git checkout main
|
|
136
|
+
git pull origin main
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Step 9: 创建 GitHub Release
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
gh release create v{new_version} \
|
|
143
|
+
--title "v{new_version}" \
|
|
144
|
+
--notes "$(cat <<'EOF'
|
|
145
|
+
{changelog_preview}
|
|
146
|
+
EOF
|
|
147
|
+
)"
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
告知用户:
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
Release v{new_version} 已创建!
|
|
154
|
+
GitHub Actions 将自动发布到 PyPI。
|
|
155
|
+
可在 https://github.com/zhuxixi/jfox/actions 监控发布状态。
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## 错误处理
|
|
159
|
+
|
|
160
|
+
- 脚本返回非零退出码 → 读取错误 JSON,展示给用户,停止流程
|
|
161
|
+
- git 操作失败 → 展示错误信息,建议用户手动修复
|
|
162
|
+
- PR 创建失败 → 检查是否已有同名 PR,或提示权限问题
|
|
163
|
+
- Release 创建失败 → 检查 tag 是否已存在,或提示权限问题
|
|
164
|
+
|
|
165
|
+
## 注意事项
|
|
166
|
+
|
|
167
|
+
- **不使用 `--no-verify`**,保持 pre-commit hook 正常运行
|
|
168
|
+
- **始终在新分支操作**,不直接修改 main
|
|
169
|
+
- **每个确认点都必须等待**,不自动跳过
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
jfox release 辅助脚本
|
|
4
|
+
|
|
5
|
+
处理版本号计算、文件更新、CHANGELOG 生成。
|
|
6
|
+
输出 JSON 供 Claude 解析。
|
|
7
|
+
|
|
8
|
+
用法:
|
|
9
|
+
python release_helper.py patch # bump patch
|
|
10
|
+
python release_helper.py minor # bump minor
|
|
11
|
+
python release_helper.py major # bump major
|
|
12
|
+
python release_helper.py 0.5.0 # 指定版本
|
|
13
|
+
python release_helper.py ... --dry-run # 只计算不修改文件
|
|
14
|
+
"""
|
|
15
|
+
import json
|
|
16
|
+
import re
|
|
17
|
+
import subprocess
|
|
18
|
+
import sys
|
|
19
|
+
from datetime import date
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
# 项目根目录(脚本位于 .claude/skills/release/,向上 3 级)
|
|
23
|
+
PROJECT_ROOT = Path(__file__).resolve().parents[3]
|
|
24
|
+
PYPROJECT_TOML = PROJECT_ROOT / "pyproject.toml"
|
|
25
|
+
INIT_PY = PROJECT_ROOT / "jfox" / "__init__.py"
|
|
26
|
+
CHANGELOG_MD = PROJECT_ROOT / "CHANGELOG.md"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def output_json(data: dict):
|
|
30
|
+
"""输出 JSON 到 stdout"""
|
|
31
|
+
print(json.dumps(data, ensure_ascii=False))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def output_error(msg: str):
|
|
35
|
+
"""输出错误 JSON 并退出"""
|
|
36
|
+
output_json({"error": msg})
|
|
37
|
+
sys.exit(1)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def read_current_version() -> str:
|
|
41
|
+
"""从 pyproject.toml 读取当前版本号"""
|
|
42
|
+
if not PYPROJECT_TOML.exists():
|
|
43
|
+
output_error(f"未找到 {PYPROJECT_TOML}")
|
|
44
|
+
content = PYPROJECT_TOML.read_text(encoding="utf-8")
|
|
45
|
+
match = re.search(r'^version\s*=\s*"(\d+\.\d+\.\d+)"', content, re.MULTILINE)
|
|
46
|
+
if not match:
|
|
47
|
+
output_error(f"未在 {PYPROJECT_TOML} 中找到 version 字段")
|
|
48
|
+
return match.group(1)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def parse_bump(arg: str, current: str) -> str:
|
|
52
|
+
"""解析版本参数,返回新版本号"""
|
|
53
|
+
# 尝试作为 bump 类型
|
|
54
|
+
if arg in ("patch", "minor", "major"):
|
|
55
|
+
parts = [int(x) for x in current.split(".")]
|
|
56
|
+
if arg == "patch":
|
|
57
|
+
parts[2] += 1
|
|
58
|
+
elif arg == "minor":
|
|
59
|
+
parts[1] += 1
|
|
60
|
+
parts[2] = 0
|
|
61
|
+
elif arg == "major":
|
|
62
|
+
parts[0] += 1
|
|
63
|
+
parts[1] = 0
|
|
64
|
+
parts[2] = 0
|
|
65
|
+
return f"{parts[0]}.{parts[1]}.{parts[2]}"
|
|
66
|
+
|
|
67
|
+
# 尝试作为 semver
|
|
68
|
+
if not re.match(r"^\d+\.\d+\.\d+$", arg):
|
|
69
|
+
output_error(f"无效的版本号或 bump 类型: {arg}(期望 patch/minor/major 或 X.Y.Z)")
|
|
70
|
+
|
|
71
|
+
# 确保新版本 > 当前版本
|
|
72
|
+
new_parts = [int(x) for x in arg.split(".")]
|
|
73
|
+
cur_parts = [int(x) for x in current.split(".")]
|
|
74
|
+
if new_parts <= cur_parts:
|
|
75
|
+
output_error(f"新版本 {arg} 不大于当前版本 {current}")
|
|
76
|
+
|
|
77
|
+
return arg
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def update_files(new_version: str, current_version: str):
|
|
81
|
+
"""更新 pyproject.toml、__init__.py、uv.lock"""
|
|
82
|
+
if not PYPROJECT_TOML.exists():
|
|
83
|
+
output_error(f"未找到 {PYPROJECT_TOML}")
|
|
84
|
+
if not INIT_PY.exists():
|
|
85
|
+
output_error(f"未找到 {INIT_PY}")
|
|
86
|
+
|
|
87
|
+
# 先读取备份
|
|
88
|
+
toml_content = PYPROJECT_TOML.read_text(encoding="utf-8")
|
|
89
|
+
init_content = INIT_PY.read_text(encoding="utf-8")
|
|
90
|
+
|
|
91
|
+
# 更新 pyproject.toml
|
|
92
|
+
new_toml = re.sub(
|
|
93
|
+
r'^version\s*=\s*"\d+\.\d+\.\d+"',
|
|
94
|
+
f'version = "{new_version}"',
|
|
95
|
+
toml_content,
|
|
96
|
+
flags=re.MULTILINE,
|
|
97
|
+
)
|
|
98
|
+
PYPROJECT_TOML.write_text(new_toml, encoding="utf-8")
|
|
99
|
+
|
|
100
|
+
# 更新 __init__.py
|
|
101
|
+
new_init = re.sub(
|
|
102
|
+
r'__version__\s*=\s*"\d+\.\d+\.\d+"',
|
|
103
|
+
f'__version__ = "{new_version}"',
|
|
104
|
+
init_content,
|
|
105
|
+
)
|
|
106
|
+
INIT_PY.write_text(new_init, encoding="utf-8")
|
|
107
|
+
|
|
108
|
+
# uv.lock — 如果失败则回滚
|
|
109
|
+
result = subprocess.run(
|
|
110
|
+
["uv", "lock"],
|
|
111
|
+
cwd=str(PROJECT_ROOT),
|
|
112
|
+
capture_output=True,
|
|
113
|
+
text=True,
|
|
114
|
+
encoding="utf-8",
|
|
115
|
+
errors="replace",
|
|
116
|
+
)
|
|
117
|
+
if result.returncode != 0:
|
|
118
|
+
# 回滚
|
|
119
|
+
PYPROJECT_TOML.write_text(toml_content, encoding="utf-8")
|
|
120
|
+
INIT_PY.write_text(init_content, encoding="utf-8")
|
|
121
|
+
output_error(f"uv lock 失败,已回滚文件变更: {result.stderr}")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def get_last_tag() -> str:
|
|
125
|
+
"""获取最新的 git tag"""
|
|
126
|
+
result = subprocess.run(
|
|
127
|
+
["git", "describe", "--tags", "--abbrev=0"],
|
|
128
|
+
cwd=str(PROJECT_ROOT),
|
|
129
|
+
capture_output=True,
|
|
130
|
+
text=True,
|
|
131
|
+
encoding="utf-8",
|
|
132
|
+
errors="replace",
|
|
133
|
+
)
|
|
134
|
+
if result.returncode != 0:
|
|
135
|
+
return "" # 没有 tag
|
|
136
|
+
return result.stdout.strip()
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def parse_commits(last_tag: str) -> list[dict]:
|
|
140
|
+
"""解析 last_tag..HEAD 之间的 commit,返回分类后的条目列表"""
|
|
141
|
+
if last_tag:
|
|
142
|
+
range_spec = f"{last_tag}..HEAD"
|
|
143
|
+
else:
|
|
144
|
+
range_spec = "HEAD"
|
|
145
|
+
|
|
146
|
+
# -c core.quotepath=false 确保 git 输出中文不被转义
|
|
147
|
+
result = subprocess.run(
|
|
148
|
+
["git", "-c", "core.quotepath=false", "-c", "i18n.logoutputencoding=utf-8",
|
|
149
|
+
"log", range_spec, "--format=%s"],
|
|
150
|
+
cwd=str(PROJECT_ROOT),
|
|
151
|
+
capture_output=True,
|
|
152
|
+
text=True,
|
|
153
|
+
encoding="utf-8",
|
|
154
|
+
errors="replace",
|
|
155
|
+
)
|
|
156
|
+
if result.returncode != 0 or not result.stdout:
|
|
157
|
+
return []
|
|
158
|
+
|
|
159
|
+
entries = []
|
|
160
|
+
seen = set() # 去重(merge commit 和 squash 可能重复)
|
|
161
|
+
|
|
162
|
+
for line in result.stdout.strip().split("\n"):
|
|
163
|
+
if not line.strip():
|
|
164
|
+
continue
|
|
165
|
+
|
|
166
|
+
# 跳过 bump version 的 commit
|
|
167
|
+
if "bump version" in line.lower():
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
# 跳过纯 merge commit
|
|
171
|
+
if line.startswith("Merge ") and not any(
|
|
172
|
+
x in line for x in ["feat", "fix", "refactor", "docs", "chore", "perf"]
|
|
173
|
+
):
|
|
174
|
+
continue
|
|
175
|
+
|
|
176
|
+
# 解析 conventional commit: type(scope): message (#PR)
|
|
177
|
+
match = re.match(
|
|
178
|
+
r"^(feat|fix|refactor|docs|chore|perf)(?:\(([^)]+)\))?:\s*(.+?)(?:\s*\(#(\d+)\))?$",
|
|
179
|
+
line,
|
|
180
|
+
)
|
|
181
|
+
if match:
|
|
182
|
+
entry = {
|
|
183
|
+
"type": match.group(1),
|
|
184
|
+
"scope": match.group(2) or "",
|
|
185
|
+
"message": match.group(3).strip(),
|
|
186
|
+
"pr": int(match.group(4)) if match.group(4) else None,
|
|
187
|
+
}
|
|
188
|
+
else:
|
|
189
|
+
# 非 conventional commit 格式
|
|
190
|
+
pr_match = re.search(r"\(#(\d+)\)", line)
|
|
191
|
+
entry = {
|
|
192
|
+
"type": "other",
|
|
193
|
+
"scope": "",
|
|
194
|
+
"message": line.strip(),
|
|
195
|
+
"pr": int(pr_match.group(1)) if pr_match else None,
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
# 去重 key
|
|
199
|
+
key = (entry["type"], entry["scope"], entry["message"])
|
|
200
|
+
if key not in seen:
|
|
201
|
+
seen.add(key)
|
|
202
|
+
entries.append(entry)
|
|
203
|
+
|
|
204
|
+
return entries
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def generate_changelog(new_version: str, current_version: str, entries: list[dict], last_tag: str = "") -> str:
|
|
208
|
+
"""生成 CHANGELOG Markdown 内容"""
|
|
209
|
+
today = date.today().isoformat()
|
|
210
|
+
|
|
211
|
+
# 按 type 分类
|
|
212
|
+
features = [e for e in entries if e["type"] == "feat"]
|
|
213
|
+
fixes = [e for e in entries if e["type"] == "fix"]
|
|
214
|
+
perfs = [e for e in entries if e["type"] == "perf"]
|
|
215
|
+
changes = [e for e in entries if e["type"] not in ("feat", "fix", "perf")]
|
|
216
|
+
|
|
217
|
+
lines = [f"## [{new_version}] - {today}", ""]
|
|
218
|
+
|
|
219
|
+
def format_entry(e: dict) -> str:
|
|
220
|
+
scope = f"**{e['scope']}**: " if e["scope"] else ""
|
|
221
|
+
pr = f" (#{e['pr']})" if e["pr"] else ""
|
|
222
|
+
return f"- {scope}{e['message']}{pr}"
|
|
223
|
+
|
|
224
|
+
if features:
|
|
225
|
+
lines.append("### Features")
|
|
226
|
+
lines.extend(format_entry(e) for e in features)
|
|
227
|
+
lines.append("")
|
|
228
|
+
|
|
229
|
+
if fixes:
|
|
230
|
+
lines.append("### Fixes")
|
|
231
|
+
lines.extend(format_entry(e) for e in fixes)
|
|
232
|
+
lines.append("")
|
|
233
|
+
|
|
234
|
+
if perfs:
|
|
235
|
+
lines.append("### Performance")
|
|
236
|
+
lines.extend(format_entry(e) for e in perfs)
|
|
237
|
+
lines.append("")
|
|
238
|
+
|
|
239
|
+
if changes:
|
|
240
|
+
lines.append("### Changes")
|
|
241
|
+
lines.extend(format_entry(e) for e in changes)
|
|
242
|
+
lines.append("")
|
|
243
|
+
|
|
244
|
+
# 底部比较链接
|
|
245
|
+
tag_prev = last_tag.lstrip("v") if last_tag else current_version
|
|
246
|
+
lines.append(
|
|
247
|
+
f"[{new_version}]: https://github.com/zhuxixi/jfox/compare/"
|
|
248
|
+
f"v{tag_prev}...v{new_version}"
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
return "\n".join(lines)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def update_changelog_file(new_changelog: str):
|
|
255
|
+
"""将新条目插入 CHANGELOG.md 头部"""
|
|
256
|
+
if not CHANGELOG_MD.exists():
|
|
257
|
+
content = "# Changelog\n\n"
|
|
258
|
+
else:
|
|
259
|
+
content = CHANGELOG_MD.read_text(encoding="utf-8")
|
|
260
|
+
|
|
261
|
+
# 在第一个 ## 之前插入
|
|
262
|
+
insert_pos = content.find("\n## ")
|
|
263
|
+
if insert_pos == -1:
|
|
264
|
+
content = content.rstrip("\n") + "\n\n" + new_changelog + "\n"
|
|
265
|
+
else:
|
|
266
|
+
content = content[: insert_pos + 1] + new_changelog + "\n\n" + content[insert_pos + 1 :]
|
|
267
|
+
|
|
268
|
+
CHANGELOG_MD.write_text(content, encoding="utf-8")
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def summarize_entries(entries: list[dict]) -> str:
|
|
272
|
+
"""生成条目摘要"""
|
|
273
|
+
counts = {}
|
|
274
|
+
for e in entries:
|
|
275
|
+
t = e["type"]
|
|
276
|
+
if t == "feat":
|
|
277
|
+
counts["feature"] = counts.get("feature", 0) + 1
|
|
278
|
+
elif t == "fix":
|
|
279
|
+
counts["fix"] = counts.get("fix", 0) + 1
|
|
280
|
+
else:
|
|
281
|
+
counts["change"] = counts.get("change", 0) + 1
|
|
282
|
+
|
|
283
|
+
parts = []
|
|
284
|
+
for label in ("feature", "fix", "change"):
|
|
285
|
+
if label in counts:
|
|
286
|
+
plural = {"feature": "features", "fix": "fixes", "change": "changes"}
|
|
287
|
+
name = plural[label] if counts[label] > 1 else label
|
|
288
|
+
parts.append(f"{counts[label]} {name}")
|
|
289
|
+
return ", ".join(parts) if parts else "0 changes"
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def main():
|
|
293
|
+
if len(sys.argv) < 2:
|
|
294
|
+
output_error("用法: release_helper.py <version|patch|minor|major> [--dry-run]")
|
|
295
|
+
|
|
296
|
+
version_arg = sys.argv[1]
|
|
297
|
+
dry_run = "--dry-run" in sys.argv
|
|
298
|
+
|
|
299
|
+
# 1. 读取当前版本
|
|
300
|
+
current_version = read_current_version()
|
|
301
|
+
|
|
302
|
+
# 2. 计算新版本
|
|
303
|
+
new_version = parse_bump(version_arg, current_version)
|
|
304
|
+
|
|
305
|
+
# 3. 解析 commits
|
|
306
|
+
last_tag = get_last_tag()
|
|
307
|
+
entries = parse_commits(last_tag)
|
|
308
|
+
|
|
309
|
+
# 4. 生成 CHANGELOG
|
|
310
|
+
changelog = generate_changelog(new_version, current_version, entries, last_tag)
|
|
311
|
+
|
|
312
|
+
result = {
|
|
313
|
+
"current_version": current_version,
|
|
314
|
+
"new_version": new_version,
|
|
315
|
+
"last_tag": last_tag,
|
|
316
|
+
"changelog_preview": changelog,
|
|
317
|
+
"changelog_entries": entries,
|
|
318
|
+
"changelog_summary": summarize_entries(entries),
|
|
319
|
+
"files_modified": ["pyproject.toml", "jfox/__init__.py", "uv.lock", "CHANGELOG.md"],
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if dry_run:
|
|
323
|
+
output_json(result)
|
|
324
|
+
return
|
|
325
|
+
|
|
326
|
+
# 5. 更新文件
|
|
327
|
+
update_files(new_version, current_version)
|
|
328
|
+
|
|
329
|
+
# 6. 更新 CHANGELOG
|
|
330
|
+
update_changelog_file(changelog)
|
|
331
|
+
|
|
332
|
+
# 7. 输出结果
|
|
333
|
+
output_json(result)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
if __name__ == "__main__":
|
|
337
|
+
main()
|
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to jfox-cli will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.4.2] - 2026-04-22
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
- add Kimi CLI skill collection (#166)
|
|
9
|
+
- **skills**: add release skill with full workflow instructions
|
|
10
|
+
- **skills**: add release helper script with version bump and CHANGELOG generation
|
|
11
|
+
|
|
12
|
+
### Fixes
|
|
13
|
+
- **lint**: remove unused pytest import
|
|
14
|
+
- **skills**: improve git Chinese encoding and fix pluralization in release helper
|
|
15
|
+
- **skills**: address code review issues in release helper
|
|
16
|
+
- **cli**: list 命令 table 输出显示完整 18 位笔记 ID
|
|
17
|
+
|
|
18
|
+
### Changes
|
|
19
|
+
- style(lint): format test_release_helper.py with black
|
|
20
|
+
- Merge pull request #167 from zhuxixi/feat/kimi-cli-skills
|
|
21
|
+
- Merge pull request #165 from zhuxixi/fix-list-id-truncation
|
|
22
|
+
|
|
23
|
+
[0.4.2]: https://github.com/zhuxixi/jfox/compare/v0.4.1...v0.4.2
|
|
24
|
+
|
|
5
25
|
## [0.2.0] - 2026-04-13
|
|
6
26
|
|
|
7
27
|
### Features
|
|
@@ -787,7 +787,7 @@ def _list_impl(
|
|
|
787
787
|
|
|
788
788
|
for n in notes:
|
|
789
789
|
created_str = n.created.strftime("%Y-%m-%d") if n.created else ""
|
|
790
|
-
table.add_row(n.id
|
|
790
|
+
table.add_row(n.id, n.title[:40], n.type.value, created_str)
|
|
791
791
|
|
|
792
792
|
console.print(table)
|
|
793
793
|
elif output_format == "tree":
|
|
@@ -2612,7 +2612,10 @@ def daemon(
|
|
|
2612
2612
|
|
|
2613
2613
|
try:
|
|
2614
2614
|
if action == "start":
|
|
2615
|
+
from .daemon.process import DAEMON_LOG_FILE
|
|
2616
|
+
|
|
2615
2617
|
console.print("[yellow]正在启动 embedding daemon...[/yellow]")
|
|
2618
|
+
console.print(f"[dim]日志文件: {DAEMON_LOG_FILE}[/dim]")
|
|
2616
2619
|
ok = start_daemon(port=port)
|
|
2617
2620
|
if ok:
|
|
2618
2621
|
info = get_daemon_status()
|
|
@@ -2631,6 +2634,7 @@ def daemon(
|
|
|
2631
2634
|
console.print("[green]✓ Daemon 已启动[/green]")
|
|
2632
2635
|
else:
|
|
2633
2636
|
console.print("[red]✗ Daemon 启动失败[/red]")
|
|
2637
|
+
console.print(f"[dim]查看日志: {DAEMON_LOG_FILE}[/dim]")
|
|
2634
2638
|
raise typer.Exit(1)
|
|
2635
2639
|
|
|
2636
2640
|
elif action == "stop":
|
|
@@ -3,9 +3,16 @@
|
|
|
3
3
|
DEFAULT_HOST = "127.0.0.1"
|
|
4
4
|
DEFAULT_PORT = 18700
|
|
5
5
|
|
|
6
|
-
from .process import
|
|
6
|
+
from .process import (
|
|
7
|
+
DAEMON_LOG_FILE,
|
|
8
|
+
get_daemon_status,
|
|
9
|
+
is_daemon_running,
|
|
10
|
+
start_daemon,
|
|
11
|
+
stop_daemon,
|
|
12
|
+
)
|
|
7
13
|
|
|
8
14
|
__all__ = [
|
|
15
|
+
"DAEMON_LOG_FILE",
|
|
9
16
|
"DEFAULT_HOST",
|
|
10
17
|
"DEFAULT_PORT",
|
|
11
18
|
"start_daemon",
|