harveyz-skill 0.15.0 → 0.16.0

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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.16.0] - 2026-06-15
11
+
12
+ ### Added
13
+ - `hub git`:新增 CLI 子命令,与 TUI git 面板功能对等
14
+ - `hub git status [--project <name>]`:显示当前分支、upstream 同步状态、工作区变更、最近提交
15
+ - `hub git fetch [--project <name>]`:fetch 所有 remote
16
+ - `hub git branches [--project <name>]`:列出所有分支及 ahead/behind 状态
17
+
18
+ ### Changed
19
+ - `hub`:版本升至 1.1.0
20
+ - `hub`:安装升级机制改为基于源码文件内容哈希(sha256)检测,不再依赖版本号;npm 更新后下次启动自动重装
21
+
10
22
  ## [0.15.0] - 2026-06-15
11
23
 
12
24
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harveyz-skill",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Skill manager for Claude Code, Cursor, and Codex",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,13 +1,13 @@
1
1
  ---
2
2
  name: init-workflow
3
- description: "Initialize or update git branch management standards: reads workflow-config.yml, audits config, incrementally generates and deploys git hooks (pre-commit, commit-msg, pre-push, post-checkout), and generates workflow docs. Triggers: initializing a new git repo, first-time git setup, user asks to set/update branch protection or naming rules, reinstalling git hooks, or syncing after skill/template updates."
3
+ description: "Initialize or update git branch management standards: reads .hskill/init-workflow/workflow-config.yml, audits config, incrementally generates and deploys git hooks (pre-commit, commit-msg, pre-push, post-checkout), and generates workflow docs. Triggers: initializing a new git repo, first-time git setup, user asks to set/update branch protection or naming rules, reinstalling git hooks, or syncing after skill/template updates."
4
4
  user_invocable: true
5
5
  version: "4.1.0"
6
6
  ---
7
7
 
8
8
  # Git 工作流初始化
9
9
 
10
- 读取 `workflow-config.yml`,审核配置,差量部署 git hooks。
10
+ 读取 `.hskill/init-workflow/workflow-config.yml`,审核配置,差量部署 git hooks。
11
11
  重新运行时自动检测配置变更、用户手改冲突、用户代码与新规则的交叉冲突,一次性汇总让用户决策后再写入。
12
12
 
13
13
  ---
@@ -26,16 +26,17 @@ git rev-parse --show-toplevel
26
26
 
27
27
  ### Step 2 — 读取配置
28
28
 
29
- 按以下顺序查找 `workflow-config.yml`:
29
+ 按以下顺序查找配置文件:
30
30
 
31
- 1. 项目根目录 `workflow-config.yml`
32
- 2. 项目根目录 `.claude/workflow-config.yml`
31
+ 1. `.hskill/init-workflow/workflow-config.yml`
32
+ 2. 项目根目录 `workflow-config.yml`(向后兼容)
33
+ 3. 项目根目录 `.claude/workflow-config.yml`(向后兼容)
33
34
 
34
- 若两处均不存在,询问用户:
35
- - 使用默认配置(从此 skill 的 `references/workflow-config.yml` 复制到项目根目录)
35
+ 若均不存在,询问用户:
36
+ - 使用默认配置(从此 skill 的 `references/workflow-config.yml` 复制到 `.hskill/init-workflow/workflow-config.yml`)
36
37
  - 还是中止并自行创建后重新运行
37
38
 
38
- 找到后读取并解析 YAML
39
+ 找到后读取并解析 YAML。若文件来自向后兼容路径(2 或 3),询问用户是否将其移动到 `.hskill/init-workflow/workflow-config.yml`;用户同意则执行移动,否则继续使用原位置(不影响本次运行)。
39
40
 
40
41
  ---
41
42
 
@@ -154,13 +155,13 @@ git config merge.ff false
154
155
 
155
156
  ### Step 7 — 从配置渲染并写入工作流文档(差量)
156
157
 
