@hi-man/himan 0.2.2 → 0.3.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 +11 -0
- package/README.md +40 -1
- package/dist/adapters/source/git-source-adapter.js +243 -2
- package/dist/adapters/source/registry-source-adapter.js +3 -0
- package/dist/cli/builders.js +1 -1
- package/dist/cli/source-commands.js +22 -0
- package/dist/domain/source-docs.js +1 -0
- package/dist/services/index.js +4 -0
- package/docs/mvp/create-resource.md +76 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,17 @@ The format is based on Keep a Changelog, and this project follows semver for the
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.3.0] - 2026-05-07
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Added `himan source init-docs` to scaffold source-level `README.md` and `CHANGELOG.md` files.
|
|
14
|
+
- Added automatic source-level README/CHANGELOG maintenance for resource create and publish flows.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Documented the recommended Git source repository-level `README.md` and `CHANGELOG.md` convention.
|
|
19
|
+
|
|
9
20
|
## [0.2.2] - 2026-05-07
|
|
10
21
|
|
|
11
22
|
### Added
|
package/README.md
CHANGED
|
@@ -66,6 +66,43 @@ himan publish rule my-rule --patch
|
|
|
66
66
|
|
|
67
67
|
版本以 Git tag 为准,格式:`rule/my-rule@1.0.0`。更多设计见 [docs/mvp](./docs/mvp/README.md)。
|
|
68
68
|
|
|
69
|
+
## Source 仓库结构
|
|
70
|
+
|
|
71
|
+
himan Git source 是一个普通 Git 仓库,推荐先维护仓库级 `README.md` 和 `CHANGELOG.md`,用于说明整个资源集合,而不是把说明文档塞进每个 agent 的最终消费目录。
|
|
72
|
+
|
|
73
|
+
```text
|
|
74
|
+
your-himan-source/
|
|
75
|
+
README.md
|
|
76
|
+
CHANGELOG.md
|
|
77
|
+
rules/
|
|
78
|
+
my-rule/
|
|
79
|
+
himan.yaml
|
|
80
|
+
content.md
|
|
81
|
+
commands/
|
|
82
|
+
my-command/
|
|
83
|
+
himan.yaml
|
|
84
|
+
content.md
|
|
85
|
+
skills/
|
|
86
|
+
my-skill/
|
|
87
|
+
himan.yaml
|
|
88
|
+
SKILL.md
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
- `README.md`:source 仓库入口文档,建议记录资源目录说明、推荐安装方式、默认 agent 策略、常用资源索引和维护约定。
|
|
92
|
+
- `CHANGELOG.md`:source 仓库级变更记录,建议记录新增、变更、废弃、移除的资源,以及重要版本发布说明。
|
|
93
|
+
- `rules/`、`commands/`、`skills/`:按资源类型分组;每个子目录是一份 himan 资源。
|
|
94
|
+
- `himan.yaml`:资源元数据,供 himan 扫描、发布和安装时读取。
|
|
95
|
+
- `content.md` / `SKILL.md`:资源主入口,由 `himan.yaml` 的 `entry` 字段指定。
|
|
96
|
+
|
|
97
|
+
可通过 `himan source init-docs` 为当前 default source 生成根目录文档模板;默认只创建缺失文件,`--force` 才覆盖已有 `README.md` / `CHANGELOG.md`,`--dry-run` 可预览结果。
|
|
98
|
+
|
|
99
|
+
`himan create` 和 `himan publish` 会自动维护 source 根目录文档:
|
|
100
|
+
|
|
101
|
+
- `README.md`:只更新 `<!-- himan:resources:start -->` 和 `<!-- himan:resources:end -->` 之间的资源索引;如果没有 marker,会在文件末尾追加一个受控资源索引区。
|
|
102
|
+
- `CHANGELOG.md`:向 `[Unreleased]` 下追加资源变更条目;`create` 记录 `Added`,`publish` 记录 `Changed` / published version。
|
|
103
|
+
|
|
104
|
+
仓库根目录的 `README.md` 和 `CHANGELOG.md` 不会被安装到 agent 目录;agent 只消费被安装的具体资源目录。当前安装实现会 materialize 资源目录本身,因此对 Cursor 这类要求特定单文件格式的 agent,资源目录内应避免放入会干扰识别的额外文件。
|
|
105
|
+
|
|
69
106
|
## 常用命令
|
|
70
107
|
|
|
71
108
|
### 1) source(数据源)
|
|
@@ -76,6 +113,7 @@ himan publish rule my-rule --patch
|
|
|
76
113
|
| `source add <name> <git_url>` | 添加命名 Git 源 |
|
|
77
114
|
| `source use <name>` | 切换默认源 |
|
|
78
115
|
| `source list [--json]` | 查看已配置源(标记当前 default) |
|
|
116
|
+
| `source init-docs [--force] [--dry-run] [--json]` | 为当前 default source 生成仓库级 README/CHANGELOG |
|
|
79
117
|
| `source init <git_url>` | 与 `init` 等价,便于统一走 `himan source ...` 入口 |
|
|
80
118
|
|
|
81
119
|
等价独立命令:
|
|
@@ -84,6 +122,7 @@ himan publish rule my-rule --patch
|
|
|
84
122
|
- `himan-source add <name> <git_url>`
|
|
85
123
|
- `himan-source use <name>`
|
|
86
124
|
- `himan-source list [--json]`
|
|
125
|
+
- `himan-source init-docs [--force] [--dry-run] [--json]`
|
|
87
126
|
|
|
88
127
|
### 2) resource(资源)
|
|
89
128
|
|
|
@@ -122,7 +161,7 @@ himan publish rule my-rule --patch
|
|
|
122
161
|
说明:资源与项目相关命令统一使用 `--agent` 指定目标 Agent。
|
|
123
162
|
若未显式传 `--agent`,`create` / `install` 会使用当前项目默认 agent、全局默认 agent、资源 metadata 或内置默认 `cursor` 中最合适的一项;`dev` 会优先使用 lock 中记录的 agent。
|
|
124
163
|
|
|
125
|
-
`publish` 优先使用项目里 `.himan/dev` 对应目录,否则用源仓库里对应目录。发布前会校验 `himan.yaml` 与入口文件;需要可推送的 Git
|
|
164
|
+
`publish` 优先使用项目里 `.himan/dev` 对应目录,否则用源仓库里对应目录。发布前会校验 `himan.yaml` 与入口文件;需要可推送的 Git 权限。发布 commit 会包含资源目录以及自动维护的 source 根目录 `README.md` / `CHANGELOG.md`。若该资源已在 lock 中,发布后会同步更新 lock 版本。
|
|
126
165
|
|
|
127
166
|
`--json` 模式下,失败时会输出机器可读错误 JSON(`stderr`)。错误码定义见 [docs/error-codes.md](./docs/error-codes.md)。
|
|
128
167
|
|
|
@@ -7,6 +7,9 @@ import { createHash } from "node:crypto";
|
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import YAML from "yaml";
|
|
9
9
|
import { IndexCacheStore } from "../../state/index-cache-store.js";
|
|
10
|
+
const RESOURCE_TYPES = ["rule", "command", "skill"];
|
|
11
|
+
const README_RESOURCES_START = "<!-- himan:resources:start -->";
|
|
12
|
+
const README_RESOURCES_END = "<!-- himan:resources:end -->";
|
|
10
13
|
export class GitSourceAdapter {
|
|
11
14
|
repoManager = new RepoManager();
|
|
12
15
|
scanner = new ResourceScanner();
|
|
@@ -58,8 +61,15 @@ export class GitSourceAdapter {
|
|
|
58
61
|
const yamlPath = path.join(targetDir, "himan.yaml");
|
|
59
62
|
metadata.version = version;
|
|
60
63
|
await fs.writeFile(yamlPath, YAML.stringify(metadata), "utf8");
|
|
64
|
+
const docsPaths = await this.maintainSourceDocs(repoDir, {
|
|
65
|
+
section: "Changed",
|
|
66
|
+
line: `- Published \`${type}/${name}@${version}\`.`,
|
|
67
|
+
});
|
|
61
68
|
const tag = `${type}/${name}@${version}`;
|
|
62
|
-
await this.repoManager.commitTagAndPush(repoDir, `publish ${type}/${name}@${version}`, tag, undefined, [
|
|
69
|
+
await this.repoManager.commitTagAndPush(repoDir, `publish ${type}/${name}@${version}`, tag, undefined, [
|
|
70
|
+
path.relative(repoDir, targetDir),
|
|
71
|
+
...docsPaths.map((docPath) => path.relative(repoDir, docPath)),
|
|
72
|
+
]);
|
|
63
73
|
return { version, tag };
|
|
64
74
|
}
|
|
65
75
|
async create(type, name, options) {
|
|
@@ -67,7 +77,8 @@ export class GitSourceAdapter {
|
|
|
67
77
|
const resourceDir = path.join(repoDir, this.getTypeDir(type), name);
|
|
68
78
|
const entry = options.entry ?? this.getDefaultEntry(type);
|
|
69
79
|
const agents = options.agents?.length ? options.agents : ["cursor"];
|
|
70
|
-
|
|
80
|
+
const resourceExists = await this.exists(resourceDir);
|
|
81
|
+
if (resourceExists && !options.force) {
|
|
71
82
|
throw new HimanError(errorCodes.RESOURCE_EXISTS, `Resource already exists: ${type}/${name}`);
|
|
72
83
|
}
|
|
73
84
|
const files = [path.join(resourceDir, "himan.yaml"), path.join(resourceDir, entry)];
|
|
@@ -83,6 +94,12 @@ export class GitSourceAdapter {
|
|
|
83
94
|
agents,
|
|
84
95
|
}), "utf8");
|
|
85
96
|
await fs.writeFile(path.join(resourceDir, entry), this.getDefaultContent(type, name), "utf8");
|
|
97
|
+
await this.maintainSourceDocs(repoDir, {
|
|
98
|
+
section: resourceExists ? "Changed" : "Added",
|
|
99
|
+
line: resourceExists
|
|
100
|
+
? `- Updated \`${type}/${name}\`.`
|
|
101
|
+
: `- Added \`${type}/${name}\`.`,
|
|
102
|
+
});
|
|
86
103
|
}
|
|
87
104
|
return {
|
|
88
105
|
type,
|
|
@@ -92,6 +109,34 @@ export class GitSourceAdapter {
|
|
|
92
109
|
dryRun: Boolean(options.dryRun),
|
|
93
110
|
};
|
|
94
111
|
}
|
|
112
|
+
async initDocs(options = {}) {
|
|
113
|
+
const repoDir = this.getRepoDir();
|
|
114
|
+
const files = [
|
|
115
|
+
{
|
|
116
|
+
path: path.join(repoDir, "README.md"),
|
|
117
|
+
content: await this.buildReadmeContent(repoDir),
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
path: path.join(repoDir, "CHANGELOG.md"),
|
|
121
|
+
content: this.buildChangelogContent(),
|
|
122
|
+
},
|
|
123
|
+
];
|
|
124
|
+
const results = [];
|
|
125
|
+
for (const file of files) {
|
|
126
|
+
const exists = await this.exists(file.path);
|
|
127
|
+
const action = exists ? (options.force ? "updated" : "skipped") : "created";
|
|
128
|
+
const reason = action === "skipped" ? "file already exists" : undefined;
|
|
129
|
+
results.push({ path: file.path, action, reason });
|
|
130
|
+
if (!options.dryRun && action !== "skipped") {
|
|
131
|
+
await fs.writeFile(file.path, file.content, "utf8");
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
sourceDir: repoDir,
|
|
136
|
+
files: results,
|
|
137
|
+
dryRun: Boolean(options.dryRun),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
95
140
|
getRepoDir() {
|
|
96
141
|
if (!this.sourceConfig?.repoDir) {
|
|
97
142
|
throw new HimanError(errorCodes.CONFIG_NOT_FOUND, "Git source is not initialized.");
|
|
@@ -232,4 +277,200 @@ export class GitSourceAdapter {
|
|
|
232
277
|
}
|
|
233
278
|
return `# ${name}\n\nDescribe skill workflow here.\n`;
|
|
234
279
|
}
|
|
280
|
+
async buildReadmeContent(repoDir) {
|
|
281
|
+
const resourceLines = await this.buildResourceIndex(repoDir);
|
|
282
|
+
const repo = this.sourceConfig?.repo ?? "<git_url>";
|
|
283
|
+
return [
|
|
284
|
+
`# ${this.getSourceTitle()}`,
|
|
285
|
+
"",
|
|
286
|
+
"Himan source repository for reusable agent resources.",
|
|
287
|
+
"",
|
|
288
|
+
"## Resources",
|
|
289
|
+
"",
|
|
290
|
+
README_RESOURCES_START,
|
|
291
|
+
...resourceLines,
|
|
292
|
+
README_RESOURCES_END,
|
|
293
|
+
"",
|
|
294
|
+
"## Usage",
|
|
295
|
+
"",
|
|
296
|
+
"```bash",
|
|
297
|
+
`himan source add team ${repo}`,
|
|
298
|
+
"himan source use team",
|
|
299
|
+
"himan list rule",
|
|
300
|
+
"himan install rule <name>",
|
|
301
|
+
"```",
|
|
302
|
+
"",
|
|
303
|
+
"## Maintenance",
|
|
304
|
+
"",
|
|
305
|
+
"- Add resources with `himan create <type> <name>`.",
|
|
306
|
+
"- Publish resource versions with `himan publish <type> <name>`.",
|
|
307
|
+
"- Record source-level changes in `CHANGELOG.md`.",
|
|
308
|
+
"- Resource versions are tracked by Git tags such as `rule/code-review@1.0.0`.",
|
|
309
|
+
"",
|
|
310
|
+
].join("\n");
|
|
311
|
+
}
|
|
312
|
+
buildChangelogContent() {
|
|
313
|
+
return [
|
|
314
|
+
"# Changelog",
|
|
315
|
+
"",
|
|
316
|
+
"All notable source-level resource changes are documented in this file.",
|
|
317
|
+
"",
|
|
318
|
+
"## [Unreleased]",
|
|
319
|
+
"",
|
|
320
|
+
"### Added",
|
|
321
|
+
"",
|
|
322
|
+
"- Initial source README/CHANGELOG scaffold.",
|
|
323
|
+
"",
|
|
324
|
+
].join("\n");
|
|
325
|
+
}
|
|
326
|
+
async buildResourceIndex(repoDir) {
|
|
327
|
+
const sections = [];
|
|
328
|
+
for (const type of RESOURCE_TYPES) {
|
|
329
|
+
const resources = (await this.scanner.scanByType(repoDir, type)).sort((a, b) => a.name.localeCompare(b.name));
|
|
330
|
+
sections.push(`### ${this.getTypeLabel(type)}`, "");
|
|
331
|
+
if (resources.length === 0) {
|
|
332
|
+
sections.push(`- No ${type} resources yet.`, "");
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
for (const resource of resources) {
|
|
336
|
+
const version = await this.readResourceVersion(repoDir, resource.type, resource.name);
|
|
337
|
+
const ref = version
|
|
338
|
+
? `${resource.type}/${resource.name}@${version}`
|
|
339
|
+
: `${resource.type}/${resource.name}`;
|
|
340
|
+
sections.push(`- \`${ref}\`${resource.description ? `: ${resource.description}` : ""}`);
|
|
341
|
+
}
|
|
342
|
+
sections.push("");
|
|
343
|
+
}
|
|
344
|
+
while (sections.at(-1) === "") {
|
|
345
|
+
sections.pop();
|
|
346
|
+
}
|
|
347
|
+
return sections;
|
|
348
|
+
}
|
|
349
|
+
async maintainSourceDocs(repoDir, changelogEntry) {
|
|
350
|
+
const readmePath = await this.updateReadmeResourceIndex(repoDir);
|
|
351
|
+
const changelogPath = await this.updateChangelog(repoDir, changelogEntry);
|
|
352
|
+
return [readmePath, changelogPath];
|
|
353
|
+
}
|
|
354
|
+
async updateReadmeResourceIndex(repoDir) {
|
|
355
|
+
const readmePath = path.join(repoDir, "README.md");
|
|
356
|
+
if (!(await this.exists(readmePath))) {
|
|
357
|
+
await fs.writeFile(readmePath, await this.buildReadmeContent(repoDir), "utf8");
|
|
358
|
+
return readmePath;
|
|
359
|
+
}
|
|
360
|
+
const current = await fs.readFile(readmePath, "utf8");
|
|
361
|
+
const resourceSection = [
|
|
362
|
+
README_RESOURCES_START,
|
|
363
|
+
...(await this.buildResourceIndex(repoDir)),
|
|
364
|
+
README_RESOURCES_END,
|
|
365
|
+
].join("\n");
|
|
366
|
+
const updated = this.replaceOrAppendReadmeResourceSection(current, resourceSection);
|
|
367
|
+
if (updated !== current) {
|
|
368
|
+
await fs.writeFile(readmePath, updated, "utf8");
|
|
369
|
+
}
|
|
370
|
+
return readmePath;
|
|
371
|
+
}
|
|
372
|
+
replaceOrAppendReadmeResourceSection(content, resourceSection) {
|
|
373
|
+
const startIndex = content.indexOf(README_RESOURCES_START);
|
|
374
|
+
const endIndex = content.indexOf(README_RESOURCES_END);
|
|
375
|
+
if (startIndex >= 0 && endIndex > startIndex) {
|
|
376
|
+
const before = content.slice(0, startIndex).replace(/\s*$/, "\n");
|
|
377
|
+
const after = content
|
|
378
|
+
.slice(endIndex + README_RESOURCES_END.length)
|
|
379
|
+
.replace(/^\s*/, "\n\n");
|
|
380
|
+
return `${before}${resourceSection}${after}`.replace(/\s*$/, "\n");
|
|
381
|
+
}
|
|
382
|
+
const base = content.replace(/\s*$/, "");
|
|
383
|
+
return `${base}\n\n## Resources\n\n${resourceSection}\n`;
|
|
384
|
+
}
|
|
385
|
+
async updateChangelog(repoDir, entry) {
|
|
386
|
+
const changelogPath = path.join(repoDir, "CHANGELOG.md");
|
|
387
|
+
const current = (await this.exists(changelogPath))
|
|
388
|
+
? await fs.readFile(changelogPath, "utf8")
|
|
389
|
+
: this.buildChangelogBaseContent();
|
|
390
|
+
const updated = this.insertChangelogEntry(current, entry);
|
|
391
|
+
if (updated !== current) {
|
|
392
|
+
await fs.writeFile(changelogPath, updated, "utf8");
|
|
393
|
+
}
|
|
394
|
+
return changelogPath;
|
|
395
|
+
}
|
|
396
|
+
buildChangelogBaseContent() {
|
|
397
|
+
return [
|
|
398
|
+
"# Changelog",
|
|
399
|
+
"",
|
|
400
|
+
"All notable source-level resource changes are documented in this file.",
|
|
401
|
+
"",
|
|
402
|
+
"## [Unreleased]",
|
|
403
|
+
"",
|
|
404
|
+
].join("\n");
|
|
405
|
+
}
|
|
406
|
+
insertChangelogEntry(content, entry) {
|
|
407
|
+
const lines = content.replace(/\s*$/, "").split("\n");
|
|
408
|
+
let unreleasedIndex = lines.findIndex((line) => line.trim() === "## [Unreleased]");
|
|
409
|
+
if (unreleasedIndex === -1) {
|
|
410
|
+
const firstVersionIndex = lines.findIndex((line) => line.startsWith("## "));
|
|
411
|
+
const insertIndex = firstVersionIndex === -1 ? lines.length : firstVersionIndex;
|
|
412
|
+
lines.splice(insertIndex, 0, "## [Unreleased]", "");
|
|
413
|
+
unreleasedIndex = insertIndex;
|
|
414
|
+
}
|
|
415
|
+
const blockEnd = this.findNextHeadingIndex(lines, unreleasedIndex + 1, "## ");
|
|
416
|
+
const unreleasedLines = lines.slice(unreleasedIndex, blockEnd);
|
|
417
|
+
if (unreleasedLines.includes(entry.line)) {
|
|
418
|
+
return `${lines.join("\n")}\n`;
|
|
419
|
+
}
|
|
420
|
+
const sectionHeading = `### ${entry.section}`;
|
|
421
|
+
const sectionIndex = lines.findIndex((line, index) => index > unreleasedIndex && index < blockEnd && line.trim() === sectionHeading);
|
|
422
|
+
if (sectionIndex >= 0) {
|
|
423
|
+
const insertIndex = lines[sectionIndex + 1] === "" ? sectionIndex + 2 : sectionIndex + 1;
|
|
424
|
+
lines.splice(insertIndex, 0, entry.line);
|
|
425
|
+
return `${lines.join("\n")}\n`;
|
|
426
|
+
}
|
|
427
|
+
const insertIndex = this.findChangelogSectionInsertIndex(lines, unreleasedIndex, blockEnd, entry.section);
|
|
428
|
+
lines.splice(insertIndex, 0, `### ${entry.section}`, "", entry.line, "");
|
|
429
|
+
return `${lines.join("\n").replace(/\s*$/, "")}\n`;
|
|
430
|
+
}
|
|
431
|
+
findNextHeadingIndex(lines, startIndex, headingPrefix) {
|
|
432
|
+
const found = lines.findIndex((line, index) => index >= startIndex && line.startsWith(headingPrefix));
|
|
433
|
+
return found === -1 ? lines.length : found;
|
|
434
|
+
}
|
|
435
|
+
findChangelogSectionInsertIndex(lines, unreleasedIndex, blockEnd, section) {
|
|
436
|
+
const sectionOrder = ["Added", "Changed"];
|
|
437
|
+
const sectionRank = sectionOrder.indexOf(section);
|
|
438
|
+
for (let index = unreleasedIndex + 1; index < blockEnd; index += 1) {
|
|
439
|
+
const line = lines[index].trim();
|
|
440
|
+
if (!line.startsWith("### "))
|
|
441
|
+
continue;
|
|
442
|
+
const foundSection = line.slice(4);
|
|
443
|
+
const foundRank = sectionOrder.indexOf(foundSection);
|
|
444
|
+
if (foundRank > sectionRank) {
|
|
445
|
+
return index;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
return blockEnd;
|
|
449
|
+
}
|
|
450
|
+
async readResourceVersion(repoDir, type, name) {
|
|
451
|
+
const yamlPath = path.join(repoDir, this.getTypeDir(type), name, "himan.yaml");
|
|
452
|
+
try {
|
|
453
|
+
const raw = await fs.readFile(yamlPath, "utf8");
|
|
454
|
+
const parsed = YAML.parse(raw);
|
|
455
|
+
return typeof parsed?.version === "string" ? parsed.version : undefined;
|
|
456
|
+
}
|
|
457
|
+
catch (error) {
|
|
458
|
+
if (!this.isNotFoundError(error)) {
|
|
459
|
+
throw error;
|
|
460
|
+
}
|
|
461
|
+
return undefined;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
getSourceTitle() {
|
|
465
|
+
const repo = this.sourceConfig?.repo?.replace(/\/$/, "");
|
|
466
|
+
const repoName = repo?.split(/[/:]/).at(-1)?.replace(/\.git$/, "");
|
|
467
|
+
return repoName ? `${repoName} Himan Source` : "Himan Source";
|
|
468
|
+
}
|
|
469
|
+
getTypeLabel(type) {
|
|
470
|
+
if (type === "rule")
|
|
471
|
+
return "Rules";
|
|
472
|
+
if (type === "command")
|
|
473
|
+
return "Commands";
|
|
474
|
+
return "Skills";
|
|
475
|
+
}
|
|
235
476
|
}
|
|
@@ -18,4 +18,7 @@ export class RegistrySourceAdapter {
|
|
|
18
18
|
async create(_type, _name, _options) {
|
|
19
19
|
throw new HimanError(errorCodes.NOT_IMPLEMENTED, "Registry source is reserved for phase 2.");
|
|
20
20
|
}
|
|
21
|
+
async initDocs(_options) {
|
|
22
|
+
throw new HimanError(errorCodes.NOT_IMPLEMENTED, "Registry source is reserved for phase 2.");
|
|
23
|
+
}
|
|
21
24
|
}
|
package/dist/cli/builders.js
CHANGED
|
@@ -60,7 +60,7 @@ function appendCommandGroupsHelp(program) {
|
|
|
60
60
|
program.addHelpText("after", `
|
|
61
61
|
Command groups:
|
|
62
62
|
source Data source management (git now, registry reserved)
|
|
63
|
-
init, source init, source add, source use, source list
|
|
63
|
+
init, source init, source add, source use, source list, source init-docs
|
|
64
64
|
resource Source resource discovery and metadata
|
|
65
65
|
list, history, create, resource list, resource history, resource create
|
|
66
66
|
project Resource usage lifecycle in current project
|
|
@@ -55,4 +55,26 @@ export function registerSourceCommands(command, services, options) {
|
|
|
55
55
|
}
|
|
56
56
|
});
|
|
57
57
|
});
|
|
58
|
+
command
|
|
59
|
+
.command("init-docs")
|
|
60
|
+
.option("--force", "overwrite existing README.md and CHANGELOG.md")
|
|
61
|
+
.option("--dry-run", "show files without writing")
|
|
62
|
+
.option("--json", "output json format")
|
|
63
|
+
.description("Create source-level README.md and CHANGELOG.md")
|
|
64
|
+
.action(async (options) => {
|
|
65
|
+
await runAction(async () => {
|
|
66
|
+
const result = await services.initSourceDocs({
|
|
67
|
+
force: options.force,
|
|
68
|
+
dryRun: options.dryRun,
|
|
69
|
+
});
|
|
70
|
+
if (options.json) {
|
|
71
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
process.stdout.write(`Source docs ${result.dryRun ? "dry-run" : "initialized"}: ${result.sourceDir}\n`);
|
|
75
|
+
for (const file of result.files) {
|
|
76
|
+
process.stdout.write(`- ${file.action} ${file.path}${file.reason ? ` (${file.reason})` : ""}\n`);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
});
|
|
58
80
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/services/index.js
CHANGED
|
@@ -101,6 +101,10 @@ export class ServiceFactory {
|
|
|
101
101
|
isDefault: name === config.sources?.default,
|
|
102
102
|
}));
|
|
103
103
|
}
|
|
104
|
+
async initSourceDocs(options) {
|
|
105
|
+
const source = await this.loadSourceFromConfig();
|
|
106
|
+
return source.initDocs(options);
|
|
107
|
+
}
|
|
104
108
|
async setAgents(agents, scope, projectDir) {
|
|
105
109
|
const normalized = normalizeAgents(agents);
|
|
106
110
|
if (scope === "project") {
|
|
@@ -39,9 +39,78 @@ himan create <type> <name> [options]
|
|
|
39
39
|
|
|
40
40
|
---
|
|
41
41
|
|
|
42
|
-
## 3.
|
|
42
|
+
## 3. Source 仓库结构
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
Git source 仓库推荐先维护仓库级 `README.md` 和 `CHANGELOG.md`,用于说明整个资源集合的使用方式与变更历史。它们位于仓库根目录,不属于任何单个资源,也不会通过 `himan install` 安装到 agent 目录。
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
repo/
|
|
48
|
+
README.md
|
|
49
|
+
CHANGELOG.md
|
|
50
|
+
rules/
|
|
51
|
+
commands/
|
|
52
|
+
skills/
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
- `README.md`:说明 source 的用途、资源索引、安装示例、默认 agent 策略和维护约定
|
|
56
|
+
- `CHANGELOG.md`:记录 source 级别的新增资源、资源变更、废弃、移除和重要发布说明
|
|
57
|
+
- `rules/`、`commands/`、`skills/`:资源类型根目录,由 himan 扫描
|
|
58
|
+
|
|
59
|
+
可用 `himan source init-docs` 生成根目录文档模板。命令默认只创建缺失的 `README.md` / `CHANGELOG.md`;已有文件会保留,除非显式传 `--force`。`--dry-run` 只返回将执行的创建、覆盖或跳过动作,不写盘。
|
|
60
|
+
|
|
61
|
+
`create` 和 `publish` 会自动维护根目录文档:
|
|
62
|
+
|
|
63
|
+
- `README.md`:只维护 `<!-- himan:resources:start -->` / `<!-- himan:resources:end -->` 标记内的资源索引;如果旧 README 没有标记,则在文件末尾追加受控资源索引区
|
|
64
|
+
- `CHANGELOG.md`:向 `[Unreleased]` 写入资源变更;新增资源写入 `Added`,发布版本写入 `Changed`
|
|
65
|
+
|
|
66
|
+
推荐的 `README.md` 基本结构:
|
|
67
|
+
|
|
68
|
+
```md
|
|
69
|
+
# Team Himan Source
|
|
70
|
+
|
|
71
|
+
## Resources
|
|
72
|
+
|
|
73
|
+
<!-- himan:resources:start -->
|
|
74
|
+
|
|
75
|
+
- rule/code-review: Code review behavior for backend changes.
|
|
76
|
+
- command/create-mr: MR creation workflow.
|
|
77
|
+
- skill/api-debugging: API debugging workflow.
|
|
78
|
+
|
|
79
|
+
<!-- himan:resources:end -->
|
|
80
|
+
|
|
81
|
+
## Usage
|
|
82
|
+
|
|
83
|
+
\`\`\`bash
|
|
84
|
+
himan source add team <git_url>
|
|
85
|
+
himan source use team
|
|
86
|
+
himan install rule code-review
|
|
87
|
+
\`\`\`
|
|
88
|
+
|
|
89
|
+
## Maintenance
|
|
90
|
+
|
|
91
|
+
- Publish resource versions with himan publish.
|
|
92
|
+
- Record source-level changes in CHANGELOG.md.
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
推荐的 `CHANGELOG.md` 基本结构:
|
|
96
|
+
|
|
97
|
+
```md
|
|
98
|
+
# Changelog
|
|
99
|
+
|
|
100
|
+
## [Unreleased]
|
|
101
|
+
|
|
102
|
+
### Added
|
|
103
|
+
|
|
104
|
+
- Added rule/code-review.
|
|
105
|
+
|
|
106
|
+
### Changed
|
|
107
|
+
|
|
108
|
+
- Updated skill/api-debugging usage guidance.
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## 4. 资源目录与元数据
|
|
112
|
+
|
|
113
|
+
`create` 生成资源目录,结构示例:
|
|
45
114
|
|
|
46
115
|
```text
|
|
47
116
|
repo/
|
|
@@ -73,7 +142,7 @@ agents:
|
|
|
73
142
|
|
|
74
143
|
---
|
|
75
144
|
|
|
76
|
-
##
|
|
145
|
+
## 5. 流程概要
|
|
77
146
|
|
|
78
147
|
1. 读取本地配置,确认已初始化源
|
|
79
148
|
2. 校验类型与资源名格式
|
|
@@ -86,14 +155,14 @@ agents:
|
|
|
86
155
|
|
|
87
156
|
---
|
|
88
157
|
|
|
89
|
-
##
|
|
158
|
+
## 6. 模板
|
|
90
159
|
|
|
91
160
|
- 当前仅 **basic**:最简结构与提示性说明
|
|
92
161
|
- 后续可扩展更多模板名;自定义模板目录可作为后续增强
|
|
93
162
|
|
|
94
163
|
---
|
|
95
164
|
|
|
96
|
-
##
|
|
165
|
+
## 7. 错误场景(产品语义)
|
|
97
166
|
|
|
98
167
|
- 资源目录已存在
|
|
99
168
|
- 模板不存在(含请求了尚未支持的模板名)
|
|
@@ -103,13 +172,13 @@ agents:
|
|
|
103
172
|
|
|
104
173
|
---
|
|
105
174
|
|
|
106
|
-
##
|
|
175
|
+
## 8. 测试关注点
|
|
107
176
|
|
|
108
177
|
- 名称与类型校验、默认 entry/agents、重复创建与 `--force`、`--dry-run` 不写盘、创建后 `list` 可见
|
|
109
178
|
|
|
110
179
|
---
|
|
111
180
|
|
|
112
|
-
##
|
|
181
|
+
## 9. 与资源工作流衔接
|
|
113
182
|
|
|
114
183
|
```text
|
|
115
184
|
create → edit → publish
|