create-yss-spec 3.4.7 → 3.4.8

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 (121) hide show
  1. package/package.json +1 -1
  2. package/template/.agents/skills/.strategic-design-skills-manifest.json +1 -1
  3. package/template/.agents/skills/code-review/references/yss-review-standards.md +22 -0
  4. package/template/.agents/skills/lombok/SKILL.md +1 -1
  5. package/template/.agents/skills/mapstruct/SKILL.md +3 -1
  6. package/template/.agents/skills/yss-application/references/application-layer-guide.md +7 -0
  7. package/template/.agents/skills/yss-backend-spec-review/SKILL.md +38 -0
  8. package/template/.agents/skills/yss-backend-spec-review/references/check-inputs.md +21 -0
  9. package/template/.agents/skills/yss-backend-spec-review/references/remediation-workflow.md +48 -0
  10. package/template/.agents/skills/yss-backend-spec-review/tests/routing.test.mjs +19 -0
  11. package/template/.agents/skills/yss-ddd-scaffold-generator/SKILL.md +5 -3
  12. package/template/.agents/skills/yss-ddd-scaffold-generator/assets/templates/java/architecture-rules-test.java.template +60 -23
  13. package/template/.agents/skills/yss-ddd-scaffold-generator/assets/templates/pom/bootstrap-pom.xml.template +6 -0
  14. package/template/.agents/skills/yss-ddd-scaffold-generator/references/ARCHITECTURE.md +1 -1
  15. package/template/.agents/skills/yss-ddd-scaffold-generator/references/USAGE_EXAMPLES.md +4 -2
  16. package/template/.agents/skills/yss-ddd-scaffold-generator/references/engineering-baseline.md +7 -1
  17. package/template/.agents/skills/yss-ddd-scaffold-generator/scripts/first-slice-compatibility.test.mjs +6 -10
  18. package/template/.agents/skills/yss-ddd-scaffold-generator/scripts/first-slice-verification.test.mjs +12 -16
  19. package/template/.agents/skills/yss-ddd-scaffold-generator/scripts/run_first_slice_verification.mjs +36 -87
  20. package/template/.agents/skills/yss-ddd-scaffold-generator/scripts/run_scaffold_verification.mjs +4 -3
  21. package/template/.agents/skills/yss-domain/references/domain-layer-guide.md +8 -1
  22. package/template/.agents/skills/yss-implementation-contract-compiler/references/boundaries.md +5 -0
  23. package/template/.agents/skills/yss-layered-mvc-scaffold-generator/assets/templates/architecture-rules-test.java.template +66 -0
  24. package/template/.agents/skills/yss-layered-mvc-scaffold-generator/scripts/generate_scaffold.mjs +4 -2
  25. package/template/.agents/skills/yss-mybatis/SKILL.md +4 -0
  26. package/template/.agents/skills/yss-repository/references/profiles/target-domain-model.md +10 -2
  27. package/template/.agents/skills/yss-web-controller/references/web-adapter-layer-guide.md +8 -0
  28. package/template/.codex/skills/code-review/references/yss-review-standards.md +22 -0
  29. package/template/.codex/skills/lombok/SKILL.md +1 -1
  30. package/template/.codex/skills/mapstruct/SKILL.md +3 -1
  31. package/template/.codex/skills/yss-application/references/application-layer-guide.md +7 -0
  32. package/template/.codex/skills/yss-backend-spec-review/SKILL.md +38 -0
  33. package/template/.codex/skills/yss-backend-spec-review/references/check-inputs.md +21 -0
  34. package/template/.codex/skills/yss-backend-spec-review/references/remediation-workflow.md +48 -0
  35. package/template/.codex/skills/yss-backend-spec-review/tests/routing.test.mjs +19 -0
  36. package/template/.codex/skills/yss-ddd-scaffold-generator/SKILL.md +5 -3
  37. package/template/.codex/skills/yss-ddd-scaffold-generator/assets/templates/java/architecture-rules-test.java.template +60 -23
  38. package/template/.codex/skills/yss-ddd-scaffold-generator/assets/templates/pom/bootstrap-pom.xml.template +6 -0
  39. package/template/.codex/skills/yss-ddd-scaffold-generator/references/ARCHITECTURE.md +1 -1
  40. package/template/.codex/skills/yss-ddd-scaffold-generator/references/USAGE_EXAMPLES.md +4 -2
  41. package/template/.codex/skills/yss-ddd-scaffold-generator/references/engineering-baseline.md +7 -1
  42. package/template/.codex/skills/yss-ddd-scaffold-generator/scripts/first-slice-compatibility.test.mjs +6 -10
  43. package/template/.codex/skills/yss-ddd-scaffold-generator/scripts/first-slice-verification.test.mjs +12 -16
  44. package/template/.codex/skills/yss-ddd-scaffold-generator/scripts/run_first_slice_verification.mjs +36 -87
  45. package/template/.codex/skills/yss-ddd-scaffold-generator/scripts/run_scaffold_verification.mjs +4 -3
  46. package/template/.codex/skills/yss-domain/references/domain-layer-guide.md +8 -1
  47. package/template/.codex/skills/yss-implementation-contract-compiler/references/boundaries.md +5 -0
  48. package/template/.codex/skills/yss-layered-mvc-scaffold-generator/assets/templates/architecture-rules-test.java.template +66 -0
  49. package/template/.codex/skills/yss-layered-mvc-scaffold-generator/scripts/generate_scaffold.mjs +4 -2
  50. package/template/.codex/skills/yss-mybatis/SKILL.md +4 -0
  51. package/template/.codex/skills/yss-repository/references/profiles/target-domain-model.md +10 -2
  52. package/template/.codex/skills/yss-web-controller/references/web-adapter-layer-guide.md +8 -0
  53. package/template/.cursor/skills/code-review/references/yss-review-standards.md +22 -0
  54. package/template/.cursor/skills/lombok/SKILL.md +1 -1
  55. package/template/.cursor/skills/mapstruct/SKILL.md +3 -1
  56. package/template/.cursor/skills/yss-application/references/application-layer-guide.md +7 -0
  57. package/template/.cursor/skills/yss-backend-spec-review/SKILL.md +38 -0
  58. package/template/.cursor/skills/yss-backend-spec-review/references/check-inputs.md +21 -0
  59. package/template/.cursor/skills/yss-backend-spec-review/references/remediation-workflow.md +48 -0
  60. package/template/.cursor/skills/yss-backend-spec-review/tests/routing.test.mjs +19 -0
  61. package/template/.cursor/skills/yss-ddd-scaffold-generator/SKILL.md +5 -3
  62. package/template/.cursor/skills/yss-ddd-scaffold-generator/assets/templates/java/architecture-rules-test.java.template +60 -23
  63. package/template/.cursor/skills/yss-ddd-scaffold-generator/assets/templates/pom/bootstrap-pom.xml.template +6 -0
  64. package/template/.cursor/skills/yss-ddd-scaffold-generator/references/ARCHITECTURE.md +1 -1
  65. package/template/.cursor/skills/yss-ddd-scaffold-generator/references/USAGE_EXAMPLES.md +4 -2
  66. package/template/.cursor/skills/yss-ddd-scaffold-generator/references/engineering-baseline.md +7 -1
  67. package/template/.cursor/skills/yss-ddd-scaffold-generator/scripts/first-slice-compatibility.test.mjs +6 -10
  68. package/template/.cursor/skills/yss-ddd-scaffold-generator/scripts/first-slice-verification.test.mjs +12 -16
  69. package/template/.cursor/skills/yss-ddd-scaffold-generator/scripts/run_first_slice_verification.mjs +36 -87
  70. package/template/.cursor/skills/yss-ddd-scaffold-generator/scripts/run_scaffold_verification.mjs +4 -3
  71. package/template/.cursor/skills/yss-domain/references/domain-layer-guide.md +8 -1
  72. package/template/.cursor/skills/yss-implementation-contract-compiler/references/boundaries.md +5 -0
  73. package/template/.cursor/skills/yss-layered-mvc-scaffold-generator/assets/templates/architecture-rules-test.java.template +66 -0
  74. package/template/.cursor/skills/yss-layered-mvc-scaffold-generator/scripts/generate_scaffold.mjs +4 -2
  75. package/template/.cursor/skills/yss-mybatis/SKILL.md +4 -0
  76. package/template/.cursor/skills/yss-repository/references/profiles/target-domain-model.md +10 -2
  77. package/template/.cursor/skills/yss-web-controller/references/web-adapter-layer-guide.md +8 -0
  78. package/template/.pi/skills/code-review/references/yss-review-standards.md +22 -0
  79. package/template/.pi/skills/lombok/SKILL.md +1 -1
  80. package/template/.pi/skills/mapstruct/SKILL.md +3 -1
  81. package/template/.pi/skills/yss-application/references/application-layer-guide.md +7 -0
  82. package/template/.pi/skills/yss-backend-spec-review/SKILL.md +38 -0
  83. package/template/.pi/skills/yss-backend-spec-review/references/check-inputs.md +21 -0
  84. package/template/.pi/skills/yss-backend-spec-review/references/remediation-workflow.md +48 -0
  85. package/template/.pi/skills/yss-backend-spec-review/tests/routing.test.mjs +19 -0
  86. package/template/.pi/skills/yss-ddd-scaffold-generator/SKILL.md +5 -3
  87. package/template/.pi/skills/yss-ddd-scaffold-generator/assets/templates/java/architecture-rules-test.java.template +60 -23
  88. package/template/.pi/skills/yss-ddd-scaffold-generator/assets/templates/pom/bootstrap-pom.xml.template +6 -0
  89. package/template/.pi/skills/yss-ddd-scaffold-generator/references/ARCHITECTURE.md +1 -1
  90. package/template/.pi/skills/yss-ddd-scaffold-generator/references/USAGE_EXAMPLES.md +4 -2
  91. package/template/.pi/skills/yss-ddd-scaffold-generator/references/engineering-baseline.md +7 -1
  92. package/template/.pi/skills/yss-ddd-scaffold-generator/scripts/first-slice-compatibility.test.mjs +6 -10
  93. package/template/.pi/skills/yss-ddd-scaffold-generator/scripts/first-slice-verification.test.mjs +12 -16
  94. package/template/.pi/skills/yss-ddd-scaffold-generator/scripts/run_first_slice_verification.mjs +36 -87
  95. package/template/.pi/skills/yss-ddd-scaffold-generator/scripts/run_scaffold_verification.mjs +4 -3
  96. package/template/.pi/skills/yss-domain/references/domain-layer-guide.md +8 -1
  97. package/template/.pi/skills/yss-implementation-contract-compiler/references/boundaries.md +5 -0
  98. package/template/.pi/skills/yss-layered-mvc-scaffold-generator/assets/templates/architecture-rules-test.java.template +66 -0
  99. package/template/.pi/skills/yss-layered-mvc-scaffold-generator/scripts/generate_scaffold.mjs +4 -2
  100. package/template/.pi/skills/yss-mybatis/SKILL.md +4 -0
  101. package/template/.pi/skills/yss-repository/references/profiles/target-domain-model.md +10 -2
  102. package/template/.pi/skills/yss-web-controller/references/web-adapter-layer-guide.md +8 -0
  103. package/template/docs/agents/yss-skill-registry.yaml +13 -0
  104. package/template/docs/engineering/backend-platforms.md +14 -0
  105. package/template/docs/process/schemas/digital-human-task-package.schema.json +657 -100
  106. package/template/docs/process/schemas/slice-implementation-contract-v3.schema.json +41 -0
  107. package/template/scripts/lib/backend-platform-provenance.mjs +1 -1
  108. package/template/scripts/lib/backend-platform-verification.mjs +2 -2
  109. package/template/scripts/lib/backend-platform.mjs +62 -0
  110. package/template/scripts/lib/backend-review.mjs +102 -0
  111. package/template/scripts/lib/first-slice-artifacts.mjs +47 -0
  112. package/template/scripts/lib/first-slice-tests.mjs +24 -0
  113. package/template/scripts/lib/lifecycle-transition.mjs +6 -0
  114. package/template/scripts/lib/maintenance-candidate.mjs +160 -0
  115. package/template/scripts/lib/slice-execution-preflight.mjs +43 -0
  116. package/template/scripts/slice-contract +7 -3
  117. package/template/scripts/verify-backend-platform-matrix +8 -2
  118. package/template/skills-lock.json +28 -16
  119. package/template/yss-public-skills.json +4 -2
  120. package/template.manifest.json +0 -1
  121. package/template.snapshot.json +5 -5
