create-yss-spec 3.3.4 → 3.3.7

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 (171) hide show
  1. package/README.md +2 -2
  2. package/package.json +1 -1
  3. package/src/template/instance-runtime.js +8 -2
  4. package/src/validation/snapshot.js +7 -0
  5. package/template/.agents/skills/.strategic-design-skills-manifest.json +7 -5
  6. package/template/.agents/skills/archify/SKILL.md +1 -1
  7. package/template/.agents/skills/code-review/SKILL.md +6 -0
  8. package/template/.agents/skills/i-have-adhd/LICENSE +21 -0
  9. package/template/.agents/skills/i-have-adhd/SKILL.md +26 -0
  10. package/template/.agents/skills/i-have-adhd/agents/openai.yaml +6 -0
  11. package/template/.agents/skills/i-have-adhd/references/yss-adaptation.md +21 -0
  12. package/template/.agents/skills/i-have-adhd/tests/verify-integration.mjs +63 -0
  13. package/template/.agents/skills/prototype-review/SKILL.md +2 -0
  14. package/template/.agents/skills/yss-implementation-contract-compiler/SKILL.md +6 -0
  15. package/template/.agents/skills/yss-implementation-contract-compiler/references/boundaries.md +10 -0
  16. package/template/.agents/skills/yss-openapi-governance/SKILL.md +6 -0
  17. package/template/.agents/skills/yss-product-lifecycle/SKILL.md +3 -1
  18. package/template/.agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml +12 -0
  19. package/template/.agents/skills/yss-prototype-stage/SKILL.md +6 -0
  20. package/template/.agents/skills/yss-research/SKILL.md +6 -0
  21. package/template/.agents/skills/yss-stage-decision/SKILL.md +19 -4
  22. package/template/.agents/skills/yss-stage-decision/references/domain-strategy-contract.md +4 -3
  23. package/template/.agents/skills/yss-stage-decision/references/domain-strategy-v2.schema.json +8 -0
  24. package/template/.agents/skills/yss-stage-decision/references/domain-strategy-v3.schema.json +8 -0
  25. package/template/.agents/skills/yss-stage-decision/references/domain-strategy.schema.json +75 -5
  26. package/template/.agents/skills/yss-stage-decision/references/stage-decision-package-contract.md +4 -4
  27. package/template/.agents/skills/yss-stage-decision/references/stage-decision-package-v2.schema.json +8 -0
  28. package/template/.agents/skills/yss-stage-decision/references/stage-decision-package-v3.schema.json +8 -0
  29. package/template/.agents/skills/yss-stage-decision/references/stage-decision-package.schema.json +59 -9
  30. package/template/.agents/skills/yss-stage-decision/references/validation-rules.md +2 -2
  31. package/template/.agents/skills/yss-stage-decision/scripts/migrate-contract-v3.mjs +143 -0
  32. package/template/.agents/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +46 -4
  33. package/template/.agents/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +48 -4
  34. package/template/.agents/skills/yss-stage-decision/tests/fixtures/valid-stage-decision-package.yaml +42 -13
  35. package/template/.agents/skills/yss-stage-decision/tests/fixtures/valid-supplier-domain.yaml +37 -5
  36. package/template/.agents/skills/yss-stage-decision/tests/run-scenarios.mjs +116 -23
  37. package/template/.agents/skills/yss-technical-design/SKILL.md +6 -0
  38. package/template/.codex/skills/archify/SKILL.md +1 -1
  39. package/template/.codex/skills/code-review/SKILL.md +6 -0
  40. package/template/.codex/skills/i-have-adhd/LICENSE +21 -0
  41. package/template/.codex/skills/i-have-adhd/SKILL.md +26 -0
  42. package/template/.codex/skills/i-have-adhd/agents/openai.yaml +6 -0
  43. package/template/.codex/skills/i-have-adhd/references/yss-adaptation.md +21 -0
  44. package/template/.codex/skills/i-have-adhd/tests/verify-integration.mjs +63 -0
  45. package/template/.codex/skills/prototype-review/SKILL.md +2 -0
  46. package/template/.codex/skills/yss-implementation-contract-compiler/SKILL.md +6 -0
  47. package/template/.codex/skills/yss-implementation-contract-compiler/references/boundaries.md +10 -0
  48. package/template/.codex/skills/yss-openapi-governance/SKILL.md +6 -0
  49. package/template/.codex/skills/yss-product-lifecycle/SKILL.md +3 -1
  50. package/template/.codex/skills/yss-product-lifecycle/references/orchestration-contract.yaml +12 -0
  51. package/template/.codex/skills/yss-prototype-stage/SKILL.md +6 -0
  52. package/template/.codex/skills/yss-research/SKILL.md +6 -0
  53. package/template/.codex/skills/yss-stage-decision/SKILL.md +19 -4
  54. package/template/.codex/skills/yss-stage-decision/references/domain-strategy-contract.md +4 -3
  55. package/template/.codex/skills/yss-stage-decision/references/domain-strategy-v2.schema.json +8 -0
  56. package/template/.codex/skills/yss-stage-decision/references/domain-strategy-v3.schema.json +8 -0
  57. package/template/.codex/skills/yss-stage-decision/references/domain-strategy.schema.json +75 -5
  58. package/template/.codex/skills/yss-stage-decision/references/stage-decision-package-contract.md +4 -4
  59. package/template/.codex/skills/yss-stage-decision/references/stage-decision-package-v2.schema.json +8 -0
  60. package/template/.codex/skills/yss-stage-decision/references/stage-decision-package-v3.schema.json +8 -0
  61. package/template/.codex/skills/yss-stage-decision/references/stage-decision-package.schema.json +59 -9
  62. package/template/.codex/skills/yss-stage-decision/references/validation-rules.md +2 -2
  63. package/template/.codex/skills/yss-stage-decision/scripts/migrate-contract-v3.mjs +143 -0
  64. package/template/.codex/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +46 -4
  65. package/template/.codex/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +48 -4
  66. package/template/.codex/skills/yss-stage-decision/tests/fixtures/valid-stage-decision-package.yaml +42 -13
  67. package/template/.codex/skills/yss-stage-decision/tests/fixtures/valid-supplier-domain.yaml +37 -5
  68. package/template/.codex/skills/yss-stage-decision/tests/run-scenarios.mjs +116 -23
  69. package/template/.codex/skills/yss-technical-design/SKILL.md +6 -0
  70. package/template/.cursor/skills/archify/SKILL.md +1 -1
  71. package/template/.cursor/skills/code-review/SKILL.md +6 -0
  72. package/template/.cursor/skills/i-have-adhd/LICENSE +21 -0
  73. package/template/.cursor/skills/i-have-adhd/SKILL.md +26 -0
  74. package/template/.cursor/skills/i-have-adhd/agents/openai.yaml +6 -0
  75. package/template/.cursor/skills/i-have-adhd/references/yss-adaptation.md +21 -0
  76. package/template/.cursor/skills/i-have-adhd/tests/verify-integration.mjs +63 -0
  77. package/template/.cursor/skills/prototype-review/SKILL.md +2 -0
  78. package/template/.cursor/skills/yss-implementation-contract-compiler/SKILL.md +6 -0
  79. package/template/.cursor/skills/yss-implementation-contract-compiler/references/boundaries.md +10 -0
  80. package/template/.cursor/skills/yss-openapi-governance/SKILL.md +6 -0
  81. package/template/.cursor/skills/yss-product-lifecycle/SKILL.md +3 -1
  82. package/template/.cursor/skills/yss-product-lifecycle/references/orchestration-contract.yaml +12 -0
  83. package/template/.cursor/skills/yss-prototype-stage/SKILL.md +6 -0
  84. package/template/.cursor/skills/yss-research/SKILL.md +6 -0
  85. package/template/.cursor/skills/yss-stage-decision/SKILL.md +19 -4
  86. package/template/.cursor/skills/yss-stage-decision/references/domain-strategy-contract.md +4 -3
  87. package/template/.cursor/skills/yss-stage-decision/references/domain-strategy-v2.schema.json +8 -0
  88. package/template/.cursor/skills/yss-stage-decision/references/domain-strategy-v3.schema.json +8 -0
  89. package/template/.cursor/skills/yss-stage-decision/references/domain-strategy.schema.json +75 -5
  90. package/template/.cursor/skills/yss-stage-decision/references/stage-decision-package-contract.md +4 -4
  91. package/template/.cursor/skills/yss-stage-decision/references/stage-decision-package-v2.schema.json +8 -0
  92. package/template/.cursor/skills/yss-stage-decision/references/stage-decision-package-v3.schema.json +8 -0
  93. package/template/.cursor/skills/yss-stage-decision/references/stage-decision-package.schema.json +59 -9
  94. package/template/.cursor/skills/yss-stage-decision/references/validation-rules.md +2 -2
  95. package/template/.cursor/skills/yss-stage-decision/scripts/migrate-contract-v3.mjs +143 -0
  96. package/template/.cursor/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +46 -4
  97. package/template/.cursor/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +48 -4
  98. package/template/.cursor/skills/yss-stage-decision/tests/fixtures/valid-stage-decision-package.yaml +42 -13
  99. package/template/.cursor/skills/yss-stage-decision/tests/fixtures/valid-supplier-domain.yaml +37 -5
  100. package/template/.cursor/skills/yss-stage-decision/tests/run-scenarios.mjs +116 -23
  101. package/template/.cursor/skills/yss-technical-design/SKILL.md +6 -0
  102. package/template/.pi/skills/archify/SKILL.md +1 -1
  103. package/template/.pi/skills/code-review/SKILL.md +6 -0
  104. package/template/.pi/skills/i-have-adhd/LICENSE +21 -0
  105. package/template/.pi/skills/i-have-adhd/SKILL.md +26 -0
  106. package/template/.pi/skills/i-have-adhd/agents/openai.yaml +6 -0
  107. package/template/.pi/skills/i-have-adhd/references/yss-adaptation.md +21 -0
  108. package/template/.pi/skills/i-have-adhd/tests/verify-integration.mjs +63 -0
  109. package/template/.pi/skills/prototype-review/SKILL.md +2 -0
  110. package/template/.pi/skills/yss-implementation-contract-compiler/SKILL.md +6 -0
  111. package/template/.pi/skills/yss-implementation-contract-compiler/references/boundaries.md +10 -0
  112. package/template/.pi/skills/yss-openapi-governance/SKILL.md +6 -0
  113. package/template/.pi/skills/yss-product-lifecycle/SKILL.md +3 -1
  114. package/template/.pi/skills/yss-product-lifecycle/references/orchestration-contract.yaml +12 -0
  115. package/template/.pi/skills/yss-prototype-stage/SKILL.md +6 -0
  116. package/template/.pi/skills/yss-research/SKILL.md +6 -0
  117. package/template/.pi/skills/yss-stage-decision/SKILL.md +19 -4
  118. package/template/.pi/skills/yss-stage-decision/references/domain-strategy-contract.md +4 -3
  119. package/template/.pi/skills/yss-stage-decision/references/domain-strategy-v2.schema.json +8 -0
  120. package/template/.pi/skills/yss-stage-decision/references/domain-strategy-v3.schema.json +8 -0
  121. package/template/.pi/skills/yss-stage-decision/references/domain-strategy.schema.json +75 -5
  122. package/template/.pi/skills/yss-stage-decision/references/stage-decision-package-contract.md +4 -4
  123. package/template/.pi/skills/yss-stage-decision/references/stage-decision-package-v2.schema.json +8 -0
  124. package/template/.pi/skills/yss-stage-decision/references/stage-decision-package-v3.schema.json +8 -0
  125. package/template/.pi/skills/yss-stage-decision/references/stage-decision-package.schema.json +59 -9
  126. package/template/.pi/skills/yss-stage-decision/references/validation-rules.md +2 -2
  127. package/template/.pi/skills/yss-stage-decision/scripts/migrate-contract-v3.mjs +143 -0
  128. package/template/.pi/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +46 -4
  129. package/template/.pi/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +48 -4
  130. package/template/.pi/skills/yss-stage-decision/tests/fixtures/valid-stage-decision-package.yaml +42 -13
  131. package/template/.pi/skills/yss-stage-decision/tests/fixtures/valid-supplier-domain.yaml +37 -5
  132. package/template/.pi/skills/yss-stage-decision/tests/run-scenarios.mjs +116 -23
  133. package/template/.pi/skills/yss-technical-design/SKILL.md +6 -0
  134. package/template/docs/agents/yss-skill-registry.yaml +22 -0
  135. package/template/docs/plan/templates/plan-template.md +8 -4
  136. package/template/docs/process/document-writing.md +77 -0
  137. package/template/docs/process/frontend-backend-delivery.md +7 -4
  138. package/template/docs/process/schemas/backend-delivery.schema.json +4 -0
  139. package/template/docs/process/schemas/frontend-delivery-acceptance-v1.schema.json +200 -0
  140. package/template/docs/process/schemas/frontend-delivery-acceptance.schema.json +37 -3
  141. package/template/docs/process/schemas/frontend-strategic-preflight.schema.json +32 -0
  142. package/template/docs/process/schemas/lifecycle-checkpoint.schema.json +2 -1
  143. package/template/docs/process/schemas/strategic-consumer-feedback.schema.json +40 -0
  144. package/template/docs/process/schemas/strategic-design-handoff-v3.schema.json +743 -0
  145. package/template/docs/process/schemas/strategic-design-handoff-v4.schema.json +712 -0
  146. package/template/docs/process/schemas/strategic-feedback-adjudication.schema.json +42 -0
  147. package/template/docs/process/schemas/strategic-handoff-domain-strategy-v3.schema.json +216 -0
  148. package/template/docs/process/schemas/strategic-handoff-import-receipt.schema.json +39 -0
  149. package/template/docs/process/schemas/strategic-handoff-stage-decision-package-v3.schema.json +148 -0
  150. package/template/docs/process/strategic-handoff-package.md +37 -4
  151. package/template/docs/process/templates/lifecycle-checkpoint-template.yaml +1 -0
  152. package/template/docs/process/templates/stage-checkpoint-template.md +1 -0
  153. package/template/docs/templates/examples/lifecycle-writing-examples.md +138 -0
  154. package/template/docs/templates/spec-template.md +16 -3
  155. package/template/docs/templates/vertical-slice-ticket-template.md +8 -4
  156. package/template/scripts/lib/approval-record.mjs +2 -1
  157. package/template/scripts/lib/backend-delivery.mjs +15 -3
  158. package/template/scripts/lib/frontend-delivery-boundary.mjs +20 -4
  159. package/template/scripts/lib/frontend-delivery.mjs +66 -2
  160. package/template/scripts/lib/profile-skill-sync.mjs +336 -0
  161. package/template/scripts/lib/strategic-feedback.mjs +33 -0
  162. package/template/scripts/lib/strategic-handoff-consumption.mjs +5 -0
  163. package/template/scripts/lib/strategic-handoff-io.mjs +1 -0
  164. package/template/scripts/lib/strategic-handoff.mjs +101 -10
  165. package/template/scripts/lib/user-decision-reuse.mjs +38 -0
  166. package/template/scripts/strategic-feedback +10 -0
  167. package/template/scripts/sync-profile-skills +43 -0
  168. package/template/scripts/verify-frontend-strategic-preflight +12 -0
  169. package/template/skills-lock.json +38 -20
  170. package/template.manifest.json +1 -0
  171. package/template.snapshot.json +6 -5
