@hi-man/himan 0.2.0 → 0.2.2
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/.nvmrc +1 -0
- package/CHANGELOG.md +65 -0
- package/README.md +30 -64
- package/dist/adapters/git/repo-manager.js +41 -6
- package/dist/adapters/source/git-source-adapter.js +101 -15
- package/dist/services/index.js +72 -37
- package/dist/state/index-cache-store.js +4 -3
- package/dist/utils/errors.js +2 -0
- package/docs/development.md +83 -0
- package/docs/error-codes.md +132 -0
- package/docs/mvp/README.md +143 -0
- package/docs/mvp/create-resource.md +129 -0
- package/docs/mvp/impl.md +111 -0
- package/package.json +26 -5
package/.nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
22.22.1
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on Keep a Changelog, and this project follows semver for the npm package version.
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.2.2] - 2026-05-07
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Added a PR verify workflow that runs typecheck, tests, and build before merging to `master`.
|
|
14
|
+
|
|
15
|
+
## [0.2.1] - 2026-05-07
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Added this changelog to make release notes and user-visible changes explicit.
|
|
20
|
+
- Added npm package metadata for Node.js engine compatibility and package discovery.
|
|
21
|
+
- Added public contributing, security, and code of conduct documents.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- Clean `dist` before every build so npm packages cannot include stale generated files.
|
|
26
|
+
- Aligned MVP, v1.0, and roadmap docs with current resource type and multi-agent behavior.
|
|
27
|
+
- Added publish preflight checks for resource metadata and entry files, with stable publish error codes.
|
|
28
|
+
- Restored lock-file installs from the source recorded in `himan.lock` instead of the current default source.
|
|
29
|
+
- Updated repository links to `https://github.com/himan-group/himan`.
|
|
30
|
+
- Updated Git source refresh to fast-forward clean cached working trees after fetch while preserving dirty local edits.
|
|
31
|
+
- Updated list cache invalidation to track `himan.yaml` metadata content instead of parent directory mtimes.
|
|
32
|
+
- Moved developer, testing, release, and CI maintenance documentation from README to `docs/development.md`.
|
|
33
|
+
- Improved README installation guidance for npm, one-off execution, local development, and CLI entry points.
|
|
34
|
+
- Included README-linked user documentation in the npm package and changed GitHub workflow links to repository URLs.
|
|
35
|
+
|
|
36
|
+
## [0.2.0] - 2026-05-06
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- Added command groups for `source`, `resource`, `project`, and `agent`, while keeping backward-compatible top-level lifecycle commands.
|
|
41
|
+
- Added dedicated binaries: `himan-source`, `himan-resource`, and `himan-project`.
|
|
42
|
+
- Added default agent configuration commands for project and global scopes.
|
|
43
|
+
- Added multi-agent installation targets for `cursor`, `claude-code`, `codex`, and `openclaw`.
|
|
44
|
+
- Added `command` and `skill` lifecycle support across create, list, history, install, dev, publish, and uninstall flows.
|
|
45
|
+
- Added project `himan.lock` support for reproducible installs.
|
|
46
|
+
- Added copy install mode in addition to symlink mode.
|
|
47
|
+
- Added local index cache support for resource listing.
|
|
48
|
+
- Added repository guidance files for Codex workflows.
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
|
|
52
|
+
- Split CLI registration into source, resource, project, agent, and shared command modules.
|
|
53
|
+
- Expanded README and planning docs to reflect multi-type resources, lock behavior, multi-agent targets, and source management.
|
|
54
|
+
|
|
55
|
+
## [0.1.0] - 2026-04-08
|
|
56
|
+
|
|
57
|
+
### Added
|
|
58
|
+
|
|
59
|
+
- Initial Git-backed CLI for managing prompt and agent assets.
|
|
60
|
+
- Added source initialization, resource listing, history, install, dev, publish, create, and uninstall workflows.
|
|
61
|
+
- Added resource metadata scanning from `himan.yaml`.
|
|
62
|
+
- Added semver tag-based resource versioning.
|
|
63
|
+
- Added local store, repo cache, config, and index state foundations.
|
|
64
|
+
- Added npm publishing and version tag GitHub Actions workflows.
|
|
65
|
+
- Added Vitest coverage for adapters, state, utilities, and CLI integration paths.
|
package/README.md
CHANGED
|
@@ -2,29 +2,44 @@
|
|
|
2
2
|
|
|
3
3
|
himan(含义为"Hey, man"),AI Coding 时代的 Prompt / Agent 资产管理系统(CLI + Git source)
|
|
4
4
|
|
|
5
|
+
## 环境要求
|
|
6
|
+
|
|
7
|
+
- Node.js 22.x;本仓库开发环境由 [.nvmrc](./.nvmrc) 固定为 `22.22.1`。
|
|
8
|
+
- Git;Git source 的初始化、版本查询、安装和发布都依赖本机 Git。
|
|
9
|
+
|
|
5
10
|
## 安装与运行
|
|
6
11
|
|
|
12
|
+
### 使用 npm 包
|
|
13
|
+
|
|
14
|
+
全局安装后可直接使用 `himan`:
|
|
15
|
+
|
|
7
16
|
```bash
|
|
8
|
-
|
|
9
|
-
|
|
17
|
+
npm install -g @hi-man/himan
|
|
18
|
+
himan --help
|
|
10
19
|
```
|
|
11
20
|
|
|
12
|
-
|
|
21
|
+
也可以一次性运行主入口:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx @hi-man/himan --help
|
|
25
|
+
pnpm dlx @hi-man/himan --help
|
|
26
|
+
```
|
|
13
27
|
|
|
14
|
-
|
|
15
|
-
- 本地开发:`pnpm run dev -- <子命令>`
|
|
16
|
-
- 或直接:`node dist/bin/himan.js <子命令>`
|
|
28
|
+
### 命令入口
|
|
17
29
|
|
|
18
|
-
|
|
30
|
+
包内提供四个 CLI 入口:
|
|
19
31
|
|
|
32
|
+
- `himan <子命令>`(主入口)
|
|
20
33
|
- `himan-source <子命令>`(source 相关)
|
|
21
34
|
- `himan-resource <子命令>`(resource/project 相关)
|
|
22
35
|
- `himan-project <子命令>`(project 相关)
|
|
23
36
|
|
|
24
|
-
|
|
37
|
+
下文默认使用 `himan` 主入口;三个专用入口在对应章节单独列出。
|
|
25
38
|
|
|
26
39
|
## 一分钟上手
|
|
27
40
|
|
|
41
|
+
以下示例假设你已有一个可访问的 himan Git source 仓库,仓库中存在 `my-rule` 的资源版本 tag,并且你拥有发布所需的 Git push 权限。
|
|
42
|
+
|
|
28
43
|
```bash
|
|
29
44
|
himan init https://github.com/your-org/your-himan-registry.git
|
|
30
45
|
himan list rule
|
|
@@ -45,7 +60,7 @@ himan publish rule my-rule --patch
|
|
|
45
60
|
- `rule` -> `.himan/dev/rule/<name>`
|
|
46
61
|
- `command` -> `.himan/dev/command/<name>`
|
|
47
62
|
- `skill` -> `.himan/dev/skill/<name>`
|
|
48
|
-
- lock 文件:`install <type> <name[@version]>` 会写入 `himan.lock
|
|
63
|
+
- lock 文件:`install <type> <name[@version]>` 会写入 `himan.lock`,记录 source、精确版本、agent 和安装模式;`himan install`(无参数)会按 lock 记录的 source 批量恢复安装,不受当前 default source 切换影响。
|
|
49
64
|
- 安装模式:默认 `--mode link` 使用软链;也可用 `--mode copy` 将资源复制到目标 agent 目录,lock 会记录并复现该模式。
|
|
50
65
|
- 默认 agent:`agent use <agent>` 默认写当前项目 `.himan/config.json`;加 `--global` 写入 `~/.himan/config.json`。当前项目配置优先于全局配置。
|
|
51
66
|
|
|
@@ -82,7 +97,7 @@ himan publish rule my-rule --patch
|
|
|
82
97
|
|
|
83
98
|
| 命令 | 说明 |
|
|
84
99
|
| --------------------------------- | --------------------------------------------------------- |
|
|
85
|
-
| `install [type] [name[@version]] [--agent a,b] [--mode link\|copy]` |
|
|
100
|
+
| `install [type] [name[@version]] [--agent a,b] [--mode link\|copy]` | 有参数时从当前 default source 安装指定资源;**无参数**时按 `himan.lock` 记录的 source 批量安装;可覆盖安装目标 agent 或安装模式 |
|
|
86
101
|
| `dev <type> <name>` | 切换到开发态,并按安装模式将项目目标指向或复制自 `.himan/dev/...` |
|
|
87
102
|
| `uninstall <type> <name>` | 从项目移除安装目标,并同步删除 `himan.lock` 条目 |
|
|
88
103
|
| `publish <type> <name>` | 默认 `--patch`;可选 `--minor` / `--major`(勿同时使用多个) |
|
|
@@ -107,15 +122,16 @@ himan publish rule my-rule --patch
|
|
|
107
122
|
说明:资源与项目相关命令统一使用 `--agent` 指定目标 Agent。
|
|
108
123
|
若未显式传 `--agent`,`create` / `install` 会使用当前项目默认 agent、全局默认 agent、资源 metadata 或内置默认 `cursor` 中最合适的一项;`dev` 会优先使用 lock 中记录的 agent。
|
|
109
124
|
|
|
110
|
-
`publish` 优先使用项目里 `.himan/dev`
|
|
125
|
+
`publish` 优先使用项目里 `.himan/dev` 对应目录,否则用源仓库里对应目录。发布前会校验 `himan.yaml` 与入口文件;需要可推送的 Git 权限。若该资源已在 lock 中,发布后会同步更新 lock 版本。
|
|
111
126
|
|
|
112
127
|
`--json` 模式下,失败时会输出机器可读错误 JSON(`stderr`)。错误码定义见 [docs/error-codes.md](./docs/error-codes.md)。
|
|
113
128
|
|
|
114
|
-
|
|
129
|
+
多源说明:当前是「**多来源可配置,单来源生效**」模型。显式资源命令(`list/install <type> .../history/dev/publish`)作用于当前 default source;`himan install` 无参数恢复时使用 `himan.lock` 中记录的 source。
|
|
115
130
|
|
|
116
131
|
## 当前范围
|
|
117
132
|
|
|
118
133
|
- 源:**仅 Git**(`init`)。Registry 适配器已预留,尚未实现。
|
|
134
|
+
- 包定位:当前仅承诺 CLI 使用,不提供稳定的 Node.js 程序化 API。
|
|
119
135
|
|
|
120
136
|
## FAQ
|
|
121
137
|
|
|
@@ -133,56 +149,6 @@ himan source list
|
|
|
133
149
|
**Q: `list` 和 `source list` 有什么区别?**
|
|
134
150
|
A: `source list` 查看「我配置了哪些来源」;`list` 查看「当前 default source 里有哪些资源」。
|
|
135
151
|
|
|
136
|
-
##
|
|
137
|
-
|
|
138
|
-
```bash
|
|
139
|
-
pnpm test
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
## 发布 npm 包(维护者)
|
|
143
|
-
|
|
144
|
-
### 流程概览
|
|
145
|
-
|
|
146
|
-
1. **在分支上完成开发与合并前检查**
|
|
147
|
-
本地可执行 `pnpm run verify`(类型检查、单测、`build`),确认通过后再提 PR。
|
|
148
|
-
|
|
149
|
-
2. **更新 `package.json` 中的 `version`**
|
|
150
|
-
npm 不允许重复发布同一版本号。合并进 `master` 前,在 PR 里把版本改成 registry 上尚未存在的号。
|
|
151
|
-
- 手动改 `version` 字段,或
|
|
152
|
-
- 在分支上执行其一(只改版本号,**不会**发包):`pnpm run version:patch` / `version:minor` / `version:major`(使用 `npm version … --no-git-tag-version`,需自行 `git add` / `commit` 版本变更)。
|
|
153
|
-
Git 标签约定:与 `version` 对应、带前缀 **`v`**(如 `1.2.0` → 标签 `v1.2.0`)。
|
|
154
|
-
|
|
155
|
-
3. **合并到 `master`**
|
|
156
|
-
推送合并后的 `master` 会触发 GitHub Actions 工作流 [`.github/workflows/publish-npm.yml`](.github/workflows/publish-npm.yml):安装依赖后执行 **`pnpm run release`**(即再次 `verify` + `npm publish`)。
|
|
157
|
-
npm 发布认证使用 **Trusted Publishing**,不使用长期 `NPM_TOKEN`。需在 npmjs.com 的 `@hi-man/himan` 包设置中添加 Trusted Publisher:
|
|
158
|
-
- Provider: GitHub Actions
|
|
159
|
-
- Organization or user: `lidetao`
|
|
160
|
-
- Repository: `himan`
|
|
161
|
-
- Workflow filename: `publish-npm.yml`
|
|
162
|
-
workflow 已授予 OIDC 所需的 `id-token: write` 权限,并在发布前升级 npm CLI 到支持 Trusted Publishing 的版本。
|
|
163
|
-
|
|
164
|
-
4. **手动从 CI 再发一次(可选)**
|
|
165
|
-
在 GitHub **Actions → Publish to npm → Run workflow** 可手动运行同一流程(例如在修复密钥后重试)。
|
|
166
|
-
|
|
167
|
-
### 本地命令(与 CI 中的 `pnpm run release` 一致)
|
|
168
|
-
|
|
169
|
-
| 命令 | 作用 |
|
|
170
|
-
|------|------|
|
|
171
|
-
| `pnpm run verify` | 仅检查(类型 / 测试 / 构建) |
|
|
172
|
-
| `pnpm run release:dry` | 检查 + `npm publish --dry-run`(演练,不上传) |
|
|
173
|
-
| `pnpm run release:test` | 检查 + 将版本打成 `*-test.*` 预发布号并发布到 **`@test` 标签** |
|
|
174
|
-
| `pnpm run release` | 检查 + 发布 **latest**(维护者本地发包时用;**请写 `pnpm run release`**,勿用裸命令 `pnpm publish`,二者不是同一套流程) |
|
|
175
|
-
| `pnpm run version:patch` / `version:minor` / `version:major` | 仅提升 `package.json` 版本号,不发包 |
|
|
176
|
-
|
|
177
|
-
发测试标签后,安装示例:`npm i @hi-man/himan@test`。
|
|
178
|
-
|
|
179
|
-
### CI:合并前校验与合并后打 Git 标签
|
|
180
|
-
|
|
181
|
-
| 工作流 | 文件 | 说明 |
|
|
182
|
-
|--------|------|------|
|
|
183
|
-
| **PR version tag check** | [`.github/workflows/pr-master-version-tag.yml`](.github/workflows/pr-master-version-tag.yml) | 目标分支为 `master` 的 PR:读取 **PR 头提交**上的 `package.json` 的 `version`,若远端已存在同名标签 **`v{version}`**,则 **检查失败**(用于在合并前拦截重复版本)。 |
|
|
184
|
-
| **Tag version on master** | [`.github/workflows/push-master-version-tag.yml`](.github/workflows/push-master-version-tag.yml) | 向 `master` **推送**后(含合并 PR):在 **当前推送提交**上创建并推送注释标签 **`v{version}`**。若标签已存在、创建或 `git push` 失败,仅输出 **告警**(`::warning::`),**工作流仍成功**,不撤销已发生的 merge;请按日志提示在本机补打标签并 `git push origin v{x.y.z}`。 |
|
|
185
|
-
|
|
186
|
-
**启用「合并前拦截」**:在 GitHub **Settings → Branches** 中为 `master` 配置分支保护,勾选 **Require status checks to pass before merging**,并勾选必选检查 **`PR version tag check / version-tag-available`**(名称以仓库里 Actions 界面为准)。
|
|
152
|
+
## 开发与维护
|
|
187
153
|
|
|
188
|
-
|
|
154
|
+
源码开发、测试和 npm 发包流程见 [docs/development.md](./docs/development.md)。
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { promises as fs } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { simpleGit } from "simple-git";
|
|
4
|
+
import { HimanError, errorCodes } from "../../utils/errors.js";
|
|
4
5
|
export class RepoManager {
|
|
5
6
|
async cloneOrFetch(repo, targetDir) {
|
|
6
7
|
const gitDir = path.join(targetDir, ".git");
|
|
@@ -12,6 +13,7 @@ export class RepoManager {
|
|
|
12
13
|
}
|
|
13
14
|
const git = simpleGit(targetDir);
|
|
14
15
|
await git.fetch(["--tags", "--prune"]);
|
|
16
|
+
await this.fastForwardCleanWorkingTree(git);
|
|
15
17
|
}
|
|
16
18
|
async listTags(repoDir, pattern) {
|
|
17
19
|
const git = simpleGit(repoDir);
|
|
@@ -44,14 +46,21 @@ export class RepoManager {
|
|
|
44
46
|
await fs.writeFile(destination, content, "utf8");
|
|
45
47
|
}
|
|
46
48
|
}
|
|
47
|
-
async commitTagAndPush(repoDir, message, tag, branch) {
|
|
49
|
+
async commitTagAndPush(repoDir, message, tag, branch, paths = ["."]) {
|
|
48
50
|
const git = simpleGit(repoDir);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
const pathspecs = paths.length > 0 ? paths : ["."];
|
|
52
|
+
await git.add(pathspecs);
|
|
53
|
+
const stagedFiles = await git.raw([
|
|
54
|
+
"diff",
|
|
55
|
+
"--cached",
|
|
56
|
+
"--name-only",
|
|
57
|
+
"--",
|
|
58
|
+
...pathspecs,
|
|
59
|
+
]);
|
|
60
|
+
if (!stagedFiles.trim()) {
|
|
61
|
+
throw new HimanError(errorCodes.PUBLISH_NO_CHANGES, "No changes to publish.");
|
|
53
62
|
}
|
|
54
|
-
await git.commit(message);
|
|
63
|
+
await git.commit(message, pathspecs);
|
|
55
64
|
await git.addTag(tag);
|
|
56
65
|
const currentBranch = (await git.raw(["rev-parse", "--abbrev-ref", "HEAD"])).trim();
|
|
57
66
|
const targetBranch = branch ?? currentBranch;
|
|
@@ -67,4 +76,30 @@ export class RepoManager {
|
|
|
67
76
|
return false;
|
|
68
77
|
}
|
|
69
78
|
}
|
|
79
|
+
async fastForwardCleanWorkingTree(git) {
|
|
80
|
+
const status = await git.status();
|
|
81
|
+
if (!status.isClean()) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const upstream = await this.getCurrentUpstream(git);
|
|
85
|
+
if (!upstream) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
await git.raw(["merge", "--ff-only", upstream]);
|
|
89
|
+
}
|
|
90
|
+
async getCurrentUpstream(git) {
|
|
91
|
+
try {
|
|
92
|
+
const upstream = await git.raw([
|
|
93
|
+
"rev-parse",
|
|
94
|
+
"--abbrev-ref",
|
|
95
|
+
"--symbolic-full-name",
|
|
96
|
+
"@{u}",
|
|
97
|
+
]);
|
|
98
|
+
const trimmed = upstream.trim();
|
|
99
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
70
105
|
}
|
|
@@ -3,6 +3,7 @@ import { ResourceScanner } from "../resource/resource-scanner.js";
|
|
|
3
3
|
import semver from "semver";
|
|
4
4
|
import { HimanError, errorCodes } from "../../utils/errors.js";
|
|
5
5
|
import { promises as fs } from "node:fs";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
6
7
|
import path from "node:path";
|
|
7
8
|
import YAML from "yaml";
|
|
8
9
|
import { IndexCacheStore } from "../../state/index-cache-store.js";
|
|
@@ -23,13 +24,13 @@ export class GitSourceAdapter {
|
|
|
23
24
|
const repoId = this.sourceConfig?.repoId ?? "default";
|
|
24
25
|
const typeDir = this.getTypeDir(type);
|
|
25
26
|
const baseDir = path.join(repoDir, typeDir);
|
|
26
|
-
const
|
|
27
|
+
const metadataHash = await this.getResourceMetadataHash(baseDir);
|
|
27
28
|
const cached = await this.indexStore.get(repoId, type);
|
|
28
|
-
if (cached && cached.
|
|
29
|
+
if (cached && cached.metadataHash === metadataHash) {
|
|
29
30
|
return cached.resources;
|
|
30
31
|
}
|
|
31
32
|
const scanned = await this.scanner.scanByType(repoDir, type);
|
|
32
|
-
await this.indexStore.upsert(repoId, type,
|
|
33
|
+
await this.indexStore.upsert(repoId, type, metadataHash, scanned);
|
|
33
34
|
return scanned;
|
|
34
35
|
}
|
|
35
36
|
async history(type, name) {
|
|
@@ -47,6 +48,7 @@ export class GitSourceAdapter {
|
|
|
47
48
|
async publish(type, name, version, sourceDir) {
|
|
48
49
|
const repoDir = this.getRepoDir();
|
|
49
50
|
const targetDir = path.join(repoDir, `${type}s`, name);
|
|
51
|
+
const metadata = await this.validatePublishResource(type, name, sourceDir);
|
|
50
52
|
const sameDir = await this.isSameDirectory(sourceDir, targetDir);
|
|
51
53
|
if (!sameDir) {
|
|
52
54
|
await fs.rm(targetDir, { recursive: true, force: true });
|
|
@@ -54,14 +56,10 @@ export class GitSourceAdapter {
|
|
|
54
56
|
await fs.cp(sourceDir, targetDir, { recursive: true });
|
|
55
57
|
}
|
|
56
58
|
const yamlPath = path.join(targetDir, "himan.yaml");
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const parsed = YAML.parse(raw);
|
|
60
|
-
parsed.version = version;
|
|
61
|
-
await fs.writeFile(yamlPath, YAML.stringify(parsed), "utf8");
|
|
62
|
-
}
|
|
59
|
+
metadata.version = version;
|
|
60
|
+
await fs.writeFile(yamlPath, YAML.stringify(metadata), "utf8");
|
|
63
61
|
const tag = `${type}/${name}@${version}`;
|
|
64
|
-
await this.repoManager.commitTagAndPush(repoDir, `publish ${type}/${name}@${version}`, tag);
|
|
62
|
+
await this.repoManager.commitTagAndPush(repoDir, `publish ${type}/${name}@${version}`, tag, undefined, [path.relative(repoDir, targetDir)]);
|
|
65
63
|
return { version, tag };
|
|
66
64
|
}
|
|
67
65
|
async create(type, name, options) {
|
|
@@ -118,14 +116,66 @@ export class GitSourceAdapter {
|
|
|
118
116
|
return false;
|
|
119
117
|
}
|
|
120
118
|
}
|
|
121
|
-
async
|
|
119
|
+
async validatePublishResource(type, name, resourceDir) {
|
|
120
|
+
const yamlPath = path.join(resourceDir, "himan.yaml");
|
|
121
|
+
if (!(await this.exists(yamlPath))) {
|
|
122
|
+
throw this.invalidResourceMetadata(type, name, "Missing himan.yaml for publish.", { yamlPath });
|
|
123
|
+
}
|
|
124
|
+
const raw = await fs.readFile(yamlPath, "utf8");
|
|
125
|
+
let parsed;
|
|
122
126
|
try {
|
|
123
|
-
|
|
124
|
-
return stat.mtimeMs;
|
|
127
|
+
parsed = YAML.parse(raw);
|
|
125
128
|
}
|
|
126
|
-
catch {
|
|
127
|
-
|
|
129
|
+
catch (error) {
|
|
130
|
+
throw this.invalidResourceMetadata(type, name, "himan.yaml is not valid YAML.", { yamlPath, reason: error instanceof Error ? error.message : String(error) });
|
|
131
|
+
}
|
|
132
|
+
if (!this.isRecord(parsed)) {
|
|
133
|
+
throw this.invalidResourceMetadata(type, name, "himan.yaml must be an object.", { yamlPath });
|
|
134
|
+
}
|
|
135
|
+
if (parsed.name !== name) {
|
|
136
|
+
throw this.invalidResourceMetadata(type, name, `himan.yaml name must be "${name}".`, { yamlPath, actual: parsed.name });
|
|
137
|
+
}
|
|
138
|
+
if (parsed.type !== type) {
|
|
139
|
+
throw this.invalidResourceMetadata(type, name, `himan.yaml type must be "${type}".`, { yamlPath, actual: parsed.type });
|
|
140
|
+
}
|
|
141
|
+
if (typeof parsed.entry !== "string" || parsed.entry.trim().length === 0) {
|
|
142
|
+
throw this.invalidResourceMetadata(type, name, "himan.yaml entry is required.", { yamlPath });
|
|
143
|
+
}
|
|
144
|
+
const entry = parsed.entry.trim();
|
|
145
|
+
const entryPath = path.resolve(resourceDir, entry);
|
|
146
|
+
const resourceRoot = path.resolve(resourceDir);
|
|
147
|
+
const relativeEntryPath = path.relative(resourceRoot, entryPath);
|
|
148
|
+
if (path.isAbsolute(entry) ||
|
|
149
|
+
relativeEntryPath === "" ||
|
|
150
|
+
relativeEntryPath.startsWith("..") ||
|
|
151
|
+
path.isAbsolute(relativeEntryPath)) {
|
|
152
|
+
throw this.invalidResourceMetadata(type, name, "himan.yaml entry must point to a file inside the resource directory.", { yamlPath, entry });
|
|
153
|
+
}
|
|
154
|
+
let entryStat;
|
|
155
|
+
try {
|
|
156
|
+
entryStat = await fs.stat(entryPath);
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
if (!this.isNotFoundError(error)) {
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
throw this.invalidResourceMetadata(type, name, `Resource entry file not found: ${entry}`, { yamlPath, entry, entryPath });
|
|
128
163
|
}
|
|
164
|
+
if (!entryStat.isFile()) {
|
|
165
|
+
throw this.invalidResourceMetadata(type, name, `Resource entry is not a file: ${entry}`, { yamlPath, entry, entryPath });
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
...parsed,
|
|
169
|
+
name,
|
|
170
|
+
type,
|
|
171
|
+
entry,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
invalidResourceMetadata(type, name, message, details) {
|
|
175
|
+
return new HimanError(errorCodes.INVALID_RESOURCE_METADATA, `Invalid metadata for ${type}/${name}: ${message}`, details);
|
|
176
|
+
}
|
|
177
|
+
isRecord(value) {
|
|
178
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
129
179
|
}
|
|
130
180
|
getTypeDir(type) {
|
|
131
181
|
if (type === "rule")
|
|
@@ -134,6 +184,42 @@ export class GitSourceAdapter {
|
|
|
134
184
|
return "commands";
|
|
135
185
|
return "skills";
|
|
136
186
|
}
|
|
187
|
+
async getResourceMetadataHash(baseDir) {
|
|
188
|
+
const hash = createHash("sha256");
|
|
189
|
+
hash.update("himan-resource-index-v1");
|
|
190
|
+
if (!(await this.exists(baseDir))) {
|
|
191
|
+
hash.update("\0missing");
|
|
192
|
+
return hash.digest("hex");
|
|
193
|
+
}
|
|
194
|
+
const entries = await fs.readdir(baseDir, { withFileTypes: true });
|
|
195
|
+
const resourceDirNames = entries
|
|
196
|
+
.filter((entry) => entry.isDirectory())
|
|
197
|
+
.map((entry) => entry.name)
|
|
198
|
+
.sort();
|
|
199
|
+
for (const resourceDirName of resourceDirNames) {
|
|
200
|
+
hash.update("\0dir:");
|
|
201
|
+
hash.update(resourceDirName);
|
|
202
|
+
const yamlPath = path.join(baseDir, resourceDirName, "himan.yaml");
|
|
203
|
+
try {
|
|
204
|
+
const raw = await fs.readFile(yamlPath);
|
|
205
|
+
hash.update("\0yaml:");
|
|
206
|
+
hash.update(raw);
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
if (!this.isNotFoundError(error)) {
|
|
210
|
+
throw error;
|
|
211
|
+
}
|
|
212
|
+
hash.update("\0yaml-missing");
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return hash.digest("hex");
|
|
216
|
+
}
|
|
217
|
+
isNotFoundError(error) {
|
|
218
|
+
return (typeof error === "object" &&
|
|
219
|
+
error !== null &&
|
|
220
|
+
"code" in error &&
|
|
221
|
+
error.code === "ENOENT");
|
|
222
|
+
}
|
|
137
223
|
getDefaultEntry(type) {
|
|
138
224
|
return type === "skill" ? "SKILL.md" : "content.md";
|
|
139
225
|
}
|
package/dist/services/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { GitSourceAdapter } from "../adapters/source/git-source-adapter.js";
|
|
|
2
2
|
import { RegistrySourceAdapter } from "../adapters/source/registry-source-adapter.js";
|
|
3
3
|
import { StateStore } from "../state/state-store.js";
|
|
4
4
|
import { ProjectConfigStore } from "../state/project-config-store.js";
|
|
5
|
-
import { ProjectLockStore } from "../state/project-lock-store.js";
|
|
5
|
+
import { ProjectLockStore, } from "../state/project-lock-store.js";
|
|
6
6
|
import { PathResolver } from "../utils/path-resolver.js";
|
|
7
7
|
import { toRepoId } from "../utils/repo-id.js";
|
|
8
8
|
import { HimanError, errorCodes } from "../utils/errors.js";
|
|
@@ -158,31 +158,8 @@ export class ServiceFactory {
|
|
|
158
158
|
return source.history(type, name);
|
|
159
159
|
}
|
|
160
160
|
async install(type, name, version, projectDir, agents, mode = "link") {
|
|
161
|
-
const source = await this.
|
|
162
|
-
|
|
163
|
-
const history = await source.history(type, name);
|
|
164
|
-
if (history.length === 0) {
|
|
165
|
-
throw new HimanError(errorCodes.RESOURCE_NOT_FOUND, `Resource not found: ${type}/${name}`);
|
|
166
|
-
}
|
|
167
|
-
const resolvedVersion = this.resolveVersion(history, version);
|
|
168
|
-
const storePath = this.getStorePath(type, name, resolvedVersion);
|
|
169
|
-
if (!(await this.exists(storePath))) {
|
|
170
|
-
await source.pull(type, name, resolvedVersion, storePath);
|
|
171
|
-
}
|
|
172
|
-
const resourceMeta = await this.readResourceMetaFromDir(storePath);
|
|
173
|
-
const effectiveTargets = await this.resolveEffectiveAgents(projectDir, agents, resourceMeta?.agents);
|
|
174
|
-
const linkPaths = getProjectResourcePaths(projectDir, type, name, effectiveTargets);
|
|
175
|
-
for (const linkPath of linkPaths) {
|
|
176
|
-
await this.materializeResource(storePath, linkPath, mode);
|
|
177
|
-
}
|
|
178
|
-
await this.lockStore.upsertResource(projectDir, sourceInfo, {
|
|
179
|
-
type,
|
|
180
|
-
name,
|
|
181
|
-
version: resolvedVersion,
|
|
182
|
-
agents: effectiveTargets,
|
|
183
|
-
mode,
|
|
184
|
-
});
|
|
185
|
-
return { type, name, version: resolvedVersion, linkPath: linkPaths[0], mode };
|
|
161
|
+
const { source, sourceInfo } = await this.loadSourceWithInfoFromConfig();
|
|
162
|
+
return this.installWithSource(source, sourceInfo, type, name, version, projectDir, agents, mode);
|
|
186
163
|
}
|
|
187
164
|
async dev(type, name, projectDir) {
|
|
188
165
|
const installInfo = await this.resolveInstalledResource(projectDir, type, name);
|
|
@@ -275,21 +252,69 @@ export class ServiceFactory {
|
|
|
275
252
|
throw new HimanError(errorCodes.LOCK_NOT_FOUND, `Lock file has no resources: ${this.lockStore.getLockPath(projectDir)}`);
|
|
276
253
|
}
|
|
277
254
|
const results = [];
|
|
255
|
+
const lockSourceInfo = this.normalizeLockSourceInfo(lock.source);
|
|
256
|
+
const lockedSource = await this.loadSourceFromLock(lockSourceInfo);
|
|
278
257
|
for (const item of lock.resources) {
|
|
279
|
-
const result = await this.
|
|
258
|
+
const result = await this.installWithSource(lockedSource, lockSourceInfo, item.type, item.name, item.version, projectDir, agents ?? item.agents, mode ?? this.resolveInstallMode(item.mode));
|
|
280
259
|
results.push(result);
|
|
281
260
|
}
|
|
282
261
|
return results;
|
|
283
262
|
}
|
|
263
|
+
async installWithSource(source, sourceInfo, type, name, version, projectDir, agents, mode) {
|
|
264
|
+
const history = await source.history(type, name);
|
|
265
|
+
if (history.length === 0) {
|
|
266
|
+
throw new HimanError(errorCodes.RESOURCE_NOT_FOUND, `Resource not found: ${type}/${name}`);
|
|
267
|
+
}
|
|
268
|
+
const resolvedVersion = this.resolveVersion(history, version);
|
|
269
|
+
const storePath = this.getStorePath(type, name, resolvedVersion);
|
|
270
|
+
if (!(await this.exists(storePath))) {
|
|
271
|
+
await source.pull(type, name, resolvedVersion, storePath);
|
|
272
|
+
}
|
|
273
|
+
const resourceMeta = await this.readResourceMetaFromDir(storePath);
|
|
274
|
+
const effectiveTargets = await this.resolveEffectiveAgents(projectDir, agents, resourceMeta?.agents);
|
|
275
|
+
const linkPaths = getProjectResourcePaths(projectDir, type, name, effectiveTargets);
|
|
276
|
+
for (const linkPath of linkPaths) {
|
|
277
|
+
await this.materializeResource(storePath, linkPath, mode);
|
|
278
|
+
}
|
|
279
|
+
await this.lockStore.upsertResource(projectDir, sourceInfo, {
|
|
280
|
+
type,
|
|
281
|
+
name,
|
|
282
|
+
version: resolvedVersion,
|
|
283
|
+
agents: effectiveTargets,
|
|
284
|
+
mode,
|
|
285
|
+
});
|
|
286
|
+
return { type, name, version: resolvedVersion, linkPath: linkPaths[0], mode };
|
|
287
|
+
}
|
|
284
288
|
async loadSourceFromConfig() {
|
|
289
|
+
return (await this.loadSourceWithInfoFromConfig()).source;
|
|
290
|
+
}
|
|
291
|
+
async loadSourceWithInfoFromConfig() {
|
|
292
|
+
const { name, source: stateSource } = await this.getCurrentSourceState();
|
|
293
|
+
const sourceInfo = this.toLockSourceInfo(stateSource, name);
|
|
294
|
+
const source = await this.loadSourceFromInfo(sourceInfo);
|
|
295
|
+
return {
|
|
296
|
+
source,
|
|
297
|
+
sourceInfo,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
async loadSourceFromLock(sourceInfo) {
|
|
301
|
+
return this.loadSourceFromInfo(sourceInfo);
|
|
302
|
+
}
|
|
303
|
+
async loadSourceFromInfo(sourceInfo) {
|
|
304
|
+
const normalizedSourceInfo = this.normalizeLockSourceInfo(sourceInfo);
|
|
305
|
+
const sourceConfig = this.buildSourceConfig(normalizedSourceInfo.type, normalizedSourceInfo.repo, normalizedSourceInfo.repoId);
|
|
306
|
+
const source = this.createSource(normalizedSourceInfo.type);
|
|
307
|
+
await source.init(sourceConfig);
|
|
308
|
+
return source;
|
|
309
|
+
}
|
|
310
|
+
async getCurrentSourceState() {
|
|
285
311
|
const config = await this.stateStore.loadConfig();
|
|
286
312
|
if (!config?.source) {
|
|
287
313
|
throw new HimanError(errorCodes.CONFIG_NOT_FOUND, "Source config not found. Please run `himan init <git_repo>` first.");
|
|
288
314
|
}
|
|
289
|
-
const
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
return source;
|
|
315
|
+
const currentName = config.sources?.default ?? "default";
|
|
316
|
+
const currentSource = config.sources?.items[currentName] ?? config.source;
|
|
317
|
+
return { name: currentName, source: currentSource };
|
|
293
318
|
}
|
|
294
319
|
createSource(type) {
|
|
295
320
|
return type === "registry"
|
|
@@ -297,14 +322,24 @@ export class ServiceFactory {
|
|
|
297
322
|
: new GitSourceAdapter();
|
|
298
323
|
}
|
|
299
324
|
async getLockSourceInfo() {
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
325
|
+
const { name, source } = await this.getCurrentSourceState();
|
|
326
|
+
return this.toLockSourceInfo(source, name);
|
|
327
|
+
}
|
|
328
|
+
toLockSourceInfo(source, name) {
|
|
329
|
+
return this.normalizeLockSourceInfo({
|
|
330
|
+
name,
|
|
331
|
+
type: source.type,
|
|
332
|
+
repo: source.repo,
|
|
333
|
+
repoId: source.repoId,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
normalizeLockSourceInfo(sourceInfo) {
|
|
337
|
+
if (sourceInfo.type !== "git" || !sourceInfo.repo) {
|
|
338
|
+
return sourceInfo;
|
|
303
339
|
}
|
|
304
340
|
return {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
repoId: config.source.repoId,
|
|
341
|
+
...sourceInfo,
|
|
342
|
+
repoId: sourceInfo.repoId ?? toRepoId(sourceInfo.repo),
|
|
308
343
|
};
|
|
309
344
|
}
|
|
310
345
|
async getLockedResource(projectDir, type, name) {
|
|
@@ -12,12 +12,13 @@ export class IndexCacheStore {
|
|
|
12
12
|
return null;
|
|
13
13
|
return data.entries.find((item) => item.repoId === repoId && item.type === type) ?? null;
|
|
14
14
|
}
|
|
15
|
-
async upsert(repoId, type,
|
|
15
|
+
async upsert(repoId, type, metadataHash, resources) {
|
|
16
16
|
const now = new Date().toISOString();
|
|
17
17
|
const file = (await this.load()) ?? { version: 1, entries: [] };
|
|
18
18
|
const found = file.entries.find((item) => item.repoId === repoId && item.type === type);
|
|
19
19
|
if (found) {
|
|
20
|
-
found.
|
|
20
|
+
found.metadataHash = metadataHash;
|
|
21
|
+
delete found.baseDirMtimeMs;
|
|
21
22
|
found.resources = resources;
|
|
22
23
|
found.updatedAt = now;
|
|
23
24
|
}
|
|
@@ -25,7 +26,7 @@ export class IndexCacheStore {
|
|
|
25
26
|
file.entries.push({
|
|
26
27
|
repoId,
|
|
27
28
|
type,
|
|
28
|
-
|
|
29
|
+
metadataHash,
|
|
29
30
|
resources,
|
|
30
31
|
updatedAt: now,
|
|
31
32
|
});
|
package/dist/utils/errors.js
CHANGED
|
@@ -21,5 +21,7 @@ export const errorCodes = {
|
|
|
21
21
|
RESOURCE_EXISTS: "E_RESOURCE_EXISTS",
|
|
22
22
|
TEMPLATE_NOT_FOUND: "E_TEMPLATE_NOT_FOUND",
|
|
23
23
|
INVALID_RESOURCE_NAME: "E_INVALID_RESOURCE_NAME",
|
|
24
|
+
INVALID_RESOURCE_METADATA: "E_INVALID_RESOURCE_METADATA",
|
|
25
|
+
PUBLISH_NO_CHANGES: "E_PUBLISH_NO_CHANGES",
|
|
24
26
|
UNSUPPORTED_RESOURCE_TYPE: "E_UNSUPPORTED_RESOURCE_TYPE",
|
|
25
27
|
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Development
|
|
2
|
+
|
|
3
|
+
本文面向 `himan` 仓库开发者和 npm 包维护者。用户安装和使用 CLI 请优先阅读 [README.md](../README.md)。
|
|
4
|
+
|
|
5
|
+
## 环境要求
|
|
6
|
+
|
|
7
|
+
- Node.js 22.x;本仓库开发环境由 [.nvmrc](../.nvmrc) 固定为 `22.22.1`。
|
|
8
|
+
- pnpm 10.32.1;包管理器版本由 [package.json](../package.json) 固定。
|
|
9
|
+
- Git;测试和发布流程会使用本地 Git。
|
|
10
|
+
|
|
11
|
+
## 本地源码开发
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm install
|
|
15
|
+
pnpm run build
|
|
16
|
+
pnpm run dev -- --help
|
|
17
|
+
node dist/bin/himan.js --help
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 开发与测试
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pnpm test
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
常用脚本:
|
|
27
|
+
|
|
28
|
+
| 命令 | 作用 |
|
|
29
|
+
|------|------|
|
|
30
|
+
| `pnpm run clean` | 删除 `dist/` |
|
|
31
|
+
| `pnpm run build` | 清理并编译 TypeScript 到 `dist/` |
|
|
32
|
+
| `pnpm run typecheck` | 运行 TypeScript 类型检查,不输出文件 |
|
|
33
|
+
| `pnpm test` | 运行 Vitest 一次 |
|
|
34
|
+
| `pnpm run verify` | 依次运行 typecheck、test、build |
|
|
35
|
+
|
|
36
|
+
## 发布 npm 包(维护者)
|
|
37
|
+
|
|
38
|
+
### 流程概览
|
|
39
|
+
|
|
40
|
+
1. **在分支上完成开发与合并前检查**
|
|
41
|
+
本地可执行 `pnpm run verify`(类型检查、单测、`build`),确认通过后再提 PR。PR 会自动运行同一组核心校验。
|
|
42
|
+
|
|
43
|
+
2. **更新 `package.json` 中的 `version` 与 `CHANGELOG.md`**
|
|
44
|
+
npm 不允许重复发布同一版本号。合并进 `master` 前,在 PR 里把版本改成 registry 上尚未存在的号,并把用户可见变更记录到 [CHANGELOG.md](../CHANGELOG.md)。
|
|
45
|
+
- 手动改 `version` 字段,或
|
|
46
|
+
- 在分支上执行其一(只改版本号,**不会**发包):`pnpm run version:patch` / `version:minor` / `version:major`(使用 `npm version … --no-git-tag-version`,需自行 `git add` / `commit` 版本变更)。
|
|
47
|
+
Git 标签约定:与 `version` 对应、带前缀 **`v`**(如 `1.2.0` → 标签 `v1.2.0`)。
|
|
48
|
+
|
|
49
|
+
3. **合并到 `master`**
|
|
50
|
+
推送合并后的 `master` 会触发 GitHub Actions 工作流 [`.github/workflows/publish-npm.yml`](https://github.com/himan-group/himan/blob/master/.github/workflows/publish-npm.yml):安装依赖后执行 **`pnpm run release`**(即再次 `verify` + `npm publish`)。
|
|
51
|
+
npm 发布认证使用 **Trusted Publishing**,不使用长期 `NPM_TOKEN`。需在 npmjs.com 的 `@hi-man/himan` 包设置中添加 Trusted Publisher:
|
|
52
|
+
- Provider: GitHub Actions
|
|
53
|
+
- Organization or user: `himan-group`
|
|
54
|
+
- Repository: `himan`
|
|
55
|
+
- Workflow filename: `publish-npm.yml`
|
|
56
|
+
workflow 已授予 OIDC 所需的 `id-token: write` 权限,并在发布前升级 npm CLI 到支持 Trusted Publishing 的版本。
|
|
57
|
+
|
|
58
|
+
4. **手动从 CI 再发一次(可选)**
|
|
59
|
+
在 GitHub **Actions → Publish to npm → Run workflow** 可手动运行同一流程(例如在修复密钥后重试)。
|
|
60
|
+
|
|
61
|
+
### 本地命令(与 CI 中的 `pnpm run release` 一致)
|
|
62
|
+
|
|
63
|
+
| 命令 | 作用 |
|
|
64
|
+
|------|------|
|
|
65
|
+
| `pnpm run verify` | 仅检查(类型 / 测试 / 构建) |
|
|
66
|
+
| `pnpm run release:dry` | 检查 + `npm publish --dry-run`(演练,不上传) |
|
|
67
|
+
| `pnpm run release:test` | 检查 + 将版本打成 `*-test.*` 预发布号并发布到 **`@test` 标签** |
|
|
68
|
+
| `pnpm run release` | 检查 + 发布 **latest**(维护者本地发包时用;**请写 `pnpm run release`**,勿用裸命令 `pnpm publish`,二者不是同一套流程) |
|
|
69
|
+
| `pnpm run version:patch` / `version:minor` / `version:major` | 仅提升 `package.json` 版本号,不发包 |
|
|
70
|
+
|
|
71
|
+
发测试标签后,安装示例:`npm i @hi-man/himan@test`。
|
|
72
|
+
|
|
73
|
+
### CI:PR 校验、发布与合并后打 Git 标签
|
|
74
|
+
|
|
75
|
+
| 工作流 | 文件 | 说明 |
|
|
76
|
+
|--------|------|------|
|
|
77
|
+
| **PR verify** | [`.github/workflows/pr-verify.yml`](https://github.com/himan-group/himan/blob/master/.github/workflows/pr-verify.yml) | 目标分支为 `master` 的 PR:安装依赖后依次运行 `pnpm run typecheck`、`pnpm run test`、`pnpm run build`。 |
|
|
78
|
+
| **PR version tag check** | [`.github/workflows/pr-master-version-tag.yml`](https://github.com/himan-group/himan/blob/master/.github/workflows/pr-master-version-tag.yml) | 目标分支为 `master` 的 PR:读取 **PR 头提交**上的 `package.json` 的 `version`,若远端已存在同名标签 **`v{version}`**,则 **检查失败**(用于在合并前拦截重复版本)。 |
|
|
79
|
+
| **Tag version on master** | [`.github/workflows/push-master-version-tag.yml`](https://github.com/himan-group/himan/blob/master/.github/workflows/push-master-version-tag.yml) | 向 `master` **推送**后(含合并 PR):在 **当前推送提交**上创建并推送注释标签 **`v{version}`**。若标签已存在、创建或 `git push` 失败,仅输出 **告警**(`::warning::`),**工作流仍成功**,不撤销已发生的 merge;请按日志提示在本机补打标签并 `git push origin v{x.y.z}`。 |
|
|
80
|
+
|
|
81
|
+
**启用「合并前拦截」**:在 GitHub **Settings → Branches** 中为 `master` 配置分支保护,勾选 **Require status checks to pass before merging**,并勾选必选检查 **`PR verify / verify`** 和 **`PR version tag check / version-tag-available`**(名称以仓库里 Actions 界面为准)。
|
|
82
|
+
|
|
83
|
+
说明:来自 fork 的 PR 同样会跑上述 PR 检查;打标签工作流需要 **Actions 对仓库有写权限**(工作流内已设 `contents: write`)。若组织策略禁止 `GITHUB_TOKEN` 写标签,推送标签会失败,需按告警手动推送。
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# himan 错误码说明
|
|
2
|
+
|
|
3
|
+
本文档面向 CLI 使用者与自动化脚本维护者,说明 `himan` 的错误码、典型触发场景和建议处理方式。
|
|
4
|
+
|
|
5
|
+
## `--json` 错误输出格式
|
|
6
|
+
|
|
7
|
+
当命令带 `--json` 且执行失败时,CLI 会输出结构化错误(输出到 `stderr`)。
|
|
8
|
+
该规则同时覆盖:
|
|
9
|
+
|
|
10
|
+
- 命令执行业务错误(如资源不存在)
|
|
11
|
+
- 参数/命令解析错误(如缺参数、未知命令)
|
|
12
|
+
|
|
13
|
+
输出格式:
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"ok": false,
|
|
18
|
+
"error": {
|
|
19
|
+
"code": "E_RESOURCE_NOT_FOUND",
|
|
20
|
+
"message": "Resource not found: rule/code-review",
|
|
21
|
+
"details": {
|
|
22
|
+
"key": "value"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
字段说明:
|
|
29
|
+
|
|
30
|
+
- `ok`: 固定为 `false`
|
|
31
|
+
- `error.code`: 稳定错误码,建议脚本优先按此分流
|
|
32
|
+
- `error.message`: 人类可读错误信息
|
|
33
|
+
- `error.details`: 可选,附加上下文
|
|
34
|
+
|
|
35
|
+
## 错误码列表
|
|
36
|
+
|
|
37
|
+
### `E_CONFIG_NOT_FOUND`
|
|
38
|
+
|
|
39
|
+
- **含义**:未找到源配置。
|
|
40
|
+
- **常见触发**:首次使用前直接执行 `list/install/dev/publish`。
|
|
41
|
+
- **建议处理**:先执行 `himan init <git_repo>`,或检查 `~/.himan/config.json` 是否存在。
|
|
42
|
+
|
|
43
|
+
### `E_NOT_IMPLEMENTED`
|
|
44
|
+
|
|
45
|
+
- **含义**:功能已预留但尚未实现。
|
|
46
|
+
- **常见触发**:使用 registry 相关路径。
|
|
47
|
+
- **建议处理**:改用 Git source;关注后续版本计划。
|
|
48
|
+
|
|
49
|
+
### `E_INVALID_INPUT`
|
|
50
|
+
|
|
51
|
+
- **含义**:输入参数不合法。
|
|
52
|
+
- **常见触发**:source 名称不符合 kebab-case、git source 未提供 repo、install mode 不是 `link|copy`、agent 名称不支持等。
|
|
53
|
+
- **建议处理**:按命令帮助修正参数格式。
|
|
54
|
+
|
|
55
|
+
### `E_RESOURCE_NOT_FOUND`
|
|
56
|
+
|
|
57
|
+
- **含义**:资源或来源不存在。
|
|
58
|
+
- **常见触发**:安装不存在的资源、切换到不存在的 source 名称、发布目标资源不存在。
|
|
59
|
+
- **建议处理**:先执行 `himan list <type>` / `himan source list` 确认名称。
|
|
60
|
+
|
|
61
|
+
### `E_VERSION_NOT_FOUND`
|
|
62
|
+
|
|
63
|
+
- **含义**:指定版本不存在。
|
|
64
|
+
- **常见触发**:`install <type> <name@version>` 中版本号不在 tag 历史内。
|
|
65
|
+
- **建议处理**:先执行 `himan history <type> <name>` 查看可用版本。
|
|
66
|
+
|
|
67
|
+
### `E_INSTALL_NOT_FOUND`
|
|
68
|
+
|
|
69
|
+
- **含义**:项目内未找到已安装目标。
|
|
70
|
+
- **常见触发**:未安装直接 `dev`、或手动删除了 `.cursor/*/<name>` 等安装目标。
|
|
71
|
+
- **建议处理**:先重新执行 `himan install <type> <name[@version]>`。
|
|
72
|
+
|
|
73
|
+
### `E_LOCK_NOT_FOUND`
|
|
74
|
+
|
|
75
|
+
- **含义**:未找到 lock 文件,或 lock 中无资源可恢复。
|
|
76
|
+
- **常见触发**:项目未生成 `himan.lock` 就执行 `himan install`(无参数)。
|
|
77
|
+
- **建议处理**:先安装至少一个资源以生成 lock,或检查 `himan.lock` 路径。
|
|
78
|
+
|
|
79
|
+
### `E_LOCK_INVALID`
|
|
80
|
+
|
|
81
|
+
- **含义**:`himan.lock` 存在但格式不合法。
|
|
82
|
+
- **常见触发**:手动编辑导致 JSON 结构损坏、`version/resources` 字段异常。
|
|
83
|
+
- **建议处理**:修复 lock JSON,或删除后重新通过 `install <type> <name[@version]>` 生成。
|
|
84
|
+
|
|
85
|
+
### `E_CLI_USAGE`
|
|
86
|
+
|
|
87
|
+
- **含义**:CLI 参数或命令解析错误(Commander 层)。
|
|
88
|
+
- **常见触发**:缺少必填参数、未知命令、未知选项。
|
|
89
|
+
- **建议处理**:检查命令拼写与参数数量,使用 `himan <command> --help` 查看正确用法。
|
|
90
|
+
- **附加信息**:`error.details.commanderCode` 可用于进一步区分错误类别(如 `commander.missingArgument`)。
|
|
91
|
+
|
|
92
|
+
### `E_RESOURCE_EXISTS`
|
|
93
|
+
|
|
94
|
+
- **含义**:资源已存在,无法重复创建。
|
|
95
|
+
- **常见触发**:重复执行 `create <type> <name>`。
|
|
96
|
+
- **建议处理**:改名,或显式使用 `--force`。
|
|
97
|
+
|
|
98
|
+
### `E_TEMPLATE_NOT_FOUND`
|
|
99
|
+
|
|
100
|
+
- **含义**:模板不存在。
|
|
101
|
+
- **常见触发**:`create` 使用了不支持的模板名。
|
|
102
|
+
- **建议处理**:使用当前支持模板(默认 `basic`)。
|
|
103
|
+
|
|
104
|
+
### `E_INVALID_RESOURCE_NAME`
|
|
105
|
+
|
|
106
|
+
- **含义**:资源名不符合命名规则。
|
|
107
|
+
- **常见触发**:使用非 kebab-case 的名称。
|
|
108
|
+
- **建议处理**:改为 `kebab-case`(如 `code-review`)。
|
|
109
|
+
|
|
110
|
+
### `E_INVALID_RESOURCE_METADATA`
|
|
111
|
+
|
|
112
|
+
- **含义**:资源元数据不合法,无法发布或读取为有效资源。
|
|
113
|
+
- **常见触发**:`publish` 目标缺少 `himan.yaml`,`name/type/entry` 不匹配,或 `entry` 指向的入口文件不存在。
|
|
114
|
+
- **建议处理**:检查资源目录下的 `himan.yaml`,确认 `name`、`type`、`entry` 与命令参数和文件结构一致。
|
|
115
|
+
|
|
116
|
+
### `E_PUBLISH_NO_CHANGES`
|
|
117
|
+
|
|
118
|
+
- **含义**:发布时没有可提交的资源变更。
|
|
119
|
+
- **常见触发**:重复发布已写入相同内容和版本的资源目录。
|
|
120
|
+
- **建议处理**:确认资源内容或元数据已经变更,再重新执行 `publish`。
|
|
121
|
+
|
|
122
|
+
### `E_UNSUPPORTED_RESOURCE_TYPE`
|
|
123
|
+
|
|
124
|
+
- **含义**:资源类型不受支持。
|
|
125
|
+
- **常见触发**:输入了 `rule|command|skill` 之外的类型。
|
|
126
|
+
- **建议处理**:修正类型为 `rule`、`command` 或 `skill`。
|
|
127
|
+
|
|
128
|
+
### `E_UNKNOWN`
|
|
129
|
+
|
|
130
|
+
- **含义**:未映射到业务错误码的通用异常。
|
|
131
|
+
- **常见触发**:程序运行期意外错误、普通 `Error` 抛出。
|
|
132
|
+
- **建议处理**:保留完整命令与错误文本,提交 issue 或定位堆栈来源。
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# himan MVP 功能点与技术设计
|
|
2
|
+
|
|
3
|
+
> 状态说明:本文记录 MVP 设计和当前实现范围的收敛版。当前 CLI 行为以仓库根目录 [README.md](../../README.md) 和 [Codex repo map](../codex/repo-map.md) 为准。
|
|
4
|
+
|
|
5
|
+
## 1. MVP 目标
|
|
6
|
+
|
|
7
|
+
在 1 周内交付一个可实际使用的最小版本,完成资源资产的管理与发布闭环。
|
|
8
|
+
|
|
9
|
+
**当前实现范围:**
|
|
10
|
+
- 默认源模型:Git(`himan init <git_repo>`,本地缓存仓库并写配置);已支持命名 source 的 `add/use/list`,业务命令按当前 default source 生效。
|
|
11
|
+
- 本地 CLI,命令说明见仓库根目录 [README.md](../../README.md)
|
|
12
|
+
- 资源版本以 Git Tag 为准,格式 `<type>/<name>@<semver>`
|
|
13
|
+
- 资源类型能力:
|
|
14
|
+
- `rule`:`create` / `list` / `history` / `install` / `dev` / `publish` / `uninstall`
|
|
15
|
+
- `command`:`create` / `list` / `history` / `install` / `dev` / `publish` / `uninstall`
|
|
16
|
+
- `skill`:`create` / `list` / `history` / `install` / `dev` / `publish` / `uninstall`
|
|
17
|
+
- 远程 Registry 源:仅占位,二期实现
|
|
18
|
+
|
|
19
|
+
**不包含:** 可用 Registry、AI 搜索、PR 自动发布。
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 2. MVP 功能清单
|
|
24
|
+
|
|
25
|
+
### 2.1 `init`
|
|
26
|
+
|
|
27
|
+
- `himan init <git_repo>`
|
|
28
|
+
- 克隆或更新远程仓库到用户目录下的缓存路径,并记录默认源配置。
|
|
29
|
+
|
|
30
|
+
### 2.2 `list`
|
|
31
|
+
|
|
32
|
+
- `himan list [type]`,`--json` 可选
|
|
33
|
+
- 扫描源仓库中各类型目录下的 `himan.yaml`,返回名称、描述、目标 agent、入口文件等。
|
|
34
|
+
|
|
35
|
+
### 2.3 `history`
|
|
36
|
+
|
|
37
|
+
- `himan history <type> <name>`
|
|
38
|
+
- 按 tag 模式 `<type>/<name>@*` 列出历史,semver 合法项排序输出。
|
|
39
|
+
|
|
40
|
+
### 2.4 `install`
|
|
41
|
+
|
|
42
|
+
- `himan install <type> <name>` 或 `<name>@version`,`type` 支持 `rule|command|skill`
|
|
43
|
+
- 也支持 `himan install`(无参数)按 `himan.lock` 批量复现安装。
|
|
44
|
+
- 未指定版本则安装该资源最新 tag 对应版本。
|
|
45
|
+
- 若本地 store 中已有该版本缓存,则复用、不重新从 Git 导出;否则导出到 store。
|
|
46
|
+
- 在项目下按安装模式创建目标(默认 `--mode link` 软链;`--mode copy` 复制)。
|
|
47
|
+
- 目标路径由 agent 和资源类型共同决定:
|
|
48
|
+
- `cursor` -> `.cursor/{rules|commands|skills}/<name>`
|
|
49
|
+
- `claude-code` -> `.claude/{rules|commands|skills}/<name>`
|
|
50
|
+
- `codex` -> `.agents/{rules|commands|skills}/<name>`
|
|
51
|
+
- `openclaw` -> `.openclaw/{rules|commands|skills}/<name>`
|
|
52
|
+
|
|
53
|
+
### 2.5 `dev`
|
|
54
|
+
|
|
55
|
+
- `himan dev <type> <name>`,`type` 支持 `rule|command|skill`;需先 `install`。
|
|
56
|
+
- 将当前安装内容复制到项目开发目录(已存在则默认不覆盖),再按安装模式更新项目目标:
|
|
57
|
+
- `rule`:`.himan/dev/rule/<name>`
|
|
58
|
+
- `command`:`.himan/dev/command/<name>`
|
|
59
|
+
- `skill`:`.himan/dev/skill/<name>`
|
|
60
|
+
|
|
61
|
+
### 2.6 `publish`
|
|
62
|
+
|
|
63
|
+
- `himan publish <type> <name> --patch|--minor|--major`(默认 patch,三选一)
|
|
64
|
+
- 发布内容优先取项目 `.himan/dev/<type>/<name>`,否则取源仓库内对应资源目录。
|
|
65
|
+
- 新版本:基于已有 tag 最新 semver 递增;无任何历史时从 `0.0.0` 起算。
|
|
66
|
+
- 写回源仓库、提交、打 tag、推送,并将该版本同步到本地 store。
|
|
67
|
+
- 若该资源在项目中已有安装目标,会按 lock 中的安装模式同步到新版本目录。
|
|
68
|
+
|
|
69
|
+
### 2.7 `create`
|
|
70
|
+
|
|
71
|
+
- `himan create <type> <name>` 及常用选项(描述、目标 agent、dry-run、force、json 等)
|
|
72
|
+
- 生成 `rule` / `command` / `skill` 标准目录与 `himan.yaml`、入口模板
|
|
73
|
+
- 与 `publish` 衔接:`create → 编辑 → publish`
|
|
74
|
+
|
|
75
|
+
### 2.8 `agent`
|
|
76
|
+
|
|
77
|
+
- `himan agent list` 查看支持的 agent。
|
|
78
|
+
- `himan agent use <agent[,agent]>` 设置当前项目默认 agent;加 `--global` 设置全局默认 agent。
|
|
79
|
+
- `himan agent current` 查看当前项目、全局和最终生效的默认 agent。
|
|
80
|
+
- `himan agent clear` 清除默认 agent 配置;默认清除当前项目,加 `--global` 清除全局配置。
|
|
81
|
+
- 默认 agent 解析顺序:显式 `--agent` > 当前项目配置 > 全局配置 > 资源 metadata > `cursor`。
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## 3. MVP 技术架构
|
|
86
|
+
|
|
87
|
+
### 3.1 分层(概念)
|
|
88
|
+
|
|
89
|
+
- **CLI**:解析参数、格式化输出、帮助与错误信息
|
|
90
|
+
- **编排**:初始化源、列表、历史、安装、开发态、发布、创建等资源生命周期
|
|
91
|
+
- **领域**:资源类型、版本、路径约定
|
|
92
|
+
- **适配**:Git 实现 + Registry 预留;扫描与解析元数据;版本计算;配置与全局路径
|
|
93
|
+
|
|
94
|
+
**原则:** store 按版本目录追加、不覆盖已有缓存;开发目录与项目安装目标分离;项目侧默认以软链引用资源,也支持复制。
|
|
95
|
+
|
|
96
|
+
### 3.2 目录与数据
|
|
97
|
+
|
|
98
|
+
**用户目录(如 `~/.himan`):**
|
|
99
|
+
- `repos/…`:Git 源缓存
|
|
100
|
+
- `store/<type>/<name>/<version>/`:按版本的资源快照
|
|
101
|
+
- `config.json`:当前源类型(git / registry 预留)、仓库地址、全局默认 agent 等
|
|
102
|
+
|
|
103
|
+
**项目目录:**
|
|
104
|
+
- `.cursor` / `.claude` / `.agents` / `.openclaw`:按 agent 和资源类型保存运行态目标(软链或副本)
|
|
105
|
+
- `.himan/config.json`:项目默认 agent 配置
|
|
106
|
+
- `.himan/dev/<type>/<name>`:资源开发态可编辑副本
|
|
107
|
+
|
|
108
|
+
**源仓库内资源布局:**
|
|
109
|
+
- `rules/<name>/`、`commands/<name>/`、`skills/<name>/`,各含 `himan.yaml` 与约定入口文件(如 `content.md`、`SKILL.md`)。
|
|
110
|
+
|
|
111
|
+
### 3.3 技术依赖(概要)
|
|
112
|
+
|
|
113
|
+
- Git:克隆、拉取、tag、按 tag 导出目录、提交与推送
|
|
114
|
+
- Semver:排序与下一版本计算
|
|
115
|
+
- YAML:资源元数据
|
|
116
|
+
- 文件系统:目录复制、符号链接
|
|
117
|
+
|
|
118
|
+
更细的实现说明见 [impl.md](./impl.md);创建资源见 [create-resource.md](./create-resource.md)。
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## 4. 非功能要求
|
|
123
|
+
|
|
124
|
+
- 幂等:`install` / `dev` 重复执行不破坏合理预期状态
|
|
125
|
+
- 可恢复:发布失败可重试,不依赖未文档化的中间态
|
|
126
|
+
- 可诊断:错误信息能指向仓库、tag、路径或权限问题
|
|
127
|
+
- 可测试:主流程有自动化测试覆盖
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 5. 测试策略
|
|
132
|
+
|
|
133
|
+
- 自动化测试覆盖:版本解析、元数据扫描、配置、以及 CLI 主流程(含临时用户目录与模拟 Git 远端)。
|
|
134
|
+
- 建议人工补充:真实网络 clone、鉴权失败、推送拒绝、脏工作区等。
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## 6. 交付标准
|
|
139
|
+
|
|
140
|
+
- 命令可执行,帮助信息完整
|
|
141
|
+
- 主流程在自动化测试中可回归
|
|
142
|
+
- 用户文档(根 README)与本 MVP 文档一致
|
|
143
|
+
- 配置与适配层为二期 Registry 预留扩展空间
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# himan `create` 命令说明(rule / command / skill)
|
|
2
|
+
|
|
3
|
+
说明「新建资源」的命令、目录与元数据约定,支撑 `create -> edit -> publish` 工作流。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. 设计目标
|
|
8
|
+
|
|
9
|
+
- 支持三类资源:`rule`、`command`、`skill`
|
|
10
|
+
- 统一目录结构、`himan.yaml` 与默认入口内容
|
|
11
|
+
- 与 `list` / `history` / `publish` / `install` / `dev` / `uninstall` 对所有类型保持一致
|
|
12
|
+
|
|
13
|
+
**本阶段不做:** AI 生成正文、通过 Registry 在线创建、创建后自动发布。
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 2. 命令
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
himan create <type> <name> [options]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- `name`:kebab-case,例如 `code-review`
|
|
24
|
+
|
|
25
|
+
**常用选项:**
|
|
26
|
+
|
|
27
|
+
- `--description`:描述
|
|
28
|
+
- `--agent`:目标 Agent,逗号分隔;未指定时使用当前项目默认 agent、全局默认 agent,最终回退到 `cursor`
|
|
29
|
+
- `--entry`:入口文件名(各类型有默认值)
|
|
30
|
+
- `--template`:模板名;MVP 仅内置 **basic**,其它名称会报错
|
|
31
|
+
- `--force`:目录已存在时覆盖
|
|
32
|
+
- `--dry-run`:只展示将创建的内容,不写盘
|
|
33
|
+
- `--json`:结构化输出
|
|
34
|
+
|
|
35
|
+
**默认入口文件:**
|
|
36
|
+
|
|
37
|
+
- `rule`、`command` → `content.md`
|
|
38
|
+
- `skill` → `SKILL.md`
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 3. 资源目录与元数据
|
|
43
|
+
|
|
44
|
+
创建后的仓库结构示例:
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
repo/
|
|
48
|
+
rules/<name>/
|
|
49
|
+
himan.yaml
|
|
50
|
+
content.md
|
|
51
|
+
commands/<name>/
|
|
52
|
+
himan.yaml
|
|
53
|
+
content.md
|
|
54
|
+
skills/<name>/
|
|
55
|
+
himan.yaml
|
|
56
|
+
SKILL.md
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`himan.yaml` 最小字段示例:
|
|
60
|
+
|
|
61
|
+
```yaml
|
|
62
|
+
name: code-review
|
|
63
|
+
type: rule
|
|
64
|
+
version: 0.1.0
|
|
65
|
+
entry: content.md
|
|
66
|
+
description: enforce code review standards
|
|
67
|
+
agents:
|
|
68
|
+
- cursor
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- `version` 为初始占位;**正式发布版本以 Git Tag 为准**
|
|
72
|
+
- `agents` 来自 `--agent` 或默认 agent 解析结果
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## 4. 流程概要
|
|
77
|
+
|
|
78
|
+
1. 读取本地配置,确认已初始化源
|
|
79
|
+
2. 校验类型与资源名格式
|
|
80
|
+
3. 解析目标路径 `rules|commands|skills/<name>`
|
|
81
|
+
4. 目录已存在且无 `--force` → 报错
|
|
82
|
+
5. 生成 `himan.yaml` 与入口模板(`--dry-run` 则不落盘)
|
|
83
|
+
6. 终端或 `--json` 输出结果;下一步由用户编辑再 `publish`
|
|
84
|
+
|
|
85
|
+
创建能力随源类型走同一抽象:**Git 已实现,Registry 未实现。**
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 5. 模板
|
|
90
|
+
|
|
91
|
+
- 当前仅 **basic**:最简结构与提示性说明
|
|
92
|
+
- 后续可扩展更多模板名;自定义模板目录可作为后续增强
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 6. 错误场景(产品语义)
|
|
97
|
+
|
|
98
|
+
- 资源目录已存在
|
|
99
|
+
- 模板不存在(含请求了尚未支持的模板名)
|
|
100
|
+
- 资源名不合法
|
|
101
|
+
- 不支持的资源类型
|
|
102
|
+
- 源未初始化或当前源不支持创建
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## 7. 测试关注点
|
|
107
|
+
|
|
108
|
+
- 名称与类型校验、默认 entry/agents、重复创建与 `--force`、`--dry-run` 不写盘、创建后 `list` 可见
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 8. 与资源工作流衔接
|
|
113
|
+
|
|
114
|
+
```text
|
|
115
|
+
create → edit → publish
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`create` 会在当前 Git source 缓存仓库中生成资源目录;用户编辑该目录后执行 `publish`。资源已有发布版本并安装到项目后,可再进入 `dev` 工作流:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
himan create rule code-review --description "enforce standards"
|
|
122
|
+
himan publish rule code-review --patch
|
|
123
|
+
himan install rule code-review
|
|
124
|
+
himan dev rule code-review
|
|
125
|
+
# 编辑 .himan/dev/rule/code-review/
|
|
126
|
+
himan publish rule code-review --patch
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
创建与发布职责分离,便于审核与版本治理。
|
package/docs/mvp/impl.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# himan MVP 详细技术实现方案
|
|
2
|
+
|
|
3
|
+
本文档从**行为与技术选型**说明 MVP 如何实现,不绑定具体源码文件或符号名。
|
|
4
|
+
|
|
5
|
+
> 状态说明:本文是 MVP 设计落地说明;当前 CLI 行为以仓库根目录 [README.md](../../README.md) 和 [Codex repo map](../codex/repo-map.md) 为准。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. 技术栈与原则
|
|
10
|
+
|
|
11
|
+
- TypeScript,运行于 Node.js LTS
|
|
12
|
+
- CLI 解析、Git 封装、YAML 解析、Semver、基于 Promise 的文件与软链操作
|
|
13
|
+
|
|
14
|
+
**原则:**
|
|
15
|
+
|
|
16
|
+
- 本地 store 按版本存放,已存在的版本目录不被覆盖(安装时复用缓存)
|
|
17
|
+
- 开发目录 `.himan/dev` 与运行态 agent 目录(`.cursor` / `.claude` / `.agents` / `.openclaw`)分离
|
|
18
|
+
- 正式发布版本以 **Git Tag** 为唯一事实来源;`himan.yaml` 中的 version 在发布时会与 tag 对齐
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 2. 功能到实现要点
|
|
23
|
+
|
|
24
|
+
### 2.1 `init <git_repo>`
|
|
25
|
+
|
|
26
|
+
- 确保全局目录(缓存仓库、store、配置)存在
|
|
27
|
+
- 由仓库 URL 推导稳定 id,首次克隆、后续拉取(含 tags)
|
|
28
|
+
- 写入本地配置:当前源为 Git、仓库地址与 id
|
|
29
|
+
- CLI 仅提供「Git URL」初始化;Registry 类型虽可在配置中预留,但尚无可用实现
|
|
30
|
+
|
|
31
|
+
### 2.2 `list [type]`
|
|
32
|
+
|
|
33
|
+
- 在缓存仓库内按类型扫描子目录,读取 `himan.yaml`
|
|
34
|
+
- 校验类型与必填字段(如 name、entry),不符的目录跳过
|
|
35
|
+
- 支持人类可读与 `--json` 输出
|
|
36
|
+
|
|
37
|
+
### 2.3 `history <type> <name>`
|
|
38
|
+
|
|
39
|
+
- 列出匹配 `<type>/<name>@*` 的 tag,解析 semver,非法 tag 忽略
|
|
40
|
+
- 按版本倒序输出
|
|
41
|
+
|
|
42
|
+
### 2.4 `install <type> <name>[@version]`
|
|
43
|
+
|
|
44
|
+
- 命令层接受 `rule|command|skill`
|
|
45
|
+
- 无版本则取该资源历史中的最新 semver
|
|
46
|
+
- 若本地 store 已有该版本目录则不再从 Git 导出;否则从对应 tag 导出资源树到 store
|
|
47
|
+
- 在项目中按 agent 和资源类型创建/更新安装目标,例如 `cursor -> .cursor/{rules|commands|skills}`、`codex -> .agents/{rules|commands|skills}`;默认软链到 store 中该版本,也可通过 `--mode copy` 复制内容
|
|
48
|
+
|
|
49
|
+
### 2.5 `dev <type> <name>`
|
|
50
|
+
|
|
51
|
+
- 命令层接受 `rule|command|skill`;依赖已安装(能解析当前安装目标)
|
|
52
|
+
- 将当前安装内容复制到 `.himan/dev/<type>/<name>`(目录已存在则默认不覆盖)
|
|
53
|
+
- 按安装模式将项目目标更新为 dev 目录的软链或副本
|
|
54
|
+
|
|
55
|
+
### 2.6 `publish <type> <name>`
|
|
56
|
+
|
|
57
|
+
- 发布源:优先项目 `.himan/dev/<type>/<name>`,否则缓存仓库内该资源目录
|
|
58
|
+
- 下一版本:基于历史最新 tag;无历史则从 `0.0.0` 按 patch/minor/major 递增
|
|
59
|
+
- 将内容同步回缓存仓库中的规范路径,更新元数据中的版本字段,提交、打 tag、推送
|
|
60
|
+
- 将新 tag 对应内容拉取到 store 新版本目录
|
|
61
|
+
- 若当前项目已安装该资源,则按 lock 中的安装模式同步更新项目内对应类型目标到新版本
|
|
62
|
+
|
|
63
|
+
### 2.7 `create <type> <name>`
|
|
64
|
+
|
|
65
|
+
- 校验类型与资源命名规则
|
|
66
|
+
- 在缓存仓库中创建 `rules|commands|skills/<name>` 及 `himan.yaml`、入口模板
|
|
67
|
+
- 支持覆盖、试运行、JSON 输出;创建后不自动发布
|
|
68
|
+
|
|
69
|
+
### 2.8 `agent`
|
|
70
|
+
|
|
71
|
+
- 命令层支持 `agent list|use|current|clear`
|
|
72
|
+
- 当前项目默认 agent 写入 `.himan/config.json`
|
|
73
|
+
- 全局默认 agent 写入 `~/.himan/config.json`
|
|
74
|
+
- 默认 agent 生效顺序:显式 `--agent` > 当前项目配置 > 全局配置 > 资源 metadata > `cursor`
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 3. 架构:源适配与编排
|
|
79
|
+
|
|
80
|
+
为避免上层与「一定是 Git」强耦合:
|
|
81
|
+
|
|
82
|
+
- 抽象一层 **资源源**:初始化、列表、历史、按版本拉取内容、发布、创建
|
|
83
|
+
- **Git** 为当前唯一完整实现
|
|
84
|
+
- **Registry** 为占位:调用即提示未实现,二期对接 API 与下载/上传
|
|
85
|
+
|
|
86
|
+
编排层负责:解析配置选择源、默认 agent、资源 store 路径、dev 拷贝、项目目标 materialize(软链或复制);与具体 Git 子命令细节隔离。
|
|
87
|
+
|
|
88
|
+
配置中可区分源类型并预留 Registry 所需字段(如 endpoint);当前 CLI 初始化路径只写入 Git 源。
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 4. 其他实现注意点
|
|
93
|
+
|
|
94
|
+
- 全局路径与用户主目录约定一致,避免魔法字符串散落
|
|
95
|
+
- 错误应能区分:未初始化、资源不存在、版本不存在、模板不支持、重复创建等
|
|
96
|
+
- 幂等与重试:安装、dev、发布在合理重复执行下行为可预期
|
|
97
|
+
- 平台:优先保证 macOS/Linux;Windows 软链与路径差异可后续单独验证
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 5. 测试
|
|
102
|
+
|
|
103
|
+
- 运行仓库内测试脚本(如 `pnpm test`),包含单元场景与 CLI 端到端场景(临时 HOME、本地裸仓库模拟远端等)
|
|
104
|
+
- 仍建议补充:真实网络失败、Windows、并发安装等
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 6. 文档与示例
|
|
109
|
+
|
|
110
|
+
- 命令参数与快速上手以根 README 为准
|
|
111
|
+
- 创建资源字段与目录约定见 [create-resource.md](./create-resource.md)
|
package/package.json
CHANGED
|
@@ -1,22 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hi-man/himan",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Prompt and agent asset management CLI",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ai",
|
|
7
|
+
"agent",
|
|
8
|
+
"prompt",
|
|
9
|
+
"prompt-management",
|
|
10
|
+
"cli",
|
|
11
|
+
"git",
|
|
12
|
+
"codex",
|
|
13
|
+
"cursor",
|
|
14
|
+
"claude"
|
|
15
|
+
],
|
|
5
16
|
"license": "MIT",
|
|
6
17
|
"type": "module",
|
|
7
18
|
"packageManager": "pnpm@10.32.1",
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=22 <23"
|
|
21
|
+
},
|
|
8
22
|
"repository": {
|
|
9
23
|
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/
|
|
24
|
+
"url": "git+https://github.com/himan-group/himan.git"
|
|
11
25
|
},
|
|
12
26
|
"bugs": {
|
|
13
|
-
"url": "https://github.com/
|
|
27
|
+
"url": "https://github.com/himan-group/himan/issues"
|
|
14
28
|
},
|
|
15
|
-
"homepage": "https://github.com/
|
|
29
|
+
"homepage": "https://github.com/himan-group/himan#readme",
|
|
16
30
|
"files": [
|
|
17
31
|
"dist",
|
|
32
|
+
"docs/development.md",
|
|
33
|
+
"docs/mvp",
|
|
34
|
+
"docs/error-codes.md",
|
|
18
35
|
"README.md",
|
|
19
|
-
"
|
|
36
|
+
"CHANGELOG.md",
|
|
37
|
+
"LICENSE",
|
|
38
|
+
".nvmrc"
|
|
20
39
|
],
|
|
21
40
|
"publishConfig": {
|
|
22
41
|
"access": "public",
|
|
@@ -29,6 +48,8 @@
|
|
|
29
48
|
"himan-project": "dist/bin/himan-project.js"
|
|
30
49
|
},
|
|
31
50
|
"scripts": {
|
|
51
|
+
"clean": "rm -rf dist",
|
|
52
|
+
"prebuild": "rm -rf dist",
|
|
32
53
|
"build": "tsc -p tsconfig.json",
|
|
33
54
|
"dev": "tsx src/bin/himan.ts",
|
|
34
55
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|