@@ -126,19 +126,18 @@ const environment = { ...process.env, JAVA_HOME: "/fixture/java", YSS_MAVEN_REPO
126
126
 
127
127
  test("refuses first-slice promotion when a required persistence seam is absent", async () => {
128
128
  const data = await fixture({ omitGatewayImpl: true });
129
- const report = await runFirstSliceVerification(data.project, data.evidence, data.contractPath, environment);
129
+ const report = await runFirstSliceVerification(data.project, data.evidence, data.contractPath, environment, { contractRoot: data.root });
130
130
  assert.equal(report.status, "failed");
131
- assert.ok(report.missing_artifacts.includes("infrastructure-gateway-implementation"));
131
+ assert.ok(report.missing_artifacts.includes("backend.first_slice.artifacts-required"));
132
132
  const manifest = JSON.parse(await readFile(data.manifestPath, "utf8"));
133
133
  assert.equal(manifest.completion_level, "empty-scaffold-verified");
134
134
  });
135
135
 
136
- test("refuses first-slice promotion when downstream skill or contract digests are absent", async () => {
136
+ test("missing generation provenance does not substitute for current approved contract", async () => {
137
137
  const data = await fixture({ omitReadiness: true });
138
- const report = await runFirstSliceVerification(data.project, data.evidence, data.contractPath, environment);
138
+ const report = await runFirstSliceVerification(data.project, data.evidence, data.contractPath, environment, { contractRoot: data.root });
139
139
  assert.equal(report.status, "failed");
140
- assert.ok(report.downstream_skill_drift.includes("yss-domain:digest-missing"));
141
- assert.ok(report.downstream_skill_drift.includes("compiler_contract:digest-missing"));
140
+ assert.ok(report.contract_failures.includes("slice-execution-not-authorized"));
142
141
  const manifest = JSON.parse(await readFile(data.manifestPath, "utf8"));
143
142
  assert.equal(manifest.completion_level, "empty-scaffold-verified");
144
143
  });
@@ -148,20 +147,17 @@ test("refuses first-slice promotion when a work unit references a stale contract
148
147
  const contract = JSON.parse(await readFile(data.contractPath, "utf8"));
149
148
  contract.work_units[0].contract_version = 0;
150
149
  await writeFile(data.contractPath, JSON.stringify(contract));
151
- const report = await runFirstSliceVerification(data.project, data.evidence, data.contractPath, environment);
150
+ const report = await runFirstSliceVerification(data.project, data.evidence, data.contractPath, environment, { contractRoot: data.root });
152
151
  assert.equal(report.status, "failed");
153
152
  assert.ok(report.contract_failures.includes("work-unit-contract-version-mismatch:slice-backend"));
154
153
  });
155
154
 
156
- test("promotes a complete approved slice only after all root wrapper commands pass", async () => {
155
+ test("fake wrapper success and an approved status cannot promote an incomplete v2 contract", async () => {
157
156
  const data = await fixture();
158
- const report = await runFirstSliceVerification(data.project, data.evidence, data.contractPath, environment);
159
- assert.equal(report.status, "passed");
160
- assert.equal(report.completion_level, "first-slice-verified");
161
- assert.deepEqual(report.commands.map((item) => item.exit_code), [0, 0, 0]);
157
+ const report = await runFirstSliceVerification(data.project, data.evidence, data.contractPath, environment, { contractRoot: data.root });
158
+ assert.equal(report.status, "failed");
159
+ assert.ok(report.contract_failures.includes("slice-execution-not-authorized"));
160
+ assert.deepEqual(report.commands, []);
162
161
  const manifest = JSON.parse(await readFile(data.manifestPath, "utf8"));
163
- assert.equal(manifest.completion_level, "first-slice-verified");
164
- assert.equal(manifest.first_slice_contract.contract_id, "slice-1");
165
- assert.equal(manifest.first_slice_contract.slice_id, "quality-rule-first-slice");
166
- assert.equal(JSON.parse(await readFile(path.join(data.evidence, "first-slice-verification.json"), "utf8")).status, "passed");
162
+ assert.equal(manifest.completion_level, "empty-scaffold-verified");
167
163
  });
@@ -8,33 +8,15 @@ import { fileURLToPath } from "node:url";
8
8
 
9
9
  import { run as runScaffoldVerification } from "./run_scaffold_verification.mjs";
10
10
  import { assertArchitectureAgreement } from "../../../../scripts/lib/backend-architecture.mjs";
11
- import { mvcFirstSliceProfile } from "../../../../scripts/lib/first-slice-architecture.mjs";
11
+ import { inspectSliceContract } from "../../../../scripts/lib/slice-execution-preflight.mjs";
12
+ import { selectSliceWorkUnit } from "../../../../scripts/lib/slice-contract.mjs";
13
+ import { verifyFirstSliceTests } from "../../../../scripts/lib/first-slice-tests.mjs";
14
+ import { inspectFirstSliceArtifacts } from "../../../../scripts/lib/first-slice-artifacts.mjs";
12
15
 
13
16
  const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
14
17
  const REPOSITORY_ROOT = path.resolve(SCRIPT_DIR, "../../../..");
15
18
  const REQUIRED_SKILLS = ["yss-domain", "yss-application", "yss-repository", "yss-mybatis", "yss-web-controller", "yss-dto", "yss-exception", "yss-validation", "mapstruct", "lombok", "alibaba-java-code-style"];
16
19
  const REQUIRED_LAYERS = ["domain", "application", "infrastructure", "web"];
17
- const ARTIFACT_CHECKS = [
18
- ["domain-model", /-domain\/src\/main\/java\/.+\/domain\/.+\/model\/.+\.java$/],
19
- ["domain-gateway", /-domain\/src\/main\/java\/.+\/domain\/.+\/gateway\/.+Gateway\.java$/],
20
- ["domain-behavior-test", /-domain\/src\/test\/java\/.+(?:Test|Tests)\.java$/],
21
- ["application-command-or-query", /-application\/src\/main\/java\/.+\/application\/(?:command|query)\/.+\.java$/],
22
- ["application-result", /-application\/src\/main\/java\/.+\/application\/result\/.+Result\.java$/],
23
- ["application-service", /-application\/src\/main\/java\/.+\/application\/service\/.+Service\.java$/],
24
- ["application-service-implementation", /-application\/src\/main\/java\/.+\/application\/service\/impl\/.+ServiceImpl\.java$/],
25
- ["application-behavior-test", /-application\/src\/test\/java\/.+(?:Test|Tests)\.java$/],
26
- ["infrastructure-po", /-infrastructure\/src\/main\/java\/.+\/infrastructure\/persistence\/po\/.+PO\.java$/],
27
- ["infrastructure-repository", /-infrastructure\/src\/main\/java\/.+\/infrastructure\/persistence\/repository\/.+Repository\.java$/],
28
- ["infrastructure-convertor", /-infrastructure\/src\/main\/java\/.+\/infrastructure\/persistence\/convertor\/.+Convertor\.java$/],
29
- ["infrastructure-gateway-implementation", /-infrastructure\/src\/main\/java\/.+\/infrastructure\/persistence\/gateway\/.+GatewayImpl\.java$/],
30
- ["infrastructure-query-adapter", /-infrastructure\/src\/main\/java\/.+\/infrastructure\/query\/adapter\/.+QueryAdapter\.java$/],
31
- ["infrastructure-integration-test", /-infrastructure\/src\/test\/java\/.+(?:Test|Tests)\.java$/],
32
- ["web-controller", /-adapter\/.+-web\/src\/main\/java\/.+\/rest\/.+Controller\.java$/],
33
- ["web-convertor", /-adapter\/.+-web\/src\/main\/java\/.+\/rest\/convertor\/.+WebConvertor\.java$/],
34
- ["web-request", /-adapter\/.+-web\/src\/main\/java\/.+\/rest\/dto\/request\/.+Request\.java$/],
35
- ["web-response", /-adapter\/.+-web\/src\/main\/java\/.+\/rest\/dto\/response\/.+Response\.java$/],
36
- ["web-contract-test", /-adapter\/.+-web\/src\/test\/java\/.+(?:Test|Tests)\.java$/]
37
- ];
38
20
 
39
21
  const isoNow = () => new Date().toISOString();
40
22
  const sha256 = (content) => createHash("sha256").update(content).digest("hex");
@@ -62,59 +44,7 @@ async function writeJsonAtomic(target, value) {
62
44
  await writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`, "utf8");
63
45
  await rename(temporary, target);
64
46
  }
65
- function invalidContractReasons(contract, requiredSkills = REQUIRED_SKILLS, requiredLayers = REQUIRED_LAYERS) {
66
- const reasons = [];
67
- if (contract.schema_version !== 2) reasons.push("slice-contract-schema");
68
- if (contract.status !== "approved") reasons.push("slice-contract-not-approved");
69
- for (const field of ["contract_id", "contract_version", "slice_id"]) if (contract[field] === undefined || contract[field] === null || contract[field] === "") reasons.push(`slice-contract-${field}-missing`);
70
- if (contract.readiness?.blockers?.length) reasons.push("slice-contract-has-blockers");
71
- if (contract.readiness?.stale_inputs?.length) reasons.push("slice-contract-has-stale-inputs");
72
- const actualSkills = new Set([...(contract.common?.required_skills ?? []), ...(contract.backend?.required_skills ?? [])]);
73
- for (const skill of requiredSkills) if (!actualSkills.has(skill)) reasons.push(`required-skill-missing:${skill}`);
74
- const actualLayers = new Set(contract.backend?.affected_layers ?? []);
75
- for (const layer of requiredLayers) if (!actualLayers.has(layer)) reasons.push(`required-layer-missing:${layer}`);
76
- if (!Array.isArray(contract.work_units) || !contract.work_units.length) reasons.push("slice-contract-work-units-missing");
77
- else for (const [index, unit] of contract.work_units.entries()) {
78
- const id = unit.id || `index-${index}`;
79
- if (unit.contract_id !== contract.contract_id) reasons.push(`work-unit-contract-id-mismatch:${id}`);
80
- if (unit.contract_version !== contract.contract_version) reasons.push(`work-unit-contract-version-mismatch:${id}`);
81
- if (!unit.work_unit?.primary_skill) reasons.push(`work-unit-primary-skill-missing:${id}`);
82
- }
83
- return reasons;
84
- }
85
- async function downstreamDrift(manifest, requiredSkills = REQUIRED_SKILLS) {
86
- const drift = [];
87
- const recordedSkills = manifest.readiness?.downstream_skills ?? {};
88
- for (const skill of requiredSkills) {
89
- const expected = recordedSkills[skill];
90
- if (typeof expected !== "string" || !/^[a-f0-9]{64}$/.test(expected)) {
91
- drift.push(`${skill}:digest-missing`);
92
- continue;
93
- }
94
- const root = path.join(REPOSITORY_ROOT, ".agents", "skills", skill);
95
- if (!await isFile(path.join(root, "SKILL.md"))) drift.push(`${skill}:unavailable`);
96
- else if (await treeDigest(root) !== expected) drift.push(`${skill}:digest-mismatch`);
97
- }
98
- const contracts = manifest.readiness?.contracts ?? {};
99
- const contractFiles = {
100
- compiler_contract: path.join(REPOSITORY_ROOT, ".agents", "skills", "yss-implementation-contract-compiler", "references", "compiler-contract.yaml")
101
- };
102
- if (manifest.architecture_family === "layered-mvc") {
103
- contractFiles.architecture_profiles = path.join(REPOSITORY_ROOT, "docs/agents/backend-architecture-profiles.md");
104
- } else contractFiles.engineering_baseline = path.join(REPOSITORY_ROOT, ".agents", "skills", "yss-ddd-scaffold-generator", "references", "engineering-baseline.md");
105
- for (const [name, target] of Object.entries(contractFiles)) {
106
- const expected = contracts[name];
107
- if (typeof expected !== "string" || !/^[a-f0-9]{64}$/.test(expected)) {
108
- drift.push(`${name}:digest-missing`);
109
- continue;
110
- }
111
- if (!await isFile(target)) drift.push(`${name}:unavailable`);
112
- else if (sha256(await readFile(target)) !== expected) drift.push(`${name}:digest-mismatch`);
113
- }
114
- return drift;
115
- }
116
-
117
- export async function runFirstSliceVerification(projectRoot, evidenceDir, sliceContractFile, environment = process.env) {
47
+ export async function runFirstSliceVerification(projectRoot, evidenceDir, sliceContractFile, environment = process.env, options = {}) {
118
48
  projectRoot = path.resolve(projectRoot);
119
49
  evidenceDir = path.resolve(evidenceDir);
120
50
  sliceContractFile = path.resolve(sliceContractFile);
@@ -123,20 +53,33 @@ export async function runFirstSliceVerification(projectRoot, evidenceDir, sliceC
123
53
  if (!await isFile(manifestPath)) throw new Error(`missing scaffold manifest: ${manifestPath}`);
124
54
  if (!await isFile(sliceContractFile)) throw new Error(`missing Slice Implementation Contract: ${sliceContractFile}`);
125
55
  const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
126
- const contract = JSON.parse(await readFile(sliceContractFile, "utf8"));
56
+ const contractRoot = path.resolve(options.contractRoot || projectRoot);
57
+ let contract, preflight;
58
+ try {
59
+ const loaded = inspectSliceContract(path.relative(contractRoot, sliceContractFile), {
60
+ root: contractRoot, approval_ref: options.approvalRef, work_unit_id: options.workUnit
61
+ });
62
+ preflight = loaded.report;
63
+ contract = selectSliceWorkUnit(loaded.contract, options.workUnit);
64
+ } catch (error) {
65
+ preflight = { execution_allowed: false, blockers: [error.message] };
66
+ contract = {};
67
+ }
127
68
  if (manifest.platform_verification === "candidate") throw new Error("backend-platform: candidate scaffold cannot become first-slice-verified");
128
- const adapter = manifest.architecture_family === "layered-mvc" ? mvcFirstSliceProfile(manifest.architecture_identity ?? {}) : { skills: REQUIRED_SKILLS, layers: REQUIRED_LAYERS, artifacts: ARTIFACT_CHECKS };
129
- const contractFailures = invalidContractReasons(contract, adapter.skills, adapter.layers);
69
+ const contractFailures = [...preflight.blockers];
70
+ if (!preflight.execution_allowed) contractFailures.push("slice-execution-not-authorized");
71
+ if (!(contract.common?.project_roots || []).some(ref => path.resolve(contractRoot, ref) === projectRoot)) contractFailures.push("slice-project-root-mismatch");
130
72
  try {
131
73
  const identity = contract.architecture_identity ?? contract.resolution?.architecture_identity;
132
- assertArchitectureAgreement(identity, { manifest });
74
+ if (identity?.source_kind !== "existing-registration") assertArchitectureAgreement(identity, { manifest });
133
75
  for (const unit of contract.work_units ?? []) assertArchitectureAgreement(identity, { work_unit: unit.architecture_identity });
134
76
  } catch (error) { contractFailures.push(`architecture-identity:${error.message}`); }
135
77
  if (![2, 3, 4].includes(manifest.schema_version) || manifest.completion_level !== "empty-scaffold-verified") contractFailures.push("scaffold-not-empty-scaffold-verified");
136
78
  if (manifest.schema_version === 3 && manifest.legacy_reconciliation?.status !== "approved") contractFailures.push("legacy-scaffold-reconciliation-required");
137
- const projectFiles = await files(projectRoot);
138
- const missingArtifacts = adapter.artifacts.filter(([, pattern]) => !projectFiles.some((file) => pattern.test(file))).map(([name]) => name);
139
- const skillDrift = await downstreamDrift(manifest, adapter.skills);
79
+ const artifacts = inspectFirstSliceArtifacts(contract, projectRoot);
80
+ const missingArtifacts = artifacts.failures;
81
+ // Generation-time Skill hashes are provenance. Current authority was checked by execution preflight.
82
+ const skillDrift = [];
140
83
  if (contractFailures.length || missingArtifacts.length || skillDrift.length) {
141
84
  const report = { verification_mode: "first-slice", project_root: projectRoot, slice_contract_ref: sliceContractFile, scaffold_manifest_ref: manifestPath, generated_at: isoNow(), status: "failed", completion_level: manifest.completion_level, contract_failures: contractFailures, missing_artifacts: missingArtifacts, downstream_skill_drift: skillDrift, commands: [] };
142
85
  await writeJsonAtomic(reportPath, report);
@@ -144,9 +87,15 @@ export async function runFirstSliceVerification(projectRoot, evidenceDir, sliceC
144
87
  }
145
88
 
146
89
  const mavenEvidence = path.join(evidenceDir, "maven");
147
- const wrapper = await runScaffoldVerification(projectRoot, mavenEvidence, environment, { firstSlice: true });
148
- const passed = wrapper.status === "passed";
149
- const report = { verification_mode: "first-slice", project_root: projectRoot, slice_contract_ref: sliceContractFile, scaffold_manifest_ref: manifestPath, generated_at: isoNow(), status: passed ? "passed" : "failed", failure_category: wrapper.failure_category, completion_level: passed ? "first-slice-verified" : manifest.completion_level, contract_failures: [], missing_artifacts: [], downstream_skill_drift: [], commands: wrapper.commands };
90
+ const startedAt = Date.now();
91
+ const wrapper = await runScaffoldVerification(projectRoot, mavenEvidence, environment, { firstSlice: true, systemProperties: artifacts.properties });
92
+ const mvc = manifest.architecture_family === "layered-mvc";
93
+ const tests = wrapper.status === "passed" ? verifyFirstSliceTests(contract, projectRoot, startedAt, {
94
+ module: `${manifest.project_name}-${mvc ? "server" : "bootstrap"}`,
95
+ type: `${manifest.base_package}.${mvc ? "architecture.LayeredMvcArchitectureTest" : "ArchitectureRulesTest"}`
96
+ }) : { status: "not-executed", failures: [], evidence: [] };
97
+ const passed = wrapper.status === "passed" && tests.status === "passed";
98
+ const report = { verification_mode: "first-slice", project_root: projectRoot, slice_contract_ref: sliceContractFile, scaffold_manifest_ref: manifestPath, generated_at: isoNow(), status: passed ? "passed" : "failed", test_execution: tests, failure_category: wrapper.failure_category || (tests.status === "failed" ? "first-slice-tests" : null), completion_level: passed ? "first-slice-verified" : manifest.completion_level, contract_failures: [], missing_artifacts: [], downstream_skill_drift: [], commands: wrapper.commands };
150
99
  await writeJsonAtomic(reportPath, report);
151
100
  if (passed) {
152
101
  manifest.completion_level = "first-slice-verified";
@@ -162,7 +111,7 @@ function parseArgs(argv) {
162
111
  const result = {};
163
112
  for (let index = 0; index < argv.length; index += 2) {
164
113
  const flag = argv[index], value = argv[index + 1];
165
- if (!["--project-root", "--evidence-dir", "--slice-contract-file"].includes(flag) || !value) throw new Error("usage: run_first_slice_verification.mjs --project-root DIR --evidence-dir DIR --slice-contract-file FILE");
114
+ if (!["--project-root", "--evidence-dir", "--slice-contract-file", "--contract-root", "--approval-ref", "--work-unit"].includes(flag) || !value) throw new Error("usage: run_first_slice_verification.mjs --project-root DIR --evidence-dir DIR --slice-contract-file FILE");
166
115
  result[flag.slice(2).replaceAll(/-([a-z])/g, (_, letter) => letter.toUpperCase())] = value;
167
116
  }
168
117
  if (!result.projectRoot || !result.evidenceDir || !result.sliceContractFile) throw new Error("project root, evidence dir, and slice contract file are required");
@@ -172,7 +121,7 @@ function parseArgs(argv) {
172
121
  async function main() {
173
122
  try {
174
123
  const args = parseArgs(process.argv.slice(2));
175
- const report = await runFirstSliceVerification(args.projectRoot, args.evidenceDir, args.sliceContractFile);
124
+ const report = await runFirstSliceVerification(args.projectRoot, args.evidenceDir, args.sliceContractFile, process.env, args);
176
125
  process.stdout.write(`${report.status}: ${path.join(path.resolve(args.evidenceDir), "first-slice-verification.json")}\n`);
177
126
  return report.status === "passed" ? 0 : 1;
178
127
  } catch (error) {
@@ -62,7 +62,7 @@ function validateManifest(manifest) {
62
62
  if (manifest.schema_version === 4 ? manifest.current_version !== true : manifest.current_version !== manifest.contract_version) throw new Error("脚手架生成元数据清单不是当前合同版本");
63
63
  if (JSON.stringify(manifest.verification_commands) !== JSON.stringify(COMMANDS)) throw new Error("脚手架生成元数据清单验证命令不符合固定合同");
64
64
  }
65
- export async function run(projectRoot, evidenceDir, environment = process.env, { timeoutMs = 0, signal, platformOptions = {}, firstSlice = false } = {}) {
65
+ export async function run(projectRoot, evidenceDir, environment = process.env, { timeoutMs = 0, signal, platformOptions = {}, firstSlice = false, systemProperties = {} } = {}) {
66
66
  const wrapper = path.join(projectRoot, "mvnw"), manifestPath = path.join(projectRoot, ".yss", "scaffold-generation.json");
67
67
  if (!await isFile(wrapper)) throw new Error(`项目根目录缺少 Maven wrapper: ${wrapper}`);
68
68
  if (!await isFile(manifestPath)) throw new Error(`项目根目录缺少脚手架生成元数据清单: ${manifestPath}`);
@@ -114,13 +114,14 @@ export async function run(projectRoot, evidenceDir, environment = process.env, {
114
114
  const stderrPath = path.join(evidenceDir, `mvnw-${phase}.stderr.log`);
115
115
  const startedAt = isoNow();
116
116
  const started = process.hrtime.bigint();
117
- const execution = await runCommand(wrapper, [phase], { cwd: projectRoot, env: environment, timeoutMs, signal, stdoutFile: stdoutPath, stderrFile: stderrPath, secrets: [environment.MAVEN_REPO_USERNAME, environment.MAVEN_REPO_PASSWORD], progress: true });
117
+ const args = [phase, ...Object.entries(systemProperties).map(([key, value]) => `-D${key}=${value}`)];
118
+ const execution = await runCommand(wrapper, args, { cwd: projectRoot, env: environment, timeoutMs, signal, stdoutFile: stdoutPath, stderrFile: stderrPath, secrets: [environment.MAVEN_REPO_USERNAME, environment.MAVEN_REPO_PASSWORD], progress: true });
118
119
  const outcome = { ...execution, exitCode: execution.status };
119
120
  const durationMs = Number(process.hrtime.bigint() - started) / 1e6;
120
121
  await writeText(stdoutPath, redactSecrets(outcome.stdout, environment));
121
122
  await writeText(stderrPath, redactSecrets(outcome.stderr, environment));
122
123
  commands.push({
123
- command: `./mvnw ${phase}`,
124
+ command: `./mvnw ${args.join(" ")}`,
124
125
  phase,
125
126
  exit_code: outcome.exitCode,
126
127
  failure_category: outcome.termination || classifyFailure(phase, outcome),
@@ -62,7 +62,7 @@ public interface QualityTemplateGateway {
62
62
  - `*VO`、`PageResult`、Repository PO。
63
63
  - MyBatis、Spring MVC、Jackson、Swagger 类型。
64
64
 
65
- 分页、列表和投影查询由 Application Query Port 定义,Infrastructure 实现。
65
+ 展示分页、列表和投影查询由 Application Query Port 定义,Infrastructure 实现。
66
66
 
67
67
  ## 5. Domain Error
68
68
 
@@ -99,3 +99,10 @@ Web Exception Translator 再将它映射为批准的状态码、错误码和消
99
99
  - 返回 YSS Skill Execution Result,包含 Domain 文件、行为测试、ArchUnit 结果及实际 `./mvnw ...` 证据。
100
100
  - 新聚合、不变量、状态机或跨上下文影响进入 `new_impacts` 并暂停。
101
101
  - 检测到旧架构时返回 `unsupported`;如需现代化,退出 scaffold 工作流并单独立项评估。
102
+
103
+ ## 聚合创建与重建
104
+
105
+ - 创建入口执行当前创建规则;持久化重建入口恢复已批准状态并校验结构不变量,不重复发送创建事件、生成身份或执行外部调用。两者不得通过公开任意 setter 混为一体。
106
+ - 值对象按值相等;聚合按稳定身份相等。集合对外只读,修改经业务方法;Java 8 使用普通类与防御性复制,不从示例推导 record 等高版本语法。
107
+ - 领域错误携带稳定标识与参数;公开消息和 HTTP 状态由 Web 翻译。测试通过公开行为覆盖非法状态、重复动作和重建后语义一致性。
108
+ - 为业务决策加载一个或一组聚合仍可使用 Domain Gateway;展示列表、分页、排序、统计和投影走 Application Query Port。返回 List 本身不是违规,须按调用目的判定。
@@ -18,6 +18,7 @@
18
18
 
19
19
  | Capability | Primary skill | Task modes |
20
20
  | --- | --- | --- |
21
+ | `governance.backend-spec-conformance` | `yss-backend-spec-review` | `guidance`, `troubleshooting`, `review-input` |
21
22
  | `writing.lifecycle-document` | `i-have-adhd` | `guidance` |
22
23
  | `architecture.technical-design` | `yss-technical-design` | `guidance`, `integration`, `review-input` |
23
24
  | `architecture.mvc-design` | `yss-technical-design` | `guidance`, `integration`, `review-input` |
@@ -57,6 +58,7 @@
57
58
 
58
59
  | Recipe | Capabilities |
59
60
  | --- | --- |
61
+ | `backend.spec-conformance` | `governance.backend-spec-conformance`, `quality.java-code-style` |
60
62
  | `backend.ddd-domain-behavior` | `layer.domain`, `layer.application`, `quality.java-code-style` |
61
63
  | `backend.ddd-persistence-mybatis` | `layer.persistence`, `framework.mybatis`, `codegen.mapstruct`, `codegen.lombok`, `quality.java-code-style` |
62
64
  | `backend.ddd-http-api` | `layer.web-adapter`, `contract.dto-wire`, `contract.request-validation`, `contract.error-mapping`, `codegen.mapstruct`, `codegen.lombok`, `quality.java-code-style` |
@@ -106,6 +108,9 @@
106
108
 
107
109
  | Skill | Dependency | Type | Condition |
108
110
  | --- | --- | --- | --- |
111
+ | `yss-backend-spec-review` | `alibaba-java-code-style` | `context-required` | — |
112
+ | `yss-backend-spec-review` | `yss-implementation-contract-compiler` | `coordination-only` | — |
113
+ | `yss-backend-spec-review` | `code-review` | `review-only` | — |
109
114
  | `prototype-review` | `i-have-adhd` | `context-conditional` | `lifecycle-document-output` |
110
115
  | `yss-implementation-contract-compiler` | `i-have-adhd` | `context-conditional` | `lifecycle-document-output` |
111
116
  | `yss-openapi-governance` | `i-have-adhd` | `context-conditional` | `lifecycle-document-output` |
@@ -0,0 +1,66 @@
1
+ package {{base_package}}.architecture;
2
+
3
+ import com.tngtech.archunit.core.domain.JavaClass;
4
+ import com.tngtech.archunit.core.domain.JavaClasses;
5
+ import com.tngtech.archunit.core.domain.Dependency;
6
+ import com.tngtech.archunit.core.importer.ImportOption;
7
+ import com.tngtech.archunit.junit.AnalyzeClasses;
8
+ import com.tngtech.archunit.junit.ArchTest;
9
+
10
+ /** 空骨架可无业务类型;首切片由批准合同传入职责包和必须加载的实际类型。 */
11
+ @AnalyzeClasses(packages = "{{base_package}}", importOptions = ImportOption.DoNotIncludeTests.class)
12
+ class LayeredMvcArchitectureTest {
13
+ private static boolean inPackage(JavaClass type, String prefixes) {
14
+ for (String prefix : prefixes.split(",")) {
15
+ if (!prefix.isEmpty() && (type.getPackageName().equals(prefix)
16
+ || type.getPackageName().startsWith(prefix + "."))) return true;
17
+ }
18
+ return false;
19
+ }
20
+
21
+ private static boolean role(JavaClass type, String name, String defaults) {
22
+ return inPackage(type, defaults) || inPackage(type, System.getProperty("yss.arch." + name, ""));
23
+ }
24
+
25
+ private static boolean controller(JavaClass type) {
26
+ return type.isAnnotatedWith("org.springframework.web.bind.annotation.RestController")
27
+ || type.isAnnotatedWith("org.springframework.stereotype.Controller")
28
+ || type.isMetaAnnotatedWith("org.springframework.stereotype.Controller");
29
+ }
30
+
31
+ private static void reject(boolean forbidden, Dependency dependency) {
32
+ if (forbidden) throw new AssertionError(dependency.getDescription());
33
+ }
34
+
35
+ @ArchTest
36
+ static void boundaries(JavaClasses classes) {
37
+ for (JavaClass origin : classes) {
38
+ boolean application = role(origin, "application", "{{base_package}}.service,{{base_package}}.core");
39
+ boolean infrastructure = role(origin, "infrastructure", "{{base_package}}.repository");
40
+ for (Dependency dependency : origin.getDirectDependenciesFromSelf()) {
41
+ JavaClass target = dependency.getTargetClass();
42
+ boolean targetWeb = controller(target) || role(target, "web",
43
+ "{{base_package}}.server,{{base_package}}.rest,{{base_package}}.adapter.web");
44
+ boolean targetInfrastructure = role(target, "infrastructure", "{{base_package}}.repository");
45
+ reject(application && targetWeb, dependency);
46
+ reject(infrastructure && (targetWeb || role(target, "application",
47
+ "{{base_package}}.service,{{base_package}}.core")), dependency);
48
+ reject(controller(origin) && targetInfrastructure, dependency);
49
+ }
50
+ }
51
+ if (Boolean.getBoolean("yss.firstSlice")) {
52
+ if (Boolean.getBoolean("yss.arch.requireController")) {
53
+ boolean foundController = false;
54
+ for (JavaClass type : classes) if (controller(type)) foundController = true;
55
+ if (!foundController) throw new AssertionError("Web slice did not load an actual Controller");
56
+ }
57
+ String required = System.getProperty("yss.arch.requiredTypes", "");
58
+ if (required.isEmpty()) throw new AssertionError("first slice requires contract-bound types");
59
+ for (String name : required.split(",")) {
60
+ boolean found = false;
61
+ for (JavaClass type : classes) if (type.getName().equals(name)) found = true;
62
+ if (!found) throw new AssertionError("architecture scope did not load " + name);
63
+ }
64
+ }
65
+ }
66
+ }
@@ -120,7 +120,7 @@ function parentPom(contract, modules, platform) {
120
120
  <properties><java.version>${v.java_version}</java.version><spring-boot.version>${v.boot_version}</spring-boot.version><maven.compiler.source>${v.java_version}</maven.compiler.source><maven.compiler.target>${v.java_version}</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><yss-components.version>${xml(c.yss_components_version)}</yss-components.version></properties>
121
121
  <modules>${modules.map((module) => `<module>${contract.project_name}-${module}</module>`).join("")}</modules>
122
122
  <dependencyManagement><dependencies>${dependency("org.springframework.boot", "spring-boot-dependencies", v.boot_version, "import").replace("</dependency>", "<type>pom</type></dependency>")}${dependency("com.yss.cloud", "yss-components-bom", "${yss-components.version}", "import").replace("</dependency>", "<type>pom</type></dependency>")}</dependencies></dependencyManagement>
123
- <build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-enforcer-plugin</artifactId><version>${v.enforcer_version}</version><executions><execution><id>enforce-platform</id><goals><goal>enforce</goal></goals><configuration><rules><requireJavaVersion><version>${v.java_range}</version></requireJavaVersion><requireMavenVersion><version>[3.6.3,)</version></requireMavenVersion><dependencyConvergence/></rules></configuration></execution></executions></plugin></plugins><pluginManagement><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>${v.boot_version}</version><executions><execution><goals><goal>repackage</goal></goals></execution></executions></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>${v.surefire_version}</version></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>${v.compiler_version}</version><configuration><source>${v.java_version}</source><target>${v.java_version}</target><parameters>true</parameters><annotationProcessorPaths><path><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${v.lombok_version}</version></path><path><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>${v.mapstruct_version}</version></path><path><groupId>org.projectlombok</groupId><artifactId>lombok-mapstruct-binding</artifactId><version>0.2.0</version></path></annotationProcessorPaths></configuration></plugin></plugins></pluginManagement></build>
123
+ <build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-enforcer-plugin</artifactId><version>${v.enforcer_version}</version><executions><execution><id>enforce-platform</id><goals><goal>enforce</goal></goals><configuration><rules><requireJavaVersion><version>${v.java_range}</version></requireJavaVersion><requireMavenVersion><version>[3.6.3,)</version></requireMavenVersion><dependencyConvergence/></rules></configuration></execution></executions></plugin></plugins><pluginManagement><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>${v.boot_version}</version><executions><execution><goals><goal>repackage</goal></goals></execution></executions></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>${v.surefire_version}</version></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>${v.compiler_version}</version><configuration><source>${v.java_version}</source><target>${v.java_version}</target><parameters>true</parameters><annotationProcessorPaths><path><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${v.lombok_version}</version></path><path><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>${v.mapstruct_version}</version></path><path><groupId>org.projectlombok</groupId><artifactId>lombok-mapstruct-binding</artifactId><version>${v.lombok_mapstruct_binding_version}</version></path></annotationProcessorPaths></configuration></plugin></plugins></pluginManagement></build>
124
124
  </project>`;
125
125
  }
126
126
 
@@ -130,6 +130,7 @@ function modulePom(contract, module, modules, platform) {
130
130
  const dependencies = [];
131
131
  if (module === "server") {
132
132
  dependencies.push(own(applicationModule), dependency("org.springframework.boot", platform.web_starter), dependency("org.springframework.boot", "spring-boot-starter-validation"), dependency("org.springframework.boot", "spring-boot-starter-test", null, "test"), dependency("com.tngtech.archunit", "archunit-junit5", platform.versions.archunit, "test"));
133
+ dependencies.push(dependency("org.projectlombok", "lombok", platform.versions.lombok, "test"));
133
134
  if (platform.web_test_starter !== "spring-boot-starter-test") dependencies.push(dependency("org.springframework.boot", platform.web_test_starter, null, "test"));
134
135
  if (modules.includes("client")) dependencies.push(own("client"));
135
136
  dependencies.push(dependency("com.h2database", "h2", null, "test"), dependency("org.mapstruct", "mapstruct", platform.versions.mapstruct), dependency("com.yss.cloud", "yss-component-dto"));
@@ -263,7 +264,8 @@ export async function generate(options, { skillId = SKILL_ID, architectureProfil
263
264
  await put(projectRoot, `${serverRoot}/src/main/resources/application-scaffold-local.yml`, localDatabaseConfiguration(options.projectName));
264
265
  await put(projectRoot, `${serverRoot}/src/test/resources/application-scaffold-test.yml`, localDatabaseConfiguration(`${options.projectName}_test`));
265
266
  await put(projectRoot, `${serverRoot}/src/test/java/${packagePath}/${applicationClass}Test.java`, `package ${options.basePackage};\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.ActiveProfiles;\n\n@SpringBootTest\n@ActiveProfiles("scaffold-test")\nclass ${applicationClass}Test {\n @Test\n void contextLoads() {\n }\n}`);
266
- await put(projectRoot, `${serverRoot}/src/test/java/${packagePath}/architecture/LayeredMvcArchitectureTest.java`, `package ${options.basePackage}.architecture;\n\nimport static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;\n\nimport com.tngtech.archunit.core.importer.ImportOption;\nimport com.tngtech.archunit.junit.AnalyzeClasses;\nimport com.tngtech.archunit.junit.ArchTest;\nimport com.tngtech.archunit.lang.ArchRule;\n\n@AnalyzeClasses(packages = "${options.basePackage}", importOptions = ImportOption.DoNotIncludeTests.class)\nclass LayeredMvcArchitectureTest {\n @ArchTest\n static final ArchRule repositoryDoesNotDependOnUpperLayers = noClasses().that().resideInAPackage("..repository..").should().dependOnClassesThat().resideInAnyPackage("..service..", "..server..");\n}`);
267
+ const architectureTemplate = await readFile(path.join(SKILL_ROOT, "assets/templates/architecture-rules-test.java.template"), "utf8");
268
+ await put(projectRoot, `${serverRoot}/src/test/java/${packagePath}/architecture/LayeredMvcArchitectureTest.java`, architectureTemplate.replaceAll("{{base_package}}", options.basePackage));
267
269
  await put(projectRoot, `${serverRoot}/src/test/java/${packagePath}/PlatformIntegrationTest.java`, platformSmokeTest(options.basePackage, platform, contract.module_profile.requested_capabilities));
268
270
  const wrapper = path.join(REPOSITORY_ROOT, ".agents/skills/yss-ddd-scaffold-generator/assets/wrapper");
269
271
  await cp(wrapper, projectRoot, { recursive: true });
@@ -55,3 +55,7 @@ description: 用于 YSS MyBatis / MyBatis-Plus 组件能力核验、接入决策
55
55
  ## 平台与源码门禁
56
56
 
57
57
  接入、修改、代码生成或给出精确类名/配置前,读取 [后端组件平台与源码门禁](../yss-skill-source-index-refresh/references/backend-component-platform-compatibility.md),从批准的 `platform_configuration.component_platform_line` 选择 `source-index.boot2-java8.md` 或 `source-index.boot3-java17.md`,并以 `--skill yss-mybatis --platform-line <line> --source-root <matching-root>` 运行统一 freshness 校验。平台线与源码根不匹配、组件 tree 不一致、组件子树 dirty、索引缺少平台信号,或 Manifest / 组件 GAV 缺少 verified 兼容证据时返回 `blocked`;不得回退另一代索引,也不在业务实现中升级、降级或替换 YSS 组件。既有工程只读分诊可继续,但不得据此宣称跨 Boot/JDK 兼容。
58
+
59
+ ## 持久化选择记录
60
+
61
+ 在已有数据合同 / persistence_strategy 中记录:实际 Mapper 基类、组件源码与平台绑定、分页唯一责任者、主键与审计填充,以及命中批量时的 SQL 入口、分批大小和方言证据。`BaseMapper` 与组件扩展基类按所需能力选择,不因命名不同判违规;依赖存在和循环 insert 均不证明批量能力。未命中的能力不强制生成。
@@ -4,9 +4,9 @@
4
4
 
5
5
  ## 前置与所有权
6
6
 
7
- - 写模型必须已有批准的 Domain Gateway;读模型必须已有批准的 Application Query Port。缺失或需要改变签名时返回 `new_impacts`,由 `yss-domain` / `yss-application` 和实现合同编译器处理。
7
+ - 写模型必须已有批准的 Domain Gateway;展示读模型必须已有批准的 Application Query Port。缺失或需要改变签名时返回 `new_impacts`,由 `yss-domain` / `yss-application` 和实现合同编译器处理。
8
8
  - Infrastructure 实现既有端口,拥有 PO、Repository/Mapper、XML、持久化 Convertor、GatewayImpl 和 QueryAdapter;不拥有领域规则、HTTP DTO 或业务事务。
9
- - Domain Gateway 只交换 Aggregate、Domain Model、领域值和标识;分页、列表和读模型走 Application Query Port,`PageQuery` 不进入 Domain。
9
+ - Domain Gateway 只交换 Aggregate、Domain Model、领域值和标识;展示分页、列表和读模型走 Application Query Port,`PageQuery` 不进入 Domain。
10
10
 
11
11
  ## 结构决策
12
12
 
@@ -21,3 +21,11 @@
21
21
  - 经 Domain Gateway 保存后可重新加载语义等价的 Aggregate。
22
22
  - Query Port 覆盖分页 total、空结果、排序/过滤白名单和字段转换。
23
23
  - Mapper/Repository 使用批准数据库 fixture;H2 不能证明生产方言。
24
+
25
+ ## 持久化决策与映射
26
+
27
+ - 在既有 persistence_strategy / 数据合同中明确 Mapper 基类及源码依据、主键、逻辑删除、审计填充、并发版本、敏感字段、分页责任和命中时的批量策略。未命中分页或批量时写明理由,不创建空适配器。
28
+ - 多表聚合按同一 Application 事务保存,重载后比较身份、值对象、集合与业务状态;并发写入按批准策略验证冲突或幂等结果。
29
+ - 机械字段使用 Spring MapStruct Bean;聚合重建可通过显式工厂或自定义映射方法。业务状态校验由领域入口拥有,禁止为了映射而开放破坏不变量的 setter。
30
+ - 创建与更新的 null 语义、枚举未知值和审计字段保留必须有测试;转换器不得静默创造业务默认值或吞掉非法持久化状态。
31
+ - 包名与类型后缀来自批准工程基线;Mapper 与 Repository 的命名不能替代职责、运行时发现和行为验证。
@@ -84,3 +84,11 @@ public interface QualityRuleWebConvertor {
84
84
  - Wire forbidden fields 无法绑定。
85
85
  - OpenAPI 3 注解、YSS Result wrapper、Validation namespace 与平台 Profile 一致。
86
86
  - 执行项目根 `./mvnw test/package`,并保留 HTTP/serialization/exception 证据。
87
+
88
+ ## 非 CRUD 适配
89
+
90
+ 认证、OAuth 回调、Cookie、流式响应及协议端点可以手工实现,不强行套 CRUD metadata;仍消费批准的 wire 合同、Application seam 和平台配置。Controller 不直连 Domain Gateway,也不自行编排登录、撤销或持久化。
91
+
92
+ 协议转换、Cookie/Header 处理归 Web;业务决策归 Application/Domain。异常翻译器可以依赖领域错误类型,不能据此允许 Controller 调用领域端口。MapStruct 处理机械字段映射,协议编码及显式领域工厂适配使用有测试的自定义方法。
93
+
94
+ 生成器推荐 rest 布局;已登记的 adapter.web 等布局通过合同绑定识别。审查按模块、实际 Controller 类型和依赖方向进行,包名偏离不能让检查空匹配通过。
@@ -59,3 +59,25 @@ YSS 页面模块约定(YTable、YFormily、页面骨架等)走 Standards,
59
59
  | `drift`、`new_impacts`、`required_skills` 与真实影响不一致 | 合同标 `stale`,回 实现合同编译器 或更早生命周期阶段;禁止在旧合同上继续编码 |
60
60
 
61
61
  `not-applicable` 仅当影响面未命中。命中后的 mandatory 不得豁免;只允许修复,或写完整 `seam-deferred`(风险、责任人、后续 Ticket、验证计划、目标版本或发布日期)。禁止为日常 Alibaba / YSS 新增生物人豁免门禁;安全 / 公共 API 仍走既有 `TODO-HUMAN-REVIEW` 与生物人门禁。
62
+
63
+
64
+ ## 可执行后端审查结果
65
+
66
+ 后端 `work-unit.code-review` 到交付的流转由 `scripts/lib/backend-review.mjs` 检查。
67
+ `decision_state.review_input` 绑定 `slice_contract_ref`、`approval_ref`、跨仓时的 `work_unit_id`、`project_root`、既有候选的 `review_mode`、
68
+ `candidate_snapshot_ref`、`implementation_candidate_ref`、`candidate_digest`、
69
+ `implementation_actor_id`、`implementation_instance_id` 和 `actual_skill_impacts`(无新增影响为 `[]`)。
70
+ `review_result_ref` 指向唯一 `code-review` 结果;记录 `result: completed`、合同原始字节的
71
+ `contract_digest`、候选摘要、`reviewer` / `implementer` 的 actor_id / runtime_id / instance_id,
72
+ 并分别记录 `axes.Standards` 和 `axes.Spec`。自审不能用不同角色名替代不同执行实例。
73
+
74
+ 复用 `constraint_results`,每个适用技能至少一项包含 `axis: Standards`、`skill`、`constraint`、
75
+ `status`、`rule_ref`、规则文件 SHA-256 `rule_digest`、`code_ref`、`evidence_ref`、证据 SHA-256
76
+ `evidence_digest`。按原文定位具体规则,禁止仅写“符合规范”。无持久化影响时 Repository/MyBatis
77
+ 可写有原因的 not-applicable;合同必需技能不得豁免。`findings` 的阻断项必须关闭;建议可绑定
78
+ `follow_up_ref` 进入待办。修复后重新捕获候选,重验受影响规则及依赖,并显式重绑复用证据。
79
+
80
+ 工作树候选复用现有 packed candidate 工具;审查证据可排除,业务文件不可排除。已提交候选绑定
81
+ 不可变 tree ID,并核对当前清洁 checkout。读取结构化结果不会代替 Reviewer 的实际规则判断。
82
+
83
+ `verification_results` 必须包含实际 command、executed_at、exit_code=0、candidate_digest,以及日志 evidence_ref/evidence_digest。缺少本次机器检查或日志漂移不能完成审查。
@@ -6,7 +6,7 @@ description: "配置或排查 Lombok 注解、构造器、Builder 和注解处
6
6
 
7
7
  ## YSS 阶段 7 执行结果
8
8
 
9
- - 消费工程 Java 与处理器基线;三个新后端 Profile 为 Java 8。MapStruct binding 进入 annotationProcessorPaths,不作为业务依赖单独引入;示例不授权升级依赖。MVC PO 的身份/敏感字段风险与 DDD 相同,不能默认全量 @Data。
9
+ - 消费工程 Java 与处理器基线;Java 语法、API、处理器与测试工具消费批准的精确平台配置。MapStruct binding 进入 annotationProcessorPaths,不作为业务依赖单独引入;示例不授权升级依赖。MVC PO 的身份/敏感字段风险与 DDD 相同,不能默认全量 @Data。
10
10
  - 消费批准后的 Slice Contract/work unit,只在允许路径内调整 POJO 样板和注解处理器配置。
11
11
  - 受控生成必须记录对象类型、选用/排除注解、实体关系和敏感字段风险、编译/测试实际结果。
12
12
  - 按 `yss-implementation-contract-compiler` 的统一 Execution Result 返回 changed files、证据、偏离和新增影响;`@Data` 实体风险、处理器配置缺失或越界路径返回 `violation`。
@@ -6,7 +6,7 @@ description: "实现或排查 MapStruct DTO、PO、Domain 映射及 Lombok 集
6
6
 
7
7
  ## YSS 阶段 7 执行结果
8
8
 
9
- - Java、依赖与处理器版本来自工程基线,不从下面通用示例推断。三个新后端 Profile 为 Java 8;MVC 的 wire/internal 转换在 server,core/service 不依赖 client,Repository 不依赖上层 DTO。采用 Spring Bean 和构造器注入,显式映射或忽略字段;禁止通过全局 IGNORE 掩盖合同字段遗漏。
9
+ - Java、依赖与处理器版本来自工程基线,不从下面通用示例推断。Java 语法、API、处理器与测试工具均消费批准的精确平台配置;MVC 的 wire/internal 转换在 server,core/service 不依赖 client,Repository 不依赖上层 DTO。采用 Spring Bean 和构造器注入,显式映射或忽略字段;禁止通过全局 IGNORE 掩盖合同字段遗漏。
10
10
  - 消费批准后的 Slice Contract/work unit,只为合同中的 DTO/VO/PO/Domain 转换生成 Mapper/Convertor。
11
11
  - 受控生成必须记录 source/target、忽略字段、更新语义、Lombok processor 配置和 mapper 行为测试。
12
12
  - 按 `yss-implementation-contract-compiler` 的统一 Execution Result 返回生成/源文件、测试和实际验证;未映射字段、反射/BeanUtils 退化或越界路径返回 `violation`。
@@ -26,3 +26,5 @@ description: "实现或排查 MapStruct DTO、PO、Domain 映射及 Lombok 集
26
26
  按变更验证字段覆盖、null、更新和边界值;处理器调整需实际编译。不要把所有参考示例当作必跑清单。
27
27
 
28
28
  查第三方行为时使用 `yss-research`,来源为 [MapStruct 官方文档](https://mapstruct.org/documentation/stable/reference/html/);不假定某个 MCP 工具已安装。
29
+
30
+ 聚合重建使用领域工厂或有测试的显式映射方法;MapStruct 只承担机械映射,不得为处理器增加破坏不变量的 setter。该方式不豁免字段覆盖、null 语义和往返一致性验证。
@@ -85,3 +85,10 @@ public interface QualityTemplateApplicationConvertor {
85
85
  ## 8. 旧架构边界
86
86
 
87
87
  检测到 AppService 接收 client Cmd、返回 client VO、调用返回 VO 的 Domain Gateway 或位于 `core/service` 时,返回 `unsupported`,不得由新 scaffold 或本指南自动改写。旧项目继续按原工程维护;现代化改造必须单独立项、先评估再逐切片迁移。
88
+
89
+ ## 事务与外部副作用
90
+
91
+ - 合同标明需要原子提交的数据库写入及所属用例;事务从 Application 公共代理 seam 进入。无需原子写入的查询或纯计算不强制增加事务注解。
92
+ - Repository 参与既有事务,不另行决定业务提交边界。多表聚合保存的回滚测试从用例入口注入中途失败,不能只验证单个 Mapper。
93
+ - Redis、邮件、HTTP 调用不因数据库事务自动获得原子性。按批准合同选择提交后缓存失效、可重试事件或 outbox;需要持久重试时不能只依赖内存事件。
94
+ - 幂等键、重复请求结果、重试范围及失败恢复来自合同;不在技能中默认引入分布式事务或通用重试。测试覆盖回滚不发布、提交后失败及重复消费。
@@ -0,0 +1,38 @@
1
+ ---
2
+ name: yss-backend-spec-review
3
+ description: 审计既有 Java 后端工程对 YSS 技能规范和已批准业务 Spec 的符合性,并在授权范围内编排整改、独立审查和复验直至达标。适用于历史工程规范补齐、全工程体检和持续整改;普通 PR 审查使用 code-review。
4
+ ---
5
+
6
+ # 既有后端工程规格审查与整改
7
+
8
+ 这是既有工程的符合性与整改入口,不是第二个通用代码审查器。用户要求“修复直到符合规范”时持续推进已授权整改;要求“只审查”时仅输出差距与整改建议。仅创建本 Skill 不代表获准修改某个业务工程。
9
+
10
+ ## 输入与事实来源
11
+
12
+ - 明确工程根、审查范围与读写授权;记录 HEAD、已有工作区改动、当前实现仓登记及回滚点。不能将“当前系统”解释为所有子项目,也不以固定 `apps/backend` 目录替代登记。
13
+ - 读取治理仓身份、根 `CONTEXT.md`、工程基线、架构身份、精确平台、业务 Spec / ADR / OpenAPI、已有 Slice 合同与批准记录。未纳管工程可以先做只读盘点;修改前按所属 Harness 的实现接入流程补齐输入。
14
+ - 规范来自当前工程适用且已锁定的 canonical YSS Skill、版本/hash 和组件源码索引。当前最新 Skill 与工程锁定版本有差异时单列迁移差距,不能静默改锁或追着浮动版本整改。
15
+ - 技能规范属于 **Standards**;业务行为属于 **Spec**。技能文件不能冒充产品 Spec,测试全绿也不能替代任一轴。
16
+
17
+ ## 执行闭环
18
+
19
+ 1. **盘点完整范围**:按模块、责任类型、入口、持久化和组件建立覆盖清单。初次审计包含未修改的既有代码、手工适配器、配置/XML 和测试,不局限于 diff;登记未检查项及原因。详细步骤见 [审计与整改流程](references/remediation-workflow.md)。
20
+ 2. **编译适用规则**:以合同 `required_skills` 加实际影响形成 Skill 清单,按 [检查输入](references/check-inputs.md) 加载适用规则。每项 finding 关联规则、适用原因、代码位置、证据和验收方法;复用 `constraint_results`,不另建规范注册表。
21
+ 3. **形成可执行整改单元**:按业务路径和风险拆窄切片,保护现有行为,明确允许写路径、回归 seam 与回滚方式。已有授权且批准合同当前时直接推进;缺合同则由实现合同编译器起草、所属编排器批准,本 Skill 不自批、不设置 `ready-for-agent`。现有违规是修复输入,不能要求违规先消失才能提出整改。
22
+ 4. **逐单元修复并验证**:调用适用 YSS 分层/组件技能。业务行为按 `tdd`;纯配置或机械修改记录不适用原因与可执行验证。优先工程根 `./mvnw`,保留实际命令、退出码、测试执行数量和证据摘要。禁止为通过检查删除测试、缩小检查包、改弱规则或恢复退役组件。
23
+ 5. **独立复审**:修复者不能自审。由独立 Reviewer 使用唯一 `code-review`,消费当前合同、技能集、候选摘要、所有未关闭 finding 及修复影响的依赖,分别给出 Standards / Spec 结论。Reviewer 只报告,不写实现;缺独立执行条件时记录阻塞,不能自行替代签字。
24
+ 6. **继续闭环**:`violation` 返回实施者修复;`drift` / `new_impacts` 使合同 stale 并回编译器;缺凭据或外部组件证据明确 blocked。更新候选,复验受影响项及依赖;其他项只有证据仍适用才可显式复用。同一原因连续未收敛时诊断根因、拆小整改单元,不以无限重试或增加审查轮数绕过既有 review_round 合同。
25
+ 7. **全范围对账后退出**:回到最初覆盖清单,确认没有遗漏、未解决强制违规或未经验证的整改,且当前候选上的适用机器检查和独立审查通过。阶段修复完成不等于整个系统达标;残留项必须继续或报告具体阻塞。
26
+
27
+ ## 必须保留的边界
28
+
29
+ - 保留已登记 DDD / Layered MVC 架构及目标 JDK / Boot;Java 8 合规不要求升级 Java 17。环境 JDK 不决定工程平台;架构转换、平台迁移、组件替换须另行路由。
30
+ - 规范含糊、规则冲突或合理工程实践与 Skill 不符时,记录规则所有者与证据,回模板维护评估;不能直接修改 Skill 让当前工程“合规”,也不能把启发式建议升为强制规则。
31
+ - 业务 Spec 缺失或相互矛盾时,可完成有证据的 Standards 盘点;Spec 轴保留 `missing_evidence`,不从实现倒推并伪造批准。行为保持式整改也须有已确认的验收 seam。
32
+ - 生成 Manifest 是历史来源,当前状态与迁移通过工程基线对账;不得重写生成历史。既有工程不重跑脚手架覆盖代码。
33
+ - `not-applicable` 仅用于未触发的规则。延期 seam 必须满足原合同,但尚未满足的强制规范不能因此记成已达标。非阻断建议进入待办。
34
+ - 不自动提交、推送、发布、迁移生产数据或修改只读参考仓。关键决定只在实际新增影响或已有边界命中时回用户,不对每个 Skill 重复请求批准。
35
+
36
+ ## 输出与完成依据
37
+
38
+ 复用 `docs/templates/review-report-template.md`、当前 Slice 的 YSS Skill Execution Result 和 [审计与整改流程中的记录要求](references/remediation-workflow.md#证据与交接)。报告列明审查覆盖、Standards / Spec 分轴结论、修复前后 finding、候选/规则摘要、实际验证、独立 Reviewer 身份和剩余阻塞。只对本次覆盖且有证据的范围声明符合规范,不自动证明平台已认证或允许发布。