@@ -18,6 +18,7 @@ const idPatterns = {
18
18
  concept_id: /^domain-concept\.[a-z0-9][a-z0-9-]*$/,
19
19
  invariant_id: /^invariant\.[a-z0-9][a-z0-9-]*$/
20
20
  };
21
+ const consumerCapabilities = new Set(["backend-technical-design", "frontend-engineering-design", "delivery-coordination"]);
21
22
  const relationshipPatterns = new Set(["Partnership", "Customer/Supplier", "Conformist", "Anti-Corruption Layer", "Open Host Service", "Published Language", "Shared Kernel", "Separate Ways"]);
22
23
 
23
24
  function fail(errors) {
@@ -53,7 +54,7 @@ function validate(data, contextRoot) {
53
54
  if (!data || typeof data !== "object" || Array.isArray(data)) return ["合同必须是对象"];
54
55
  if (data.schema_version === 1) return ["migration-required: domain strategy v1 必须迁移到 v2 context_snapshot"];
55
56
  for (const field of required) requireField(data, field, "root", errors);
56
- if (data.schema_version !== 2) errors.push("schema_version 必须为 2");
57
+ if (![2, 3].includes(data.schema_version)) errors.push("schema_version 不受支持;支持版本: 2, 3;新交付请迁移到 3");
57
58
  if (!idPatterns.domain_strategy_id.test(String(data.domain_strategy_id ?? ""))) errors.push("domain_strategy_id 格式非法");
58
59
  if (!/^v[0-9]+$/.test(String(data.domain_version ?? ""))) errors.push("domain_version 必须形如 v1");
59
60
  if (!["draft", "ready-for-human", "approved", "stale", "blocked"].includes(data.status)) errors.push("status 非法");
@@ -71,6 +72,23 @@ function validate(data, contextRoot) {
71
72
  if (context?.subdomain_type && !["Core Domain", "Supporting Subdomain", "Generic Subdomain"].includes(context.subdomain_type)) errors.push(`${path}.subdomain_type 非法`);
72
73
  }
73
74
 
75
+ const sourceIds = new Set();
76
+ if (data.schema_version === 3) {
77
+ if (data.traceability_version !== 1) errors.push("traceability_version 必须为 1");
78
+ requireArray(data, "rule_catalog", "root", errors, 1, "object");
79
+ const rules = list(data.rule_catalog) ? data.rule_catalog : [];
80
+ unique(rules.map((item) => item?.rule_id).filter(Boolean), "rule_id", errors);
81
+ for (const [index, rule] of rules.entries()) {
82
+ const path = `rule_catalog[${index}]`;
83
+ for (const field of ["rule_id", "statement", "responsible_context", "status"]) requireString(rule ?? {}, field, path, errors);
84
+ requireArray(rule ?? {}, "evidence_refs", path, errors, 1);
85
+ if (!/^rule\.[a-z0-9][a-z0-9-]*$/.test(String(rule?.rule_id ?? ""))) errors.push(`${path}.rule_id 格式非法`);
86
+ if (rule?.responsible_context && !contextIds.includes(rule.responsible_context)) errors.push(`${path}.responsible_context 未声明`);
87
+ if (rule?.status !== "confirmed") errors.push(`${path}.status 必须为 confirmed`);
88
+ if (rule?.rule_id) sourceIds.add(rule.rule_id);
89
+ }
90
+ }
91
+
74
92
  requireArray(data, "subdomains", "root", errors, 1, "object");
75
93
  const subdomains = list(data.subdomains) ? data.subdomains : [];
76
94
  unique(subdomains.map((item) => item?.subdomain_id).filter(Boolean), "subdomain_id", errors);
@@ -110,6 +128,14 @@ function validate(data, contextRoot) {
110
128
  if (scenario?.scenario_id && !idPatterns.scenario_id.test(scenario.scenario_id)) errors.push(`${path}.scenario_id 格式非法`);
111
129
  if (scenario?.responsible_context && !contextIds.includes(scenario.responsible_context)) errors.push(`${path}.responsible_context 未声明`);
112
130
  for (const consumer of scenario?.consumers ?? []) if (!contextIds.includes(consumer)) errors.push(`${path}.consumers 引用了未声明上下文: ${consumer}`);
131
+ if (scenario?.scenario_id) sourceIds.add(scenario.scenario_id);
132
+ if (data.schema_version === 3) {
133
+ requireArray(scenario ?? {}, "rule_refs", path, errors, 1);
134
+ requireArray(scenario ?? {}, "success_results", path, errors, 1);
135
+ requireArray(scenario ?? {}, "evidence_refs", path, errors, 1);
136
+ if (typeof scenario?.critical !== "boolean") errors.push(`${path}.critical 必须是 boolean`);
137
+ for (const reference of scenario?.rule_refs ?? []) if (!sourceIds.has(reference)) errors.push(`${path}.rule_refs 引用了未声明规则: ${reference}`);
138
+ }
113
139
  }
114
140
 
115
141
  const concepts = list(data.concept_candidates) ? data.concept_candidates : [];
@@ -132,6 +158,12 @@ function validate(data, contextRoot) {
132
158
  if (invariant?.invariant_id && !idPatterns.invariant_id.test(invariant.invariant_id)) errors.push(`${path}.invariant_id 格式非法`);
133
159
  if (invariant?.responsible_context && !contextIds.includes(invariant.responsible_context)) errors.push(`${path}.responsible_context 未声明`);
134
160
  for (const reference of invariant?.scenario_refs ?? []) if (!scenarioIds.includes(reference)) errors.push(`${path}.scenario_refs 引用了未声明场景: ${reference}`);
161
+ if (invariant?.invariant_id) sourceIds.add(invariant.invariant_id);
162
+ if (data.schema_version === 3) {
163
+ requireString(invariant ?? {}, "rule_ref", path, errors);
164
+ requireArray(invariant ?? {}, "evidence_refs", path, errors, 1);
165
+ if (invariant?.rule_ref && !sourceIds.has(invariant.rule_ref)) errors.push(`${path}.rule_ref 引用了未声明规则: ${invariant.rule_ref}`);
166
+ }
135
167
  }
136
168
 
137
169
  try {
@@ -141,11 +173,21 @@ function validate(data, contextRoot) {
141
173
  }
142
174
  requireArray(data, "evidence_refs", "root", errors, 1);
143
175
  const mappings = list(data.downstream_mapping) ? data.downstream_mapping : [];
176
+ if (data.schema_version === 3) unique(mappings.map((item) => item?.mapping_id).filter(Boolean), "mapping_id", errors);
144
177
  for (const [index, mapping] of mappings.entries()) {
145
178
  const path = `downstream_mapping[${index}]`;
146
- for (const field of ["domain_change", "propagation", "reapproval_condition"]) requireString(mapping ?? {}, field, path, errors);
147
- requireArray(mapping ?? {}, "impacts", path, errors, 1);
148
- if (mapping?.propagation && !["direct", "transitive", "stale"].includes(mapping.propagation)) errors.push(`${path}.propagation 非法`);
179
+ if (data.schema_version === 3) {
180
+ for (const field of ["mapping_id", "consumer_capability", "propagation", "reapproval_condition"]) requireString(mapping ?? {}, field, path, errors);
181
+ for (const field of ["source_refs", "impacts", "evidence_refs"]) requireArray(mapping ?? {}, field, path, errors, 1);
182
+ if (!/^mapping\.[a-z0-9][a-z0-9-]*$/.test(String(mapping?.mapping_id ?? ""))) errors.push(`${path}.mapping_id 格式非法`);
183
+ if (mapping?.consumer_capability && !consumerCapabilities.has(mapping.consumer_capability)) errors.push(`${path}.consumer_capability 非法`);
184
+ for (const reference of mapping?.source_refs ?? []) if (!sourceIds.has(reference)) errors.push(`${path}.source_refs 引用了未声明来源: ${reference}`);
185
+ if (mapping?.propagation && !["direct", "transitive", "not-applicable", "stale"].includes(mapping.propagation)) errors.push(`${path}.propagation 非法`);
186
+ } else {
187
+ for (const field of ["domain_change", "propagation", "reapproval_condition"]) requireString(mapping ?? {}, field, path, errors);
188
+ requireArray(mapping ?? {}, "impacts", path, errors, 1);
189
+ if (mapping?.propagation && !["direct", "transitive", "stale"].includes(mapping.propagation)) errors.push(`${path}.propagation 非法`);
190
+ }
149
191
  }
150
192
  const approval = data.approval ?? {};
151
193
  for (const field of ["approval_ref", "approver", "persisted_ref", "current_version"]) requireString(approval, field, "approval", errors);
@@ -10,6 +10,7 @@ import { verifyContextSnapshot } from "../../../../scripts/lib/context-contract.
10
10
 
11
11
  const required = ["schema_version", "stage_decision_id", "package_version", "status", "problem_statement", "target_users", "mvp", "non_goals", "success_criteria", "test_seams", "confirmed_decisions", "assumptions", "constraints", "unresolved_items", "context_snapshot", "domain_strategy_ref", "impact_assessment", "downstream_mapping", "evidence_refs", "approval"];
12
12
  const idPattern = /^stage-decision\.[a-z0-9][a-z0-9-]*$/;
13
+ const consumerCapabilities = new Set(["backend-technical-design", "frontend-engineering-design", "delivery-coordination"]);
13
14
 
14
15
  function fail(errors) {
15
16
  process.stderr.write(`${JSON.stringify({ result: "blocked", errors }, null, 2)}\n`);
@@ -18,7 +19,8 @@ function fail(errors) {
18
19
  function nonEmpty(value) { return typeof value === "string" && value.trim().length > 0; }
19
20
  function requireField(object, field, path, errors) { if (!(field in object) || object[field] === null || object[field] === undefined) errors.push(`${path}.${field} 缺失`); }
20
21
  function requireString(object, field, path, errors) { requireField(object, field, path, errors); if (field in object && !nonEmpty(object[field])) errors.push(`${path}.${field} 必须是非空字符串`); }
21
- function requireArray(object, field, path, errors, min = 0) { requireField(object, field, path, errors); if (field in object && (!Array.isArray(object[field]) || object[field].length < min)) errors.push(`${path}.${field} 必须是至少 ${min} 项的数组`); if (field in object && Array.isArray(object[field])) object[field].forEach((item, index) => { if (!nonEmpty(item)) errors.push(`${path}.${field}[${index}] 必须是非空字符串`); }); }
22
+ function requireArray(object, field, path, errors, min = 0, itemKind = "string") { requireField(object, field, path, errors); if (field in object && (!Array.isArray(object[field]) || object[field].length < min)) errors.push(`${path}.${field} 必须是至少 ${min} 项的数组`); if (field in object && Array.isArray(object[field]) && itemKind === "string") object[field].forEach((item, index) => { if (!nonEmpty(item)) errors.push(`${path}.${field}[${index}] 必须是非空字符串`); }); }
23
+ function unique(items, label, errors) { const seen = new Set(); for (const item of items) { if (seen.has(item)) errors.push(`${label} 重复: ${item}`); seen.add(item); } }
22
24
  function canonical(value) { if (Array.isArray(value)) return value.map(canonical); if (value && typeof value === "object") return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonical(value[key])])); return value; }
23
25
  function digest(value) { return `sha256:${createHash("sha256").update(JSON.stringify(canonical(value))).digest("hex")}`; }
24
26
 
@@ -27,12 +29,13 @@ async function validate(data, contextRoot) {
27
29
  if (!data || typeof data !== "object" || Array.isArray(data)) return ["合同必须是对象"];
28
30
  if (data.schema_version === 1) return ["migration-required: stage decision package v1 必须迁移到 v2 context_snapshot"];
29
31
  for (const field of required) requireField(data, field, "root", errors);
30
- if (data.schema_version !== 2) errors.push("schema_version 必须为 2");
32
+ if (![2, 3].includes(data.schema_version)) errors.push("schema_version 不受支持;支持版本: 2, 3;新交付请迁移到 3");
31
33
  if (!idPattern.test(String(data.stage_decision_id ?? ""))) errors.push("stage_decision_id 格式非法");
32
34
  if (!/^v[0-9]+$/.test(String(data.package_version ?? ""))) errors.push("package_version 必须形如 v1");
33
35
  if (!["draft", "ready-for-human", "approved", "stale", "blocked"].includes(data.status)) errors.push("status 非法");
34
36
  for (const field of ["problem_statement"]) requireString(data, field, "root", errors);
35
- for (const field of ["target_users", "mvp", "non_goals", "success_criteria", "test_seams", "confirmed_decisions", "assumptions", "constraints", "evidence_refs"]) requireArray(data, field, "root", errors, ["target_users", "mvp", "success_criteria", "test_seams", "confirmed_decisions", "evidence_refs"].includes(field) ? 1 : 0);
37
+ for (const field of ["target_users", "mvp", "non_goals", "evidence_refs"]) requireArray(data, field, "root", errors, ["target_users", "mvp", "evidence_refs"].includes(field) ? 1 : 0);
38
+ for (const field of ["success_criteria", "test_seams", "confirmed_decisions", "assumptions", "constraints"]) requireArray(data, field, "root", errors, ["success_criteria", "test_seams", "confirmed_decisions"].includes(field) ? 1 : 0, data.schema_version === 3 ? "object" : "string");
36
39
 
37
40
  const unresolved = Array.isArray(data.unresolved_items) ? data.unresolved_items : [];
38
41
  for (const [index, item] of unresolved.entries()) {
@@ -56,9 +59,38 @@ async function validate(data, contextRoot) {
56
59
  if (strategy.domain_version !== domainRef.domain_version) errors.push("domain_strategy_ref.domain_version 与实际文件不一致");
57
60
  if (strategy.status !== domainRef.status) errors.push("domain_strategy_ref.status 与实际文件不一致");
58
61
  if (domainRef.digest !== digest(strategy)) errors.push("domain_strategy_ref.digest 与实际领域战略内容不一致");
62
+ if (data.schema_version === 3 && strategy.schema_version !== 3) errors.push("Stage Decision v3 必须引用 Domain Strategy v3");
59
63
  }
60
64
  } catch (error) { errors.push(`domain_strategy_ref.persisted_ref 无法读取: ${error.message}`); }
61
65
  }
66
+
67
+ if (data.schema_version === 3) {
68
+ const sourceIds = new Set([
69
+ ...(strategy?.rule_catalog ?? []).map((item) => item?.rule_id),
70
+ ...(strategy?.scenarios ?? []).map((item) => item?.scenario_id),
71
+ ...(strategy?.invariants ?? []).map((item) => item?.invariant_id)
72
+ ].filter(Boolean));
73
+ const recordFields = {
74
+ success_criteria: "success-criterion",
75
+ test_seams: "test-seam",
76
+ confirmed_decisions: "decision",
77
+ assumptions: "assumption",
78
+ constraints: "constraint"
79
+ };
80
+ const recordIds = [];
81
+ for (const [field, prefix] of Object.entries(recordFields)) {
82
+ for (const [index, record] of (Array.isArray(data[field]) ? data[field] : []).entries()) {
83
+ const path = `${field}[${index}]`;
84
+ for (const name of ["id", "statement"]) requireString(record ?? {}, name, path, errors);
85
+ requireArray(record ?? {}, "evidence_refs", path, errors, 1);
86
+ if (!new RegExp(`^${prefix}\\.[a-z0-9][a-z0-9-]*$`).test(String(record?.id ?? ""))) errors.push(`${path}.id 格式非法`);
87
+ for (const reference of record?.source_refs ?? []) if (!sourceIds.has(reference)) errors.push(`${path}.source_refs 未在领域战略中声明: ${reference}`);
88
+ if (record?.status && !["confirmed", "candidate", "stale"].includes(record.status)) errors.push(`${path}.status 非法`);
89
+ if (record?.id) recordIds.push(record.id);
90
+ }
91
+ }
92
+ unique(recordIds, "stage record id", errors);
93
+ }
62
94
  try {
63
95
  const contextIds = Array.isArray(strategy?.contexts) ? strategy.contexts.map((item) => item?.context_id).filter(Boolean) : [];
64
96
  verifyContextSnapshot(data.context_snapshot, { root: contextRoot, allowedContextIds: contextIds });
@@ -73,9 +105,21 @@ async function validate(data, contextRoot) {
73
105
  }
74
106
  const mappings = Array.isArray(data.downstream_mapping) ? data.downstream_mapping : [];
75
107
  if (mappings.length < 1) errors.push("downstream_mapping 至少需要一项");
108
+ if (data.schema_version === 3) unique(mappings.map((item) => item?.mapping_id).filter(Boolean), "mapping_id", errors);
109
+ const strategySourceIds = new Set([
110
+ ...(strategy?.rule_catalog ?? []).map((item) => item?.rule_id),
111
+ ...(strategy?.scenarios ?? []).map((item) => item?.scenario_id),
112
+ ...(strategy?.invariants ?? []).map((item) => item?.invariant_id)
113
+ ].filter(Boolean));
76
114
  for (const [index, mapping] of mappings.entries()) {
77
115
  const path = `downstream_mapping[${index}]`;
78
- for (const field of ["domain_change", "consumer", "propagation", "reapproval_condition"]) requireString(mapping ?? {}, field, path, errors);
116
+ if (data.schema_version === 3) {
117
+ for (const field of ["mapping_id", "consumer_capability", "propagation", "reapproval_condition"]) requireString(mapping ?? {}, field, path, errors);
118
+ requireArray(mapping ?? {}, "source_refs", path, errors, 1);
119
+ requireArray(mapping ?? {}, "evidence_refs", path, errors, 1);
120
+ if (mapping?.consumer_capability && !consumerCapabilities.has(mapping.consumer_capability)) errors.push(`${path}.consumer_capability 非法`);
121
+ for (const reference of mapping?.source_refs ?? []) if (!strategySourceIds.has(reference)) errors.push(`${path}.source_refs 未在领域战略中声明: ${reference}`);
122
+ } else for (const field of ["domain_change", "consumer", "propagation", "reapproval_condition"]) requireString(mapping ?? {}, field, path, errors);
79
123
  if (mapping?.propagation && !["direct", "transitive", "not-applicable", "stale"].includes(mapping.propagation)) errors.push(`${path}.propagation 非法`);
80
124
  }
81
125
  const approval = data.approval ?? {};
@@ -1,16 +1,41 @@
1
- schema_version: 2
1
+ schema_version: 3
2
2
  stage_decision_id: stage-decision.supplier-admission
3
- package_version: v2
3
+ package_version: v3
4
4
  status: approved
5
5
  problem_statement: 合规准入决定分散,采购无法稳定判断当前有效资格。
6
6
  target_users: [采购专员、合规专员]
7
7
  mvp: [提交供应商申请、合规准入决定、采购侧有效性校验]
8
8
  non_goals: [供应商结算、采购付款、通用工作流平台]
9
- success_criteria: [新采购订单 100% 校验当前有效准入决定]
10
- test_seams: [supplier-application-submit, admission-validity-check]
11
- confirmed_decisions: [ComplianceReview 拥有准入规则语义,ProcurementExecution 只消费决定]
12
- assumptions: [供应商已有唯一 subject_ref]
13
- constraints: [保留现有采购订单流程]
9
+ success_criteria:
10
+ - id: success-criterion.current-admission
11
+ statement: 新采购订单 100% 校验当前有效准入决定。
12
+ source_refs: [rule.current-admission-required]
13
+ evidence_refs: [evidence.discovery.compliance]
14
+ status: confirmed
15
+ test_seams:
16
+ - id: test-seam.admission-validity-check
17
+ statement: admission-validity-check
18
+ source_refs: [scenario.admission-expires, invariant.current-admission-required]
19
+ evidence_refs: [evidence.discovery.compliance]
20
+ status: confirmed
21
+ confirmed_decisions:
22
+ - id: decision.compliance-owns-admission-rules
23
+ statement: ComplianceReview 拥有准入规则语义,ProcurementExecution 只消费决定。
24
+ source_refs: [rule.current-admission-required]
25
+ evidence_refs: [evidence.domain-strategy-review.supplier]
26
+ status: confirmed
27
+ assumptions:
28
+ - id: assumption.unique-subject-ref
29
+ statement: 供应商已有唯一 subject_ref。
30
+ source_refs: [scenario.supplier-submits]
31
+ evidence_refs: [evidence.discovery.supplier]
32
+ status: confirmed
33
+ constraints:
34
+ - id: constraint.preserve-procurement-flow
35
+ statement: 保留现有采购订单流程。
36
+ source_refs: [scenario.admission-expires]
37
+ evidence_refs: [evidence.discovery.compliance]
38
+ status: confirmed
14
39
  unresolved_items:
15
40
  - item: 供应商历史资料迁移范围
16
41
  type: deferred
@@ -25,7 +50,7 @@ context_snapshot:
25
50
  term_refs: [Global/Supplier, ComplianceReview/AdmissionDecision]
26
51
  domain_strategy_ref:
27
52
  domain_strategy_id: domain-strategy.supplier-admission
28
- domain_version: v2
53
+ domain_version: v3
29
54
  digest: <domain-strategy-digest>
30
55
  status: approved
31
56
  persisted_ref: domain-strategy.yaml
@@ -38,17 +63,21 @@ impact_assessment:
38
63
  cross_repo: false
39
64
  high_risk: false
40
65
  downstream_mapping:
41
- - domain_change: admission-decision-validity
42
- consumer: spec-synthesis
66
+ - mapping_id: mapping.backend-admission-validity
67
+ source_refs: [rule.current-admission-required, invariant.current-admission-required]
68
+ consumer_capability: backend-technical-design
43
69
  propagation: direct
44
70
  reapproval_condition: 领域策略版本或有效性规则变化
45
- - domain_change: admission-decision-validity
46
- consumer: ticket-decomposition
71
+ evidence_refs: [evidence.domain-strategy-review.supplier]
72
+ - mapping_id: mapping.frontend-admission-validity
73
+ source_refs: [scenario.admission-expires]
74
+ consumer_capability: frontend-engineering-design
47
75
  propagation: transitive
48
76
  reapproval_condition: Spec 基线变更
77
+ evidence_refs: [evidence.domain-strategy-review.supplier]
49
78
  evidence_refs: [evidence.discovery.supplier, evidence.domain-strategy-review.supplier]
50
79
  approval:
51
80
  approval_ref: .agents/skills/yss-stage-decision/tests/fixtures/stage-decision-approval.yaml
52
81
  approver: role.product-manager
53
82
  persisted_ref: docs/.scratch/supplier/stage-decision/stage-decision-package-v1.yaml
54
- current_version: v2
83
+ current_version: v3
@@ -1,7 +1,19 @@
1
- schema_version: 2
1
+ schema_version: 3
2
2
  domain_strategy_id: domain-strategy.supplier-admission
3
- domain_version: v2
3
+ domain_version: v3
4
4
  status: approved
5
+ traceability_version: 1
6
+ rule_catalog:
7
+ - rule_id: rule.application-material-complete
8
+ statement: 申请材料必须完整
9
+ responsible_context: SupplierManagement
10
+ status: confirmed
11
+ evidence_refs: [evidence.discovery.supplier]
12
+ - rule_id: rule.current-admission-required
13
+ statement: 新采购订单必须引用当前有效的准入决定。
14
+ responsible_context: ComplianceReview
15
+ status: confirmed
16
+ evidence_refs: [evidence.discovery.compliance]
5
17
  contexts:
6
18
  - context_id: SupplierManagement
7
19
  name: 供应商管理
@@ -64,6 +76,10 @@ scenarios:
64
76
  preconditions: [供应商资料可编辑]
65
77
  commands: [SubmitSupplierApplication]
66
78
  rules: [申请材料必须完整]
79
+ rule_refs: [rule.application-material-complete]
80
+ critical: true
81
+ success_results: [完整申请进入合规审查]
82
+ evidence_refs: [evidence.discovery.supplier]
67
83
  events: [SupplierApplicationSubmitted]
68
84
  failure_results: [材料不完整时拒绝提交]
69
85
  responsible_context: SupplierManagement
@@ -73,7 +89,11 @@ scenarios:
73
89
  actor: compliance-officer
74
90
  preconditions: [批准决定存在]
75
91
  commands: [EvaluateAdmissionValidity]
76
- rules: [过期决定不得支持新采购订单]
92
+ rules: [新采购订单必须引用当前有效的准入决定。]
93
+ rule_refs: [rule.current-admission-required]
94
+ critical: true
95
+ success_results: [过期准入决定不能支持新采购订单]
96
+ evidence_refs: [evidence.discovery.compliance]
77
97
  events: [SupplierAdmissionExpired]
78
98
  failure_results: [采购下单时资格校验失败]
79
99
  responsible_context: ComplianceReview
@@ -94,6 +114,8 @@ invariants:
94
114
  responsible_context: ComplianceReview
95
115
  scenario_refs: [scenario.admission-expires]
96
116
  verification_method: 领域场景测试和事件版本校验
117
+ rule_ref: rule.current-admission-required
118
+ evidence_refs: [evidence.discovery.compliance]
97
119
  context_snapshot:
98
120
  context_ref: CONTEXT.md
99
121
  context_schema_version: 1
@@ -101,13 +123,23 @@ context_snapshot:
101
123
  referenced_terms_digest: <referenced-terms-digest>
102
124
  term_refs: [Global/Supplier, ComplianceReview/AdmissionDecision]
103
125
  downstream_mapping:
104
- - domain_change: admission-decision-validity
126
+ - mapping_id: mapping.backend-admission-validity
127
+ source_refs: [rule.current-admission-required, invariant.current-admission-required]
128
+ consumer_capability: backend-technical-design
105
129
  impacts: [spec, openapi, procurement-acceptance-tests]
106
130
  propagation: stale
107
131
  reapproval_condition: ComplianceReview 决定和有效期重新批准
132
+ evidence_refs: [evidence.discovery.compliance]
133
+ - mapping_id: mapping.frontend-admission-validity
134
+ source_refs: [scenario.admission-expires]
135
+ consumer_capability: frontend-engineering-design
136
+ impacts: [ui, frontend]
137
+ propagation: direct
138
+ reapproval_condition: 场景结果或视觉基线变化
139
+ evidence_refs: [evidence.discovery.compliance]
108
140
  evidence_refs: [evidence.discovery.supplier, evidence.discovery.compliance]
109
141
  approval:
110
142
  approval_ref: .agents/skills/yss-stage-decision/tests/fixtures/domain-strategy-approval.yaml
111
143
  approver: role.product-manager
112
144
  persisted_ref: docs/.scratch/supplier/domain/domain-strategy-v1.yaml
113
- current_version: v2
145
+ current_version: v3
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { buildDecisionFixture } from "../../../../scripts/fixtures/user-decision/build-fixture.mjs";
3
2
  import { createHash } from "node:crypto";
4
3
  import { spawnSync } from "node:child_process";
5
4
  import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
@@ -14,6 +13,7 @@ const validator = join(testsRoot, "..", "scripts", "validate-domain-strategy.mjs
14
13
  const packageValidator = join(testsRoot, "..", "scripts", "validate-stage-decision-package.mjs");
15
14
  const contextValidator = join(projectRoot, "scripts", "verify-context-contract");
16
15
  const migrationTool = join(testsRoot, "..", "scripts", "migrate-context-references.mjs");
16
+ const v3MigrationTool = join(testsRoot, "..", "scripts", "migrate-contract-v3.mjs");
17
17
  const validTemplate = join(testsRoot, "fixtures", "valid-supplier-domain.yaml");
18
18
  const validPackageTemplate = join(testsRoot, "fixtures", "valid-stage-decision-package.yaml");
19
19
 
@@ -29,6 +29,58 @@ function digestYaml(source) {
29
29
  return `sha256:${createHash("sha256").update(JSON.stringify(canonical(document.toJS({ maxAliasCount: 0 })))).digest("hex")}`;
30
30
  }
31
31
 
32
+ function parseYaml(source) {
33
+ const document = parseDocument(source, { maxAliasCount: 0, uniqueKeys: true });
34
+ if (document.errors.length) throw new Error(document.errors[0].message);
35
+ return document.toJS({ maxAliasCount: 0 });
36
+ }
37
+
38
+ function toV2Domain(source) {
39
+ const value = structuredClone(parseYaml(source));
40
+ value.schema_version = 2;
41
+ value.domain_version = "v2";
42
+ delete value.traceability_version;
43
+ delete value.rule_catalog;
44
+ for (const scenario of value.scenarios) {
45
+ delete scenario.rule_refs;
46
+ delete scenario.critical;
47
+ delete scenario.success_results;
48
+ delete scenario.evidence_refs;
49
+ }
50
+ for (const invariant of value.invariants) {
51
+ delete invariant.rule_ref;
52
+ delete invariant.evidence_refs;
53
+ }
54
+ value.downstream_mapping = [{
55
+ domain_change: "admission-decision-validity",
56
+ impacts: ["spec", "openapi", "procurement-acceptance-tests"],
57
+ propagation: "stale",
58
+ reapproval_condition: "ComplianceReview 决定和有效期重新批准"
59
+ }];
60
+ value.approval.current_version = "v2";
61
+ return value;
62
+ }
63
+
64
+ function toV2Stage(source, domainDigest) {
65
+ const value = structuredClone(parseYaml(source));
66
+ value.schema_version = 2;
67
+ value.package_version = "v2";
68
+ for (const field of ["success_criteria", "test_seams", "confirmed_decisions", "assumptions", "constraints"]) {
69
+ value[field] = value[field].map((item) => item.statement);
70
+ }
71
+ value.domain_strategy_ref.domain_version = "v2";
72
+ value.domain_strategy_ref.digest = domainDigest;
73
+ value.domain_strategy_ref.persisted_ref = "domain-strategy-v2.yaml";
74
+ value.downstream_mapping = [{
75
+ domain_change: "admission-decision-validity",
76
+ consumer: "spec-synthesis",
77
+ propagation: "direct",
78
+ reapproval_condition: "领域策略版本或有效性规则变化"
79
+ }];
80
+ value.approval.current_version = "v2";
81
+ return value;
82
+ }
83
+
32
84
  function run(command, file, contextRoot) {
33
85
  return spawnSync(process.execPath, [command, file, "--root", contextRoot], { cwd: projectRoot, encoding: "utf8" });
34
86
  }
@@ -56,34 +108,59 @@ context_schema_version: 1
56
108
  | 准入决定 | 合规审查形成的准入结论。 | AdmissionDecision | ComplianceReview | 避免:\`审批结果\` |
57
109
  `;
58
110
 
59
- const temporaryRoot = await mkdtemp(join(tmpdir(), "yss-stage-decision-v2-"));
111
+ const temporaryRoot = await mkdtemp(join(tmpdir(), "yss-stage-decision-v3-"));
60
112
  try {
61
113
  await writeFile(join(temporaryRoot, "CONTEXT.md"), contextSource);
62
114
  const contextResult = spawnSync(process.execPath, [contextValidator, "--root", temporaryRoot, "--allowed-context", "SupplierManagement", "--allowed-context", "ComplianceReview", "--allowed-context", "ProcurementExecution", "--term-ref", "Global/Supplier", "--term-ref", "ComplianceReview/AdmissionDecision", "--json"], { cwd: projectRoot, encoding: "utf8" });
63
115
  if (contextResult.status !== 0) throw new Error(`context fixture should pass: ${contextResult.stderr}`);
64
116
  const context = JSON.parse(contextResult.stdout);
65
117
 
66
- const domainApprovalRef = join(temporaryRoot, "domain-approval.json");
67
118
  const domainSource = (await readFile(validTemplate, "utf8"))
68
- .replace(".agents/skills/yss-stage-decision/tests/fixtures/domain-strategy-approval.yaml", domainApprovalRef)
69
119
  .replace("<document-digest>", context.document_digest)
70
120
  .replace("<referenced-terms-digest>", context.referenced_terms_digest);
71
121
  const domainFile = join(temporaryRoot, "domain-strategy.yaml");
72
122
  await writeFile(domainFile, domainSource);
73
- const domainDecision = buildDecisionFixture(join(temporaryRoot, "domain-decision"), { boundary: "check.domain-strategy-approved", subjectRef: domainFile });
74
- const domainApproval = parseDocument(await readFile(join(testsRoot, "fixtures/domain-strategy-approval.yaml"), "utf8")).toJS();
75
- await writeFile(domainApprovalRef, JSON.stringify({ ...domainApproval, subject_ref: domainFile, approval_scope: domainDecision.requirement.scope, user_decision_ref: domainDecision.ref }));
76
123
  const pass = run(validator, domainFile, temporaryRoot);
77
- if (pass.status !== 0) throw new Error(`valid v2 fixture should pass: ${pass.stderr}`);
124
+ if (pass.status !== 0) throw new Error(`valid v3 fixture should pass: ${pass.stderr}`);
125
+
126
+ const v2Domain = toV2Domain(domainSource);
127
+ const v2DomainSource = JSON.stringify(v2Domain, null, 2);
128
+ const v2DomainFile = join(temporaryRoot, "domain-strategy-v2.yaml");
129
+ await writeFile(v2DomainFile, v2DomainSource);
130
+ const v2Pass = run(validator, v2DomainFile, temporaryRoot);
131
+ if (v2Pass.status !== 0) throw new Error(`read-only v2 fixture should pass: ${v2Pass.stderr}`);
132
+
133
+ const migratedDomainV3 = spawnSync(process.execPath, [v3MigrationTool, v2DomainFile, "--kind", "domain-strategy", "--root", temporaryRoot], { cwd: projectRoot, encoding: "utf8" });
134
+ if (migratedDomainV3.status !== 0) throw new Error(`v2 domain strategy should migrate: ${migratedDomainV3.stderr}`);
135
+ const migratedDomainV3Value = JSON.parse(migratedDomainV3.stdout);
136
+ if (migratedDomainV3Value.schema_version !== 3 || migratedDomainV3Value.status !== "draft" || migratedDomainV3Value.approval.approval_ref !== "migration-required") throw new Error("domain v2 -> v3 migration must reset approval and status");
137
+
138
+ const duplicateRule = join(temporaryRoot, "duplicate-rule.yaml");
139
+ const duplicateRuleValue = parseYaml(domainSource);
140
+ duplicateRuleValue.rule_catalog.push(structuredClone(duplicateRuleValue.rule_catalog[0]));
141
+ await writeFile(duplicateRule, JSON.stringify(duplicateRuleValue, null, 2));
142
+ expectBlocked(run(validator, duplicateRule, temporaryRoot), /rule_id.*重复|重复.*rule_id/, "duplicate v3 rule id");
143
+
144
+ const danglingRule = join(temporaryRoot, "dangling-rule.yaml");
145
+ const danglingRuleValue = parseYaml(domainSource);
146
+ danglingRuleValue.scenarios[0].rule_refs = ["rule.missing"];
147
+ await writeFile(danglingRule, JSON.stringify(danglingRuleValue, null, 2));
148
+ expectBlocked(run(validator, danglingRule, temporaryRoot), /rule_refs.*未声明|悬空/, "dangling v3 rule ref");
149
+
150
+ const missingEvidence = join(temporaryRoot, "missing-rule-evidence.yaml");
151
+ const missingEvidenceValue = parseYaml(domainSource);
152
+ missingEvidenceValue.rule_catalog[0].evidence_refs = [];
153
+ await writeFile(missingEvidence, JSON.stringify(missingEvidenceValue, null, 2));
154
+ expectBlocked(run(validator, missingEvidence, temporaryRoot), /evidence_refs/, "missing v3 evidence");
78
155
 
79
156
  const legacyFile = join(temporaryRoot, "legacy-domain.yaml");
80
- await writeFile(legacyFile, domainSource.replace("schema_version: 2", "schema_version: 1"));
157
+ await writeFile(legacyFile, JSON.stringify({ ...v2Domain, schema_version: 1 }, null, 2));
81
158
  expectBlocked(run(validator, legacyFile, temporaryRoot), /migration-required/, "legacy domain contract");
82
159
 
83
160
  const migratableLegacy = join(temporaryRoot, "migratable-legacy-domain.yaml");
84
- const migratableLegacySource = domainSource
85
- .replace("schema_version: 2", "schema_version: 1")
86
- .replace(/context_snapshot:\n[\s\S]*?\ndownstream_mapping:/, "terminology_refs: [CONTEXT.md#Supplier, contexts/ComplianceReview/CONTEXT.md#AdmissionDecision]\ndownstream_mapping:");
161
+ const migratableLegacyValue = { ...structuredClone(v2Domain), schema_version: 1, terminology_refs: ["CONTEXT.md#Supplier", "contexts/ComplianceReview/CONTEXT.md#AdmissionDecision"] };
162
+ delete migratableLegacyValue.context_snapshot;
163
+ const migratableLegacySource = JSON.stringify(migratableLegacyValue, null, 2);
87
164
  await writeFile(migratableLegacy, migratableLegacySource);
88
165
  const migrated = spawnSync(process.execPath, [migrationTool, migratableLegacy, "--root", temporaryRoot], { cwd: projectRoot, encoding: "utf8" });
89
166
  if (migrated.status !== 0) throw new Error(`migratable v1 contract should migrate: ${migrated.stderr}`);
@@ -91,7 +168,7 @@ try {
91
168
  if (migratedValue.schema_version !== 2 || migratedValue.terminology_refs !== undefined || migratedValue.context_snapshot.context_ref !== "CONTEXT.md" || migratedValue.context_snapshot.term_refs.join(",") !== "ComplianceReview/AdmissionDecision,Global/Supplier") throw new Error("migrated context snapshot is incomplete");
92
169
 
93
170
  const ambiguousLegacy = join(temporaryRoot, "ambiguous-legacy-domain.yaml");
94
- await writeFile(ambiguousLegacy, migratableLegacySource.replace("CONTEXT.md#Supplier, contexts/ComplianceReview/CONTEXT.md#AdmissionDecision", "contexts/ComplianceReview/CONTEXT.md"));
171
+ await writeFile(ambiguousLegacy, JSON.stringify({ ...migratableLegacyValue, terminology_refs: ["contexts/ComplianceReview/CONTEXT.md"] }, null, 2));
95
172
  const ambiguousMigration = spawnSync(process.execPath, [migrationTool, ambiguousLegacy, "--root", temporaryRoot], { cwd: projectRoot, encoding: "utf8" });
96
173
  expectBlocked(ambiguousMigration, /migration-required.*无法唯一定位|无法唯一定位.*migration-required/, "ambiguous legacy reference");
97
174
 
@@ -115,32 +192,48 @@ try {
115
192
  await writeFile(unknownContext, domainSource.replace("to_context: ComplianceReview", "to_context: UnknownContext"));
116
193
  expectBlocked(run(validator, unknownContext, temporaryRoot), /未引用已声明上下文|未在领域战略中登记/, "unknown context");
117
194
 
118
- const packageApprovalRef = join(temporaryRoot, "package-approval.json");
119
195
  const packageSource = (await readFile(validPackageTemplate, "utf8"))
120
- .replace(".agents/skills/yss-stage-decision/tests/fixtures/stage-decision-approval.yaml", packageApprovalRef)
121
196
  .replace("<document-digest>", context.document_digest)
122
197
  .replace("<referenced-terms-digest>", context.referenced_terms_digest)
123
198
  .replace("<domain-strategy-digest>", digestYaml(domainSource));
124
199
  const packageFile = join(temporaryRoot, "stage-decision.yaml");
125
200
  await writeFile(packageFile, packageSource);
126
- const packageDecision = buildDecisionFixture(join(temporaryRoot, "package-decision"), { boundary: "check.stage-decision-package-approved", subjectRef: packageFile });
127
- const packageApproval = parseDocument(await readFile(join(testsRoot, "fixtures/stage-decision-approval.yaml"), "utf8")).toJS();
128
- await writeFile(packageApprovalRef, JSON.stringify({ ...packageApproval, subject_ref: packageFile, approval_scope: packageDecision.requirement.scope, user_decision_ref: packageDecision.ref }));
129
201
  const packagePass = run(packageValidator, packageFile, temporaryRoot);
130
- if (packagePass.status !== 0) throw new Error(`valid v2 stage decision package should pass: ${packagePass.stderr}`);
202
+ if (packagePass.status !== 0) throw new Error(`valid v3 stage decision package should pass: ${packagePass.stderr}`);
203
+
204
+ const v2Stage = toV2Stage(packageSource, digestYaml(v2DomainSource));
205
+ const v2StageFile = join(temporaryRoot, "stage-decision-v2.yaml");
206
+ await writeFile(v2StageFile, JSON.stringify(v2Stage, null, 2));
207
+ const v2StagePass = run(packageValidator, v2StageFile, temporaryRoot);
208
+ if (v2StagePass.status !== 0) throw new Error(`read-only v2 stage decision package should pass: ${v2StagePass.stderr}`);
209
+
210
+ const migratedV3 = spawnSync(process.execPath, [v3MigrationTool, v2StageFile, "--kind", "stage-decision", "--root", temporaryRoot], { cwd: projectRoot, encoding: "utf8" });
211
+ if (migratedV3.status !== 0) throw new Error(`v2 stage decision should migrate: ${migratedV3.stderr}`);
212
+ const migratedV3Value = JSON.parse(migratedV3.stdout);
213
+ if (migratedV3Value.schema_version !== 3 || migratedV3Value.status !== "draft" || migratedV3Value.approval.approval_ref !== "migration-required") throw new Error("v2 -> v3 migration must reset approval and status");
214
+
215
+ const wrongSourceRef = join(temporaryRoot, "stage-decision-wrong-source-ref.yaml");
216
+ const wrongSourceRefValue = parseYaml(packageSource);
217
+ wrongSourceRefValue.success_criteria[0].source_refs = ["rule.missing"];
218
+ await writeFile(wrongSourceRef, JSON.stringify(wrongSourceRefValue, null, 2));
219
+ expectBlocked(run(packageValidator, wrongSourceRef, temporaryRoot), /source_refs.*未在领域战略中声明|悬空/, "stage decision dangling source ref");
131
220
 
132
221
  const packageLegacy = join(temporaryRoot, "legacy-stage-decision.yaml");
133
- await writeFile(packageLegacy, packageSource.replace("schema_version: 2", "schema_version: 1"));
222
+ await writeFile(packageLegacy, JSON.stringify({ ...v2Stage, schema_version: 1 }, null, 2));
134
223
  expectBlocked(run(packageValidator, packageLegacy, temporaryRoot), /migration-required/, "legacy stage decision contract");
135
224
 
136
225
  const packageBlocker = join(temporaryRoot, "stage-decision-blocker.yaml");
137
- await writeFile(packageBlocker, packageSource.replace("type: deferred", "type: blocker"));
226
+ const blockerValue = parseYaml(packageSource);
227
+ blockerValue.unresolved_items[0].type = "blocker";
228
+ await writeFile(packageBlocker, JSON.stringify(blockerValue, null, 2));
138
229
  expectBlocked(run(packageValidator, packageBlocker, temporaryRoot), /blocker/, "blocker package");
139
230
 
140
231
  const packageWrongArray = join(temporaryRoot, "stage-decision-wrong-array.yaml");
141
- await writeFile(packageWrongArray, packageSource.replace("target_users: [采购专员、合规专员]", "target_users: [42]"));
232
+ const wrongArrayValue = parseYaml(packageSource);
233
+ wrongArrayValue.target_users = [42];
234
+ await writeFile(packageWrongArray, JSON.stringify(wrongArrayValue, null, 2));
142
235
  expectBlocked(run(packageValidator, packageWrongArray, temporaryRoot), /target_users\[0\]/, "package non-string array item");
143
236
  } finally {
144
237
  await rm(temporaryRoot, { recursive: true, force: true });
145
238
  }
146
- process.stdout.write("业务边界与规则设计 v2 场景验证通过\n");
239
+ process.stdout.write("业务边界与规则设计 v2/v3 场景验证通过\n");
@@ -7,6 +7,12 @@ description: 在 YSS 技术分析中统一承接需求与战略输入,按已
7
7
 
8
8
  由 `yss-product-lifecycle` 调度,负责 `work-unit.technical-analysis` 的技术设计,不另起生命周期、不生成生产代码。生命周期维护批准和状态;`yss-implementation-contract-compiler` 消费批准且当前的设计,起草实现合同。
9
9
 
10
+ 文档输出时按 `lifecycle-document-output` 条件调用 `i-have-adhd`,读取 `docs/process/document-writing.md`;作用域仅限当前产物,派发时传递条件及引用。
11
+
12
+ ## 文档写作
13
+
14
+ 起草或修订技术设计解释正文前,读取 `docs/process/document-writing.md` 的共用写法及工程契约指引,并在分支派发中传递该引用;结构化合同保持原 schema 和验证要求。
15
+
10
16
  ## 输入与架构选择
11
17
 
12
18
  先读根 `CONTEXT.md`、已批准且当前的 Spec、业务规则、关键场景、适用的 API 契约、ADR 和工程约束。没有 API 或其他专项影响时记录有依据的不适用,不生成占位设计。
@@ -16,7 +16,7 @@ Create a self-contained, interactive HTML diagram from a small typed JSON specif
16
16
 
17
17
  When the current repository contains `yss-project.yaml`, these rules override the upstream update and delivery commands below:
18
18
 
19
- - Treat Archify as a conditional specialist. Use it only when the user or the current YSS contract explicitly requires an architecture, workflow, sequence, data-flow, lifecycle, or Mermaid-beautification artifact. A diagram is derived evidence; it never replaces Spec, ADR, OpenAPI, Tactical Design Contract, Slice Implementation Contract, review, or approval evidence.
19
+ - Treat Archify as a conditional specialist. Use it only when the user or the current YSS contract explicitly requires an architecture, workflow, sequence, data-flow, lifecycle, or Mermaid-beautification artifact. A diagram is derived evidence; it never replaces the active profile's architecture contract (such as backend Tactical Design or Frontend Engineering Design), Spec, ADR, OpenAPI, Slice Implementation Contract, review, or approval evidence.
20
20
  - Do not run `scripts/check-update.mjs`. YSS pins the upstream revision in `skills-lock.json`; upgrades happen only through the repository's skill-maintenance workflow.
21
21
  - Validate with the upstream `validate` command, but deliver through the repository-safe wrapper from the repository root:
22
22
 
@@ -13,6 +13,12 @@ Standards and Spec run as **parallel sub-agents** so they don't pollute each oth
13
13
 
14
14
  If `docs/agents/issue-tracker.md` is missing, tell the user to run `/setup-matt-pocock-skills`; do not invoke another user-invoked skill yourself.
15
15
 
16
+ 文档输出时按 `lifecycle-document-output` 条件调用 `i-have-adhd`,读取 `docs/process/document-writing.md`;作用域仅限当前产物,派发时传递条件及引用。
17
+
18
+ ## 文档写作
19
+
20
+ 撰写审查报告时,读取 `docs/process/document-writing.md` 的共用写法及审查指引;保留各审查轴的 findings、严重性、定位、证据及原裁决,不因压缩表达合并或降级。
21
+
16
22
  ## Process
17
23
 
18
24
  ### 1. Pin the fixed point and candidate
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ayoub Ghriss
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.