@stofone/dsh-agent-roster 0.9.2 → 0.10.1

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.
Files changed (167) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +80 -5
  3. package/README.zh.md +73 -6
  4. package/bin/cli.mjs +228 -2
  5. package/docs/roster-v2.md +220 -0
  6. package/examples/dist/core-review-1.0.0.json +160 -0
  7. package/examples/dist/research-0.1.0.json +95 -0
  8. package/examples/roles/core-review-correctness/AGENT.md +20 -0
  9. package/examples/roles/core-review-lead/AGENT.md +23 -0
  10. package/examples/roles/core-review-lead-captain/AGENT.md +13 -0
  11. package/examples/roles/core-review-maintainer/AGENT.md +20 -0
  12. package/examples/roles/research-scout/AGENT.md +16 -0
  13. package/examples/roles/research-verifier/AGENT.md +16 -0
  14. package/examples/rosters/core-review/roster.yaml +39 -0
  15. package/examples/rosters/research/roster.yaml +20 -0
  16. package/examples-orchestration/dist/core-review-1.0.0.json +185 -0
  17. package/examples-orchestration/dist/core-review-1.0.0.tasks.json +76 -0
  18. package/examples-orchestration/roles/fixer/AGENT.md +18 -0
  19. package/examples-orchestration/roles/lead/AGENT.md +18 -0
  20. package/examples-orchestration/roles/reviewer/AGENT.md +23 -0
  21. package/examples-orchestration/roles/reviewer/parts/checklist.md +6 -0
  22. package/examples-orchestration/rosters/core-review/overrides/fixer.md +1 -0
  23. package/examples-orchestration/rosters/core-review/roster.yaml +43 -0
  24. package/index.js +8 -1
  25. package/lib/compile2.js +522 -0
  26. package/lib/format-v2.js +196 -0
  27. package/lib/frontmatter.js +113 -0
  28. package/lib/migrate.js +326 -0
  29. package/lib/plan.js +167 -0
  30. package/lib/registry.js +65 -11
  31. package/lib/role.js +498 -0
  32. package/lib/roster2.js +591 -0
  33. package/lib/run.js +188 -0
  34. package/lib/write.js +14 -8
  35. package/orgs/agent-roster-collective/dist/api-design-1.0.0.json +130 -0
  36. package/orgs/agent-roster-collective/dist/content-ops-1.0.0.json +130 -0
  37. package/orgs/agent-roster-collective/dist/contract-review-1.0.0.json +130 -0
  38. package/orgs/agent-roster-collective/dist/data-quality-1.0.0.json +130 -0
  39. package/orgs/agent-roster-collective/dist/db-migration-1.0.0.json +130 -0
  40. package/orgs/agent-roster-collective/dist/docs-writer-1.0.0.json +130 -0
  41. package/orgs/agent-roster-collective/dist/finance-recon-1.0.0.json +130 -0
  42. package/orgs/agent-roster-collective/dist/frontend-ui-1.0.0.json +130 -0
  43. package/orgs/agent-roster-collective/dist/i18n-l10n-1.0.0.json +130 -0
  44. package/orgs/agent-roster-collective/dist/incident-response-1.0.0.json +130 -0
  45. package/orgs/agent-roster-collective/dist/literature-review-1.0.0.json +130 -0
  46. package/orgs/agent-roster-collective/dist/market-research-1.0.0.json +130 -0
  47. package/orgs/agent-roster-collective/dist/ml-eval-1.0.0.json +130 -0
  48. package/orgs/agent-roster-collective/dist/performance-1.0.0.json +130 -0
  49. package/orgs/agent-roster-collective/dist/product-discovery-1.0.0.json +130 -0
  50. package/orgs/agent-roster-collective/dist/qa-regression-1.0.0.json +130 -0
  51. package/orgs/agent-roster-collective/dist/recruiting-1.0.0.json +130 -0
  52. package/orgs/agent-roster-collective/dist/release-manager-1.0.0.json +130 -0
  53. package/orgs/agent-roster-collective/dist/security-audit-1.0.0.json +132 -0
  54. package/orgs/agent-roster-collective/dist/sre-oncall-1.0.0.json +130 -0
  55. package/orgs/agent-roster-collective/dist/support-triage-1.0.0.json +130 -0
  56. package/orgs/agent-roster-collective/roles/api-design-failure-mode/AGENT.md +19 -0
  57. package/orgs/agent-roster-collective/roles/api-design-lead/AGENT.md +19 -0
  58. package/orgs/agent-roster-collective/roles/api-design-schema/AGENT.md +20 -0
  59. package/orgs/agent-roster-collective/roles/content-ops-distributor/AGENT.md +19 -0
  60. package/orgs/agent-roster-collective/roles/content-ops-editor/AGENT.md +19 -0
  61. package/orgs/agent-roster-collective/roles/content-ops-lead/AGENT.md +19 -0
  62. package/orgs/agent-roster-collective/roles/contract-review-clause-reader/AGENT.md +19 -0
  63. package/orgs/agent-roster-collective/roles/contract-review-lead/AGENT.md +19 -0
  64. package/orgs/agent-roster-collective/roles/contract-review-risk-note/AGENT.md +19 -0
  65. package/orgs/agent-roster-collective/roles/data-quality-lead/AGENT.md +19 -0
  66. package/orgs/agent-roster-collective/roles/data-quality-profiler/AGENT.md +19 -0
  67. package/orgs/agent-roster-collective/roles/data-quality-rule-author/AGENT.md +19 -0
  68. package/orgs/agent-roster-collective/roles/db-migration-lead/AGENT.md +19 -0
  69. package/orgs/agent-roster-collective/roles/db-migration-migrator/AGENT.md +19 -0
  70. package/orgs/agent-roster-collective/roles/db-migration-verifier/AGENT.md +19 -0
  71. package/orgs/agent-roster-collective/roles/docs-writer-example-runner/AGENT.md +19 -0
  72. package/orgs/agent-roster-collective/roles/docs-writer-lead/AGENT.md +19 -0
  73. package/orgs/agent-roster-collective/roles/docs-writer-writer/AGENT.md +19 -0
  74. package/orgs/agent-roster-collective/roles/finance-recon-exception/AGENT.md +19 -0
  75. package/orgs/agent-roster-collective/roles/finance-recon-lead/AGENT.md +19 -0
  76. package/orgs/agent-roster-collective/roles/finance-recon-matcher/AGENT.md +19 -0
  77. package/orgs/agent-roster-collective/roles/frontend-ui-a11y-reviewer/AGENT.md +19 -0
  78. package/orgs/agent-roster-collective/roles/frontend-ui-implementer/AGENT.md +19 -0
  79. package/orgs/agent-roster-collective/roles/frontend-ui-lead/AGENT.md +19 -0
  80. package/orgs/agent-roster-collective/roles/i18n-l10n-lead/AGENT.md +19 -0
  81. package/orgs/agent-roster-collective/roles/i18n-l10n-reviewer/AGENT.md +19 -0
  82. package/orgs/agent-roster-collective/roles/i18n-l10n-translator/AGENT.md +19 -0
  83. package/orgs/agent-roster-collective/roles/incident-response-commander/AGENT.md +19 -0
  84. package/orgs/agent-roster-collective/roles/incident-response-scribe/AGENT.md +19 -0
  85. package/orgs/agent-roster-collective/roles/incident-response-triage/AGENT.md +19 -0
  86. package/orgs/agent-roster-collective/roles/literature-review-critic/AGENT.md +19 -0
  87. package/orgs/agent-roster-collective/roles/literature-review-extractor/AGENT.md +19 -0
  88. package/orgs/agent-roster-collective/roles/literature-review-lead/AGENT.md +19 -0
  89. package/orgs/agent-roster-collective/roles/market-research-lead/AGENT.md +19 -0
  90. package/orgs/agent-roster-collective/roles/market-research-researcher/AGENT.md +19 -0
  91. package/orgs/agent-roster-collective/roles/market-research-synthesizer/AGENT.md +19 -0
  92. package/orgs/agent-roster-collective/roles/ml-eval-dataset/AGENT.md +19 -0
  93. package/orgs/agent-roster-collective/roles/ml-eval-lead/AGENT.md +19 -0
  94. package/orgs/agent-roster-collective/roles/ml-eval-metric/AGENT.md +19 -0
  95. package/orgs/agent-roster-collective/roles/performance-lead/AGENT.md +19 -0
  96. package/orgs/agent-roster-collective/roles/performance-load-tester/AGENT.md +19 -0
  97. package/orgs/agent-roster-collective/roles/performance-profiler/AGENT.md +19 -0
  98. package/orgs/agent-roster-collective/roles/product-discovery-interviewer/AGENT.md +19 -0
  99. package/orgs/agent-roster-collective/roles/product-discovery-lead/AGENT.md +19 -0
  100. package/orgs/agent-roster-collective/roles/product-discovery-skeptic/AGENT.md +19 -0
  101. package/orgs/agent-roster-collective/roles/qa-regression-case-writer/AGENT.md +19 -0
  102. package/orgs/agent-roster-collective/roles/qa-regression-flake-hunter/AGENT.md +19 -0
  103. package/orgs/agent-roster-collective/roles/qa-regression-lead/AGENT.md +19 -0
  104. package/orgs/agent-roster-collective/roles/recruiting-lead/AGENT.md +19 -0
  105. package/orgs/agent-roster-collective/roles/recruiting-scheduler/AGENT.md +19 -0
  106. package/orgs/agent-roster-collective/roles/recruiting-screener/AGENT.md +19 -0
  107. package/orgs/agent-roster-collective/roles/release-manager-changelog/AGENT.md +19 -0
  108. package/orgs/agent-roster-collective/roles/release-manager-lead/AGENT.md +19 -0
  109. package/orgs/agent-roster-collective/roles/release-manager-rollback/AGENT.md +19 -0
  110. package/orgs/agent-roster-collective/roles/security-audit-code-auditor/AGENT.md +19 -0
  111. package/orgs/agent-roster-collective/roles/security-audit-lead/AGENT.md +19 -0
  112. package/orgs/agent-roster-collective/roles/security-audit-threat-modeler/AGENT.md +19 -0
  113. package/orgs/agent-roster-collective/roles/sre-oncall-capacity/AGENT.md +19 -0
  114. package/orgs/agent-roster-collective/roles/sre-oncall-lead/AGENT.md +19 -0
  115. package/orgs/agent-roster-collective/roles/sre-oncall-runbook/AGENT.md +19 -0
  116. package/orgs/agent-roster-collective/roles/support-triage-classifier/AGENT.md +19 -0
  117. package/orgs/agent-roster-collective/roles/support-triage-lead/AGENT.md +19 -0
  118. package/orgs/agent-roster-collective/roles/support-triage-responder/AGENT.md +19 -0
  119. package/orgs/agent-roster-collective/rosters/api-design/roster.yaml +26 -0
  120. package/orgs/agent-roster-collective/rosters/content-ops/roster.yaml +25 -0
  121. package/orgs/agent-roster-collective/rosters/contract-review/roster.yaml +25 -0
  122. package/orgs/agent-roster-collective/rosters/data-quality/roster.yaml +26 -0
  123. package/orgs/agent-roster-collective/rosters/db-migration/roster.yaml +26 -0
  124. package/orgs/agent-roster-collective/rosters/docs-writer/roster.yaml +25 -0
  125. package/orgs/agent-roster-collective/rosters/finance-recon/roster.yaml +25 -0
  126. package/orgs/agent-roster-collective/rosters/frontend-ui/roster.yaml +26 -0
  127. package/orgs/agent-roster-collective/rosters/i18n-l10n/roster.yaml +26 -0
  128. package/orgs/agent-roster-collective/rosters/incident-response/roster.yaml +26 -0
  129. package/orgs/agent-roster-collective/rosters/literature-review/roster.yaml +25 -0
  130. package/orgs/agent-roster-collective/rosters/market-research/roster.yaml +25 -0
  131. package/orgs/agent-roster-collective/rosters/ml-eval/roster.yaml +25 -0
  132. package/orgs/agent-roster-collective/rosters/performance/roster.yaml +25 -0
  133. package/orgs/agent-roster-collective/rosters/product-discovery/roster.yaml +25 -0
  134. package/orgs/agent-roster-collective/rosters/qa-regression/roster.yaml +26 -0
  135. package/orgs/agent-roster-collective/rosters/recruiting/roster.yaml +25 -0
  136. package/orgs/agent-roster-collective/rosters/release-manager/roster.yaml +25 -0
  137. package/orgs/agent-roster-collective/rosters/security-audit/roster.yaml +30 -0
  138. package/orgs/agent-roster-collective/rosters/sre-oncall/roster.yaml +26 -0
  139. package/orgs/agent-roster-collective/rosters/support-triage/roster.yaml +25 -0
  140. package/package.json +3 -1
  141. package/schema/compiled.v1.schema.json +391 -0
  142. package/schema/role.v1.schema.json +240 -0
  143. package/schema/roster.v2.schema.json +437 -0
  144. package/examples/core-review/roster.yaml +0 -82
  145. package/examples/research/roster.yaml +0 -42
  146. package/orgs/agent-roster-collective/api-design/roster.yaml +0 -67
  147. package/orgs/agent-roster-collective/content-ops/roster.yaml +0 -55
  148. package/orgs/agent-roster-collective/contract-review/roster.yaml +0 -58
  149. package/orgs/agent-roster-collective/data-quality/roster.yaml +0 -64
  150. package/orgs/agent-roster-collective/db-migration/roster.yaml +0 -64
  151. package/orgs/agent-roster-collective/docs-writer/roster.yaml +0 -59
  152. package/orgs/agent-roster-collective/finance-recon/roster.yaml +0 -63
  153. package/orgs/agent-roster-collective/frontend-ui/roster.yaml +0 -60
  154. package/orgs/agent-roster-collective/i18n-l10n/roster.yaml +0 -60
  155. package/orgs/agent-roster-collective/incident-response/roster.yaml +0 -65
  156. package/orgs/agent-roster-collective/literature-review/roster.yaml +0 -61
  157. package/orgs/agent-roster-collective/market-research/roster.yaml +0 -60
  158. package/orgs/agent-roster-collective/ml-eval/roster.yaml +0 -63
  159. package/orgs/agent-roster-collective/performance/roster.yaml +0 -63
  160. package/orgs/agent-roster-collective/product-discovery/roster.yaml +0 -55
  161. package/orgs/agent-roster-collective/qa-regression/roster.yaml +0 -64
  162. package/orgs/agent-roster-collective/recruiting/roster.yaml +0 -58
  163. package/orgs/agent-roster-collective/release-manager/roster.yaml +0 -59
  164. package/orgs/agent-roster-collective/security-audit/roster.yaml +0 -65
  165. package/orgs/agent-roster-collective/sre-oncall/roster.yaml +0 -60
  166. package/orgs/agent-roster-collective/support-triage/roster.yaml +0 -58
  167. /package/orgs/agent-roster-collective/{security-audit → rosters/security-audit}/capabilities.yaml +0 -0
