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.
- package/README.md +2 -2
- package/package.json +1 -1
- package/src/template/instance-runtime.js +8 -2
- package/src/validation/snapshot.js +7 -0
- package/template/.agents/skills/.strategic-design-skills-manifest.json +7 -5
- package/template/.agents/skills/archify/SKILL.md +1 -1
- package/template/.agents/skills/code-review/SKILL.md +6 -0
- package/template/.agents/skills/i-have-adhd/LICENSE +21 -0
- package/template/.agents/skills/i-have-adhd/SKILL.md +26 -0
- package/template/.agents/skills/i-have-adhd/agents/openai.yaml +6 -0
- package/template/.agents/skills/i-have-adhd/references/yss-adaptation.md +21 -0
- package/template/.agents/skills/i-have-adhd/tests/verify-integration.mjs +63 -0
- package/template/.agents/skills/prototype-review/SKILL.md +2 -0
- package/template/.agents/skills/yss-implementation-contract-compiler/SKILL.md +6 -0
- package/template/.agents/skills/yss-implementation-contract-compiler/references/boundaries.md +10 -0
- package/template/.agents/skills/yss-openapi-governance/SKILL.md +6 -0
- package/template/.agents/skills/yss-product-lifecycle/SKILL.md +3 -1
- package/template/.agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml +12 -0
- package/template/.agents/skills/yss-prototype-stage/SKILL.md +6 -0
- package/template/.agents/skills/yss-research/SKILL.md +6 -0
- package/template/.agents/skills/yss-stage-decision/SKILL.md +19 -4
- package/template/.agents/skills/yss-stage-decision/references/domain-strategy-contract.md +4 -3
- package/template/.agents/skills/yss-stage-decision/references/domain-strategy-v2.schema.json +8 -0
- package/template/.agents/skills/yss-stage-decision/references/domain-strategy-v3.schema.json +8 -0
- package/template/.agents/skills/yss-stage-decision/references/domain-strategy.schema.json +75 -5
- package/template/.agents/skills/yss-stage-decision/references/stage-decision-package-contract.md +4 -4
- package/template/.agents/skills/yss-stage-decision/references/stage-decision-package-v2.schema.json +8 -0
- package/template/.agents/skills/yss-stage-decision/references/stage-decision-package-v3.schema.json +8 -0
- package/template/.agents/skills/yss-stage-decision/references/stage-decision-package.schema.json +59 -9
- package/template/.agents/skills/yss-stage-decision/references/validation-rules.md +2 -2
- package/template/.agents/skills/yss-stage-decision/scripts/migrate-contract-v3.mjs +143 -0
- package/template/.agents/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +46 -4
- package/template/.agents/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +48 -4
- package/template/.agents/skills/yss-stage-decision/tests/fixtures/valid-stage-decision-package.yaml +42 -13
- package/template/.agents/skills/yss-stage-decision/tests/fixtures/valid-supplier-domain.yaml +37 -5
- package/template/.agents/skills/yss-stage-decision/tests/run-scenarios.mjs +116 -23
- package/template/.agents/skills/yss-technical-design/SKILL.md +6 -0
- package/template/.codex/skills/archify/SKILL.md +1 -1
- package/template/.codex/skills/code-review/SKILL.md +6 -0
- package/template/.codex/skills/i-have-adhd/LICENSE +21 -0
- package/template/.codex/skills/i-have-adhd/SKILL.md +26 -0
- package/template/.codex/skills/i-have-adhd/agents/openai.yaml +6 -0
- package/template/.codex/skills/i-have-adhd/references/yss-adaptation.md +21 -0
- package/template/.codex/skills/i-have-adhd/tests/verify-integration.mjs +63 -0
- package/template/.codex/skills/prototype-review/SKILL.md +2 -0
- package/template/.codex/skills/yss-implementation-contract-compiler/SKILL.md +6 -0
- package/template/.codex/skills/yss-implementation-contract-compiler/references/boundaries.md +10 -0
- package/template/.codex/skills/yss-openapi-governance/SKILL.md +6 -0
- package/template/.codex/skills/yss-product-lifecycle/SKILL.md +3 -1
- package/template/.codex/skills/yss-product-lifecycle/references/orchestration-contract.yaml +12 -0
- package/template/.codex/skills/yss-prototype-stage/SKILL.md +6 -0
- package/template/.codex/skills/yss-research/SKILL.md +6 -0
- package/template/.codex/skills/yss-stage-decision/SKILL.md +19 -4
- package/template/.codex/skills/yss-stage-decision/references/domain-strategy-contract.md +4 -3
- package/template/.codex/skills/yss-stage-decision/references/domain-strategy-v2.schema.json +8 -0
- package/template/.codex/skills/yss-stage-decision/references/domain-strategy-v3.schema.json +8 -0
- package/template/.codex/skills/yss-stage-decision/references/domain-strategy.schema.json +75 -5
- package/template/.codex/skills/yss-stage-decision/references/stage-decision-package-contract.md +4 -4
- package/template/.codex/skills/yss-stage-decision/references/stage-decision-package-v2.schema.json +8 -0
- package/template/.codex/skills/yss-stage-decision/references/stage-decision-package-v3.schema.json +8 -0
- package/template/.codex/skills/yss-stage-decision/references/stage-decision-package.schema.json +59 -9
- package/template/.codex/skills/yss-stage-decision/references/validation-rules.md +2 -2
- package/template/.codex/skills/yss-stage-decision/scripts/migrate-contract-v3.mjs +143 -0
- package/template/.codex/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +46 -4
- package/template/.codex/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +48 -4
- package/template/.codex/skills/yss-stage-decision/tests/fixtures/valid-stage-decision-package.yaml +42 -13
- package/template/.codex/skills/yss-stage-decision/tests/fixtures/valid-supplier-domain.yaml +37 -5
- package/template/.codex/skills/yss-stage-decision/tests/run-scenarios.mjs +116 -23
- package/template/.codex/skills/yss-technical-design/SKILL.md +6 -0
- package/template/.cursor/skills/archify/SKILL.md +1 -1
- package/template/.cursor/skills/code-review/SKILL.md +6 -0
- package/template/.cursor/skills/i-have-adhd/LICENSE +21 -0
- package/template/.cursor/skills/i-have-adhd/SKILL.md +26 -0
- package/template/.cursor/skills/i-have-adhd/agents/openai.yaml +6 -0
- package/template/.cursor/skills/i-have-adhd/references/yss-adaptation.md +21 -0
- package/template/.cursor/skills/i-have-adhd/tests/verify-integration.mjs +63 -0
- package/template/.cursor/skills/prototype-review/SKILL.md +2 -0
- package/template/.cursor/skills/yss-implementation-contract-compiler/SKILL.md +6 -0
- package/template/.cursor/skills/yss-implementation-contract-compiler/references/boundaries.md +10 -0
- package/template/.cursor/skills/yss-openapi-governance/SKILL.md +6 -0
- package/template/.cursor/skills/yss-product-lifecycle/SKILL.md +3 -1
- package/template/.cursor/skills/yss-product-lifecycle/references/orchestration-contract.yaml +12 -0
- package/template/.cursor/skills/yss-prototype-stage/SKILL.md +6 -0
- package/template/.cursor/skills/yss-research/SKILL.md +6 -0
- package/template/.cursor/skills/yss-stage-decision/SKILL.md +19 -4
- package/template/.cursor/skills/yss-stage-decision/references/domain-strategy-contract.md +4 -3
- package/template/.cursor/skills/yss-stage-decision/references/domain-strategy-v2.schema.json +8 -0
- package/template/.cursor/skills/yss-stage-decision/references/domain-strategy-v3.schema.json +8 -0
- package/template/.cursor/skills/yss-stage-decision/references/domain-strategy.schema.json +75 -5
- package/template/.cursor/skills/yss-stage-decision/references/stage-decision-package-contract.md +4 -4
- package/template/.cursor/skills/yss-stage-decision/references/stage-decision-package-v2.schema.json +8 -0
- package/template/.cursor/skills/yss-stage-decision/references/stage-decision-package-v3.schema.json +8 -0
- package/template/.cursor/skills/yss-stage-decision/references/stage-decision-package.schema.json +59 -9
- package/template/.cursor/skills/yss-stage-decision/references/validation-rules.md +2 -2
- package/template/.cursor/skills/yss-stage-decision/scripts/migrate-contract-v3.mjs +143 -0
- package/template/.cursor/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +46 -4
- package/template/.cursor/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +48 -4
- package/template/.cursor/skills/yss-stage-decision/tests/fixtures/valid-stage-decision-package.yaml +42 -13
- package/template/.cursor/skills/yss-stage-decision/tests/fixtures/valid-supplier-domain.yaml +37 -5
- package/template/.cursor/skills/yss-stage-decision/tests/run-scenarios.mjs +116 -23
- package/template/.cursor/skills/yss-technical-design/SKILL.md +6 -0
- package/template/.pi/skills/archify/SKILL.md +1 -1
- package/template/.pi/skills/code-review/SKILL.md +6 -0
- package/template/.pi/skills/i-have-adhd/LICENSE +21 -0
- package/template/.pi/skills/i-have-adhd/SKILL.md +26 -0
- package/template/.pi/skills/i-have-adhd/agents/openai.yaml +6 -0
- package/template/.pi/skills/i-have-adhd/references/yss-adaptation.md +21 -0
- package/template/.pi/skills/i-have-adhd/tests/verify-integration.mjs +63 -0
- package/template/.pi/skills/prototype-review/SKILL.md +2 -0
- package/template/.pi/skills/yss-implementation-contract-compiler/SKILL.md +6 -0
- package/template/.pi/skills/yss-implementation-contract-compiler/references/boundaries.md +10 -0
- package/template/.pi/skills/yss-openapi-governance/SKILL.md +6 -0
- package/template/.pi/skills/yss-product-lifecycle/SKILL.md +3 -1
- package/template/.pi/skills/yss-product-lifecycle/references/orchestration-contract.yaml +12 -0
- package/template/.pi/skills/yss-prototype-stage/SKILL.md +6 -0
- package/template/.pi/skills/yss-research/SKILL.md +6 -0
- package/template/.pi/skills/yss-stage-decision/SKILL.md +19 -4
- package/template/.pi/skills/yss-stage-decision/references/domain-strategy-contract.md +4 -3
- package/template/.pi/skills/yss-stage-decision/references/domain-strategy-v2.schema.json +8 -0
- package/template/.pi/skills/yss-stage-decision/references/domain-strategy-v3.schema.json +8 -0
- package/template/.pi/skills/yss-stage-decision/references/domain-strategy.schema.json +75 -5
- package/template/.pi/skills/yss-stage-decision/references/stage-decision-package-contract.md +4 -4
- package/template/.pi/skills/yss-stage-decision/references/stage-decision-package-v2.schema.json +8 -0
- package/template/.pi/skills/yss-stage-decision/references/stage-decision-package-v3.schema.json +8 -0
- package/template/.pi/skills/yss-stage-decision/references/stage-decision-package.schema.json +59 -9
- package/template/.pi/skills/yss-stage-decision/references/validation-rules.md +2 -2
- package/template/.pi/skills/yss-stage-decision/scripts/migrate-contract-v3.mjs +143 -0
- package/template/.pi/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +46 -4
- package/template/.pi/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +48 -4
- package/template/.pi/skills/yss-stage-decision/tests/fixtures/valid-stage-decision-package.yaml +42 -13
- package/template/.pi/skills/yss-stage-decision/tests/fixtures/valid-supplier-domain.yaml +37 -5
- package/template/.pi/skills/yss-stage-decision/tests/run-scenarios.mjs +116 -23
- package/template/.pi/skills/yss-technical-design/SKILL.md +6 -0
- package/template/docs/agents/yss-skill-registry.yaml +22 -0
- package/template/docs/plan/templates/plan-template.md +8 -4
- package/template/docs/process/document-writing.md +77 -0
- package/template/docs/process/frontend-backend-delivery.md +7 -4
- package/template/docs/process/schemas/backend-delivery.schema.json +4 -0
- package/template/docs/process/schemas/frontend-delivery-acceptance-v1.schema.json +200 -0
- package/template/docs/process/schemas/frontend-delivery-acceptance.schema.json +37 -3
- package/template/docs/process/schemas/frontend-strategic-preflight.schema.json +32 -0
- package/template/docs/process/schemas/lifecycle-checkpoint.schema.json +2 -1
- package/template/docs/process/schemas/strategic-consumer-feedback.schema.json +40 -0
- package/template/docs/process/schemas/strategic-design-handoff-v3.schema.json +743 -0
- package/template/docs/process/schemas/strategic-design-handoff-v4.schema.json +712 -0
- package/template/docs/process/schemas/strategic-feedback-adjudication.schema.json +42 -0
- package/template/docs/process/schemas/strategic-handoff-domain-strategy-v3.schema.json +216 -0
- package/template/docs/process/schemas/strategic-handoff-import-receipt.schema.json +39 -0
- package/template/docs/process/schemas/strategic-handoff-stage-decision-package-v3.schema.json +148 -0
- package/template/docs/process/strategic-handoff-package.md +37 -4
- package/template/docs/process/templates/lifecycle-checkpoint-template.yaml +1 -0
- package/template/docs/process/templates/stage-checkpoint-template.md +1 -0
- package/template/docs/templates/examples/lifecycle-writing-examples.md +138 -0
- package/template/docs/templates/spec-template.md +16 -3
- package/template/docs/templates/vertical-slice-ticket-template.md +8 -4
- package/template/scripts/lib/approval-record.mjs +2 -1
- package/template/scripts/lib/backend-delivery.mjs +15 -3
- package/template/scripts/lib/frontend-delivery-boundary.mjs +20 -4
- package/template/scripts/lib/frontend-delivery.mjs +66 -2
- package/template/scripts/lib/profile-skill-sync.mjs +336 -0
- package/template/scripts/lib/strategic-feedback.mjs +33 -0
- package/template/scripts/lib/strategic-handoff-consumption.mjs +5 -0
- package/template/scripts/lib/strategic-handoff-io.mjs +1 -0
- package/template/scripts/lib/strategic-handoff.mjs +101 -10
- package/template/scripts/lib/user-decision-reuse.mjs +38 -0
- package/template/scripts/strategic-feedback +10 -0
- package/template/scripts/sync-profile-skills +43 -0
- package/template/scripts/verify-frontend-strategic-preflight +12 -0
- package/template/skills-lock.json +38 -20
- package/template.manifest.json +1 -0
- 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
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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);
|
package/template/.codex/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs
CHANGED
|
@@ -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
|
|
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", "
|
|
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
|
-
|
|
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 ?? {};
|
package/template/.codex/skills/yss-stage-decision/tests/fixtures/valid-stage-decision-package.yaml
CHANGED
|
@@ -1,16 +1,41 @@
|
|
|
1
|
-
schema_version:
|
|
1
|
+
schema_version: 3
|
|
2
2
|
stage_decision_id: stage-decision.supplier-admission
|
|
3
|
-
package_version:
|
|
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:
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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:
|
|
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
|
-
-
|
|
42
|
-
|
|
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
|
-
|
|
46
|
-
|
|
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:
|
|
83
|
+
current_version: v3
|
|
@@ -1,7 +1,19 @@
|
|
|
1
|
-
schema_version:
|
|
1
|
+
schema_version: 3
|
|
2
2
|
domain_strategy_id: domain-strategy.supplier-admission
|
|
3
|
-
domain_version:
|
|
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
|
-
-
|
|
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:
|
|
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-
|
|
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
|
|
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,
|
|
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
|
|
85
|
-
|
|
86
|
-
|
|
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,
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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.
|