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 +12 -0
- package/package.json +1 -1
- package/skills/coding/init-workflow/SKILL.md +12 -11
- package/skills/design/sync-design/SKILL.md +8 -2
- package/skills/meta/clean-git/SKILL.md +3 -3
- package/skills/meta/release-project/SKILL.md +9 -9
- package/skills/writing/forge-doc/scripts/__pycache__/md_to_pdf.cpython-314.pyc +0 -0
- package/tools/hub/hub/cli/__init__.py +2 -0
- package/tools/hub/hub/cli/__pycache__/__init__.cpython-314.pyc +0 -0
- package/tools/hub/hub/cli/__pycache__/git.cpython-314.pyc +0 -0
- package/tools/hub/hub/cli/__pycache__/projects.cpython-314.pyc +0 -0
- package/tools/hub/hub/cli/git.py +120 -0
- package/tools/hub/hub/core/__pycache__/projects.cpython-314.pyc +0 -0
- package/tools/hub/hub.sh +12 -2
- package/tools/hub/pyproject.toml +1 -1
- package/tools/hub/tool.json +1 -1
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,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
|
-
读取
|
|
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
|
-
|
|
29
|
+
按以下顺序查找配置文件:
|
|
30
30
|
|
|
31
|
-
1.
|
|
32
|
-
2. 项目根目录
|
|
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
|
-
每次运行都根据当前
|
|
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
|
-
读取
|
|
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. 写回
|
|
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
|
-
检查 `.
|
|
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
|
-
未检测到 .
|
|
185
|
+
未检测到 .hskill/clean-git/branch-cleanup.md。
|
|
186
186
|
是否根据本次规则生成配置文件,方便下次复用?[y/n]
|
|
187
187
|
```
|
|
188
188
|
|
|
189
|
-
若用户确认,生成 `.
|
|
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.
|
|
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
|
|
|
Binary file
|
|
@@ -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")
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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}")
|
|
Binary file
|
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
|
-
|
|
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" "$@"
|
package/tools/hub/pyproject.toml
CHANGED
package/tools/hub/tool.json
CHANGED