@stofone/dsh-agent-roster 0.10.2 → 0.10.4
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 +31 -1
- package/README.md +13 -1
- package/README.zh.md +11 -1
- package/bin/cli.mjs +25 -3
- package/docs/roster-v2.md +2 -0
- package/index/registry.json +16 -7
- package/lib/migrate.js +15 -6
- package/lib/remote.js +3 -2
- package/package.json +1 -1
- package/schema/registry.v1.schema.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,34 @@
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.10.4] - 2026-11-03
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **`compile --repo` was accepted and ignored.** The flag was parsed, then the artifact recorded `ownRepo()`
|
|
12
|
+
— the coordinates of whichever checkout ran the compiler. A repository that committed `dist/` therefore
|
|
13
|
+
disagreed with its own CI on every run: the drift check recompiled, got a different `source.repo`, and
|
|
14
|
+
failed. `migrate` had the same fault one layer down: it passed `source: {}` to the compiler instead of
|
|
15
|
+
the repository it was told to record. Both now write the coordinates they were given, and a test pins it.
|
|
16
|
+
|
|
17
|
+
## [0.10.3] - 2026-11-03
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- `migrate --from <dir>` and `--repo <owner/repo>`: a repository that publishes a collection keeps its
|
|
22
|
+
v1 rosters in `rosters/<id>/`, so the library root and the old files are not the same directory. The
|
|
23
|
+
migration now rewrites that file in place, adds `roles/` and `dist/` at the root, removes the stale
|
|
24
|
+
`roster.json`/`roster.yml` beside it (two spellings of one roster are two sources of truth), and does
|
|
25
|
+
**not** delete `rosters/<id>/` — that is where the new assembly lives.
|
|
26
|
+
- A collection's `path` may be empty, meaning the repository root *is* the library. The three example
|
|
27
|
+
repositories (`agent-teams`, `metagpt-company`, `openmanus`) now use that shape: `roles/`, `rosters/`,
|
|
28
|
+
`dist/` at the root, `files` pointing at the compiled artifact.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- The `agent-teams`, `metagpt-company` and `openmanus` collections were still published as v1, so their
|
|
33
|
+
`roster.yaml` was fetched while a v2 world had already moved on; every entry now names its artifact.
|
|
34
|
+
|
|
7
35
|
## [0.10.2] - 2026-11-03
|
|
8
36
|
|
|
9
37
|
### Fixed
|
|
@@ -472,7 +500,9 @@
|
|
|
472
500
|
- 工具返回值含 `undefined` 键,运行时按 `value is not lossless JSON` 整次拒绝;缺失值统一写 `null`,
|
|
473
501
|
且所有返回值先过 `lib/json.js#lossless`。
|
|
474
502
|
|
|
475
|
-
[Unreleased]: https://github.com/dsh-agent-roster/dsh-agent-roster/compare/v0.10.
|
|
503
|
+
[Unreleased]: https://github.com/dsh-agent-roster/dsh-agent-roster/compare/v0.10.4...main
|
|
504
|
+
[0.10.4]: https://github.com/dsh-agent-roster/dsh-agent-roster/compare/v0.10.3...v0.10.4
|
|
505
|
+
[0.10.3]: https://github.com/dsh-agent-roster/dsh-agent-roster/compare/v0.10.2...v0.10.3
|
|
476
506
|
[0.10.2]: https://github.com/dsh-agent-roster/dsh-agent-roster/compare/v0.10.1...v0.10.2
|
|
477
507
|
[0.10.1]: https://github.com/dsh-agent-roster/dsh-agent-roster/compare/v0.10.0...v0.10.1
|
|
478
508
|
[0.10.0]: https://github.com/dsh-agent-roster/dsh-agent-roster/releases/tag/v0.10.0
|
package/README.md
CHANGED
|
@@ -276,6 +276,18 @@ Migration keeps personas verbatim: a member's `systemPrompt` becomes the body of
|
|
|
276
276
|
loose field and becomes a role file of its own — `<roster>-<captain>-captain` when its name collides
|
|
277
277
|
with a member, because those are two different personas and silently merging them would lose one.
|
|
278
278
|
|
|
279
|
+
If the v1 rosters live in a subdirectory (the usual case for a repository that publishes a
|
|
280
|
+
collection), point the migration at the library root and tell it where the old files are:
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
node bin/cli.mjs migrate --dir . --from rosters --repo owner/repo --write
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
That layout rewrites `rosters/<id>/roster.yaml` in place, adds `roles/` and `dist/` at the repository
|
|
287
|
+
root, and drops the stale `roster.json`/`roster.yml` next to it — two spellings of one roster are two
|
|
288
|
+
sources of truth. The collection then uses `path: ""` (the repository root *is* the library) and a
|
|
289
|
+
`files` map pointing at `dist/`, because an assembly only references roles and cannot be pulled alone.
|
|
290
|
+
|
|
279
291
|
Role names carry the roster id prefix because v2 requires role names to be **unique per library**,
|
|
280
292
|
and a library whose 21 rosters all call their captain `lead` cannot have 21 files with the same name.
|
|
281
293
|
|
|
@@ -468,7 +480,7 @@ test/ unit tests
|
|
|
468
480
|
|
|
469
481
|
Verified on this machine:
|
|
470
482
|
|
|
471
|
-
- `npm test` —
|
|
483
|
+
- `npm test` — 293/293 passing (format, parse, validate, registry, paths, bridge, verify buckets, digests and provenance, roster diff, capability requirements and the capability gate, library organization, registry schema, lossless JSON, write plan/apply, all routes, panel snapshot and hints, dictionary consistency, tool contracts, client loader contract, library snapshot, config view, package manifest, declared fields and captain block, schema/constant alignment, the v2 role/roster/compiled layers and their schema alignment, v1→v2 migration, run records feeding the plan, remote registry and one-click install).
|
|
472
484
|
- `npm run smoke` — `apply()` registers all six tools through a real `defineTool` from a DSH bundle; `roster_list` / `roster_show` / `roster_create` / `roster_verify` / `roster_diff` execute, every output survives `lossless` checks, `path` overrides are honored, effect disposal runs cleanly. The same command then loads `client.js` against a stubbed module table and asserts the three slot registrations, style injection and effect disposal.
|
|
473
485
|
- CLI: `validate --dir examples` returns exit 0 over both example libraries.
|
|
474
486
|
- Installed into a real profile with the plugin manager (`plugin_manager install_bundle` against this directory → `link:` dependency + bundle entry); the profile re-reads the linked files on restart.
|
package/README.zh.md
CHANGED
|
@@ -269,6 +269,16 @@ node bin/cli.mjs migrate --dir examples --write # 原地重写:roles/ + ros
|
|
|
269
269
|
|
|
270
270
|
迁移逐字保留人设:成员的 `systemPrompt` 原文成为 `roles/<档案>-<成员>/AGENT.md` 的正文,成员本身变成引用。队长不再是一个散字段,而是自己的角色文件——与成员同名时得到 `-captain` 后缀,因为那是两份人设,悄悄合并会丢掉一份。
|
|
271
271
|
|
|
272
|
+
如果 v1 的档案在子目录里(把仓库当 collection 发布的常见形态),把 `--dir` 指到库根、用 `--from` 指旧目录:
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
node bin/cli.mjs migrate --dir . --from rosters --repo owner/repo --write
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
这种布局会就地重写 `rosters/<id>/roster.yaml`,在仓库根上补 `roles/` 与 `dist/`,并删掉并排的旧
|
|
279
|
+
`roster.json`/`roster.yml`——同一份档案两种写法就是两个真源。之后 collection 用 `path: ""`(仓库根就是库),
|
|
280
|
+
再用 `files` 指向 `dist/`:组装只引用角色,单独拉一个文件拉不到角色目录。
|
|
281
|
+
|
|
272
282
|
角色名带档案 id 前缀,是因为 v2 要求同库角色名唯一:一座库里 21 套组装都叫自己的队长 `lead`,不可能有 21 个同名文件。
|
|
273
283
|
|
|
274
284
|
### 把失败的一轮喂回施工图
|
|
@@ -444,7 +454,7 @@ test/ 单测
|
|
|
444
454
|
|
|
445
455
|
本机已验证:
|
|
446
456
|
|
|
447
|
-
- `npm test` ——
|
|
457
|
+
- `npm test` —— 293/293 通过(格式、解析、校验、扫描、路径、桥接、核对四桶、摘要与溯源、档案差异、能力要求与能力闸门、档案库组织、索引 schema、无损 JSON、写入计划与执行、六条路由、快照与提示、两本词典一致、工具契约、客户端加载器契约、包清单、配置视图、样式 token 约束、声明层字段与队长段、schema 与常量对齐、v2 角色/组装/编译三层及其 schema 对齐、v1→v2 迁移、执行回执并入施工图)。
|
|
448
458
|
- `npm run smoke` —— 用真实 DSH bundle 的 `defineTool` 注册六个工具,逐个调用:输出全部通过无损校验、`path` 覆盖生效、释放干净;同一条命令随后用桩模块表加载 `client.js`,断言三处槽位注册、样式只注入一次、effect 可回收。
|
|
449
459
|
- CLI:`validate --dir examples` 对两座示例库退出码 0。
|
|
450
460
|
- 用插件管理器真的装进了本机 profile(`plugin_manager install_bundle` 指向本仓库 → `link:` 依赖 + bundle 条目),重启后按链接读取本仓库文件。
|
package/bin/cli.mjs
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* dsh-agent-roster registry sync --file index/registry.json [--write]
|
|
14
14
|
* dsh-agent-roster compile --roster rosters/money-lab [--roles roles] [--run run.json] [--write] [--json]
|
|
15
15
|
* dsh-agent-roster migrate --dir examples [--write] [--json]
|
|
16
|
+
* dsh-agent-roster migrate --dir . --from rosters --repo owner/repo --write
|
|
16
17
|
*/
|
|
17
18
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
18
19
|
import nodePath from "node:path";
|
|
@@ -37,7 +38,8 @@ const USAGE = `用法:
|
|
|
37
38
|
dsh-agent-roster registry fetch --file <路径> 逐条确认 collection 真的能取到
|
|
38
39
|
dsh-agent-roster registry sync --file <路径> 按仓库实际内容重算 roster 列表
|
|
39
40
|
dsh-agent-roster compile --roster <目录> 把 v2 组装编译成自包含产物(不写盘)
|
|
40
|
-
dsh-agent-roster migrate --dir <目录>
|
|
41
|
+
dsh-agent-roster migrate --dir <目录> [--from <子目录>] [--repo <owner/repo>] [--write]
|
|
42
|
+
把 v1 档案原地迁成 v2(roles/ + rosters/ + dist/)
|
|
41
43
|
dsh-agent-roster --version 输出版本号
|
|
42
44
|
|
|
43
45
|
选项:
|
|
@@ -47,6 +49,8 @@ const USAGE = `用法:
|
|
|
47
49
|
--local-only fetch 只验本仓库登记的 collection(CI 里用,不依赖外网)
|
|
48
50
|
--write sync 把重算结果写回文件
|
|
49
51
|
--roster <path> v2 组装目录(compile 用)
|
|
52
|
+
--from <path> v1 档案所在目录(migrate 用;缺省与 --dir 相同,用于仓库根 + rosters/ 布局)
|
|
53
|
+
--repo <owner/repo> 写进 dist 产物的仓库坐标(migrate 用;缺省取本仓 remote)
|
|
50
54
|
--run <path> 执行回执(compile 用;把上一轮失败并入重试卡)
|
|
51
55
|
--roles <path> 角色库目录(compile 用;缺省按 rosters/<id> 推同级的 roles/)
|
|
52
56
|
--json 以 JSON 输出(validate / list / compile 都支持)
|
|
@@ -68,6 +72,8 @@ function parseArgs(argv) {
|
|
|
68
72
|
roster: null,
|
|
69
73
|
roles: null,
|
|
70
74
|
run: null,
|
|
75
|
+
from: null,
|
|
76
|
+
repo: null,
|
|
71
77
|
};
|
|
72
78
|
for (let index = 0; index < argv.length; index += 1) {
|
|
73
79
|
const arg = argv[index];
|
|
@@ -91,6 +97,16 @@ function parseArgs(argv) {
|
|
|
91
97
|
if (value === undefined) throw new UsageError("--roster 需要一个路径");
|
|
92
98
|
options.roster = value;
|
|
93
99
|
index += 1;
|
|
100
|
+
} else if (arg === "--from") {
|
|
101
|
+
const value = argv[index + 1];
|
|
102
|
+
if (value === undefined) throw new UsageError("--from 需要一个路径");
|
|
103
|
+
options.from = value;
|
|
104
|
+
index += 1;
|
|
105
|
+
} else if (arg === "--repo") {
|
|
106
|
+
const value = argv[index + 1];
|
|
107
|
+
if (value === undefined) throw new UsageError("--repo 需要 owner/name");
|
|
108
|
+
options.repo = value;
|
|
109
|
+
index += 1;
|
|
94
110
|
} else if (arg === "--run") {
|
|
95
111
|
const value = argv[index + 1];
|
|
96
112
|
if (value === undefined) throw new UsageError("--run 需要一个路径");
|
|
@@ -259,7 +275,7 @@ async function runCompile(options) {
|
|
|
259
275
|
const result = compileRosterAt(rosterDir, {
|
|
260
276
|
rolesDir,
|
|
261
277
|
knownToolPolicies: options.knownPolicies.length > 0 ? options.knownPolicies : null,
|
|
262
|
-
source: { repo: ownRepo(), ref: "main" },
|
|
278
|
+
source: { repo: options.repo ?? ownRepo(), ref: "main" },
|
|
263
279
|
});
|
|
264
280
|
|
|
265
281
|
const diagnostics = [...(result.diagnostics ?? [])];
|
|
@@ -520,7 +536,13 @@ function runMigrate(options) {
|
|
|
520
536
|
const reports = [];
|
|
521
537
|
for (const dir of dirs) {
|
|
522
538
|
const rootDir = resolveDirSpec(dir, process.cwd());
|
|
523
|
-
const
|
|
539
|
+
const fromDir = options.from === null ? rootDir : resolveDirSpec(options.from, process.cwd());
|
|
540
|
+
const report = migrateLibrary(rootDir, {
|
|
541
|
+
write: options.write,
|
|
542
|
+
knownToolPolicies,
|
|
543
|
+
source: { repo: options.repo ?? ownRepo(), ref: "main" },
|
|
544
|
+
fromDir,
|
|
545
|
+
});
|
|
524
546
|
reports.push({ dir, rootDir, report });
|
|
525
547
|
if (report.diagnostics.some((item) => item.severity === "error")) rc = 1;
|
|
526
548
|
if (options.json) continue;
|
package/docs/roster-v2.md
CHANGED
|
@@ -197,6 +197,8 @@ gates:
|
|
|
197
197
|
## 8. 迁移
|
|
198
198
|
|
|
199
199
|
- `roster migrate --dir <库> [--write]` 读 v1 档案,把 `systemPrompt` 拆成角色(`roles/<member>/AGENT.md`)+ 组装(成员改为引用),原地重写并生成 `dist/`。默认只出计划,`--write` 才落盘——这是唯一会删旧目录的命令。
|
|
200
|
+
- `roster migrate --dir <库根> --from <旧目录> [--repo <owner/repo>] [--write]`:v1 档案放在子目录(`rosters/<id>/roster.yaml`,即"仓库本身就是库、只是档案隔了一层")时用这条。它就地重写 `rosters/<id>/roster.yaml`、在库根补 `roles/` 与 `dist/`、删掉并排的 `roster.json`/`roster.yml`(同义文件=第二真源),**不删** `rosters/<id>/` —— 那个目录就是新档案的落脚处。`--repo` 写进产物的 `source.repo`,避免发布仓的产物署错名。
|
|
201
|
+
- **collection 的 `path` 可以是空字符串**,表示仓库根就是库:`roles/`、`rosters/`、`dist/` 直接躺在根上。配合 `files` 映射指向 `dist/<id>-<ver>.json`。
|
|
200
202
|
- **角色名带档案 id 前缀**(`core-review-reviewer`):v2 要求同库角色名唯一,而真实档案库里几十套组装都可能叫 `lead`。队长与成员同名时,队长得到 `-captain` 后缀,不与人设合并。
|
|
201
203
|
- `maintainers` 用 `handle` 模式(允许大写与点),不复用档案 id 的小写短横线规则——维护者是账号,不是标识符。
|
|
202
204
|
- v1 冻结:新字段只进 v2,不再回填 v1。
|
package/index/registry.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "../schema/registry.v1.schema.json",
|
|
3
3
|
"kind": "dsh-agent-roster-registry/v1",
|
|
4
4
|
"version": 1,
|
|
5
|
-
"generatedAt": "2026-11-
|
|
5
|
+
"generatedAt": "2026-11-03T06:14:00.000Z",
|
|
6
6
|
"generator": "dsh-agent-roster/dsh-agent-roster (hand-maintained; verified by .github/workflows/registry.yml)",
|
|
7
7
|
"source": "https://github.com/dsh-agent-roster/dsh-agent-roster",
|
|
8
8
|
"collections": [
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
"description": "把 wshobson/agents 的 agent-teams 插件(4.2 万星,MIT)迁成档案:队长、实现者、审查者、调试者,保留上游工具边界。",
|
|
106
106
|
"repo": "github:dsh-agent-roster/agent-teams",
|
|
107
107
|
"ref": "main",
|
|
108
|
-
"path": "
|
|
108
|
+
"path": "",
|
|
109
109
|
"rosters": [
|
|
110
110
|
"agent-teams"
|
|
111
111
|
],
|
|
@@ -119,7 +119,10 @@
|
|
|
119
119
|
"maintainers": [
|
|
120
120
|
"STofone"
|
|
121
121
|
],
|
|
122
|
-
"homepage": "https://github.com/dsh-agent-roster/agent-teams"
|
|
122
|
+
"homepage": "https://github.com/dsh-agent-roster/agent-teams",
|
|
123
|
+
"files": {
|
|
124
|
+
"agent-teams": "dist/agent-teams-1.0.0.json"
|
|
125
|
+
}
|
|
123
126
|
},
|
|
124
127
|
{
|
|
125
128
|
"id": "metagpt-company",
|
|
@@ -127,7 +130,7 @@
|
|
|
127
130
|
"description": "MetaGPT(5 万星+,MIT)的软件公司角色:产品经理、架构师、项目经理、工程师,字段取自上游 Role 的 profile/goal/constraints。",
|
|
128
131
|
"repo": "github:dsh-agent-roster/metagpt-company",
|
|
129
132
|
"ref": "main",
|
|
130
|
-
"path": "
|
|
133
|
+
"path": "",
|
|
131
134
|
"rosters": [
|
|
132
135
|
"metagpt-company"
|
|
133
136
|
],
|
|
@@ -140,7 +143,10 @@
|
|
|
140
143
|
"maintainers": [
|
|
141
144
|
"STofone"
|
|
142
145
|
],
|
|
143
|
-
"homepage": "https://github.com/dsh-agent-roster/metagpt-company"
|
|
146
|
+
"homepage": "https://github.com/dsh-agent-roster/metagpt-company",
|
|
147
|
+
"files": {
|
|
148
|
+
"metagpt-company": "dist/metagpt-company-1.0.0.json"
|
|
149
|
+
}
|
|
144
150
|
},
|
|
145
151
|
{
|
|
146
152
|
"id": "openmanus",
|
|
@@ -148,7 +154,7 @@
|
|
|
148
154
|
"description": "OpenManus(5.8 万星,MIT)的三个代理提示词:规划、编码、浏览器,systemPrompt 保留上游原文。",
|
|
149
155
|
"repo": "github:dsh-agent-roster/openmanus",
|
|
150
156
|
"ref": "main",
|
|
151
|
-
"path": "
|
|
157
|
+
"path": "",
|
|
152
158
|
"rosters": [
|
|
153
159
|
"openmanus"
|
|
154
160
|
],
|
|
@@ -162,7 +168,10 @@
|
|
|
162
168
|
"maintainers": [
|
|
163
169
|
"STofone"
|
|
164
170
|
],
|
|
165
|
-
"homepage": "https://github.com/dsh-agent-roster/openmanus"
|
|
171
|
+
"homepage": "https://github.com/dsh-agent-roster/openmanus",
|
|
172
|
+
"files": {
|
|
173
|
+
"openmanus": "dist/openmanus-1.0.0.json"
|
|
174
|
+
}
|
|
166
175
|
}
|
|
167
176
|
]
|
|
168
177
|
}
|
package/lib/migrate.js
CHANGED
|
@@ -206,11 +206,11 @@ function loadCapabilitiesOf(dir) {
|
|
|
206
206
|
*
|
|
207
207
|
* 不写盘时只返回计划;`write: true` 才落盘、编译、写出 `dist/`,并在最后删掉旧的 `<id>/` 目录。
|
|
208
208
|
*/
|
|
209
|
-
export function migrateLibrary(rootDir, { write = false, knownToolPolicies = null, source = {} } = {}) {
|
|
209
|
+
export function migrateLibrary(rootDir, { write = false, knownToolPolicies = null, source = {}, fromDir = rootDir } = {}) {
|
|
210
210
|
const report = { rootDir, write, entries: [], diagnostics: [], written: [], removed: [], compiled: [] };
|
|
211
211
|
let children = [];
|
|
212
212
|
try {
|
|
213
|
-
children = readdirSync(
|
|
213
|
+
children = readdirSync(fromDir, { withFileTypes: true });
|
|
214
214
|
} catch (error) {
|
|
215
215
|
report.diagnostics.push({ severity: "error", code: "unreadable-dir", path: "", file: null, reason: `档案库不可读:${error?.message ?? error}` });
|
|
216
216
|
return report;
|
|
@@ -220,7 +220,7 @@ export function migrateLibrary(rootDir, { write = false, knownToolPolicies = nul
|
|
|
220
220
|
for (const child of children) {
|
|
221
221
|
if (!child.isDirectory() || child.isSymbolicLink()) continue;
|
|
222
222
|
if (child.name.startsWith(".") || child.name === ROLES_DIR || child.name === ROSTERS_DIR || child.name === DIST_DIR) continue;
|
|
223
|
-
const dir = nodePath.join(
|
|
223
|
+
const dir = nodePath.join(fromDir, child.name);
|
|
224
224
|
// v1 的档案文件名可以是 yaml / yml / json——迁移只认内容,不认扩展名。
|
|
225
225
|
if (!MEMBER_FILE_NAMES.some((name) => existsSync(nodePath.join(dir, name)))) continue;
|
|
226
226
|
const loaded = loadRosterAt(dir, { knownToolPolicies });
|
|
@@ -269,7 +269,7 @@ export function migrateLibrary(rootDir, { write = false, knownToolPolicies = nul
|
|
|
269
269
|
report.written.push(file.path);
|
|
270
270
|
}
|
|
271
271
|
for (const name of CAPABILITIES_FILE_NAMES) {
|
|
272
|
-
const source = nodePath.join(
|
|
272
|
+
const source = nodePath.join(fromDir, entry.id, name);
|
|
273
273
|
if (!existsSync(source)) continue;
|
|
274
274
|
const relative = nodePath.join(ROSTERS_DIR, entry.id, name);
|
|
275
275
|
const target = nodePath.join(rootDir, relative);
|
|
@@ -278,6 +278,13 @@ export function migrateLibrary(rootDir, { write = false, knownToolPolicies = nul
|
|
|
278
278
|
report.written.push(relative);
|
|
279
279
|
break;
|
|
280
280
|
}
|
|
281
|
+
// 就地迁移时旧的 roster.json / roster.yml 会和新的 roster.yaml 并排躺着:
|
|
282
|
+
// 两份同义文件就是两个真源,删掉非 .yaml 的旧件。
|
|
283
|
+
for (const name of MEMBER_FILE_NAMES) {
|
|
284
|
+
if (name === "roster.yaml") continue;
|
|
285
|
+
const stale = nodePath.join(rootDir, ROSTERS_DIR, entry.id, name);
|
|
286
|
+
if (existsSync(stale)) rmSync(stale, { force: true });
|
|
287
|
+
}
|
|
281
288
|
}
|
|
282
289
|
|
|
283
290
|
const scanned = scanRoles(nodePath.join(rootDir, ROLES_DIR), { knownToolPolicies });
|
|
@@ -288,7 +295,7 @@ export function migrateLibrary(rootDir, { write = false, knownToolPolicies = nul
|
|
|
288
295
|
roles: scanned.roles,
|
|
289
296
|
expectedId: entry.id,
|
|
290
297
|
knownToolPolicies,
|
|
291
|
-
source: {},
|
|
298
|
+
source: { ...source },
|
|
292
299
|
});
|
|
293
300
|
if (!compiled.ok) {
|
|
294
301
|
report.diagnostics.push({
|
|
@@ -316,7 +323,9 @@ export function migrateLibrary(rootDir, { write = false, knownToolPolicies = nul
|
|
|
316
323
|
}
|
|
317
324
|
|
|
318
325
|
for (const entry of report.entries) {
|
|
319
|
-
const legacy = nodePath.join(
|
|
326
|
+
const legacy = nodePath.join(fromDir, entry.id);
|
|
327
|
+
// 仓库根 + rosters/ 的布局里,v1 的 rosters/<id>/ 正是 v2 新档案落脚处 —— 不能删。
|
|
328
|
+
if (nodePath.resolve(legacy) === nodePath.resolve(nodePath.join(rootDir, ROSTERS_DIR, entry.id))) continue;
|
|
320
329
|
if (!existsSync(legacy) || !statSync(legacy).isDirectory()) continue;
|
|
321
330
|
// 只删「迁移前的那份目录」:v2 布局里档案在 rosters/ 下,root 上不该再有同名目录。
|
|
322
331
|
rmSync(legacy, { recursive: true, force: true });
|
package/lib/remote.js
CHANGED
|
@@ -468,8 +468,9 @@ export function validateRegistry(value, meta = {}) {
|
|
|
468
468
|
bad = true;
|
|
469
469
|
}
|
|
470
470
|
const path = entry.path === undefined ? "rosters" : String(entry.path);
|
|
471
|
-
|
|
472
|
-
|
|
471
|
+
// 空 path 表示「仓库根就是这座库」:v2 的 roles/ + rosters/ + dist/ 直接躺在仓库根上。
|
|
472
|
+
if (path !== "" && !PATTERNS.relPath.test(path)) {
|
|
473
|
+
diagnostics.push(diagnostic("error", "collection-path", `${where}.path 必须是仓库内相对目录(空字符串表示仓库根)`, file));
|
|
473
474
|
bad = true;
|
|
474
475
|
}
|
|
475
476
|
|
package/package.json
CHANGED
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"type": "string",
|
|
72
72
|
"pattern": "^(?:/)?(?:[A-Za-z0-9._-]+/)*[A-Za-z0-9._-]*$",
|
|
73
73
|
"default": "rosters",
|
|
74
|
-
"description": "
|
|
74
|
+
"description": "仓库内存放档案的目录;空字符串表示仓库根(v2 布局的 roles/ + rosters/ + dist/ 就在根上)。"
|
|
75
75
|
},
|
|
76
76
|
"rosters": {
|
|
77
77
|
"type": "array",
|