package/lib/plan.js ADDED
@@ -0,0 +1,167 @@
1
+ /**
2
+ * 施工图:把 `orchestration` 摊成一份卡片清单。
3
+ *
4
+ * 这份文件是「声明」与「执行」之间那条缝上的东西,所以它必须说清三件事:
5
+ *
6
+ * 1. **谁执行** —— `executor: "captain"`。插件不调用 `agent_teams_create_task`:
7
+ * 写团队状态的能力只属于队长会话,越界一次,这个仓库所有「只声明不承载」的
8
+ * 承诺就全部作废。
9
+ * 2. **谁判门** —— 门的每条 `verify` 都要能被跑:命令 + 期望退出码。没有判据的门
10
+ * 在 `lib/roster2.js` 里就已经被拒了,这里只负责原样传下去。
11
+ * 3. **谁重试** —— `loop.onGateFailure`:门不过就回到第一个阶段,最多 `maxRounds`
12
+ * 轮;每轮的失败意见由执行者写进 `runRecord` 指向的文件,下一次编译读它。
13
+ *
14
+ * 这里刻意不生成「任务 id」以外的任何运行时观念:卡片真正的 id 由团队分配,
15
+ * 本文件里的 id 只在施工图内部表达依赖。
16
+ *
17
+ * @module lib/plan
18
+ */
19
+ import { PLAN_KIND } from "./format-v2.js";
20
+
21
+ /** 施工图里一条依赖最多指向几张卡(防御性上限,正常远小于它)。 */
22
+ const MAX_DEPENDENCIES = 32;
23
+
24
+ /**
25
+ * 编译施工图。
26
+ *
27
+ * @param {object} compiled 编译产物(`compileRoster` 的返回值)
28
+ * @returns {{ok: boolean, plan: object|null, diagnostics: object[]}}
29
+ */
30
+ export function compilePlan(compiled) {
31
+ const diagnostics = [];
32
+ const orchestration = compiled.orchestration;
33
+ if (orchestration === null) {
34
+ return { ok: true, plan: null, diagnostics };
35
+ }
36
+
37
+ const rosterTitle = compiled.source.title ?? compiled.source.roster;
38
+ const roleTitles = new Map(compiled.members.map((member) => [member.name, member.role]));
39
+ const tasks = [];
40
+ const stageTasks = new Map();
41
+ let previous = [];
42
+
43
+ for (const stage of orchestration.stages) {
44
+ const members = [...stage.serial, ...stage.parallel];
45
+ if (members.length === 0) {
46
+ diagnostics.push({
47
+ severity: "error",
48
+ code: "empty-stage",
49
+ path: `orchestration.stages.${stage.id}`,
50
+ reason: `阶段 ${stage.id} 没有指定成员`,
51
+ });
52
+ continue;
53
+ }
54
+ const ids = members.map((member) => {
55
+ const id = members.length === 1 ? stage.id : `${stage.id}.${member}`;
56
+ const role = roleTitles.get(member) ?? member;
57
+ tasks.push({
58
+ id,
59
+ kind: "stage",
60
+ stage: stage.id,
61
+ member,
62
+ subject: `${rosterTitle} · ${stage.id} · ${member}`,
63
+ description: stageDescription({ stage, member, role, previous, stageTasks }),
64
+ dependencies: previous.slice(0, MAX_DEPENDENCIES),
65
+ gate: null,
66
+ });
67
+ return id;
68
+ });
69
+ stageTasks.set(stage.id, ids);
70
+ previous = ids;
71
+ }
72
+
73
+ for (const gate of orchestration.gates) {
74
+ const dependencies = gate.after.flatMap((stageId) => stageTasks.get(stageId) ?? []);
75
+ const problem = dependencies.length === 0;
76
+ if (problem) {
77
+ diagnostics.push({
78
+ severity: "error",
79
+ code: "gate-without-stage",
80
+ path: `orchestration.gates.${gate.id}.after`,
81
+ reason: `门 ${gate.id} 等待的阶段没有任何卡片:${gate.after.join(", ")}`,
82
+ });
83
+ }
84
+ tasks.push({
85
+ id: gate.id,
86
+ kind: "gate",
87
+ stage: null,
88
+ member: gate.by,
89
+ subject: `${rosterTitle} · 判定 · ${gate.id}`,
90
+ description: gateDescription(gate),
91
+ dependencies: dependencies.slice(0, MAX_DEPENDENCIES),
92
+ gate: {
93
+ id: gate.id,
94
+ requires: gate.requires.slice(),
95
+ verify: gate.verify.map((item) => ({ command: item.command, expectExit: item.expectExit })),
96
+ by: gate.by,
97
+ },
98
+ });
99
+ }
100
+
101
+ const rounds = orchestration.stages.reduce((max, stage) => Math.max(max, stage.maxRounds), 1);
102
+ const first = tasks.find((task) => task.kind === "stage") ?? null;
103
+ const plan = {
104
+ kind: PLAN_KIND,
105
+ roster: { id: compiled.source.roster, version: compiled.source.version },
106
+ shape: orchestration.shape,
107
+ executor: "captain",
108
+ tasks,
109
+ loop: {
110
+ onGateFailure: {
111
+ retry: first === null ? null : first.id,
112
+ maxRounds: rounds,
113
+ },
114
+ runRecord: runRecordName(compiled.source.roster, compiled.source.version),
115
+ },
116
+ notes: [
117
+ "插件不调用任何 agent_teams_* 工具:这份文件是给队长会话的施工图。",
118
+ "卡片真正的 id 由团队分配;这里的 id 只在施工图内部表达依赖。",
119
+ "门必须先跑 verify 里的命令,全部满足期望退出码才算通过;requires 是给人读的补充判据,不能替代 verify。",
120
+ "失败意见写进 runRecord 指向的文件,下一次编译会把它带进重试卡。",
121
+ ],
122
+ };
123
+
124
+ if (orchestration.status === "declaration") {
125
+ plan.notes.push("orchestration.status = declaration:施工图描述的是「打算怎么跑」,不是「已经跑过」。");
126
+ }
127
+ return { ok: diagnostics.every((item) => item.severity !== "error"), plan, diagnostics };
128
+ }
129
+
130
+ /** 运行记录文件名:与产物同目录,避免第二处命名规则。 */
131
+ export function runRecordName(rosterId, version) {
132
+ return `dist/${rosterId}-${version}.run.json`;
133
+ }
134
+
135
+ function stageDescription({ stage, member, role, previous, stageTasks }) {
136
+ const lines = [`阶段 ${stage.id}:${member}(${role})负责。`];
137
+ if (previous.length === 0) {
138
+ lines.push("这是第一个阶段,没有前置卡片。");
139
+ } else {
140
+ const names = [...stageTasks.entries()]
141
+ .filter(([, ids]) => ids.some((id) => previous.includes(id)))
142
+ .map(([id]) => id);
143
+ lines.push(`前置:${names.length > 0 ? names.join("、") : previous.join("、")}。`);
144
+ }
145
+ if (stage.maxRounds > 1) {
146
+ lines.push(`允许返工 ${stage.maxRounds} 轮。`);
147
+ }
148
+ if (stage.join !== null) {
149
+ lines.push(`汇合到:${stage.join}。`);
150
+ }
151
+ lines.push("完成后把结果写进任务输出;不要自行判定门。");
152
+ return lines.join("\n");
153
+ }
154
+
155
+ function gateDescription(gate) {
156
+ const lines = [`门 ${gate.id}:由 ${gate.by} 判定,必须先跑完下面的命令。`];
157
+ for (const item of gate.verify) {
158
+ lines.push(` $ ${item.command}`);
159
+ lines.push(` 期望退出码 ${item.expectExit}`);
160
+ }
161
+ if (gate.requires.length > 0) {
162
+ lines.push("此外还要满足(人读判据,不能替代上面的命令):");
163
+ for (const item of gate.requires) lines.push(` - ${item}`);
164
+ }
165
+ lines.push(`判定结论写进任务输出;不通过时在 ${gate.by} 的结论里写清哪条命令失败、失败输出前 20 行。`);
166
+ return lines.join("\n");
167
+ }
package/lib/registry.js CHANGED
@@ -25,6 +25,7 @@ import { readFileSync, readdirSync, statSync } from "node:fs";
25
25
  import nodePath from "node:path";
