@cr1992/agentkit 1.0.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/LICENSE +21 -0
- package/README.en.md +107 -0
- package/README.md +103 -0
- package/bin/agentkit.mjs +4 -0
- package/bin/cli.mjs +273 -0
- package/core/atomic-fs.mjs +23 -0
- package/core/cli-help.mjs +54 -0
- package/core/content-digest.mjs +66 -0
- package/core/digest.mjs +67 -0
- package/core/json-schema-lite.mjs +60 -0
- package/core/legacy-entry.mjs +37 -0
- package/core/reflection.mjs +142 -0
- package/core/runtime-bundle.mjs +101 -0
- package/docs/loop/embedded-review-adapter.md +41 -0
- package/docs/loop/loop-state-machine.md +43 -0
- package/docs/loop/recovery-and-fuses.md +34 -0
- package/docs/orchestrate/dispatch-contract.md +92 -0
- package/docs/orchestrate/failure-routing-and-recovery.md +46 -0
- package/docs/orchestrate/host-capability-cache.md +170 -0
- package/docs/orchestrate/isolation-fallback.md +18 -0
- package/docs/orchestrate/model-routing-config.md +186 -0
- package/docs/orchestrate/orchestration-runtime.md +261 -0
- package/docs/orchestrate/review-budget.md +90 -0
- package/docs/orchestrate/task-playbooks.md +85 -0
- package/docs/orchestrate/user-facing-reporting.md +14 -0
- package/docs/verify/evidence-schema.md +167 -0
- package/docs/verify/input-preparation.md +44 -0
- package/docs/verify/verification-protocol.md +76 -0
- package/docs/worktree/batch-integration.md +176 -0
- package/docs/worktree/delivery-identity.md +41 -0
- package/docs/worktree/profile.md +107 -0
- package/docs/worktree/reclaim-and-watch.md +96 -0
- package/docs/worktree/review-lifecycle.md +92 -0
- package/docs/worktree/spawn-and-stack.md +74 -0
- package/domains/loop/loop-runtime.mjs +1056 -0
- package/domains/orchestrate/contract-tool.mjs +169 -0
- package/domains/orchestrate/host_capability_cache.mjs +437 -0
- package/domains/orchestrate/orchestration-ledger.mjs +332 -0
- package/domains/orchestrate/orchestration-metadata.mjs +4 -0
- package/domains/orchestrate/orchestration-reflection.mjs +119 -0
- package/domains/orchestrate/resolve_model_policy.mjs +311 -0
- package/domains/orchestrate/review-budget.mjs +162 -0
- package/domains/orchestrate/worker-capability-preflight.mjs +227 -0
- package/domains/verify/verification-runtime.mjs +1638 -0
- package/domains/worktree/worktree-archive.mjs +135 -0
- package/domains/worktree/worktree-artifact.mjs +123 -0
- package/domains/worktree/worktree-batch-integrate.mjs +713 -0
- package/domains/worktree/worktree-batch-plan.mjs +198 -0
- package/domains/worktree/worktree-batch-result.mjs +241 -0
- package/domains/worktree/worktree-core.mjs +908 -0
- package/domains/worktree/worktree-doctor.mjs +493 -0
- package/domains/worktree/worktree-history.mjs +377 -0
- package/domains/worktree/worktree-learning.mjs +110 -0
- package/domains/worktree/worktree-lifecycle.mjs +786 -0
- package/domains/worktree/worktree-merge-preview.mjs +409 -0
- package/domains/worktree/worktree-mgr.mjs +261 -0
- package/domains/worktree/worktree-process.mjs +55 -0
- package/domains/worktree/worktree-profile.mjs +800 -0
- package/domains/worktree/worktree-provider-gitlab.mjs +59 -0
- package/domains/worktree/worktree-reclaim.mjs +683 -0
- package/domains/worktree/worktree-review-refresh.mjs +574 -0
- package/domains/worktree/worktree-review-watch.mjs +661 -0
- package/domains/worktree/worktree-scan.mjs +510 -0
- package/domains/worktree/worktree-trace-test-worker.mjs +23 -0
- package/domains/worktree/worktree-trace.mjs +478 -0
- package/manage-worktrees/SKILL.md +87 -0
- package/manage-worktrees/agents/openai.yaml +4 -0
- package/manage-worktrees/scripts/worktree-mgr.mjs +10 -0
- package/manage-worktrees/scripts/worktree-scan.mjs +10 -0
- package/orchestrate-subagents/SKILL.md +173 -0
- package/orchestrate-subagents/agents/openai.yaml +4 -0
- package/orchestrate-subagents/scripts/contract-tool.mjs +10 -0
- package/orchestrate-subagents/scripts/host_capability_cache.mjs +10 -0
- package/orchestrate-subagents/scripts/orchestration-ledger.mjs +10 -0
- package/orchestrate-subagents/scripts/orchestration-reflection.mjs +10 -0
- package/orchestrate-subagents/scripts/resolve_model_policy.mjs +10 -0
- package/orchestrate-subagents/scripts/review-budget.mjs +10 -0
- package/orchestrate-subagents/scripts/worker-capability-preflight.mjs +10 -0
- package/package.json +48 -0
- package/run-agent-verify-loop/SKILL.md +127 -0
- package/run-agent-verify-loop/agents/openai.yaml +4 -0
- package/run-agent-verify-loop/scripts/loop-runtime.mjs +10 -0
- package/schemas/artifact-ref-v1.schema.json +23 -0
- package/schemas/batch-result-v1.schema.json +138 -0
- package/schemas/controller-recheck-record-v1.schema.json +22 -0
- package/schemas/convergence-report-v1.schema.json +9 -0
- package/schemas/effective-worker-capability-v1.schema.json +36 -0
- package/schemas/embedded-verification-record-v1.schema.json +32 -0
- package/schemas/evidence-package-v1.schema.json +41 -0
- package/schemas/improvement-proposal-v1.schema.json +18 -0
- package/schemas/loop-state-v1.schema.json +34 -0
- package/schemas/model-policy-resolution-v1.schema.json +41 -0
- package/schemas/orchestration-ledger-v1.schema.json +110 -0
- package/schemas/reflection-record-v1.schema.json +24 -0
- package/schemas/review-result-v1.schema.json +37 -0
- package/schemas/task-contract-v1.schema.json +83 -0
- package/schemas/verification-profile-v1.schema.json +60 -0
- package/schemas/worker-capability-requirements-v1.schema.json +21 -0
- package/schemas/worktree-binding-v1.schema.json +14 -0
- package/shell-manifest.json +79 -0
- package/verify-agent-output/SKILL.md +119 -0
- package/verify-agent-output/agents/openai.yaml +4 -0
- package/verify-agent-output/scripts/verification-runtime.mjs +10 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
本项目遵循 [Semantic Versioning](https://semver.org/)。
|
|
4
|
+
|
|
5
|
+
## 1.0.0 - 2026-09-04
|
|
6
|
+
|
|
7
|
+
- 将四个 Agent 工程 Skill 收敛为一个零依赖 Node.js CLI 与四个薄壳。
|
|
8
|
+
- 提供 orchestrate、worktree、verify、loop、capabilities、doctor 与 docs 命令面。
|
|
9
|
+
- 将共享运行时、canonical schema、安装身份和内容摘要统一到单个版本化发布单元。
|
|
10
|
+
- 保留 1.x Skill 脚本兼容入口,并增加 CLI/shell 版本失配的 fail-closed 门禁。
|
|
11
|
+
- 覆盖四个 Skill 的 15 种非空安装组合、跨域协议、恢复路径与 npm tarball 反装。
|
|
12
|
+
- 将 `cr1992/agentkit` 设为代码、测试、架构文档和发布流程的唯一真源。
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 cr1992
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.en.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# agentkit
|
|
2
|
+
|
|
3
|
+
[中文](./README.md) · **English**
|
|
4
|
+
|
|
5
|
+
A zero-dependency Node.js CLI with four thin skills for agentic software engineering—from task orchestration and Git
|
|
6
|
+
worktree isolation to one-shot independent verification and explicit bounded loops.
|
|
7
|
+
|
|
8
|
+

|
|
9
|
+
|
|
10
|
+
The diagram starts from request facts and selects independent capabilities. Only multi-agent or multi-node work enters
|
|
11
|
+
the `orchestrate-subagents` control plane, which then chooses lightweight or full operation and evidence-driven
|
|
12
|
+
rerouting from effective capabilities, task scale, and local model policy. Providers remain independently usable and
|
|
13
|
+
compose through frozen Artifact, Binding, and Evidence envelopes.
|
|
14
|
+
|
|
15
|
+
[Detailed collaboration contracts and safety boundaries (Chinese, v1.0.0)](./docs/architecture/skill-system-architecture.md)
|
|
16
|
+
|
|
17
|
+
## Included skills
|
|
18
|
+
|
|
19
|
+
| Skill | Purpose |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| [`orchestrate-subagents`](./orchestrate-subagents/) | Decides whether a task should be delegated, builds dependency-aware task contracts, selects suitable agent capabilities, and keeps final acceptance with the controller agent. |
|
|
22
|
+
| [`manage-worktrees`](./manage-worktrees/) | Detects write collisions, creates auditable Git worktrees, produces commit-pinned batch integration plans, and safely reclaims worktrees. |
|
|
23
|
+
| [`verify-agent-output`](./verify-agent-output/) | Independently verifies one frozen Git Artifact through L0/L1/L0 and emits digest-bound, reviewable Evidence without modifying or retrying the Artifact. |
|
|
24
|
+
| [`run-agent-verify-loop`](./run-agent-verify-loop/) | Runs an implementer/verifier loop with isolated context, deterministic checks, evidence tracking, circuit breakers, and human gates. |
|
|
25
|
+
|
|
26
|
+
This group currently contains four skills. Each works independently and composes through frozen JSON envelopes.
|
|
27
|
+
Ordinary tasks need none of them. Use `verify-agent-output` for one-shot verification of a fixed Artifact, and use the
|
|
28
|
+
Loop only when bounded repeated implementation and independent re-verification are explicitly requested. Add
|
|
29
|
+
orchestration and worktrees only when the task actually needs multiple nodes or Git isolation.
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
### CLI
|
|
34
|
+
|
|
35
|
+
The CLI requires Node.js 22 or newer:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install -g @cr1992/agentkit
|
|
39
|
+
agentkit doctor
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Skills
|
|
43
|
+
|
|
44
|
+
Install all four skills:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npx skills add https://github.com/cr1992/agentkit.git -g --agent '*'
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Install a single skill:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npx skills add https://github.com/cr1992/agentkit.git -g --agent '*' --skill manage-worktrees
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Replace `manage-worktrees` with any other skill name in the table as needed. After an install or update, start a new
|
|
57
|
+
agent task. Some hosts cache skill discovery or contents and may require a restart.
|
|
58
|
+
|
|
59
|
+
## CLI
|
|
60
|
+
|
|
61
|
+
The skills only define triggers and invariants. Their deterministic runtime is provided by `agentkit`. Existing 1.x
|
|
62
|
+
script entry points remain as compatibility forwarders; new integrations should call the CLI directly:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
agentkit capabilities --json
|
|
66
|
+
agentkit doctor --json
|
|
67
|
+
agentkit worktree --help
|
|
68
|
+
agentkit contract --help
|
|
69
|
+
agentkit orchestrate ledger --help
|
|
70
|
+
agentkit verify --help
|
|
71
|
+
agentkit docs
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Usage
|
|
75
|
+
|
|
76
|
+
Invoke a skill in natural language and state the intended workflow explicitly:
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
Use orchestrate-subagents to split this feature across multiple agents.
|
|
80
|
+
Use manage-worktrees to prepare batch integration acceptance for these feature branches.
|
|
81
|
+
Use verify-agent-output to independently verify this fixed commit once and emit Evidence.
|
|
82
|
+
Use run-agent-verify-loop so one agent implements and another independently verifies until it passes or a stop condition fires.
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Each skill adapts to the agents, terminal, Git, and task-control primitives available in the current host. When a
|
|
86
|
+
capability is unavailable, follow the documented fallback path instead of assuming a specific agent product or tool
|
|
87
|
+
exists.
|
|
88
|
+
|
|
89
|
+
## Requirements
|
|
90
|
+
|
|
91
|
+
- Git.
|
|
92
|
+
- Node.js 22 or newer.
|
|
93
|
+
- A globally available `agentkit` command; run `agentkit doctor` to validate the package version, entry points, and runtime manifest.
|
|
94
|
+
- A host with task or sub-agent isolation primitives when actual multi-agent execution is required.
|
|
95
|
+
|
|
96
|
+
## Local validation
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
npm test
|
|
100
|
+
npm run pack:check
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Repository scope
|
|
104
|
+
|
|
105
|
+
This repository is the sole source of truth for the `agentkit` CLI, all four skills, canonical schemas, tests, and
|
|
106
|
+
architecture documentation. It is no longer generated or overwritten by another repository. See
|
|
107
|
+
[source-of-truth and release maintenance](./docs/maintenance/source-of-truth.md) for the ownership boundary.
|
package/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# agentkit
|
|
2
|
+
|
|
3
|
+
**中文** · [English](./README.en.md)
|
|
4
|
+
|
|
5
|
+
一个零依赖 Node.js CLI,加四个面向 Agent 软件工程的薄壳 Skill:从任务编排、Git worktree 隔离,
|
|
6
|
+
到一次性独立验收和显式有界循环。
|
|
7
|
+
|
|
8
|
+

|
|
9
|
+
|
|
10
|
+
图中先按请求事实选择独立能力;只有多 Agent / 多节点任务才进入 `orchestrate-subagents` 控制面,
|
|
11
|
+
其内部再按有效能力、任务规模、本地模型配置和验收证据选择轻量或完整运行方式及后续重路由。
|
|
12
|
+
各 provider 仍可独立使用,并通过冻结的 Artifact、Binding 和 Evidence envelope 按需组合。
|
|
13
|
+
|
|
14
|
+
[Agent Skills 协作契约与安全边界(v1.0.0)](./docs/architecture/skill-system-architecture.md)
|
|
15
|
+
|
|
16
|
+
## 包含的 Skill
|
|
17
|
+
|
|
18
|
+
| Skill | 用途 |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| [`orchestrate-subagents`](./orchestrate-subagents/) | 判断任务是否值得拆分,设计依赖图与派发契约,选择合适的 Agent 能力,并由主控 Agent 统一验收结果。 |
|
|
21
|
+
| [`manage-worktrees`](./manage-worktrees/) | 扫描写入冲突,创建和登记可追踪的 Git worktree,为多个功能分支生成固定提交的批量验收计划,并安全回收 worktree。 |
|
|
22
|
+
| [`verify-agent-output`](./verify-agent-output/) | 对冻结的单一 Git Artifact 做一次独立验收,执行 L0/L1/L0 并输出与摘要绑定、可复核的 Evidence Package;不修改产物、不自动重试。 |
|
|
23
|
+
| [`run-agent-verify-loop`](./run-agent-verify-loop/) | 让实现 Agent 与隔离上下文中的验收 Agent 形成闭环,通过确定性检查、证据台账、熔断和人工门控制收敛。 |
|
|
24
|
+
|
|
25
|
+
本组目前包含 4 个 Skill。它们都可以独立使用,也可以通过冻结的 JSON envelope 联动。普通任务不必
|
|
26
|
+
加载任何一个;固定 Artifact 只验一次时直接使用 `verify-agent-output`;只有明确要求反复修复和独立
|
|
27
|
+
复验时才使用 Loop。多节点编排和 worktree 隔离同样按实际需要添加,不是默认必经步骤。
|
|
28
|
+
|
|
29
|
+
## 安装
|
|
30
|
+
|
|
31
|
+
### CLI
|
|
32
|
+
|
|
33
|
+
CLI 需要 Node.js 22 或更高版本:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install -g @cr1992/agentkit
|
|
37
|
+
agentkit doctor
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Skill
|
|
41
|
+
|
|
42
|
+
安装全部 Skill:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npx skills add https://github.com/cr1992/agentkit.git -g --agent '*'
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
只安装一个 Skill:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npx skills add https://github.com/cr1992/agentkit.git -g --agent '*' --skill manage-worktrees
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
将 `manage-worktrees` 替换为表中的其他 Skill 名称即可。安装或更新后,建议新建 Agent 任务;部分宿主
|
|
55
|
+
会缓存 Skill 清单或正文,需要重启后才会加载新版本。
|
|
56
|
+
|
|
57
|
+
## CLI
|
|
58
|
+
|
|
59
|
+
四个 Skill 只负责触发条件和不变量,确定性运行时统一由 `agentkit` 提供。现有 1.x 脚本入口仍保留
|
|
60
|
+
兼容转发;新调用建议直接使用下列命令:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
agentkit capabilities --json
|
|
64
|
+
agentkit doctor --json
|
|
65
|
+
agentkit worktree --help
|
|
66
|
+
agentkit contract --help
|
|
67
|
+
agentkit orchestrate ledger --help
|
|
68
|
+
agentkit verify --help
|
|
69
|
+
agentkit docs
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## 使用方式
|
|
73
|
+
|
|
74
|
+
安装完成后,直接用自然语言描述目标,并明确触发对应能力,例如:
|
|
75
|
+
|
|
76
|
+
```text
|
|
77
|
+
用 orchestrate-subagents 把这个功能拆给多个 Agent 并行处理。
|
|
78
|
+
用 manage-worktrees 为这些 feature 分支准备一批集成验收。
|
|
79
|
+
用 verify-agent-output 独立验收这个固定 commit,只验一次并输出 Evidence。
|
|
80
|
+
用 run-agent-verify-loop 让一个 Agent 实现、另一个 Agent 独立验收,直到通过或触发停止条件。
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Skill 会根据当前宿主可用的 Agent、终端、Git 和任务控制能力进行适配。宿主缺少某项能力时,应遵循
|
|
84
|
+
各 Skill 中的降级路径,而不是假设某个特定产品或工具一定存在。
|
|
85
|
+
|
|
86
|
+
## 环境要求
|
|
87
|
+
|
|
88
|
+
- Git。
|
|
89
|
+
- Node.js 22 或更高版本。
|
|
90
|
+
- 全局可用的 `agentkit` 命令;运行 `agentkit doctor` 可检查包版本、入口与运行时清单。
|
|
91
|
+
- 若要实际派生和隔离多个 Agent,上层宿主需要提供相应的任务或子 Agent 能力。
|
|
92
|
+
|
|
93
|
+
## 本地验证
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npm test
|
|
97
|
+
npm run pack:check
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## 仓库范围
|
|
101
|
+
|
|
102
|
+
本仓库是 `agentkit` CLI、上述 4 个 Skill、canonical schema、测试与架构文档的唯一真源,不再由其他
|
|
103
|
+
仓库生成或反向覆盖。维护边界与发布流程见[真源与发布维护](./docs/maintenance/source-of-truth.md)。
|
package/bin/agentkit.mjs
ADDED
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
// agentkit 顶层路由。P1 只解析前缀,argv 其余部分原样转发给现有入口:
|
|
2
|
+
// 转发路径上 stdout、stderr 分类与退出码必须与直接调用旧入口逐字节等价,
|
|
3
|
+
// 因此一律用 stdio: 'inherit' 直通,不做任何包装、重排或补充输出。
|
|
4
|
+
// CLI 自身的输出(help、version、capabilities 聚合、docs、doctor 汇总)不受该约束,
|
|
5
|
+
// 但任何迁移提示只写 stderr,避免污染可被管道消费的 stdout。
|
|
6
|
+
import { spawnSync } from 'node:child_process';
|
|
7
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
8
|
+
import { basename, dirname, join } from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
assertPublicCommandCompatibility,
|
|
13
|
+
RuntimeBundleError,
|
|
14
|
+
runtimeBundleDigest,
|
|
15
|
+
validateShellManifest,
|
|
16
|
+
} from '../core/runtime-bundle.mjs';
|
|
17
|
+
|
|
18
|
+
const PACKAGE_ROOT = dirname(dirname(fileURLToPath(import.meta.url))); // bin/ 的上一级即包根
|
|
19
|
+
|
|
20
|
+
// 一级域:前缀之后的 argv 直接交给同一个脚本。
|
|
21
|
+
const DOMAINS = {
|
|
22
|
+
worktree: { domain: 'worktree', skill: 'manage-worktrees', script: 'worktree-mgr.mjs', verbs: { scan: 'worktree-scan.mjs' } },
|
|
23
|
+
contract: { domain: 'orchestrate', skill: 'orchestrate-subagents', script: 'contract-tool.mjs' },
|
|
24
|
+
verify: { domain: 'verify', skill: 'verify-agent-output', script: 'verification-runtime.mjs' },
|
|
25
|
+
loop: { domain: 'loop', skill: 'run-agent-verify-loop', script: 'loop-runtime.mjs' },
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// 二级域:四个 orchestrate 工具都有 capabilities,preflight 与 contract 都有 normalize,
|
|
29
|
+
// 扁平化会让同名动词互相遮蔽,因此保留工具名这一级,动词本身一个都不改。
|
|
30
|
+
const GROUPS = {
|
|
31
|
+
orchestrate: {
|
|
32
|
+
ledger: { domain: 'orchestrate', skill: 'orchestrate-subagents', script: 'orchestration-ledger.mjs' },
|
|
33
|
+
preflight: { domain: 'orchestrate', skill: 'orchestrate-subagents', script: 'worker-capability-preflight.mjs' },
|
|
34
|
+
'review-budget': { domain: 'orchestrate', skill: 'orchestrate-subagents', script: 'review-budget.mjs' },
|
|
35
|
+
reflection: { domain: 'orchestrate', skill: 'orchestrate-subagents', script: 'orchestration-reflection.mjs' },
|
|
36
|
+
},
|
|
37
|
+
host: {
|
|
38
|
+
cache: { domain: 'orchestrate', skill: 'orchestrate-subagents', script: 'host_capability_cache.mjs' },
|
|
39
|
+
'model-policy': { domain: 'orchestrate', skill: 'orchestrate-subagents', script: 'resolve_model_policy.mjs' },
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// capabilities 与安装级 doctor 的成员入口。域级 doctor 需要 ledger/run/loop 等状态选择器,
|
|
44
|
+
// 顶层 doctor 只检查安装和能力发现,不能把“未指定某次运行”误报成不健康。
|
|
45
|
+
const CAPABILITY_TARGETS = [
|
|
46
|
+
{ domain: 'orchestrate', skill: 'orchestrate-subagents', script: 'orchestration-ledger.mjs' },
|
|
47
|
+
{ domain: 'worktree', skill: 'manage-worktrees', script: 'worktree-mgr.mjs' },
|
|
48
|
+
{ domain: 'verify', skill: 'verify-agent-output', script: 'verification-runtime.mjs' },
|
|
49
|
+
{ domain: 'loop', skill: 'run-agent-verify-loop', script: 'loop-runtime.mjs' },
|
|
50
|
+
];
|
|
51
|
+
const DOCTOR_TARGETS = CAPABILITY_TARGETS;
|
|
52
|
+
|
|
53
|
+
// docs 路由:参考文档按域收在包根 docs/<域>/ 下。
|
|
54
|
+
const DOC_DOMAINS = { orchestrate: 'orchestrate', worktree: 'worktree', verify: 'verify', loop: 'loop' };
|
|
55
|
+
|
|
56
|
+
function packageVersion() {
|
|
57
|
+
try { return JSON.parse(readFileSync(join(PACKAGE_ROOT, 'package.json'), 'utf8')).version; }
|
|
58
|
+
catch { return 'unknown'; }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function scriptPath(target) {
|
|
62
|
+
// 运行时住在 domains/ 下;Skill 目录只剩 SKILL.md、参考文档与 1.x 兼容 stub。
|
|
63
|
+
return join(PACKAGE_ROOT, 'domains', target.domain, target.script);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function missing(target) {
|
|
67
|
+
process.stderr.write(`agentkit: 未找到 domains/${target.domain}/${target.script},该 Skill 未随本安装分发\n`);
|
|
68
|
+
return 127;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 直通转发:子进程直接继承本进程的 stdio,父进程不碰任何一个字节。
|
|
72
|
+
function forward(target, args) {
|
|
73
|
+
const path = scriptPath(target);
|
|
74
|
+
if (!existsSync(path)) return missing(target);
|
|
75
|
+
try {
|
|
76
|
+
assertPublicCommandCompatibility({ skill: target.skill, entryName: target.script, command: args[0] });
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (!(error instanceof RuntimeBundleError)) throw error;
|
|
79
|
+
process.stderr.write(`agentkit runtime compatibility error: ${error.message}\n`);
|
|
80
|
+
return 3;
|
|
81
|
+
}
|
|
82
|
+
const result = spawnSync(process.execPath, [path, ...args], { stdio: 'inherit' });
|
|
83
|
+
if (result.error) {
|
|
84
|
+
process.stderr.write(`agentkit: 执行 ${target.script} 失败:${result.error.message}\n`);
|
|
85
|
+
return 1;
|
|
86
|
+
}
|
|
87
|
+
if (result.signal) {
|
|
88
|
+
// facade 若吞掉子进程信号并改写为 exit 1,就不再满足“退出语义一致”。
|
|
89
|
+
try { process.kill(process.pid, result.signal); } catch { return 1; }
|
|
90
|
+
return 1;
|
|
91
|
+
}
|
|
92
|
+
return result.status === null ? 1 : result.status;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 捕获式调用:只用于 CLI 自己要解析子进程输出的聚合命令。
|
|
96
|
+
function capture(target, args) {
|
|
97
|
+
const path = scriptPath(target);
|
|
98
|
+
if (!existsSync(path)) return null;
|
|
99
|
+
const result = spawnSync(process.execPath, [path, ...args], { encoding: 'utf8' });
|
|
100
|
+
if (result.status !== 0 || !result.stdout) return null;
|
|
101
|
+
try { return JSON.parse(result.stdout); } catch { return null; }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function helpText() {
|
|
105
|
+
const lines = [
|
|
106
|
+
`agentkit ${packageVersion()}`,
|
|
107
|
+
'',
|
|
108
|
+
'用法: agentkit <域> <命令> [选项]',
|
|
109
|
+
'',
|
|
110
|
+
'域:',
|
|
111
|
+
' worktree <命令> Git worktree 隔离与生命周期(含 worktree scan)',
|
|
112
|
+
' contract <命令> 任务契约规范化、校验、摘要与投影',
|
|
113
|
+
' orchestrate <工具> <命令> ledger | preflight | review-budget | reflection',
|
|
114
|
+
' host <工具> <命令> cache | model-policy',
|
|
115
|
+
' verify <命令> 冻结 Artifact 的一次性独立验收',
|
|
116
|
+
' loop <命令> 有界的实现—验收循环',
|
|
117
|
+
'',
|
|
118
|
+
'跨域命令:',
|
|
119
|
+
' capabilities [--json] 汇总四个 Skill 的能力发现结果',
|
|
120
|
+
' doctor [--json] 检查 Node、Git、安装完整性与各域能力发现',
|
|
121
|
+
' docs [<域>] [<主题>] 输出参考文档原文;缺主题时只列索引',
|
|
122
|
+
' --version 打印版本',
|
|
123
|
+
'',
|
|
124
|
+
'域与工具之后的参数原样转发给对应入口,输出与退出码保持一致。',
|
|
125
|
+
];
|
|
126
|
+
return `${lines.join('\n')}\n`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function runCapabilities(args) {
|
|
130
|
+
const json = args.includes('--json');
|
|
131
|
+
const skills = {};
|
|
132
|
+
for (const target of CAPABILITY_TARGETS) {
|
|
133
|
+
const payload = capture(target, ['capabilities', '--json']);
|
|
134
|
+
if (payload) skills[target.skill] = payload;
|
|
135
|
+
}
|
|
136
|
+
if (!Object.keys(skills).length) {
|
|
137
|
+
process.stderr.write('agentkit: 没有任何 Skill 能力可发现\n');
|
|
138
|
+
return 1;
|
|
139
|
+
}
|
|
140
|
+
if (json) {
|
|
141
|
+
process.stdout.write(`${JSON.stringify({ cli: 'agentkit', cli_version: packageVersion(), runtime_bundle_digest: runtimeBundleDigest(), skills }, null, 2)}\n`);
|
|
142
|
+
return 0;
|
|
143
|
+
}
|
|
144
|
+
for (const [skill, payload] of Object.entries(skills)) {
|
|
145
|
+
process.stdout.write(`${skill} runtime ${payload.runtime_version ?? '?'}\n`);
|
|
146
|
+
}
|
|
147
|
+
return 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function runDoctor(args) {
|
|
151
|
+
const unknown = args.filter((arg) => arg !== '--json');
|
|
152
|
+
if (unknown.length) {
|
|
153
|
+
process.stderr.write(`agentkit doctor: 未知选项「${unknown[0]}」;状态检查请使用对应域的 doctor 并传入 ledger/run/loop\n`);
|
|
154
|
+
return 2;
|
|
155
|
+
}
|
|
156
|
+
const json = args.includes('--json');
|
|
157
|
+
const nodeMajor = Number.parseInt(process.versions.node.split('.')[0], 10);
|
|
158
|
+
const node = { healthy: Number.isSafeInteger(nodeMajor) && nodeMajor >= 22, version: process.versions.node, required: '>=22' };
|
|
159
|
+
const gitResult = spawnSync('git', ['--version'], { encoding: 'utf8' });
|
|
160
|
+
const git = {
|
|
161
|
+
healthy: gitResult.status === 0,
|
|
162
|
+
version: gitResult.status === 0 ? gitResult.stdout.trim().replace(/^git version\s+/u, '') : null,
|
|
163
|
+
error: gitResult.status === 0 ? null : (gitResult.error?.message ?? gitResult.stderr?.trim() ?? 'unavailable'),
|
|
164
|
+
};
|
|
165
|
+
let manifest;
|
|
166
|
+
try {
|
|
167
|
+
const value = validateShellManifest();
|
|
168
|
+
manifest = { healthy: true, package_name: value.package_name, package_version: value.package_version, error: null };
|
|
169
|
+
} catch (error) {
|
|
170
|
+
manifest = { healthy: false, package_name: null, package_version: null, error: error instanceof Error ? error.message : String(error) };
|
|
171
|
+
}
|
|
172
|
+
const skills = {};
|
|
173
|
+
let installed = 0;
|
|
174
|
+
for (const target of DOCTOR_TARGETS) {
|
|
175
|
+
if (!existsSync(scriptPath(target))) {
|
|
176
|
+
skills[target.skill] = { installed: false, healthy: null, state_doctor: 'not_run' };
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
installed += 1;
|
|
180
|
+
const capabilities = capture(target, ['capabilities', '--json']);
|
|
181
|
+
skills[target.skill] = {
|
|
182
|
+
installed: true,
|
|
183
|
+
healthy: capabilities !== null,
|
|
184
|
+
runtime_version: capabilities?.runtime_version ?? null,
|
|
185
|
+
content_digest: capabilities?.content_digest ?? null,
|
|
186
|
+
state_doctor: 'requires_explicit_state',
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const healthy = node.healthy && git.healthy && manifest.healthy && installed > 0
|
|
190
|
+
&& Object.values(skills).filter((item) => item.installed).every((item) => item.healthy);
|
|
191
|
+
const result = { cli: 'agentkit', cli_version: packageVersion(), runtime_bundle_digest: runtimeBundleDigest(), healthy, checks: { node, git, manifest }, skills };
|
|
192
|
+
if (json) process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
193
|
+
else {
|
|
194
|
+
process.stdout.write(`agentkit ${packageVersion()} doctor: ${healthy ? 'healthy' : 'unhealthy'}\n`);
|
|
195
|
+
process.stdout.write(`node ${node.version} (${node.healthy ? 'ok' : `需要 ${node.required}`})\n`);
|
|
196
|
+
process.stdout.write(`git ${git.version ?? git.error} (${git.healthy ? 'ok' : 'unavailable'})\n`);
|
|
197
|
+
process.stdout.write(`shell manifest ${manifest.package_version ?? manifest.error} (${manifest.healthy ? 'ok' : 'invalid'})\n`);
|
|
198
|
+
for (const [skill, check] of Object.entries(skills)) {
|
|
199
|
+
process.stdout.write(`${skill}: ${check.installed ? (check.healthy ? `runtime ${check.runtime_version ?? '?'} ok` : 'capabilities failed') : 'not installed'}\n`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (!installed) return 127;
|
|
203
|
+
return healthy ? 0 : 1;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function docsIndex(domain) {
|
|
207
|
+
const dir = join(PACKAGE_ROOT, 'docs', DOC_DOMAINS[domain]);
|
|
208
|
+
if (!existsSync(dir)) return [];
|
|
209
|
+
return readdirSync(dir).filter((name) => name.endsWith('.md')).map((name) => basename(name, '.md')).sort();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function runDocs(args) {
|
|
213
|
+
const [domain, topic] = args;
|
|
214
|
+
if (!domain) {
|
|
215
|
+
process.stdout.write(`${Object.keys(DOC_DOMAINS).sort().join('\n')}\n`);
|
|
216
|
+
return 0;
|
|
217
|
+
}
|
|
218
|
+
if (!DOC_DOMAINS[domain]) {
|
|
219
|
+
process.stderr.write(`agentkit: 未知文档域「${domain}」,可选:${Object.keys(DOC_DOMAINS).sort().join(', ')}\n`);
|
|
220
|
+
return 2;
|
|
221
|
+
}
|
|
222
|
+
const topics = docsIndex(domain);
|
|
223
|
+
if (!topic) {
|
|
224
|
+
if (!topics.length) { process.stderr.write(`agentkit: ${domain} 没有可用参考文档\n`); return 127; }
|
|
225
|
+
process.stdout.write(`${topics.join('\n')}\n`);
|
|
226
|
+
return 0;
|
|
227
|
+
}
|
|
228
|
+
if (!topics.includes(topic)) {
|
|
229
|
+
// 主题不存在时只列索引,不猜测最相近的文档。
|
|
230
|
+
process.stderr.write(`agentkit: ${domain} 没有主题「${topic}」\n`);
|
|
231
|
+
process.stdout.write(`${topics.join('\n')}\n`);
|
|
232
|
+
return 2;
|
|
233
|
+
}
|
|
234
|
+
process.stdout.write(readFileSync(join(PACKAGE_ROOT, 'docs', DOC_DOMAINS[domain], `${topic}.md`), 'utf8'));
|
|
235
|
+
return 0;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export async function main(argv) {
|
|
239
|
+
const [head, ...rest] = argv;
|
|
240
|
+
|
|
241
|
+
if (!head || head === '--help' || head === '-h' || head === 'help') {
|
|
242
|
+
process.stdout.write(helpText());
|
|
243
|
+
return 0;
|
|
244
|
+
}
|
|
245
|
+
if (head === '--version' || head === '-v') {
|
|
246
|
+
process.stdout.write(`${packageVersion()}\n`);
|
|
247
|
+
return 0;
|
|
248
|
+
}
|
|
249
|
+
if (head === 'capabilities') return runCapabilities(rest);
|
|
250
|
+
if (head === 'doctor') return runDoctor(rest);
|
|
251
|
+
if (head === 'docs') return runDocs(rest);
|
|
252
|
+
|
|
253
|
+
const domain = DOMAINS[head];
|
|
254
|
+
if (domain) {
|
|
255
|
+
const override = domain.verbs?.[rest[0]];
|
|
256
|
+
if (override) return forward({ ...domain, script: override }, rest);
|
|
257
|
+
return forward(domain, rest);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const group = GROUPS[head];
|
|
261
|
+
if (group) {
|
|
262
|
+
const [tool, ...args] = rest;
|
|
263
|
+
if (!tool || !group[tool]) {
|
|
264
|
+
process.stderr.write(`agentkit ${head}: 需要工具名,可选:${Object.keys(group).join(', ')}\n`);
|
|
265
|
+
return 2;
|
|
266
|
+
}
|
|
267
|
+
return forward(group[tool], args);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
process.stderr.write(`agentkit: 未知域「${head}」\n`);
|
|
271
|
+
process.stderr.write(helpText());
|
|
272
|
+
return 2;
|
|
273
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// 文件写入原语。五处 writeNewJson 此前各带一份,两种写法只有选项顺序差异,语义相同。
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { renameSync, writeFileSync } from 'node:fs';
|
|
4
|
+
|
|
5
|
+
// wx:目标已存在即失败,保证「只写新文件」不会覆盖既有状态。
|
|
6
|
+
/** @param {string} path @param {unknown} value */
|
|
7
|
+
export function writeNewJson(path, value) {
|
|
8
|
+
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** @param {string} path @param {unknown} value */
|
|
12
|
+
export function atomicWriteJson(path, value) {
|
|
13
|
+
const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
14
|
+
writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
|
|
15
|
+
renameSync(temporary, path);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** @param {string} path @param {string} value */
|
|
19
|
+
export function atomicWriteText(path, value) {
|
|
20
|
+
const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
21
|
+
writeFileSync(temporary, value, { flag: 'wx', mode: 0o600 });
|
|
22
|
+
renameSync(temporary, path);
|
|
23
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
// 五个编排脚本共用的 `--help` 渲染。命令与参数清单只从各脚本已有的 CLI_SPEC 表推导,
|
|
4
|
+
// 不允许在正文里手写第二份清单——否则表和帮助会各自漂移。
|
|
5
|
+
|
|
6
|
+
/** @typedef {{ required?: string[], optional?: string[], flags?: string[] }} CommandSpec */
|
|
7
|
+
|
|
8
|
+
const HELP_TOKENS = new Set(['--help', '-h', 'help']);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 无参数、`help`、`--help`、`-h` 都按求助处理。
|
|
12
|
+
* @param {string[]} argv
|
|
13
|
+
*/
|
|
14
|
+
export function isHelpRequest(argv) {
|
|
15
|
+
return argv.length === 0 || HELP_TOKENS.has(argv[0]);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* CLI_SPEC 中一条命令接受的全部带值选项(required + optional)。
|
|
20
|
+
* @param {CommandSpec} spec
|
|
21
|
+
*/
|
|
22
|
+
export function specOptionNames(spec) {
|
|
23
|
+
return [...(spec.required ?? []), ...(spec.optional ?? [])];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* CLI_SPEC 全部命令接受的带值选项并集,供只做全局选项校验的脚本使用。
|
|
28
|
+
* @param {Record<string, CommandSpec>} cliSpec
|
|
29
|
+
*/
|
|
30
|
+
export function allOptionNames(cliSpec) {
|
|
31
|
+
return new Set(Object.values(cliSpec).flatMap((spec) => specOptionNames(spec)));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {string} script 脚本文件名
|
|
36
|
+
* @param {Record<string, CommandSpec>} cliSpec
|
|
37
|
+
* @param {string[]} notes 额外说明行
|
|
38
|
+
*/
|
|
39
|
+
export function renderCliHelp(script, cliSpec, notes = []) {
|
|
40
|
+
const lines = [`用法: node ${script} <command> [options]`, '', '命令:'];
|
|
41
|
+
for (const [command, spec] of Object.entries(cliSpec)) {
|
|
42
|
+
const parts = [command];
|
|
43
|
+
for (const name of spec.required ?? []) parts.push(`--${name} <value>`);
|
|
44
|
+
for (const name of spec.optional ?? []) parts.push(`[--${name} <value>]`);
|
|
45
|
+
for (const name of spec.flags ?? []) parts.push(`[--${name}]`);
|
|
46
|
+
lines.push(` ${parts.join(' ')}`);
|
|
47
|
+
}
|
|
48
|
+
if (notes.length) {
|
|
49
|
+
lines.push('', '说明:');
|
|
50
|
+
for (const note of notes) lines.push(` ${note}`);
|
|
51
|
+
}
|
|
52
|
+
lines.push('', '无参数 / help / --help / -h 打印本清单并退出 0;未知命令与非法输入仍按各脚本原有错误形状返回并非零退出。');
|
|
53
|
+
return `${lines.join('\n')}\n`;
|
|
54
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
// 域级内容摘要:覆盖一个 Skill 实际执行所依赖的完整分发内容。
|
|
3
|
+
//
|
|
4
|
+
// 收敛前四个 Skill 各带一份实现,都只遍历自己的目录。schema 收敛与 core 抽取之后,
|
|
5
|
+
// 执行还依赖包根的 core/ 与 schemas/,只算 Skill 目录会漏掉真正会漂移的代码——
|
|
6
|
+
// 改这两处不会触发 skill_drift,冻结的任务却已经跑在不同的实现上。
|
|
7
|
+
//
|
|
8
|
+
// 因此摘要改为多根:每个根用固定的逻辑前缀(skill / domain / core / schemas)记录,
|
|
9
|
+
// 与安装绝对路径无关。CLI 路由(bin/)不在覆盖范围内:它决定命令怎么分发,
|
|
10
|
+
// 不参与任何 Skill 的执行语义,把它算进来会让改一行帮助文案就让四个域一起漂移。
|
|
11
|
+
import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync } from 'node:fs';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
|
|
14
|
+
import { createDigestKit, sha256 } from './digest.mjs';
|
|
15
|
+
|
|
16
|
+
// 摘要的序列化口径与各 Skill 的 canonicalJson 解耦:四处摘要必须算得出同一个结果,
|
|
17
|
+
// 不能因为某个 Skill 的严格度不同而分叉。
|
|
18
|
+
const { canonicalJson } = createDigestKit({ ValidationError: Error, strict: false });
|
|
19
|
+
|
|
20
|
+
const SKILL_ENTRIES = ['SKILL.md', 'agents', 'scripts'];
|
|
21
|
+
|
|
22
|
+
function collect(root, prefix, entries, output) {
|
|
23
|
+
if (!existsSync(root)) return;
|
|
24
|
+
const base = realpathSync(root);
|
|
25
|
+
const visit = (absolute, relativePath) => {
|
|
26
|
+
const stat = lstatSync(absolute);
|
|
27
|
+
if (stat.isDirectory()) {
|
|
28
|
+
for (const name of readdirSync(absolute).sort()) visit(join(absolute, name), `${relativePath}/${name}`);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (stat.isFile() || stat.isSymbolicLink()) {
|
|
32
|
+
const bytes = readFileSync(absolute);
|
|
33
|
+
output.push({ path: relativePath, size: bytes.length, sha256: sha256(bytes) });
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
if (!entries) { visit(base, prefix); return; }
|
|
37
|
+
for (const name of entries) if (existsSync(join(base, name))) visit(join(base, name), `${prefix}/${name}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** @param {{ prefix: string, path: string, entries?: string[] }[]} roots */
|
|
41
|
+
export function distributionManifest(roots) {
|
|
42
|
+
const output = [];
|
|
43
|
+
for (const { prefix, path, entries } of roots) collect(path, prefix, entries, output);
|
|
44
|
+
// 码点序,不用 localeCompare:后者随运行环境的 collation 变化,摘要必须与环境无关。
|
|
45
|
+
return output.sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** @param {{ prefix: string, path: string, entries?: string[] }[]} roots */
|
|
49
|
+
export function distributionDigest(roots) {
|
|
50
|
+
return sha256(Buffer.from(canonicalJson(distributionManifest(roots)), 'utf8'));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 一个 Skill 的分发内容 = 自己的 Skill 目录 + 对应 domain 运行时 + 按需文档
|
|
55
|
+
* + 共享 core + canonical schemas。
|
|
56
|
+
* @param {{ packageRoot: string, skillRoot: string, domainRoot?: string, docsRoot?: string }} options
|
|
57
|
+
*/
|
|
58
|
+
export function skillDistributionRoots({ packageRoot, skillRoot, domainRoot, docsRoot }) {
|
|
59
|
+
const roots = [{ prefix: 'skill', path: skillRoot, entries: SKILL_ENTRIES }];
|
|
60
|
+
if (domainRoot) roots.push({ prefix: 'domain', path: domainRoot });
|
|
61
|
+
if (docsRoot) roots.push({ prefix: 'docs', path: docsRoot });
|
|
62
|
+
roots.push({ prefix: 'core', path: join(packageRoot, 'core') });
|
|
63
|
+
roots.push({ prefix: 'schemas', path: join(packageRoot, 'schemas') });
|
|
64
|
+
roots.push({ prefix: 'shell-manifest', path: join(packageRoot, 'shell-manifest.json') });
|
|
65
|
+
return roots;
|
|
66
|
+
}
|