create-yss-spec 2.2.1 → 2.2.3
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/README.md +2 -2
- package/package.json +1 -1
- package/src/cli.js +56 -8
- package/template/.agents/skills/yss-product-lifecycle/SKILL.md +5 -5
- package/template/.agents/skills/yss-product-lifecycle/references/matt-yss-adapter.md +1 -1
- package/template/.agents/skills/yss-product-lifecycle/references/orchestration.md +3 -3
- package/template/.agents/skills/yss-product-lifecycle/references/state-model.md +3 -3
- package/template/.agents/skills/yss-router/SKILL.md +1 -1
- package/template/.agents/skills/yss-router/references/slice-implementation-contract.md +1 -0
- package/template/.claude/skills/yss-product-lifecycle/SKILL.md +5 -5
- package/template/.claude/skills/yss-product-lifecycle/references/matt-yss-adapter.md +1 -1
- package/template/.claude/skills/yss-product-lifecycle/references/orchestration.md +3 -3
- package/template/.claude/skills/yss-product-lifecycle/references/state-model.md +3 -3
- package/template/.claude/skills/yss-router/SKILL.md +1 -1
- package/template/.claude/skills/yss-router/references/slice-implementation-contract.md +1 -0
- package/template/.codex/skills/yss-product-lifecycle/SKILL.md +5 -5
- package/template/.codex/skills/yss-product-lifecycle/references/matt-yss-adapter.md +1 -1
- package/template/.codex/skills/yss-product-lifecycle/references/orchestration.md +3 -3
- package/template/.codex/skills/yss-product-lifecycle/references/state-model.md +3 -3
- package/template/.codex/skills/yss-router/SKILL.md +1 -1
- package/template/.codex/skills/yss-router/references/slice-implementation-contract.md +1 -0
- package/template/.cursor/skills/yss-product-lifecycle/SKILL.md +5 -5
- package/template/.cursor/skills/yss-product-lifecycle/references/matt-yss-adapter.md +1 -1
- package/template/.cursor/skills/yss-product-lifecycle/references/orchestration.md +3 -3
- package/template/.cursor/skills/yss-product-lifecycle/references/state-model.md +3 -3
- package/template/.cursor/skills/yss-router/SKILL.md +1 -1
- package/template/.cursor/skills/yss-router/references/slice-implementation-contract.md +1 -0
- package/template/.hermes/skills/yss-product-lifecycle/SKILL.md +5 -5
- package/template/.hermes/skills/yss-product-lifecycle/references/matt-yss-adapter.md +1 -1
- package/template/.hermes/skills/yss-product-lifecycle/references/orchestration.md +3 -3
- package/template/.hermes/skills/yss-product-lifecycle/references/state-model.md +3 -3
- package/template/.hermes/skills/yss-router/SKILL.md +1 -1
- package/template/.hermes/skills/yss-router/references/slice-implementation-contract.md +1 -0
- package/template/.pi/skills/yss-product-lifecycle/SKILL.md +5 -5
- package/template/.pi/skills/yss-product-lifecycle/references/matt-yss-adapter.md +1 -1
- package/template/.pi/skills/yss-product-lifecycle/references/orchestration.md +3 -3
- package/template/.pi/skills/yss-product-lifecycle/references/state-model.md +3 -3
- package/template/.pi/skills/yss-router/SKILL.md +1 -1
- package/template/.pi/skills/yss-router/references/slice-implementation-contract.md +1 -0
- package/template/.qoder/skills/yss-product-lifecycle/SKILL.md +5 -5
- package/template/.qoder/skills/yss-product-lifecycle/references/matt-yss-adapter.md +1 -1
- package/template/.qoder/skills/yss-product-lifecycle/references/orchestration.md +3 -3
- package/template/.qoder/skills/yss-product-lifecycle/references/state-model.md +3 -3
- package/template/.qoder/skills/yss-router/SKILL.md +1 -1
- package/template/.qoder/skills/yss-router/references/slice-implementation-contract.md +1 -0
- package/template/.trae/skills/yss-product-lifecycle/SKILL.md +5 -5
- package/template/.trae/skills/yss-product-lifecycle/references/matt-yss-adapter.md +1 -1
- package/template/.trae/skills/yss-product-lifecycle/references/orchestration.md +3 -3
- package/template/.trae/skills/yss-product-lifecycle/references/state-model.md +3 -3
- package/template/.trae/skills/yss-router/SKILL.md +1 -1
- package/template/.trae/skills/yss-router/references/slice-implementation-contract.md +1 -0
- package/template/AGENTS.md +7 -16
- package/template/CONTEXT.md +14 -2
- package/template/docs/adr/README.md +7 -0
- package/template/docs/agents/digital-human-roles.md +83 -0
- package/template/docs/agents/digital-human-roles.yaml +253 -0
- package/template/docs/agents/skills-maintenance.md +1 -1
- package/template/docs/agents/triage-labels.md +5 -5
- package/template/docs/process/implementation-repo-integration.md +1 -1
- package/template/docs/process/schemas/lifecycle-checkpoint.schema.json +2 -1
- package/template/docs/process/subagent-collaboration.md +5 -3
- package/template/docs/process/templates/stage-checkpoint-template.md +3 -1
- package/template/docs/templates/approval-record-template.yaml +14 -0
- package/template/docs/templates/digital-human-runtime-profile-template.md +38 -0
- package/template/docs/templates/grok-bot-profile-template.md +24 -0
- package/template/docs/templates/implementation-routing-template.md +3 -3
- package/template/docs/templates/local-parent-ticket-template.md +8 -0
- package/template/docs/templates/subagent-task-package-template.md +4 -0
- package/template/docs/templates/vertical-slice-ticket-template.md +7 -0
- package/template/docs/user-guide//344/272/247/345/223/201/347/240/224/345/217/221/345/205/250/347/224/237/345/221/275/345/221/250/346/234/237/346/234/200/344/275/263/345/256/236/350/267/265.md +2 -0
- package/template/scripts/lib/approval-record.mjs +123 -0
- package/template/scripts/lib/digital-human-roles.mjs +398 -0
- package/template/scripts/verify-approval-record +13 -0
- package/template/scripts/verify-digital-human-roles +10 -0
- package/template/scripts/verify-digital-human-roles-scenarios +147 -0
- package/template/scripts/verify-governance-release +7 -5
- package/template/scripts/verify-lifecycle-checkpoint +2 -0
- package/template/scripts/verify-template +5 -2
- package/template/skills-lock.json +2 -2
- package/template.manifest.json +30 -7
- package/template.snapshot.json +5 -4
- package/template/docs/adr/0001-product-design-prototype-entrypoint.md +0 -54
- package/template/docs/adr/0002-yss-project-repository-mode.md +0 -3
- package/template/docs/adr/0003-machine-readable-lifecycle-registry.md +0 -5
- package/template/docs/adr/0006-stable-lifecycle-ids-and-generated-structure.md +0 -5
- package/template/docs/adr/0007-lifecycle-native-work-units.md +0 -5
|
@@ -46,6 +46,14 @@ Status: ready-for-human
|
|
|
46
46
|
- Verification:`docs/.scratch/<feature>/verification/`
|
|
47
47
|
- Vertical slices:`docs/.scratch/<feature>/issues/`
|
|
48
48
|
|
|
49
|
+
## 会签
|
|
50
|
+
|
|
51
|
+
会签记录写在 `docs/.scratch/<feature>/gates/<gate-id>-approval.yaml`。会签桶内门禁标为 `approved` 前必须通过 `scripts/verify-approval-record`。
|
|
52
|
+
|
|
53
|
+
| 门禁 | 记录路径 | 会签角色 | 状态 |
|
|
54
|
+
|---|---|---|---|
|
|
55
|
+
| | `docs/.scratch/<feature>/gates/<gate-id>-approval.yaml` | 见 `docs/agents/digital-human-roles.yaml` | pending / approved / blocked / not-applicable |
|
|
56
|
+
|
|
49
57
|
## 阻塞关系
|
|
50
58
|
|
|
51
59
|
- 无 / 被 `<ticket-path>` 阻塞
|
|
@@ -17,6 +17,10 @@ owner: ai
|
|
|
17
17
|
| 对应门禁 | |
|
|
18
18
|
| Ticket / MR / PR | |
|
|
19
19
|
| 主控 Agent | |
|
|
20
|
+
| 数字人角色 | `role.` 见 `docs/agents/digital-human-roles.yaml` |
|
|
21
|
+
| runtime_id | `runtime.generic` / `runtime.skill-projection` / `runtime.grok` |
|
|
22
|
+
| core_skills | 从角色表复制,禁止手写 |
|
|
23
|
+
| forbidden_skills | 从角色表复制,禁止手写 |
|
|
20
24
|
| subagent 角色 | Explorer / Drafter / Worker / Reviewer / Verifier |
|
|
21
25
|
| 任务类型 | explore / draft / work / review / verify |
|
|
22
26
|
|
|
@@ -53,6 +53,7 @@ Status: ready-for-human
|
|
|
53
53
|
| contract_ref | |
|
|
54
54
|
| Router 状态 | draft / blocked / ready-for-lifecycle-review |
|
|
55
55
|
| 生命周期批准状态 | pending / approved / rejected |
|
|
56
|
+
| suggested_owner_role_id | `role.frontend-engineer` / `role.backend-engineer` / `role.test-engineer`(Router 建议,编排器派活) |
|
|
56
57
|
| Build Architecture Checklist | |
|
|
57
58
|
|
|
58
59
|
> Router 不得自行将合同批准或将本 Ticket 推进为 `ready-for-agent`。只有生命周期编排器核验并持久化当前版本合同、清除阻塞边后,才能推进状态。
|
|
@@ -107,6 +108,12 @@ Status: ready-for-human
|
|
|
107
108
|
|
|
108
109
|
出现 `drift`、`violation` 或非空 `new_impacts` 时暂停受影响工作单元,不得先完成代码再补合同;更新合同版本并通过生命周期审查后才能恢复。
|
|
109
110
|
|
|
111
|
+
## 会签
|
|
112
|
+
|
|
113
|
+
| 门禁 | 记录路径 | 会签角色 | 状态 |
|
|
114
|
+
|---|---|---|---|
|
|
115
|
+
| | `docs/.scratch/<feature>/gates/<gate-id>-approval.yaml` | 见 `docs/agents/digital-human-roles.yaml` | pending / approved / blocked / not-applicable |
|
|
116
|
+
|
|
110
117
|
## 状态
|
|
111
118
|
|
|
112
119
|
`ready-for-agent`
|
|
@@ -197,6 +197,8 @@ agents 是角色分工,不是越多越好。只有当任务边界清楚、上
|
|
|
197
197
|
|
|
198
198
|
Agent 交接必须写成自包含 brief,不能只丢一句“继续上面的”。推荐使用 `docs/templates/agent-brief-template.md`。
|
|
199
199
|
|
|
200
|
+
职称数字人(前端工程师、产品经理等)是叠加在上表职能分工上的配置,权威清单和会签级别见 `docs/agents/digital-human-roles.yaml`。不要用职称实例替换生命周期编排器。阶段协作组是逻辑成员;Grok 等平台的群聊人数上限只约束该运行时如何开会,不改角色表。
|
|
201
|
+
|
|
200
202
|
Agent brief 最少包含:
|
|
201
203
|
|
|
202
204
|
- 背景和相关资产路径。
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { parseDocument } from "../vendor/yaml.mjs";
|
|
4
|
+
import {
|
|
5
|
+
BIOLOGICAL_ROLE_ID,
|
|
6
|
+
countersignRuleForGate,
|
|
7
|
+
loadDigitalHumanRoles,
|
|
8
|
+
collectCountersignGateIds
|
|
9
|
+
} from "./digital-human-roles.mjs";
|
|
10
|
+
import { ROOT } from "./lifecycle-registry.mjs";
|
|
11
|
+
|
|
12
|
+
const DECISIONS = new Set(["approved", "rejected", "vetoed"]);
|
|
13
|
+
const ACTOR_KINDS = new Set(["digital-human", "biological-human", "orchestrator"]);
|
|
14
|
+
|
|
15
|
+
function fail(message) {
|
|
16
|
+
throw new TypeError(message);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function requireString(value, field) {
|
|
20
|
+
if (typeof value !== "string" || !value.trim()) fail(`${field} 不能为空`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function yamlFromFile(filePath, label) {
|
|
24
|
+
let source;
|
|
25
|
+
try {
|
|
26
|
+
source = readFileSync(filePath, "utf8");
|
|
27
|
+
} catch (error) {
|
|
28
|
+
if (error.code === "ENOENT") fail(`缺少${label}: ${filePath}`);
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
const document = parseDocument(source, { maxAliasCount: 0, uniqueKeys: true });
|
|
32
|
+
if (document.errors.length > 0) fail(`无法解析${label}: ${document.errors[0].message}`);
|
|
33
|
+
const value = document.toJS({ maxAliasCount: 0 });
|
|
34
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) fail(`${label}必须是对象`);
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function loadApprovalRecord(filePath) {
|
|
39
|
+
return yamlFromFile(filePath, "会签记录");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function resolveApprovalRef(approvalRef, fromFile = ROOT) {
|
|
43
|
+
requireString(approvalRef, "approval_ref");
|
|
44
|
+
if (path.isAbsolute(approvalRef)) return approvalRef;
|
|
45
|
+
const fromRoot = path.join(ROOT, approvalRef);
|
|
46
|
+
if (existsSync(fromRoot)) return fromRoot;
|
|
47
|
+
return path.resolve(path.dirname(fromFile), approvalRef);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function validateApprovalRecord(record, { rolesDoc, requireApproved = false } = {}) {
|
|
51
|
+
if (!record || typeof record !== "object" || Array.isArray(record)) fail("会签记录必须是对象");
|
|
52
|
+
if (record.schema_version !== 1) fail("会签记录 schema_version 必须为 1");
|
|
53
|
+
requireString(record.gate_id, "gate_id");
|
|
54
|
+
requireString(record.decision, "decision");
|
|
55
|
+
if (!DECISIONS.has(record.decision)) fail("decision 必须是 approved、rejected 或 vetoed");
|
|
56
|
+
requireString(record.actor_kind, "actor_kind");
|
|
57
|
+
if (!ACTOR_KINDS.has(record.actor_kind)) fail("actor_kind 无效");
|
|
58
|
+
requireString(record.role_id, "role_id");
|
|
59
|
+
requireString(record.runtime_id, "runtime_id");
|
|
60
|
+
requireString(record.principal_ref, "principal_ref");
|
|
61
|
+
|
|
62
|
+
const registry = rolesDoc || loadDigitalHumanRoles();
|
|
63
|
+
const runtimeIds = new Set((registry.runtimes || []).map((runtime) => runtime.id));
|
|
64
|
+
if (!runtimeIds.has(record.runtime_id)) fail(`未知 runtime_id: ${record.runtime_id}`);
|
|
65
|
+
if (record.actor_kind === "orchestrator") fail("编排器门禁不使用会签记录关闭");
|
|
66
|
+
|
|
67
|
+
const rule = countersignRuleForGate(registry.gate_policy, record.gate_id);
|
|
68
|
+
if (!rule) fail(`${record.gate_id} 不是会签门禁;evidence_only / orchestrator 门禁不写 approval-record`);
|
|
69
|
+
|
|
70
|
+
if (requireApproved && record.decision !== "approved") {
|
|
71
|
+
fail(`${record.gate_id} 会签 decision 必须为 approved 才能关闭门禁`);
|
|
72
|
+
}
|
|
73
|
+
if (record.decision === "approved" && record.biological_veto === true) {
|
|
74
|
+
fail(`${record.gate_id} 已被生物人否决,不能标为 approved`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (rule.bucket === "biological_human") {
|
|
78
|
+
if (record.actor_kind !== "biological-human") fail(`${record.gate_id} 必须由生物人会签`);
|
|
79
|
+
if (record.role_id !== BIOLOGICAL_ROLE_ID) fail(`${record.gate_id} 的 role_id 必须为 ${BIOLOGICAL_ROLE_ID}`);
|
|
80
|
+
return rule;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (record.actor_kind !== "digital-human") fail(`${record.gate_id} 必须由数字人会签`);
|
|
84
|
+
if (!rule.countersigners.includes(record.role_id)) {
|
|
85
|
+
fail(`${record.gate_id} 会签角色必须是 ${rule.countersigners.join(" / ")}`);
|
|
86
|
+
}
|
|
87
|
+
if (rule.drafter && record.role_id === rule.drafter) fail(`${record.gate_id} 起草者不得会签自己`);
|
|
88
|
+
if (Array.isArray(record.countersigner_role_ids) && rule.drafter && record.countersigner_role_ids.includes(rule.drafter)) {
|
|
89
|
+
fail(`${record.gate_id} 起草者不得出现在 countersigner_role_ids`);
|
|
90
|
+
}
|
|
91
|
+
if (rule.drafter && record.drafter_role_id && record.drafter_role_id !== rule.drafter) {
|
|
92
|
+
fail(`${record.gate_id} drafter_role_id 必须为 ${rule.drafter}`);
|
|
93
|
+
}
|
|
94
|
+
return rule;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function validateApprovalRecordFile(filePath, options = {}) {
|
|
98
|
+
return validateApprovalRecord(loadApprovalRecord(filePath), options);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function assertApprovedGateHasValidApproval(gateId, gateState, { checkpointPath } = {}) {
|
|
102
|
+
const rolesDoc = loadDigitalHumanRoles();
|
|
103
|
+
const countersignGates = new Set(collectCountersignGateIds(rolesDoc.gate_policy));
|
|
104
|
+
if (!countersignGates.has(gateId)) return;
|
|
105
|
+
if (!gateState || typeof gateState !== "object") fail(`${gateId} 缺少门禁状态`);
|
|
106
|
+
if (gateState.status !== "approved") return;
|
|
107
|
+
if (!gateState.approval_ref || !String(gateState.approval_ref).trim()) {
|
|
108
|
+
fail(`${gateId} 已 approved 但缺少 approval_ref`);
|
|
109
|
+
}
|
|
110
|
+
const resolved = resolveApprovalRef(gateState.approval_ref, checkpointPath || ROOT);
|
|
111
|
+
if (!existsSync(resolved)) fail(`${gateId} 的 approval_ref 不可读: ${gateState.approval_ref}`);
|
|
112
|
+
const record = loadApprovalRecord(resolved);
|
|
113
|
+
if (record.gate_id !== gateId) fail(`${gateId} 的会签记录 gate_id 不匹配`);
|
|
114
|
+
validateApprovalRecord(record, { rolesDoc, requireApproved: true });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function assertCheckpointApprovals(checkpoint, checkpointPath) {
|
|
118
|
+
const gates = checkpoint?.gates;
|
|
119
|
+
if (!gates || typeof gates !== "object") return;
|
|
120
|
+
for (const [gateId, state] of Object.entries(gates)) {
|
|
121
|
+
assertApprovedGateHasValidApproval(gateId, state, { checkpointPath });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { parseDocument } from "../vendor/yaml.mjs";
|
|
4
|
+
import { loadRegistry, ROOT } from "./lifecycle-registry.mjs";
|
|
5
|
+
import { loadSkillRegistry } from "./skill-registry.mjs";
|
|
6
|
+
|
|
7
|
+
export const DEFAULT_DIGITAL_HUMAN_ROLES = path.join(ROOT, "docs/agents/digital-human-roles.yaml");
|
|
8
|
+
|
|
9
|
+
const ROLE_ID = /^role\.[a-z0-9][a-z0-9-]*$/;
|
|
10
|
+
const GROUP_ID = /^group\.[a-z0-9][a-z0-9-]*$/;
|
|
11
|
+
const RUNTIME_ID = /^runtime\.[a-z0-9][a-z0-9-]*$/;
|
|
12
|
+
const REQUIRED_ROLES = [
|
|
13
|
+
"role.requirements-manager",
|
|
14
|
+
"role.product-manager",
|
|
15
|
+
"role.business",
|
|
16
|
+
"role.project-manager",
|
|
17
|
+
"role.frontend-engineer",
|
|
18
|
+
"role.backend-engineer",
|
|
19
|
+
"role.test-engineer"
|
|
20
|
+
];
|
|
21
|
+
const REQUIRED_RUNTIMES = ["runtime.generic", "runtime.skill-projection", "runtime.grok"];
|
|
22
|
+
const REQUIRED_DUAL_HAT_SPLIT = ["cross-repo-load", "responsibility-conflict"];
|
|
23
|
+
const STRING_GATE_BUCKETS = [
|
|
24
|
+
"evidence_only",
|
|
25
|
+
"orchestrator",
|
|
26
|
+
"product_digital_human_with_biological_veto",
|
|
27
|
+
"biological_human"
|
|
28
|
+
];
|
|
29
|
+
const COUNTERSIGN_STRING_BUCKETS = ["product_digital_human_with_biological_veto", "biological_human"];
|
|
30
|
+
export const BIOLOGICAL_ROLE_ID = "role.biological-human";
|
|
31
|
+
const OVERFLOWS = new Set(["not-applicable", "one-to-one-handoff", "forbid"]);
|
|
32
|
+
const GROK_FIELDS = ["grok_title", "grok_description", "grok_default_dual_hat", "grok_runtime_root", "grok_platform_approval"];
|
|
33
|
+
|
|
34
|
+
function fail(message) {
|
|
35
|
+
throw new TypeError(message);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function yamlFromFile(filePath, label) {
|
|
39
|
+
let source;
|
|
40
|
+
try {
|
|
41
|
+
source = readFileSync(filePath, "utf8");
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if (error.code === "ENOENT") fail(`缺少${label}: ${filePath}`);
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
const document = parseDocument(source, { maxAliasCount: 0, uniqueKeys: true });
|
|
47
|
+
if (document.errors.length > 0) fail(`无法解析${label}: ${document.errors[0].message}`);
|
|
48
|
+
const value = document.toJS({ maxAliasCount: 0 });
|
|
49
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) fail(`${label}必须是对象`);
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function requireString(value, field) {
|
|
54
|
+
if (typeof value !== "string" || !value.trim()) fail(`${field} 不能为空`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function requireStringArray(value, field) {
|
|
58
|
+
if (!Array.isArray(value) || value.length === 0 || value.some((item) => typeof item !== "string" || !item.trim())) {
|
|
59
|
+
fail(`${field} 必须是非空字符串数组`);
|
|
60
|
+
}
|
|
61
|
+
if (new Set(value).size !== value.length) fail(`${field} 包含重复项`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function idsFromCollection(records, kind) {
|
|
65
|
+
if (!Array.isArray(records)) fail(`生命周期注册表缺少 ${kind}`);
|
|
66
|
+
return new Set(records.map((record) => record.id));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function skillIdsFromRegistry(skillRegistry) {
|
|
70
|
+
const ids = new Set();
|
|
71
|
+
for (const collection of ["skills", "platform_skills", "external_skills"]) {
|
|
72
|
+
const records = skillRegistry[collection];
|
|
73
|
+
if (!records) continue;
|
|
74
|
+
if (!Array.isArray(records)) fail(`技能注册表 ${collection} 必须是数组`);
|
|
75
|
+
for (const record of records) {
|
|
76
|
+
if (!record?.id) fail(`技能注册表 ${collection} 缺少 id`);
|
|
77
|
+
ids.add(record.id);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return ids;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function rejectGrokCoupling(object, label) {
|
|
84
|
+
if (!object || typeof object !== "object") return;
|
|
85
|
+
for (const field of GROK_FIELDS) {
|
|
86
|
+
if (Object.hasOwn(object, field)) fail(`${label} 不得包含平台耦合字段 ${field}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function knownActorIds(doc) {
|
|
91
|
+
return new Set([doc.orchestrator.id, ...doc.roles.map((role) => role.id)]);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function validateSkills(list, field, skillIds) {
|
|
95
|
+
requireStringArray(list, field);
|
|
96
|
+
for (const skill of list) {
|
|
97
|
+
if (!skillIds.has(skill)) fail(`${field} 引用了未登记技能: ${skill}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function validateActor(actor, { skillIds, stageIds, artifactIds, evidenceIds, expectedKind }) {
|
|
102
|
+
rejectGrokCoupling(actor, actor?.id || expectedKind);
|
|
103
|
+
requireString(actor.id, `${expectedKind}.id`);
|
|
104
|
+
if (!ROLE_ID.test(actor.id)) fail(`${actor.id} 不符合 role.*`);
|
|
105
|
+
requireString(actor.name, `${actor.id}.name`);
|
|
106
|
+
if (actor.kind !== expectedKind) fail(`${actor.id}.kind 必须为 ${expectedKind}`);
|
|
107
|
+
requireString(actor.title, `${actor.id}.title`);
|
|
108
|
+
requireString(actor.description, `${actor.id}.description`);
|
|
109
|
+
requireStringArray(actor.stages, `${actor.id}.stages`);
|
|
110
|
+
for (const stage of actor.stages) {
|
|
111
|
+
if (!stageIds.has(stage)) fail(`${actor.id} 引用了未知阶段: ${stage}`);
|
|
112
|
+
}
|
|
113
|
+
if (actor.draft_artifact_ids) {
|
|
114
|
+
requireStringArray(actor.draft_artifact_ids, `${actor.id}.draft_artifact_ids`);
|
|
115
|
+
for (const artifact of actor.draft_artifact_ids) {
|
|
116
|
+
if (!artifactIds.has(artifact)) fail(`${actor.id} 引用了未知产物: ${artifact}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (actor.owned_evidence_ids) {
|
|
120
|
+
requireStringArray(actor.owned_evidence_ids, `${actor.id}.owned_evidence_ids`);
|
|
121
|
+
for (const evidence of actor.owned_evidence_ids) {
|
|
122
|
+
if (!evidenceIds || !evidenceIds.has(evidence)) fail(`${actor.id} 引用了未知证据: ${evidence}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
validateSkills(actor.core_skills, `${actor.id}.core_skills`, skillIds);
|
|
126
|
+
validateSkills(actor.forbidden_skills, `${actor.id}.forbidden_skills`, skillIds);
|
|
127
|
+
const overlap = actor.core_skills.filter((skill) => actor.forbidden_skills.includes(skill));
|
|
128
|
+
if (overlap.length > 0) fail(`${actor.id} 的 core_skills 与 forbidden_skills 重叠: ${overlap.join(", ")}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function validateRuntimes(doc, skillRegistry, groupSizes) {
|
|
132
|
+
if (!Array.isArray(doc.runtimes) || doc.runtimes.length === 0) fail("缺少 runtimes");
|
|
133
|
+
const ids = [];
|
|
134
|
+
let sawProjection = false;
|
|
135
|
+
for (const runtime of doc.runtimes) {
|
|
136
|
+
requireString(runtime.id, "runtimes.id");
|
|
137
|
+
if (!RUNTIME_ID.test(runtime.id)) fail(`${runtime.id} 不符合 runtime.*`);
|
|
138
|
+
ids.push(runtime.id);
|
|
139
|
+
requireString(runtime.kind, `${runtime.id}.kind`);
|
|
140
|
+
requireString(runtime.instance_kind, `${runtime.id}.instance_kind`);
|
|
141
|
+
requireString(runtime.collaboration, `${runtime.id}.collaboration`);
|
|
142
|
+
requireString(runtime.side_effect_approval, `${runtime.id}.side_effect_approval`);
|
|
143
|
+
if (runtime.side_effect_approval !== "biological-human") {
|
|
144
|
+
fail(`${runtime.id}.side_effect_approval 必须为 biological-human`);
|
|
145
|
+
}
|
|
146
|
+
if (!OVERFLOWS.has(runtime.overflow)) fail(`${runtime.id}.overflow 无效`);
|
|
147
|
+
if (typeof runtime.covers_skill_registry_roots !== "boolean") {
|
|
148
|
+
fail(`${runtime.id}.covers_skill_registry_roots 必须是布尔值`);
|
|
149
|
+
}
|
|
150
|
+
if (runtime.covers_skill_registry_roots) sawProjection = true;
|
|
151
|
+
const max = runtime.max_collaboration_group_size;
|
|
152
|
+
if (max == null) {
|
|
153
|
+
if (runtime.overflow !== "not-applicable") fail(`${runtime.id} 无人数上限时 overflow 必须为 not-applicable`);
|
|
154
|
+
} else {
|
|
155
|
+
if (!Number.isInteger(max) || max < 2) fail(`${runtime.id}.max_collaboration_group_size 必须是 >=2 的整数`);
|
|
156
|
+
if (runtime.overflow === "not-applicable") fail(`${runtime.id} 有人数上限时必须声明 overflow`);
|
|
157
|
+
if (runtime.overflow === "forbid") {
|
|
158
|
+
for (const [groupId, size] of groupSizes) {
|
|
159
|
+
if (size > max) fail(`${runtime.id} 禁止超过 ${max} 人的阶段协作组: ${groupId} 有 ${size} 人`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (runtime.id === "runtime.grok") {
|
|
164
|
+
if (runtime.kind !== "grok-bot") fail("runtime.grok.kind 必须为 grok-bot");
|
|
165
|
+
if (runtime.max_collaboration_group_size !== 6) fail("runtime.grok 群聊上限必须为 6");
|
|
166
|
+
if (runtime.shared_workspace_is_not_security_boundary !== true) {
|
|
167
|
+
fail("runtime.grok 必须声明共享工作区不是安全边界");
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (runtime.id === "runtime.generic" && runtime.max_collaboration_group_size != null) {
|
|
171
|
+
fail("runtime.generic 不得设置平台群聊上限");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (new Set(ids).size !== ids.length) fail("runtimes.id 重复");
|
|
175
|
+
for (const required of REQUIRED_RUNTIMES) {
|
|
176
|
+
if (!ids.includes(required)) fail(`缺少运行时绑定: ${required}`);
|
|
177
|
+
}
|
|
178
|
+
if (!sawProjection) fail("必须有 covers_skill_registry_roots=true 的运行时,以覆盖技能注册表投影根");
|
|
179
|
+
const roots = skillRegistry.agent_runtime_roots;
|
|
180
|
+
if (!roots || typeof roots !== "object") fail("技能注册表缺少 agent_runtime_roots");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function validateSignRule(rule, { actors, gateIds, claimed, field }) {
|
|
184
|
+
if (!rule || typeof rule !== "object") fail(`${field} 必须是对象`);
|
|
185
|
+
requireString(rule.gate, `${field}.gate`);
|
|
186
|
+
if (!gateIds.has(rule.gate)) fail(`${field} 引用了未知门禁: ${rule.gate}`);
|
|
187
|
+
if (claimed.has(rule.gate)) fail(`门禁被多个会签桶重复占用: ${rule.gate}`);
|
|
188
|
+
claimed.add(rule.gate);
|
|
189
|
+
if (rule.drafter != null) {
|
|
190
|
+
requireString(rule.drafter, `${rule.gate}.drafter`);
|
|
191
|
+
if (!actors.has(rule.drafter)) fail(`${rule.gate} 起草者未知: ${rule.drafter}`);
|
|
192
|
+
}
|
|
193
|
+
requireStringArray(rule.countersigners, `${rule.gate}.countersigners`);
|
|
194
|
+
if (rule.drafter && rule.countersigners.includes(rule.drafter)) fail(`${rule.gate} 起草者不得会签自己`);
|
|
195
|
+
for (const signer of rule.countersigners) {
|
|
196
|
+
if (!actors.has(signer)) fail(`${rule.gate} 会签人未知: ${signer}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function collectCountersignGateIds(policy) {
|
|
201
|
+
const ids = [];
|
|
202
|
+
for (const bucket of COUNTERSIGN_STRING_BUCKETS) {
|
|
203
|
+
const gates = policy?.[bucket];
|
|
204
|
+
if (Array.isArray(gates)) ids.push(...gates);
|
|
205
|
+
}
|
|
206
|
+
for (const rule of policy?.digital_human_review || []) {
|
|
207
|
+
if (rule?.gate) ids.push(rule.gate);
|
|
208
|
+
}
|
|
209
|
+
for (const rule of policy?.dual_digital_human || []) {
|
|
210
|
+
if (rule?.gate) ids.push(rule.gate);
|
|
211
|
+
}
|
|
212
|
+
return ids;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function countersignRuleForGate(policy, gateId) {
|
|
216
|
+
const dual = (policy?.dual_digital_human || []).find((rule) => rule.gate === gateId);
|
|
217
|
+
if (dual) return { bucket: "dual_digital_human", ...dual };
|
|
218
|
+
const review = (policy?.digital_human_review || []).find((rule) => rule.gate === gateId);
|
|
219
|
+
if (review) return { bucket: "digital_human_review", ...review };
|
|
220
|
+
if ((policy?.product_digital_human_with_biological_veto || []).includes(gateId)) {
|
|
221
|
+
return { bucket: "product_digital_human_with_biological_veto", gate: gateId, countersigners: ["role.product-manager"] };
|
|
222
|
+
}
|
|
223
|
+
if ((policy?.biological_human || []).includes(gateId)) {
|
|
224
|
+
return { bucket: "biological_human", gate: gateId, countersigners: [BIOLOGICAL_ROLE_ID] };
|
|
225
|
+
}
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function taskPackageDefaults(roleId, doc) {
|
|
230
|
+
const registry = doc || loadDigitalHumanRoles();
|
|
231
|
+
const actor = roleId === registry.orchestrator.id
|
|
232
|
+
? registry.orchestrator
|
|
233
|
+
: registry.roles.find((role) => role.id === roleId);
|
|
234
|
+
if (!actor) fail(`未知角色: ${roleId}`);
|
|
235
|
+
return {
|
|
236
|
+
role_id: actor.id,
|
|
237
|
+
kind: actor.kind,
|
|
238
|
+
stages: [...actor.stages],
|
|
239
|
+
core_skills: [...actor.core_skills],
|
|
240
|
+
forbidden_skills: [...actor.forbidden_skills],
|
|
241
|
+
draft_artifact_ids: actor.draft_artifact_ids ? [...actor.draft_artifact_ids] : [],
|
|
242
|
+
owned_evidence_ids: actor.owned_evidence_ids ? [...actor.owned_evidence_ids] : []
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function validateDigitalHumanRoles(doc, { skillIds, stageIds, gateIds, artifactIds, evidenceIds, workUnitIds, skillRegistry } = {}) {
|
|
247
|
+
if (!doc || typeof doc !== "object" || Array.isArray(doc)) fail("数字人角色注册表必须是对象");
|
|
248
|
+
if (doc.schema_version !== 1) fail("schema_version 必须为 1");
|
|
249
|
+
if (doc.registry_id !== "yss.digital-human-roles") fail("registry_id 必须为 yss.digital-human-roles");
|
|
250
|
+
if (doc.status !== "active") fail("status 必须为 active");
|
|
251
|
+
requireString(doc.description, "description");
|
|
252
|
+
if (doc.canonical_path !== "docs/agents/digital-human-roles.yaml") fail("canonical_path 必须为 docs/agents/digital-human-roles.yaml");
|
|
253
|
+
rejectGrokCoupling(doc.runtime_policy, "runtime_policy");
|
|
254
|
+
rejectGrokCoupling(doc.gate_policy, "gate_policy");
|
|
255
|
+
const runtime = doc.runtime_policy;
|
|
256
|
+
if (!runtime || typeof runtime !== "object") fail("缺少 runtime_policy");
|
|
257
|
+
if (runtime.overlay_on_lifecycle !== true) fail("runtime_policy.overlay_on_lifecycle 必须为 true");
|
|
258
|
+
if (runtime.git_is_ssot !== true) fail("runtime_policy.git_is_ssot 必须为 true");
|
|
259
|
+
if (runtime.default_invocation !== "orchestrator") fail("default_invocation 必须为 orchestrator");
|
|
260
|
+
if (runtime.dispatch !== "orchestrator-one-to-one") fail("dispatch 必须为 orchestrator-one-to-one");
|
|
261
|
+
if (runtime.per_feature_instances !== "forbidden") fail("per_feature_instances 必须为 forbidden");
|
|
262
|
+
if (runtime.implementer_must_differ !== true) fail("implementer_must_differ 必须为 true");
|
|
263
|
+
if (runtime.biological_veto !== true) fail("biological_veto 必须为 true");
|
|
264
|
+
if (runtime.write_isolation !== "task-package-policy") fail("write_isolation 必须为 task-package-policy");
|
|
265
|
+
requireStringArray(runtime.dual_hat_split_when, "runtime_policy.dual_hat_split_when");
|
|
266
|
+
for (const required of REQUIRED_DUAL_HAT_SPLIT) {
|
|
267
|
+
if (!runtime.dual_hat_split_when.includes(required)) {
|
|
268
|
+
fail(`runtime_policy.dual_hat_split_when 必须包含 ${required}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (runtime.max_group_size != null || runtime.min_group_size != null) {
|
|
272
|
+
fail("平台群聊人数上限属于 runtimes,不得写在 runtime_policy");
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const orchestrator = doc.orchestrator;
|
|
276
|
+
if (!orchestrator || typeof orchestrator !== "object") fail("缺少 orchestrator");
|
|
277
|
+
validateActor(orchestrator, { skillIds, stageIds, artifactIds, evidenceIds, expectedKind: "orchestrator" });
|
|
278
|
+
requireStringArray(orchestrator.default_dual_hat, "orchestrator.default_dual_hat");
|
|
279
|
+
if (!Array.isArray(doc.roles) || doc.roles.length === 0) fail("roles 不能为空");
|
|
280
|
+
const roleIds = [];
|
|
281
|
+
for (const role of doc.roles) {
|
|
282
|
+
validateActor(role, { skillIds, stageIds, artifactIds, evidenceIds, expectedKind: "digital-human" });
|
|
283
|
+
roleIds.push(role.id);
|
|
284
|
+
}
|
|
285
|
+
if (new Set(roleIds).size !== roleIds.length) fail("roles.id 重复");
|
|
286
|
+
if (roleIds.slice().sort().join("\0") !== REQUIRED_ROLES.slice().sort().join("\0")) {
|
|
287
|
+
fail(`v1 必须恰好包含: ${REQUIRED_ROLES.join(", ")}`);
|
|
288
|
+
}
|
|
289
|
+
if (roleIds.includes(orchestrator.id)) fail("orchestrator 不得再出现在 roles");
|
|
290
|
+
for (const dual of orchestrator.default_dual_hat) {
|
|
291
|
+
if (!roleIds.includes(dual)) fail(`default_dual_hat 引用了未知角色: ${dual}`);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const actors = knownActorIds(doc);
|
|
295
|
+
if (!Array.isArray(doc.stage_groups) || doc.stage_groups.length === 0) fail("缺少 stage_groups");
|
|
296
|
+
const groupIds = [];
|
|
297
|
+
const groupSizes = [];
|
|
298
|
+
for (const group of doc.stage_groups) {
|
|
299
|
+
requireString(group.id, "stage_groups.id");
|
|
300
|
+
if (!GROUP_ID.test(group.id)) fail(`${group.id} 不符合 group.*`);
|
|
301
|
+
groupIds.push(group.id);
|
|
302
|
+
requireString(group.name, `${group.id}.name`);
|
|
303
|
+
if (!stageIds.has(group.stage)) fail(`${group.id} 引用了未知阶段: ${group.stage}`);
|
|
304
|
+
requireStringArray(group.members, `${group.id}.members`);
|
|
305
|
+
if (group.members.length < 2) fail(`${group.id} 逻辑协作组至少 2 人`);
|
|
306
|
+
groupSizes.push([group.id, group.members.length]);
|
|
307
|
+
for (const member of group.members) {
|
|
308
|
+
if (!actors.has(member)) fail(`${group.id} 包含未知成员: ${member}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (new Set(groupIds).size !== groupIds.length) fail("stage_groups.id 重复");
|
|
312
|
+
validateRuntimes(doc, skillRegistry, groupSizes);
|
|
313
|
+
|
|
314
|
+
const policy = doc.gate_policy;
|
|
315
|
+
if (!policy || typeof policy !== "object") fail("缺少 gate_policy");
|
|
316
|
+
if (policy.default_if_unlisted !== "biological-human") fail("default_if_unlisted 必须为 biological-human");
|
|
317
|
+
if (policy.runtime_side_effect_approval !== "biological-human") fail("runtime_side_effect_approval 必须为 biological-human");
|
|
318
|
+
if (policy.commercial_contract !== "biological-human") fail("commercial_contract 必须为 biological-human");
|
|
319
|
+
if (policy.unlisted_kept_biological) {
|
|
320
|
+
requireStringArray(policy.unlisted_kept_biological, "gate_policy.unlisted_kept_biological");
|
|
321
|
+
for (const gate of policy.unlisted_kept_biological) {
|
|
322
|
+
if (!gateIds.has(gate)) fail(`unlisted_kept_biological 引用了未知门禁: ${gate}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
const claimed = new Set();
|
|
326
|
+
for (const bucket of STRING_GATE_BUCKETS) {
|
|
327
|
+
requireStringArray(policy[bucket], `gate_policy.${bucket}`);
|
|
328
|
+
for (const gate of policy[bucket]) {
|
|
329
|
+
if (!gateIds.has(gate)) fail(`gate_policy.${bucket} 引用了未知门禁: ${gate}`);
|
|
330
|
+
if (claimed.has(gate)) fail(`门禁被多个会签桶重复占用: ${gate}`);
|
|
331
|
+
claimed.add(gate);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (!Array.isArray(policy.digital_human_review) || policy.digital_human_review.length === 0) {
|
|
335
|
+
fail("缺少 digital_human_review");
|
|
336
|
+
}
|
|
337
|
+
for (const [index, rule] of policy.digital_human_review.entries()) {
|
|
338
|
+
if (typeof rule === "string") fail("digital_human_review 必须是含 gate 与 countersigners 的规则");
|
|
339
|
+
validateSignRule(rule, { actors, gateIds, claimed, field: `digital_human_review[${index}]` });
|
|
340
|
+
if (rule.gate === "gate.release-ready") fail("gate.release-ready 不得放入 digital_human_review");
|
|
341
|
+
}
|
|
342
|
+
if (!policy.biological_human.includes("gate.release-ready")) {
|
|
343
|
+
fail("gate.release-ready 必须属于 biological_human");
|
|
344
|
+
}
|
|
345
|
+
if (!Array.isArray(policy.digital_human_review_work_units) || policy.digital_human_review_work_units.length === 0) {
|
|
346
|
+
fail("缺少 digital_human_review_work_units");
|
|
347
|
+
}
|
|
348
|
+
const workUnitSeen = new Set();
|
|
349
|
+
for (const [index, rule] of policy.digital_human_review_work_units.entries()) {
|
|
350
|
+
if (typeof rule === "string") fail("digital_human_review_work_units 必须是含 work_unit 与 countersigners 的规则");
|
|
351
|
+
requireString(rule.work_unit, `digital_human_review_work_units[${index}].work_unit`);
|
|
352
|
+
if (!workUnitIds.has(rule.work_unit)) fail(`未知工作单元: ${rule.work_unit}`);
|
|
353
|
+
if (workUnitSeen.has(rule.work_unit)) fail(`工作单元重复: ${rule.work_unit}`);
|
|
354
|
+
workUnitSeen.add(rule.work_unit);
|
|
355
|
+
requireStringArray(rule.countersigners, `${rule.work_unit}.countersigners`);
|
|
356
|
+
for (const signer of rule.countersigners) {
|
|
357
|
+
if (!actors.has(signer)) fail(`${rule.work_unit} 会签人未知: ${signer}`);
|
|
358
|
+
}
|
|
359
|
+
if (rule.implementer_must_differ !== true) {
|
|
360
|
+
fail(`${rule.work_unit}.implementer_must_differ 必须为 true`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (!Array.isArray(policy.dual_digital_human) || policy.dual_digital_human.length === 0) {
|
|
364
|
+
fail("缺少 dual_digital_human");
|
|
365
|
+
}
|
|
366
|
+
for (const [index, rule] of policy.dual_digital_human.entries()) {
|
|
367
|
+
validateSignRule(rule, { actors, gateIds, claimed, field: `dual_digital_human[${index}]` });
|
|
368
|
+
if (!rule.drafter) fail(`${rule.gate} 双数字人会签必须有 drafter`);
|
|
369
|
+
}
|
|
370
|
+
for (const gate of policy.unlisted_kept_biological || []) {
|
|
371
|
+
if (claimed.has(gate)) fail(`unlisted_kept_biological 与会签桶重复: ${gate}`);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return {
|
|
375
|
+
role_count: doc.roles.length,
|
|
376
|
+
group_count: doc.stage_groups.length,
|
|
377
|
+
runtime_count: doc.runtimes.length,
|
|
378
|
+
claimed_gates: claimed.size
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export function loadDigitalHumanRoles(filePath = DEFAULT_DIGITAL_HUMAN_ROLES) {
|
|
383
|
+
return yamlFromFile(filePath, "数字人角色注册表");
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export function validateDefaultDigitalHumanRoles() {
|
|
387
|
+
const lifecycle = loadRegistry();
|
|
388
|
+
const skills = loadSkillRegistry();
|
|
389
|
+
return validateDigitalHumanRoles(loadDigitalHumanRoles(), {
|
|
390
|
+
skillIds: skillIdsFromRegistry(skills),
|
|
391
|
+
stageIds: idsFromCollection(lifecycle.stages, "stages"),
|
|
392
|
+
gateIds: idsFromCollection(lifecycle.gates, "gates"),
|
|
393
|
+
artifactIds: idsFromCollection(lifecycle.artifacts, "artifacts"),
|
|
394
|
+
evidenceIds: idsFromCollection(lifecycle.evidence, "evidence"),
|
|
395
|
+
workUnitIds: idsFromCollection(lifecycle.work_units, "work_units"),
|
|
396
|
+
skillRegistry: skills
|
|
397
|
+
});
|
|
398
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
3
|
+
import { validateApprovalRecordFile } from "./lib/approval-record.mjs";
|
|
4
|
+
|
|
5
|
+
try {
|
|
6
|
+
const { positionals } = parseArgs({ allowPositionals: true, strict: true });
|
|
7
|
+
if (positionals.length !== 1) throw new TypeError("用法: scripts/verify-approval-record <approval-record.yaml>");
|
|
8
|
+
validateApprovalRecordFile(positionals[0]);
|
|
9
|
+
process.stdout.write("会签记录校验通过\n");
|
|
10
|
+
} catch (error) {
|
|
11
|
+
process.stderr.write(`会签记录校验失败: ${error.message}\n`);
|
|
12
|
+
process.exitCode = 1;
|
|
13
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { validateDefaultDigitalHumanRoles } from "./lib/digital-human-roles.mjs";
|
|
3
|
+
|
|
4
|
+
try {
|
|
5
|
+
const result = validateDefaultDigitalHumanRoles();
|
|
6
|
+
process.stdout.write(`数字人角色注册表校验通过(${result.role_count} 个角色,${result.group_count} 个协作组,${result.runtime_count} 个运行时)\n`);
|
|
7
|
+
} catch (error) {
|
|
8
|
+
process.stderr.write(`数字人角色注册表校验失败: ${error.message}\n`);
|
|
9
|
+
process.exitCode = 1;
|
|
10
|
+
}
|