26
26
 
27
27
  import { CAPABILITIES_FILE_NAMES, validateCapabilities } from "./capabilities.js";
28
+ import { compileRosterAt, rosterViewOf, scanRoles } from "./compile2.js";
28
29
  import { sha256Hex } from "./digest.js";
29
30
  import { MEMBER_FILE_NAMES } from "./format.js";
30
31
  import {
@@ -36,6 +37,24 @@ import {
36
37
  import { parseDocumentText } from "./parse.js";
37
38
  import { validateRoster } from "./validate.js";
38
39
 
40
+ /** 目录能否读成目录;不存在或不是目录都返回 false。 */
41
+ function isDirectory(path) {
42
+ try {
43
+ return statSync(path).isDirectory();
44
+ } catch {
45
+ return false;
46
+ }
47
+ }
48
+
49
+ /** 列出目录项;读不到就返回 null,交给调用方记 `missing`。 */
50
+ function listDirectory(path) {
51
+ try {
52
+ return readdirSync(path, { withFileTypes: true });
53
+ } catch {
54
+ return null;
55
+ }
56
+ }
57
+
39
58
  /** 与档案无关、必须跳过的目录名(依赖、隐藏状态、虚拟环境)。 */
40
59
  const SKIP_DIRS = new Set(["node_modules", ".git", ".agent-teams", ".dsh", ".venv", "__pycache__"]);
41
60
 
@@ -84,6 +103,19 @@ export function scanRosterLibrary({ rootDir, knownToolPolicies = null }) {
84
103
  childDirs = null;
85
104
  }
86
105
 
106
+ // 两种布局并存:v1 是「库里每个目录就是一份档案」,v2 是「库里 roles/ + rosters/ + dist/」。
107
+ // 判别方式只看结构,不看版本字段——v1 库里不可能同时有 roles/ 与 rosters/。
108
+ const rostersRoot = nodePath.join(rootDir, "rosters");
109
+ const rolesRoot = nodePath.join(rootDir, "roles");
110
+ const v2 = isDirectory(rostersRoot) && isDirectory(rolesRoot);
111
+ let roleScan = null;
112
+ if (v2) {
113
+ roleScan = scanRoles(rolesRoot, { knownToolPolicies });
114
+ diagnostics.push(...roleScan.diagnostics);
115
+ childDirs = listDirectory(rostersRoot);
116
+ }
117
+ const dirRoot = v2 ? rostersRoot : rootDir;
118
+
87
119
  // 目录不存在只记 missing 标记:首次保存会自动建,空库不该产出 warn/info 噪音。
88
120
  if (childDirs !== null) {
89
121
  childDirs.sort((left, right) => left.name.localeCompare(right.name));
@@ -106,8 +138,8 @@ export function scanRosterLibrary({ rootDir, knownToolPolicies = null }) {
106
138
  continue;
107
139
  }
108
140
 
109
- const dirPath = nodePath.join(rootDir, child.name);
110
- const found = locateDocument(dirPath, child.name, diagnostics);
141
+ const dirPath = nodePath.join(dirRoot, child.name);
142
+ const found = locateDocument(dirPath, child.name, diagnostics, v2 ? "rosters/" : "");
111
143
  if (found === null) {
112
144
  stats.skippedDirs += 1;
113
145
  continue;
@@ -123,14 +155,36 @@ export function scanRosterLibrary({ rootDir, knownToolPolicies = null }) {
123
155
  capabilities[child.name] = wanted.capabilities;
124
156
  capabilityFiles[child.name] = wanted.file;
125
157
  }
126
- const outcome = loadDocument(found, child.name, { knownToolPolicies });
127
- diagnostics.push(...outcome.diagnostics);
128
- if (outcome.ok) {
129
- entry.roster = outcome.roster;
130
- rosters.push(outcome.roster);
131
- stats.ok += 1;
158
+ if (v2) {
159
+ const compiledOutcome = compileRosterAt(dirPath, {
160
+ rolesDir: rolesRoot,
161
+ expectedId: child.name,
162
+ knownToolPolicies,
163
+ source: { file: found.relative },
164
+ });
165
+ // 角色扫描的诊断已经在库级报过一次,这里只留这份档案自己的。
166
+ const own = compiledOutcome.diagnostics.filter((item) => item.file !== null && !String(item.file).startsWith("roles/"));
167
+ diagnostics.push(...own);
168
+ if (compiledOutcome.ok) {
169
+ const view = rosterViewOf(compiledOutcome.compiled, compiledOutcome.roster, { roles: roleScan?.roles });
170
+ view.rolesDir = "roles";
171
+ view.compiled = compiledOutcome.compiled;
172
+ entry.roster = view;
173
+ rosters.push(view);
174
+ stats.ok += 1;
175
+ } else {
176
+ stats.failed += 1;
177
+ }
132
178
  } else {
133
- stats.failed += 1;
179
+ const outcome = loadDocument(found, child.name, { knownToolPolicies });
180
+ diagnostics.push(...outcome.diagnostics);
181
+ if (outcome.ok) {
182
+ entry.roster = outcome.roster;
183
+ rosters.push(outcome.roster);
184
+ stats.ok += 1;
185
+ } else {
186
+ stats.failed += 1;
187
+ }
134
188
  }
135
189
  entries.push(entry);
136
190
  }
@@ -213,7 +267,7 @@ export function scanRosterLibrary({ rootDir, knownToolPolicies = null }) {
213
267
  * @param {object[]} diagnostics
214
268
  * @returns {{ absolute: string, relative: string, format: string } | null}
215
269
  */
216
- function locateDocument(dirPath, dirName, diagnostics) {
270
+ function locateDocument(dirPath, dirName, diagnostics, prefix = "") {
217
271
  let names;
218
272
  try {
219
273
  names = readdirSync(dirPath);
@@ -254,7 +308,7 @@ function locateDocument(dirPath, dirName, diagnostics) {
254
308
  }
255
309
  return {
256
310
  absolute: nodePath.join(dirPath, present[0]),
257
- relative: `${dirName}/${present[0]}`,
311
+ relative: `${prefix}${dirName}/${present[0]}`,
258
312
  format: present[0].endsWith(".json") ? "json" : "yaml",
259
313
  };
260
314
  }