157
- 每次运行都根据当前 `workflow-config.yml` 的最终状态重新渲染文档,确保文档与配置始终一致。
158
+ 每次运行都根据当前 `.hskill/init-workflow/workflow-config.yml` 的最终状态重新渲染文档,确保文档与配置始终一致。
158
159
 
159
160
  #### 7a. 渲染文档
160
161
 
161
162
  ```bash
162
163
  python3 <skill-path>/references/render_docs.py \
163
- workflow-config.yml \
164
+ .hskill/init-workflow/workflow-config.yml \
164
165
  <skill-path>/references/git-workflow-template.md \
165
166
  docs/reference/git-workflow.md
166
167
  # 输出 NEW / UPDATED / UNCHANGED
@@ -205,7 +206,7 @@ python3 <skill-path>/references/render_docs.py \
205
206
  ```
206
207
  文件 状态
207
208
  ─────────────────────────────────────────────────────
208
- workflow-config.yml 已读取
209
+ .hskill/init-workflow/workflow-config.yml 已读取
209
210
  .githooks/.workflow-config.lock UPDATED(记录新配置快照)
210
211
  .githooks/pre-commit UPDATED(main 块 hash 更新;develop 块新增)
211
212
  .githooks/commit-msg UNCHANGED
@@ -15,7 +15,7 @@ user_invocable: true
15
15
 
16
16
  ## 步骤 ①:读取 Manifest
17
17
 
18
- 读取 `docs/reference/design-html-manifest.json`。
18
+ 读取 `.hskill/sync-design/manifest.json`。
19
19
 
20
20
  **如果文件不存在:**
21
21
  执行【附录 A:首次初始化流程】,完成后继续步骤 ②。
