@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.
- package/CHANGELOG.md +68 -0
- package/README.md +80 -5
- package/README.zh.md +73 -6
- package/bin/cli.mjs +228 -2
- package/docs/roster-v2.md +220 -0
- package/examples/dist/core-review-1.0.0.json +160 -0
- package/examples/dist/research-0.1.0.json +95 -0
- package/examples/roles/core-review-correctness/AGENT.md +20 -0
- package/examples/roles/core-review-lead/AGENT.md +23 -0
- package/examples/roles/core-review-lead-captain/AGENT.md +13 -0
- package/examples/roles/core-review-maintainer/AGENT.md +20 -0
- package/examples/roles/research-scout/AGENT.md +16 -0
- package/examples/roles/research-verifier/AGENT.md +16 -0
- package/examples/rosters/core-review/roster.yaml +39 -0
- package/examples/rosters/research/roster.yaml +20 -0
- package/examples-orchestration/dist/core-review-1.0.0.json +185 -0
- package/examples-orchestration/dist/core-review-1.0.0.tasks.json +76 -0
- package/examples-orchestration/roles/fixer/AGENT.md +18 -0
- package/examples-orchestration/roles/lead/AGENT.md +18 -0
- package/examples-orchestration/roles/reviewer/AGENT.md +23 -0
- package/examples-orchestration/roles/reviewer/parts/checklist.md +6 -0
- package/examples-orchestration/rosters/core-review/overrides/fixer.md +1 -0
- package/examples-orchestration/rosters/core-review/roster.yaml +43 -0
- package/index.js +8 -1
- package/lib/compile2.js +522 -0
- package/lib/format-v2.js +196 -0
- package/lib/frontmatter.js +113 -0
- package/lib/migrate.js +326 -0
- package/lib/plan.js +167 -0
- package/lib/registry.js +65 -11
- package/lib/role.js +498 -0
- package/lib/roster2.js +591 -0
- package/lib/run.js +188 -0
- package/lib/write.js +14 -8
- package/orgs/agent-roster-collective/dist/api-design-1.0.0.json +130 -0
- package/orgs/agent-roster-collective/dist/content-ops-1.0.0.json +130 -0
- package/orgs/agent-roster-collective/dist/contract-review-1.0.0.json +130 -0
- package/orgs/agent-roster-collective/dist/data-quality-1.0.0.json +130 -0
- package/orgs/agent-roster-collective/dist/db-migration-1.0.0.json +130 -0
- package/orgs/agent-roster-collective/dist/docs-writer-1.0.0.json +130 -0
- package/orgs/agent-roster-collective/dist/finance-recon-1.0.0.json +130 -0
- package/orgs/agent-roster-collective/dist/frontend-ui-1.0.0.json +130 -0
- package/orgs/agent-roster-collective/dist/i18n-l10n-1.0.0.json +130 -0
- package/orgs/agent-roster-collective/dist/incident-response-1.0.0.json +130 -0
- package/orgs/agent-roster-collective/dist/literature-review-1.0.0.json +130 -0
- package/orgs/agent-roster-collective/dist/market-research-1.0.0.json +130 -0
- package/orgs/agent-roster-collective/dist/ml-eval-1.0.0.json +130 -0
- package/orgs/agent-roster-collective/dist/performance-1.0.0.json +130 -0
- package/orgs/agent-roster-collective/dist/product-discovery-1.0.0.json +130 -0
- package/orgs/agent-roster-collective/dist/qa-regression-1.0.0.json +130 -0
- package/orgs/agent-roster-collective/dist/recruiting-1.0.0.json +130 -0
- package/orgs/agent-roster-collective/dist/release-manager-1.0.0.json +130 -0
- package/orgs/agent-roster-collective/dist/security-audit-1.0.0.json +132 -0
- package/orgs/agent-roster-collective/dist/sre-oncall-1.0.0.json +130 -0
- package/orgs/agent-roster-collective/dist/support-triage-1.0.0.json +130 -0
- package/orgs/agent-roster-collective/roles/api-design-failure-mode/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/api-design-lead/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/api-design-schema/AGENT.md +20 -0
- package/orgs/agent-roster-collective/roles/content-ops-distributor/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/content-ops-editor/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/content-ops-lead/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/contract-review-clause-reader/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/contract-review-lead/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/contract-review-risk-note/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/data-quality-lead/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/data-quality-profiler/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/data-quality-rule-author/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/db-migration-lead/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/db-migration-migrator/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/db-migration-verifier/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/docs-writer-example-runner/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/docs-writer-lead/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/docs-writer-writer/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/finance-recon-exception/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/finance-recon-lead/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/finance-recon-matcher/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/frontend-ui-a11y-reviewer/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/frontend-ui-implementer/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/frontend-ui-lead/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/i18n-l10n-lead/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/i18n-l10n-reviewer/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/i18n-l10n-translator/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/incident-response-commander/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/incident-response-scribe/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/incident-response-triage/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/literature-review-critic/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/literature-review-extractor/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/literature-review-lead/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/market-research-lead/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/market-research-researcher/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/market-research-synthesizer/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/ml-eval-dataset/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/ml-eval-lead/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/ml-eval-metric/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/performance-lead/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/performance-load-tester/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/performance-profiler/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/product-discovery-interviewer/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/product-discovery-lead/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/product-discovery-skeptic/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/qa-regression-case-writer/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/qa-regression-flake-hunter/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/qa-regression-lead/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/recruiting-lead/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/recruiting-scheduler/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/recruiting-screener/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/release-manager-changelog/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/release-manager-lead/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/release-manager-rollback/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/security-audit-code-auditor/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/security-audit-lead/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/security-audit-threat-modeler/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/sre-oncall-capacity/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/sre-oncall-lead/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/sre-oncall-runbook/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/support-triage-classifier/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/support-triage-lead/AGENT.md +19 -0
- package/orgs/agent-roster-collective/roles/support-triage-responder/AGENT.md +19 -0
- package/orgs/agent-roster-collective/rosters/api-design/roster.yaml +26 -0
- package/orgs/agent-roster-collective/rosters/content-ops/roster.yaml +25 -0
- package/orgs/agent-roster-collective/rosters/contract-review/roster.yaml +25 -0
- package/orgs/agent-roster-collective/rosters/data-quality/roster.yaml +26 -0
- package/orgs/agent-roster-collective/rosters/db-migration/roster.yaml +26 -0
- package/orgs/agent-roster-collective/rosters/docs-writer/roster.yaml +25 -0
- package/orgs/agent-roster-collective/rosters/finance-recon/roster.yaml +25 -0
- package/orgs/agent-roster-collective/rosters/frontend-ui/roster.yaml +26 -0
- package/orgs/agent-roster-collective/rosters/i18n-l10n/roster.yaml +26 -0
- package/orgs/agent-roster-collective/rosters/incident-response/roster.yaml +26 -0
- package/orgs/agent-roster-collective/rosters/literature-review/roster.yaml +25 -0
- package/orgs/agent-roster-collective/rosters/market-research/roster.yaml +25 -0
- package/orgs/agent-roster-collective/rosters/ml-eval/roster.yaml +25 -0
- package/orgs/agent-roster-collective/rosters/performance/roster.yaml +25 -0
- package/orgs/agent-roster-collective/rosters/product-discovery/roster.yaml +25 -0
- package/orgs/agent-roster-collective/rosters/qa-regression/roster.yaml +26 -0
- package/orgs/agent-roster-collective/rosters/recruiting/roster.yaml +25 -0
- package/orgs/agent-roster-collective/rosters/release-manager/roster.yaml +25 -0
- package/orgs/agent-roster-collective/rosters/security-audit/roster.yaml +30 -0
- package/orgs/agent-roster-collective/rosters/sre-oncall/roster.yaml +26 -0
- package/orgs/agent-roster-collective/rosters/support-triage/roster.yaml +25 -0
- package/package.json +3 -1
- package/schema/compiled.v1.schema.json +391 -0
- package/schema/role.v1.schema.json +240 -0
- package/schema/roster.v2.schema.json +437 -0
- package/examples/core-review/roster.yaml +0 -82
- package/examples/research/roster.yaml +0 -42
- package/orgs/agent-roster-collective/api-design/roster.yaml +0 -67
- package/orgs/agent-roster-collective/content-ops/roster.yaml +0 -55
- package/orgs/agent-roster-collective/contract-review/roster.yaml +0 -58
- package/orgs/agent-roster-collective/data-quality/roster.yaml +0 -64
- package/orgs/agent-roster-collective/db-migration/roster.yaml +0 -64
- package/orgs/agent-roster-collective/docs-writer/roster.yaml +0 -59
- package/orgs/agent-roster-collective/finance-recon/roster.yaml +0 -63
- package/orgs/agent-roster-collective/frontend-ui/roster.yaml +0 -60
- package/orgs/agent-roster-collective/i18n-l10n/roster.yaml +0 -60
- package/orgs/agent-roster-collective/incident-response/roster.yaml +0 -65
- package/orgs/agent-roster-collective/literature-review/roster.yaml +0 -61
- package/orgs/agent-roster-collective/market-research/roster.yaml +0 -60
- package/orgs/agent-roster-collective/ml-eval/roster.yaml +0 -63
- package/orgs/agent-roster-collective/performance/roster.yaml +0 -63
- package/orgs/agent-roster-collective/product-discovery/roster.yaml +0 -55
- package/orgs/agent-roster-collective/qa-regression/roster.yaml +0 -64
- package/orgs/agent-roster-collective/recruiting/roster.yaml +0 -58
- package/orgs/agent-roster-collective/release-manager/roster.yaml +0 -59
- package/orgs/agent-roster-collective/security-audit/roster.yaml +0 -65
- package/orgs/agent-roster-collective/sre-oncall/roster.yaml +0 -60
- package/orgs/agent-roster-collective/support-triage/roster.yaml +0 -58
- /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(
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
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
|
}
|