@luckychen1993/pm-mcp 0.1.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 (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +304 -0
  3. package/dist/acceptance-evaluator.js +330 -0
  4. package/dist/acceptance-model.js +440 -0
  5. package/dist/acceptance-report.js +335 -0
  6. package/dist/acceptance-tools.js +318 -0
  7. package/dist/audit-tools.js +61 -0
  8. package/dist/audit.js +364 -0
  9. package/dist/budget.js +71 -0
  10. package/dist/dashboard.js +241 -0
  11. package/dist/governance-audit.js +126 -0
  12. package/dist/governance-model.js +306 -0
  13. package/dist/governance-tools.js +240 -0
  14. package/dist/health.js +58 -0
  15. package/dist/index-db.js +97 -0
  16. package/dist/index-store.js +381 -0
  17. package/dist/index.js +210 -0
  18. package/dist/init.js +40 -0
  19. package/dist/knowledge-tools.js +204 -0
  20. package/dist/language-adapters.js +450 -0
  21. package/dist/language-dependencies.js +351 -0
  22. package/dist/license.js +261 -0
  23. package/dist/paths.js +51 -0
  24. package/dist/polyglot-ast.js +329 -0
  25. package/dist/portfolio.js +349 -0
  26. package/dist/project-fingerprint.js +83 -0
  27. package/dist/project-tools.js +217 -0
  28. package/dist/quality-store.js +77 -0
  29. package/dist/registry.js +38 -0
  30. package/dist/roadmap.js +96 -0
  31. package/dist/scan.js +271 -0
  32. package/dist/search.js +312 -0
  33. package/dist/security.js +269 -0
  34. package/dist/semantic-evidence-store.js +133 -0
  35. package/dist/semantic-evidence.js +100 -0
  36. package/dist/semantic-graph-algorithms.js +86 -0
  37. package/dist/semantic-graph.js +390 -0
  38. package/dist/semantic-parsers.js +311 -0
  39. package/dist/store.js +185 -0
  40. package/dist/task-tools.js +198 -0
  41. package/dist/tool-base.js +32 -0
  42. package/dist/tools.js +21 -0
  43. package/dist/types.js +208 -0
  44. package/dist/typescript-semantic.js +282 -0
  45. package/package.json +70 -0
@@ -0,0 +1,440 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { z } from "zod";
4
+ import { pmPath } from "./paths.js";
5
+ import { loadJson, saveJson, withLedgerLock } from "./store.js";
6
+ export const ACCEPTANCE_SCHEMA_VERSION = 1;
7
+ export const ISO_25010_CHARACTERISTICS = [
8
+ "functional_suitability",
9
+ "performance_efficiency",
10
+ "compatibility",
11
+ "interaction_capability",
12
+ "reliability",
13
+ "security",
14
+ "maintainability",
15
+ "flexibility",
16
+ "safety",
17
+ ];
18
+ export const ISO_25040_STAGES = ["define", "design", "plan", "execute", "conclude"];
19
+ export const RISK_LEVELS = ["low", "medium", "high", "critical"];
20
+ const IdSchema = z.string().trim().min(1).max(120).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/, "只能包含字母、数字、点、下划线、冒号和连字符");
21
+ const TextSchema = z.string().trim().min(1).max(4_000);
22
+ const RationaleSchema = z.string().trim().min(8, "理由至少需要 8 个字符").max(4_000);
23
+ const TimestampSchema = z.string().datetime({ offset: true });
24
+ const SemVerSchema = z.string().regex(/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/, "必须是语义化版本");
25
+ const Sha256Schema = z.string().regex(/^[a-f0-9]{64}$/i, "必须是 64 位 SHA-256 十六进制摘要");
26
+ export const JsonPointerSchema = z
27
+ .string()
28
+ .max(2_000)
29
+ .refine((value) => value === "" || /^\/(?:[^~/]|~[01])*(?:\/(?:[^~/]|~[01])*)*$/.test(value), "必须是合法 RFC 6901 JSON Pointer");
30
+ export const FrozenEvidencePointerSchema = z
31
+ .object({
32
+ evidence_id: IdSchema,
33
+ json_pointer: JsonPointerSchema,
34
+ })
35
+ .strict();
36
+ export const MachineAssertionSchema = FrozenEvidencePointerSchema.extend({
37
+ operator: z.enum(["equal", "not_equal", "at_least", "at_most"]),
38
+ expected: z.union([z.string(), z.number().finite(), z.boolean(), z.null()]),
39
+ })
40
+ .strict()
41
+ .superRefine((assertion, ctx) => {
42
+ if ((assertion.operator === "at_least" || assertion.operator === "at_most") && typeof assertion.expected !== "number") {
43
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["expected"], message: `${assertion.operator} 的 expected 必须是数字` });
44
+ }
45
+ });
46
+ export const QualityCharacteristicSchema = z
47
+ .object({
48
+ id: z.enum(ISO_25010_CHARACTERISTICS),
49
+ applicable: z.boolean(),
50
+ tailoring_reason: RationaleSchema.nullable(),
51
+ })
52
+ .strict()
53
+ .superRefine((characteristic, ctx) => {
54
+ if (characteristic.applicable && characteristic.tailoring_reason !== null) {
55
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["tailoring_reason"], message: "适用特性不得填写裁剪理由" });
56
+ }
57
+ if (!characteristic.applicable && characteristic.tailoring_reason === null) {
58
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["tailoring_reason"], message: "裁剪特性必须说明理由" });
59
+ }
60
+ });
61
+ export const QualityRequirementSchema = z
62
+ .object({
63
+ id: IdSchema,
64
+ characteristic: z.enum(ISO_25010_CHARACTERISTICS),
65
+ statement: TextSchema,
66
+ metric: z
67
+ .object({
68
+ name: TextSchema,
69
+ unit: TextSchema,
70
+ direction: z.enum(["at_least", "at_most", "equal"]),
71
+ threshold: z.number().finite(),
72
+ tolerance: z.number().finite().min(0).default(0),
73
+ })
74
+ .strict(),
75
+ measurement_source: FrozenEvidencePointerSchema,
76
+ risk_ids: z.array(IdSchema).min(1),
77
+ test_ids: z.array(IdSchema).min(1),
78
+ })
79
+ .strict();
80
+ export const AcceptanceRiskSchema = z
81
+ .object({
82
+ id: IdSchema,
83
+ title: TextSchema,
84
+ description: TextSchema,
85
+ owner: TextSchema,
86
+ likelihood: z.enum(["rare", "unlikely", "possible", "likely", "almost_certain"]),
87
+ impact: z.enum(["negligible", "minor", "moderate", "major", "severe"]),
88
+ inherent_level: z.enum(RISK_LEVELS),
89
+ treatment: TextSchema,
90
+ compensating_controls: z.array(TextSchema).min(1),
91
+ requirement_ids: z.array(IdSchema).min(1),
92
+ test_ids: z.array(IdSchema).min(1),
93
+ })
94
+ .strict();
95
+ export const AcceptanceTestSchema = z
96
+ .object({
97
+ id: IdSchema,
98
+ title: TextSchema,
99
+ procedure: TextSchema,
100
+ expected_result: TextSchema,
101
+ expected_evidence_kind: z.enum(["test_result", "benchmark", "audit", "inspection", "review", "measurement", "certificate"]),
102
+ verification_mode: z.enum(["automated", "manual"]).default("automated"),
103
+ assertion: MachineAssertionSchema.nullable().default(null),
104
+ requirement_ids: z.array(IdSchema).min(1),
105
+ risk_ids: z.array(IdSchema).min(1),
106
+ })
107
+ .strict()
108
+ .superRefine((acceptanceTest, ctx) => {
109
+ if (acceptanceTest.verification_mode === "automated" && acceptanceTest.assertion === null) {
110
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["assertion"], message: "automated 测试必须冻结机器断言" });
111
+ }
112
+ if (acceptanceTest.verification_mode === "manual") {
113
+ if (acceptanceTest.assertion !== null) {
114
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["assertion"], message: "manual 测试不得伪装为机器断言" });
115
+ }
116
+ if (!["inspection", "review", "certificate"].includes(acceptanceTest.expected_evidence_kind)) {
117
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["expected_evidence_kind"], message: "manual 测试仅允许 inspection/review/certificate 证据" });
118
+ }
119
+ }
120
+ });
121
+ export const BaselineApprovalSchema = z
122
+ .object({
123
+ status: z.enum(["draft", "approved", "rejected", "superseded"]),
124
+ approved_by: TextSchema.nullable(),
125
+ approved_at: TimestampSchema.nullable(),
126
+ rationale: RationaleSchema.nullable(),
127
+ })
128
+ .strict()
129
+ .superRefine((approval, ctx) => {
130
+ const complete = approval.approved_by !== null && approval.approved_at !== null && approval.rationale !== null;
131
+ if (approval.status === "approved" && !complete) {
132
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "approved 状态必须包含批准人、批准时间和批准理由" });
133
+ }
134
+ if (approval.status !== "approved" && (approval.approved_by !== null || approval.approved_at !== null)) {
135
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "非 approved 状态不得携带批准人或批准时间" });
136
+ }
137
+ });
138
+ export const EvaluationPlanStageSchema = z
139
+ .object({
140
+ stage: z.enum(ISO_25040_STAGES),
141
+ owner: TextSchema,
142
+ objective: TextSchema,
143
+ entry_criteria: z.array(TextSchema).min(1),
144
+ activities: z.array(TextSchema).min(1),
145
+ planned_outputs: z.array(TextSchema).min(1),
146
+ exit_criteria: z.array(TextSchema).min(1),
147
+ })
148
+ .strict();
149
+ export const AcceptancePolicySchema = z
150
+ .object({
151
+ require_all_requirements: z.literal(true),
152
+ require_all_tests: z.literal(true),
153
+ require_all_stages: z.literal(true),
154
+ require_independent_evaluator: z.boolean(),
155
+ maximum_residual_risk_level: z.enum(RISK_LEVELS),
156
+ authorized_risk_acceptors: z.array(TextSchema).min(1),
157
+ })
158
+ .strict();
159
+ function addDuplicateIssues(values, pathName, ctx) {
160
+ const seen = new Set();
161
+ values.forEach((value, index) => {
162
+ if (seen.has(value)) {
163
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: [pathName, index, "id"], message: `重复 ID "${value}"` });
164
+ }
165
+ seen.add(value);
166
+ });
167
+ }
168
+ function addDuplicateReferenceIssues(values, pathPrefix, ctx) {
169
+ const seen = new Set();
170
+ values.forEach((value, index) => {
171
+ if (seen.has(value)) {
172
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: [...pathPrefix, index], message: `重复引用 "${value}"` });
173
+ }
174
+ seen.add(value);
175
+ });
176
+ }
177
+ function requireExactEnumSet(values, expected, pathName, ctx) {
178
+ const actual = new Set(values);
179
+ for (const value of expected) {
180
+ if (!actual.has(value)) {
181
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: [pathName], message: `缺少必需项 "${value}"` });
182
+ }
183
+ }
184
+ }
185
+ export const AcceptanceBaselineSchema = z
186
+ .object({
187
+ schema_version: z.literal(ACCEPTANCE_SCHEMA_VERSION),
188
+ baseline_id: IdSchema,
189
+ baseline_version: SemVerSchema,
190
+ title: TextSchema,
191
+ product: TextSchema,
192
+ scope: TextSchema,
193
+ created_at: TimestampSchema,
194
+ approval: BaselineApprovalSchema,
195
+ characteristics: z.array(QualityCharacteristicSchema).length(ISO_25010_CHARACTERISTICS.length),
196
+ requirements: z.array(QualityRequirementSchema).min(1),
197
+ risks: z.array(AcceptanceRiskSchema).min(1),
198
+ tests: z.array(AcceptanceTestSchema).min(1),
199
+ evaluation_plan: z.array(EvaluationPlanStageSchema).length(ISO_25040_STAGES.length),
200
+ acceptance_policy: AcceptancePolicySchema,
201
+ })
202
+ .strict()
203
+ .superRefine((baseline, ctx) => {
204
+ addDuplicateIssues(baseline.characteristics.map((item) => item.id), "characteristics", ctx);
205
+ addDuplicateIssues(baseline.requirements.map((item) => item.id), "requirements", ctx);
206
+ addDuplicateIssues(baseline.risks.map((item) => item.id), "risks", ctx);
207
+ addDuplicateIssues(baseline.tests.map((item) => item.id), "tests", ctx);
208
+ addDuplicateIssues(baseline.evaluation_plan.map((item) => item.stage), "evaluation_plan", ctx);
209
+ requireExactEnumSet(baseline.characteristics.map((item) => item.id), ISO_25010_CHARACTERISTICS, "characteristics", ctx);
210
+ requireExactEnumSet(baseline.evaluation_plan.map((item) => item.stage), ISO_25040_STAGES, "evaluation_plan", ctx);
211
+ const characteristicById = new Map(baseline.characteristics.map((item) => [item.id, item]));
212
+ const requirementById = new Map(baseline.requirements.map((item) => [item.id, item]));
213
+ const riskById = new Map(baseline.risks.map((item) => [item.id, item]));
214
+ const testById = new Map(baseline.tests.map((item) => [item.id, item]));
215
+ for (const characteristic of baseline.characteristics) {
216
+ const linked = baseline.requirements.filter((item) => item.characteristic === characteristic.id);
217
+ if (characteristic.applicable && linked.length === 0) {
218
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["characteristics"], message: `适用特性 "${characteristic.id}" 至少需要一条量化需求` });
219
+ }
220
+ if (!characteristic.applicable && linked.length > 0) {
221
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["requirements"], message: `已裁剪特性 "${characteristic.id}" 不得关联质量需求` });
222
+ }
223
+ }
224
+ baseline.requirements.forEach((requirement, requirementIndex) => {
225
+ if (!characteristicById.get(requirement.characteristic)?.applicable) {
226
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["requirements", requirementIndex, "characteristic"], message: `需求关联了未纳入的特性 "${requirement.characteristic}"` });
227
+ }
228
+ addDuplicateReferenceIssues(requirement.risk_ids, ["requirements", requirementIndex, "risk_ids"], ctx);
229
+ addDuplicateReferenceIssues(requirement.test_ids, ["requirements", requirementIndex, "test_ids"], ctx);
230
+ requirement.risk_ids.forEach((riskId, index) => {
231
+ const risk = riskById.get(riskId);
232
+ if (!risk) {
233
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["requirements", requirementIndex, "risk_ids", index], message: `未知 risk 引用 "${riskId}"` });
234
+ }
235
+ else if (!risk.requirement_ids.includes(requirement.id)) {
236
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["requirements", requirementIndex, "risk_ids", index], message: `requirement-risk 反向追踪缺失:"${riskId}" 未引用 "${requirement.id}"` });
237
+ }
238
+ });
239
+ requirement.test_ids.forEach((testId, index) => {
240
+ const acceptanceTest = testById.get(testId);
241
+ if (!acceptanceTest) {
242
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["requirements", requirementIndex, "test_ids", index], message: `未知 test 引用 "${testId}"` });
243
+ }
244
+ else if (!acceptanceTest.requirement_ids.includes(requirement.id)) {
245
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["requirements", requirementIndex, "test_ids", index], message: `requirement-test 反向追踪缺失:"${testId}" 未引用 "${requirement.id}"` });
246
+ }
247
+ });
248
+ });
249
+ baseline.risks.forEach((risk, riskIndex) => {
250
+ addDuplicateReferenceIssues(risk.requirement_ids, ["risks", riskIndex, "requirement_ids"], ctx);
251
+ addDuplicateReferenceIssues(risk.test_ids, ["risks", riskIndex, "test_ids"], ctx);
252
+ risk.requirement_ids.forEach((requirementId, index) => {
253
+ const requirement = requirementById.get(requirementId);
254
+ if (!requirement) {
255
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["risks", riskIndex, "requirement_ids", index], message: `未知 requirement 引用 "${requirementId}"` });
256
+ }
257
+ else if (!requirement.risk_ids.includes(risk.id)) {
258
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["risks", riskIndex, "requirement_ids", index], message: `risk-requirement 反向追踪缺失:"${requirementId}" 未引用 "${risk.id}"` });
259
+ }
260
+ });
261
+ risk.test_ids.forEach((testId, index) => {
262
+ const acceptanceTest = testById.get(testId);
263
+ if (!acceptanceTest) {
264
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["risks", riskIndex, "test_ids", index], message: `未知 test 引用 "${testId}"` });
265
+ }
266
+ else if (!acceptanceTest.risk_ids.includes(risk.id)) {
267
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["risks", riskIndex, "test_ids", index], message: `risk-test 反向追踪缺失:"${testId}" 未引用 "${risk.id}"` });
268
+ }
269
+ });
270
+ });
271
+ baseline.tests.forEach((acceptanceTest, testIndex) => {
272
+ addDuplicateReferenceIssues(acceptanceTest.requirement_ids, ["tests", testIndex, "requirement_ids"], ctx);
273
+ addDuplicateReferenceIssues(acceptanceTest.risk_ids, ["tests", testIndex, "risk_ids"], ctx);
274
+ acceptanceTest.requirement_ids.forEach((requirementId, index) => {
275
+ const requirement = requirementById.get(requirementId);
276
+ if (!requirement) {
277
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["tests", testIndex, "requirement_ids", index], message: `未知 requirement 引用 "${requirementId}"` });
278
+ }
279
+ else if (!requirement.test_ids.includes(acceptanceTest.id)) {
280
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["tests", testIndex, "requirement_ids", index], message: `test-requirement 反向追踪缺失:"${requirementId}" 未引用 "${acceptanceTest.id}"` });
281
+ }
282
+ });
283
+ acceptanceTest.risk_ids.forEach((riskId, index) => {
284
+ const risk = riskById.get(riskId);
285
+ if (!risk) {
286
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["tests", testIndex, "risk_ids", index], message: `未知 risk 引用 "${riskId}"` });
287
+ }
288
+ else if (!risk.test_ids.includes(acceptanceTest.id)) {
289
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["tests", testIndex, "risk_ids", index], message: `test-risk 反向追踪缺失:"${riskId}" 未引用 "${acceptanceTest.id}"` });
290
+ }
291
+ });
292
+ for (const requirementId of acceptanceTest.requirement_ids) {
293
+ const requirement = requirementById.get(requirementId);
294
+ if (requirement && !acceptanceTest.risk_ids.some((riskId) => requirement.risk_ids.includes(riskId))) {
295
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["tests", testIndex], message: `测试 "${acceptanceTest.id}" 与需求 "${requirementId}" 没有共同风险,追踪链不完整` });
296
+ }
297
+ }
298
+ });
299
+ });
300
+ export const AcceptanceEvidenceSchema = z
301
+ .object({
302
+ id: IdSchema,
303
+ kind: z.enum(["test_result", "benchmark", "audit", "inspection", "review", "measurement", "certificate"]),
304
+ locator: TextSchema,
305
+ captured_at: TimestampSchema,
306
+ sha256: Sha256Schema,
307
+ produced_by: TextSchema,
308
+ summary: TextSchema,
309
+ })
310
+ .strict();
311
+ export const EvaluationStageResultSchema = z
312
+ .object({
313
+ stage: z.enum(ISO_25040_STAGES),
314
+ status: z.enum(["completed", "blocked", "not_started"]),
315
+ started_at: TimestampSchema.nullable(),
316
+ completed_at: TimestampSchema.nullable(),
317
+ artifact_evidence_ids: z.array(IdSchema),
318
+ result_note: TextSchema,
319
+ })
320
+ .strict()
321
+ .superRefine((stage, ctx) => {
322
+ if (stage.status === "completed" && (stage.started_at === null || stage.completed_at === null || stage.artifact_evidence_ids.length === 0)) {
323
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "completed 阶段必须包含起止时间和至少一项产物证据" });
324
+ }
325
+ if (stage.started_at !== null && stage.completed_at !== null && Date.parse(stage.completed_at) < Date.parse(stage.started_at)) {
326
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["completed_at"], message: "完成时间不得早于开始时间" });
327
+ }
328
+ });
329
+ export const RequirementMeasurementSchema = z
330
+ .object({
331
+ requirement_id: IdSchema,
332
+ observed_value: z.number().finite(),
333
+ measured_at: TimestampSchema,
334
+ evidence_ids: z.array(IdSchema).min(1),
335
+ })
336
+ .strict();
337
+ export const AcceptanceTestResultSchema = z
338
+ .object({
339
+ test_id: IdSchema,
340
+ status: z.enum(["passed", "failed", "blocked", "not_run"]),
341
+ executed_at: TimestampSchema,
342
+ evidence_ids: z.array(IdSchema).min(1),
343
+ result_note: TextSchema,
344
+ })
345
+ .strict();
346
+ export const ResidualRiskAcceptanceSchema = z
347
+ .object({
348
+ accepted_by: TextSchema,
349
+ accepted_at: TimestampSchema,
350
+ rationale: RationaleSchema,
351
+ review_due: TimestampSchema,
352
+ })
353
+ .strict();
354
+ export const ResidualRiskResultSchema = z
355
+ .object({
356
+ risk_id: IdSchema,
357
+ residual_level: z.enum(RISK_LEVELS),
358
+ disposition: z.enum(["mitigated", "accepted", "open"]),
359
+ acceptance: ResidualRiskAcceptanceSchema.nullable(),
360
+ evidence_ids: z.array(IdSchema).min(1),
361
+ })
362
+ .strict()
363
+ .superRefine((risk, ctx) => {
364
+ if (risk.disposition === "accepted" && risk.acceptance === null) {
365
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["acceptance"], message: "accepted 残余风险必须包含正式接受记录" });
366
+ }
367
+ if (risk.disposition !== "accepted" && risk.acceptance !== null) {
368
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["acceptance"], message: "非 accepted 残余风险不得携带接受记录" });
369
+ }
370
+ });
371
+ export const AcceptanceEvaluationSchema = z
372
+ .object({
373
+ schema_version: z.literal(ACCEPTANCE_SCHEMA_VERSION),
374
+ report_id: IdSchema,
375
+ evaluation_id: IdSchema,
376
+ baseline_id: IdSchema,
377
+ baseline_version: SemVerSchema,
378
+ evaluated_at: TimestampSchema,
379
+ report_generated_at: TimestampSchema,
380
+ evaluator: z
381
+ .object({
382
+ name: TextSchema,
383
+ organization: TextSchema,
384
+ role: TextSchema,
385
+ independent: z.boolean(),
386
+ })
387
+ .strict(),
388
+ stage_results: z.array(EvaluationStageResultSchema).length(ISO_25040_STAGES.length),
389
+ evidence: z.array(AcceptanceEvidenceSchema).min(1),
390
+ measurements: z.array(RequirementMeasurementSchema).min(1),
391
+ test_results: z.array(AcceptanceTestResultSchema).min(1),
392
+ residual_risks: z.array(ResidualRiskResultSchema).min(1),
393
+ })
394
+ .strict()
395
+ .superRefine((evaluation, ctx) => {
396
+ addDuplicateIssues(evaluation.stage_results.map((item) => item.stage), "stage_results", ctx);
397
+ addDuplicateIssues(evaluation.evidence.map((item) => item.id), "evidence", ctx);
398
+ addDuplicateIssues(evaluation.measurements.map((item) => item.requirement_id), "measurements", ctx);
399
+ addDuplicateIssues(evaluation.test_results.map((item) => item.test_id), "test_results", ctx);
400
+ addDuplicateIssues(evaluation.residual_risks.map((item) => item.risk_id), "residual_risks", ctx);
401
+ requireExactEnumSet(evaluation.stage_results.map((item) => item.stage), ISO_25040_STAGES, "stage_results", ctx);
402
+ if (Date.parse(evaluation.report_generated_at) < Date.parse(evaluation.evaluated_at)) {
403
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["report_generated_at"], message: "报告生成时间不得早于评价时间" });
404
+ }
405
+ });
406
+ function safeSegment(value) {
407
+ return IdSchema.parse(value);
408
+ }
409
+ export function acceptanceBaselinePath(root, baselineId, baselineVersion) {
410
+ return pmPath(path.resolve(root), "acceptance", "baselines", safeSegment(baselineId), `${SemVerSchema.parse(baselineVersion)}.json`);
411
+ }
412
+ export function loadAcceptanceBaseline(root, baselineId, baselineVersion) {
413
+ const file = acceptanceBaselinePath(root, baselineId, baselineVersion);
414
+ const value = loadJson(file, AcceptanceBaselineSchema);
415
+ if (value === undefined)
416
+ throw new Error(`验收基线不存在:${file}`);
417
+ return value;
418
+ }
419
+ /**
420
+ * Draft baselines can be refined. Once approved, the same ID/version is
421
+ * immutable; changes require a new semantic version.
422
+ */
423
+ export function saveAcceptanceBaseline(root, input) {
424
+ const parsed = AcceptanceBaselineSchema.parse(input);
425
+ return withLedgerLock(path.resolve(root), () => {
426
+ const file = acceptanceBaselinePath(root, parsed.baseline_id, parsed.baseline_version);
427
+ fs.mkdirSync(path.dirname(file), { recursive: true });
428
+ const existing = loadJson(file, AcceptanceBaselineSchema);
429
+ if (existing?.approval.status === "approved") {
430
+ const before = JSON.stringify(existing);
431
+ const after = JSON.stringify(parsed);
432
+ if (before !== after) {
433
+ throw new Error(`已批准验收基线不可覆盖:${parsed.baseline_id}@${parsed.baseline_version};请创建新版本。`);
434
+ }
435
+ return existing;
436
+ }
437
+ saveJson(file, parsed);
438
+ return parsed;
439
+ });
440
+ }