@@ -182,7 +182,7 @@ git diff <entry.lastSyncCommit 或 manifest.baseBranch(如无 lastSyncCommit
182
182
  **用户选「是」:**
183
183
  1. `git rev-parse HEAD` 获取当前 commit hash
184
184
  2. 将所有已处理 entry 的 `lastSyncCommit` 更新为该 hash
185
- 3. 写回 `docs/reference/design-html-manifest.json`(含配置演进的 patterns 变更)
185
+ 3. 写回 `.hskill/sync-design/manifest.json`(含配置演进的 patterns 变更)
186
186
  4. 询问是否 `git add` 并给出建议 commit message:
187
187
  `chore: sync HTML design backups — <entry id 列表>`
188
188
 
@@ -304,6 +304,12 @@ find src -name "*.scss" -o -name "variables.css" 2>/dev/null | head -3
304
304
 
305
305
  ### A-5. 创建 Manifest
306
306
 
307
+ ```bash
308
+ mkdir -p .hskill/sync-design
309
+ ```
310
+
311
+ 将以下内容写入 `.hskill/sync-design/manifest.json`:
312
+
307
313
  ```json
308
314
  {
309
315
  "version": 3,
@@ -21,7 +21,7 @@ version: "1.0.0"
21
21
 
22
22
  ## Step 1:读取配置
23
23
 
24
- 检查 `.claude/branch-cleanup.md` 是否存在:
24
+ 检查 `.hskill/clean-git/branch-cleanup.md` 是否存在:
25
25
 
26
26
  **存在** → 解析三段内容:
27
27
  - `## Always Delete` 段:每行 `` `pattern` `` 为必删 glob 规则
@@ -182,8 +182,8 @@ git branch -r | grep "origin/<branch>"
182
182
  **若本次无配置文件,询问:**
183
183
 
184
184
  ```
185
- 未检测到 .claude/branch-cleanup.md。
185
+ 未检测到 .hskill/clean-git/branch-cleanup.md。
186
186
  是否根据本次规则生成配置文件,方便下次复用?[y/n]
187
187
  ```
188
188
 
189
- 若用户确认,生成 `.claude/branch-cleanup.md`,内容包含本次使用的 Always Delete / Always Keep 规则和 LLM context。
189
+ 若用户确认,生成 `.hskill/clean-git/branch-cleanup.md`,内容包含本次使用的 Always Delete / Always Keep 规则和 LLM context。
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: release-project
3
- description: "Universal project release skill. Triggers on: release, bump version, publish, new version, cut release. Two phases: Init (first run — scans project and generates .hskill/release-profile.md); Execute (daily use — reads profile and runs full release flow). Works with npm, Python, Rust, Java, or any versioned project."
3
+ description: "Universal project release skill. Triggers on: release, bump version, publish, new version, cut release. Two phases: Init (first run — scans project and generates .hskill/release-project/release-profile.md); Execute (daily use — reads profile and runs full release flow). Works with npm, Python, Rust, Java, or any versioned project."
4
4
  user_invocable: true
5
5
  version: "1.1.0"
6
6
  ---
@@ -9,17 +9,17 @@ version: "1.1.0"
9
9
 
10
10
  通用项目发布 skill,两个阶段:
11
11
 
12
- - **Init** — 扫描项目,和用户确认,生成 `.hskill/release-profile.md`(每个项目做一次)
12
+ - **Init** — 扫描项目,和用户确认,生成 `.hskill/release-project/release-profile.md`(每个项目做一次)
13
13
  - **Execute** — 读取 profile,完全按照它走发布流程
14
14
 
15
15
  ---
16
16
 
17
17
  ## 入口判断
18
18
 
19
- 检查 `.hskill/release-profile.md` 是否存在:
19
+ 检查 `.hskill/release-project/release-profile.md` 是否存在:
20
20
 
21
21
  ```bash
22
- ls .hskill/release-profile.md 2>/dev/null && echo "EXISTS" || echo "NOT_FOUND"
22
+ ls .hskill/release-project/release-profile.md 2>/dev/null && echo "EXISTS" || echo "NOT_FOUND"
23
23
  ```
24
24
 
25
25
  - **NOT_FOUND** → 执行 Init 阶段
@@ -36,7 +36,7 @@ ls .hskill/release-profile.md 2>/dev/null && echo "EXISTS" || echo "NOT_FOUND"
36
36
 
37
37
  依次从以下来源收集信息:
38
38
 
39
- 1. 项目配置文件:`workflow-config.yml`、`CLAUDE.md`、`AGENTS.md`、`CONTRIBUTING.md`
39
+ 1. 项目配置文件:`.hskill/init-workflow/workflow-config.yml`、`CLAUDE.md`、`AGENTS.md`、`CONTRIBUTING.md`
40
40
  2. git 历史和现有分支:
41
41
 
42
42
  ```bash
@@ -101,18 +101,18 @@ ls .github/workflows/ .gitlab-ci.yml .circleci/ 2>/dev/null
101
101
  读取 `references/release-profile-template.md` 作为骨架,把 I-1 到 I-4 收集到的信息填进去。
102
102
 
103
103
  ```bash
104
- mkdir -p .hskill
104
+ mkdir -p .hskill/release-project
105
105
  ```
106
106
 
107
107
  模板只提供四个顶级节(分支模型、版本文件、发布方式、特殊规则),每节的具体内容完全由项目决定,用最能清楚表达意图的方式写——几行文字、表格、命令示例均可。没有特殊规则就删掉那节。
108
108
 
109
- 写完后给用户看,确认无误再保存。告诉用户:**`.hskill/release-profile.md` 可以随时手动编辑**,是下次发版的唯一依据。
109
+ 写完后给用户看,确认无误再保存。告诉用户:**`.hskill/release-project/release-profile.md` 可以随时手动编辑**,是下次发版的唯一依据。
110
110
 
111
111
  ---
112
112
 
113
113
  ## Execute 阶段
114
114
 
115
- **读取 `.hskill/release-profile.md`,完全按照它执行。** profile 是唯一依据,不在 profile 里的事不要自己发明。
115
+ **读取 `.hskill/release-project/release-profile.md`,完全按照它执行。** profile 是唯一依据,不在 profile 里的事不要自己发明。
116
116
 
117
117
  ### Step E-0 — 读取 profile + 前置检查
118
118
 
@@ -215,7 +215,7 @@ git push origin <tag>
215
215
 
216
216
  ## 常见问题
217
217
 
218
- **profile 信息有误** — 直接编辑 `.hskill/release-profile.md` 后重新触发 Execute。
218
+ **profile 信息有误** — 直接编辑 `.hskill/release-project/release-profile.md` 后重新触发 Execute。
219
219
 
220
220
  **发版前检查失败** — 按失败信息修复后,重新从 E-0 开始。
221
221
 
@@ -2,6 +2,7 @@ import json
2
2
  import sys
3
3
  import typer
4
4
 
5
+ from hub.cli import git as git_cmd
5
6
  from hub.cli import projects as proj_cmd
6
7
  from hub.cli import tasks as task_cmd
7
8
 
@@ -12,3 +13,4 @@ app = typer.Typer(
12
13
  )
13
14
  app.add_typer(proj_cmd.app, name="projects")
14
15
  app.add_typer(task_cmd.app, name="tasks")
16
+ app.add_typer(git_cmd.app, name="git")
@@ -0,0 +1,120 @@
1
+ import sys
2
+ from pathlib import Path
3
+ from typing import Optional
4
+
5
+ import typer
6
+
7
+ from hub.core.db import HubDB
8
+ from hub.core.projects import get_project_path
9
+ from hub.tui.git import (
10
+ fetch_repo,
11
+ get_branches,
12
+ get_recent_commits,
13
+ get_working_tree,
14
+ is_git_with_remote,
15
+ )
16
+
17
+ app = typer.Typer(no_args_is_help=True)
18
+
19
+
20
+ def _resolve_path(project: Optional[str]) -> Path:
21
+ if project:
22
+ db = HubDB()
23
+ p = get_project_path(db, project)
24
+ if not p:
25
+ typer.echo(f"Error: project '{project}' not found", err=True)
26
+ raise SystemExit(1)
27
+ return Path(p)
28
+ return Path.cwd()
29
+
30
+
31
+ @app.command("status")
32
+ def git_status(
33
+ project: Optional[str] = typer.Option(None, "--project", "-p", help="Project name"),
34
+ commits: int = typer.Option(5, "--commits", "-n", help="Number of recent commits to show"),
35
+ ):
36
+ """Show branch, working tree, and recent commits."""
37
+ path = _resolve_path(project)
38
+
39
+ branches = get_branches(path)
40
+ current = next((b for b in branches if b["is_current"]), None)
41
+ wt = get_working_tree(path)
42
+ recent = get_recent_commits(path, n=commits)
43
+
44
+ typer.echo(f"\n{path.name}")
45
+
46
+ typer.echo("\nBRANCH")
47
+ if current:
48
+ typer.echo(f" local {current['name']}")
49
+ if current["upstream"]:
50
+ typer.echo(f" tracking {current['upstream']}")
51
+ parts = []
52
+ if current["ahead"]:
53
+ parts.append(f"↑{current['ahead']}")
54
+ if current["behind"]:
55
+ parts.append(f"↓{current['behind']}")
56
+ typer.echo(f" sync {' '.join(parts) if parts else 'up to date'}")
57
+ else:
58
+ typer.echo(" tracking none (local only)")
59
+ else:
60
+ typer.echo(" not a git repository")
61
+
62
+ typer.echo("\nWORKING TREE")
63
+ total = wt["modified"] + wt["new"] + wt["deleted"]
64
+ if total == 0:
65
+ typer.echo(" clean")
66
+ else:
67
+ parts = []
68
+ if wt["modified"]:
69
+ parts.append(f"{wt['modified']} modified")
70
+ if wt["new"]:
71
+ parts.append(f"{wt['new']} new")
72
+ if wt["deleted"]:
73
+ parts.append(f"{wt['deleted']} deleted")
74
+ typer.echo(f" {', '.join(parts)}")
75
+
76
+ if recent:
77
+ typer.echo("\nRECENT COMMITS")
78
+ for c in recent:
79
+ msg = c["msg"][:50] + "…" if len(c["msg"]) > 50 else c["msg"]
80
+ typer.echo(f" {c['sha']} {msg} ({c['date']})")
81
+
82
+ typer.echo("")
83
+
84
+
85
+ @app.command("fetch")
86
+ def git_fetch(
87
+ project: Optional[str] = typer.Option(None, "--project", "-p", help="Project name"),
88
+ ):
89
+ """Fetch all remotes."""
90
+ path = _resolve_path(project)
91
+ if not is_git_with_remote(path):
92
+ typer.echo("No remote configured — nothing to fetch.", err=True)
93
+ raise SystemExit(1)
94
+ typer.echo(f"Fetching {path.name}…")
95
+ fetch_repo(path)
96
+ typer.echo("Done.")
97
+
98
+
99
+ @app.command("branches")
100
+ def git_branches(
101
+ project: Optional[str] = typer.Option(None, "--project", "-p", help="Project name"),
102
+ ):
103
+ """List branches with upstream sync status."""
104
+ path = _resolve_path(project)
105
+ branches = get_branches(path)
106
+ if not branches:
107
+ typer.echo("No branches found (not a git repository?).")
108
+ return
109
+ for b in branches:
110
+ marker = "*" if b["is_current"] else " "
111
+ if b["is_local_only"]:
112
+ sync = "(local only)"
113
+ else:
114
+ parts = []
115
+ if b["ahead"]:
116
+ parts.append(f"↑{b['ahead']}")
117
+ if b["behind"]:
118
+ parts.append(f"↓{b['behind']}")
119
+ sync = " ".join(parts) if parts else "up to date"
120
+ typer.echo(f" {marker} {b['name']:<32} {sync}")
package/tools/hub/hub.sh CHANGED
@@ -15,10 +15,20 @@ fi
15
15
 
16
16
  VENV_DIR="${HOME}/.hskill/tools/hub/venv"
17
17
  INSTALL_DIR="${HOME}/.hskill/tools/hub"
18
+ HASH_FILE="${VENV_DIR}/.installed_hash"
18
19
 
19
- if [ ! -x "${VENV_DIR}/bin/hub" ]; then
20
+ _hash_source() {
21
+ find "${INSTALL_DIR}" -type f \( -name "*.py" -o -name "*.toml" -o -name "*.json" \) \
22
+ ! -path "*/__pycache__/*" \
23
+ | sort | xargs sha256sum 2>/dev/null | sha256sum | awk '{print $1}'
24
+ }
25
+
26
+ CURRENT_HASH=$(_hash_source)
27
+
28
+ if [ ! -x "${VENV_DIR}/bin/hub" ] || [ "$(cat "${HASH_FILE}" 2>/dev/null)" != "${CURRENT_HASH}" ]; then
20
29
  python3 -m venv "${VENV_DIR}"
21
- "${VENV_DIR}/bin/pip" install -q "${INSTALL_DIR}"
30
+ "${VENV_DIR}/bin/pip" install -q --upgrade "${INSTALL_DIR}"
31
+ echo "${CURRENT_HASH}" > "${HASH_FILE}"
22
32
  fi
23
33
 
24
34
  exec "${VENV_DIR}/bin/hub" "$@"
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "hub"
7
- version = "1.0.0"
7
+ version = "1.1.0"
8
8
  requires-python = ">=3.11"
9
9
  dependencies = [
10
10
  "typer>=0.12",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hub",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "personal developer OS — projects, git status, tasks",
5
5
  "extraPaths": ["hub", "pyproject.toml"],
6
6
  "uninstallPaths": ["~/.hskill/tools/hub/venv", "~/.hskill/tools/hub"],