create-yss-spec 3.4.9 → 3.5.0

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 (287) hide show
  1. package/README.md +2 -2
  2. package/package.json +1 -1
  3. package/src/api/_sync-service.js +111 -18
  4. package/src/cli/args.js +1 -0
  5. package/src/cli/help.js +10 -1
  6. package/src/cli/index.js +3 -0
  7. package/src/cli/prompts.js +5 -1
  8. package/src/cli/router.js +2 -0
  9. package/src/commands/attach.js +12 -2
  10. package/src/commands/init.js +2 -0
  11. package/src/commands/skills.js +253 -0
  12. package/src/commands/sync.js +3 -0
  13. package/src/family-identity.js +1 -1
  14. package/src/template/asset-runtime.js +220 -0
  15. package/src/template/attach-planner.js +5 -0
  16. package/src/template/distribution-runtime.js +165 -0
  17. package/src/template/instance-runtime.js +76 -29
  18. package/src/template/ownership-policy.js +17 -1
  19. package/src/template/prune-planner.js +6 -1
  20. package/src/template/sync-planner.js +8 -0
  21. package/src/template/verification-runtime.js +56 -1
  22. package/src/validation/metadata.js +24 -1
  23. package/template/.agents/skills/.strategic-design-skills-manifest.json +1 -1
  24. package/template/.agents/skills/yss-cache/SKILL.md +6 -5
  25. package/template/.agents/skills/yss-cache/references/annotations.md +3 -1
  26. package/template/.agents/skills/yss-cache/references/architecture.md +4 -2
  27. package/template/.agents/skills/yss-cache/references/configuration.md +6 -3
  28. package/template/.agents/skills/yss-cache/references/jetcache.md +21 -0
  29. package/template/.agents/skills/yss-cache/references/redis-fallback.md +5 -3
  30. package/template/.agents/skills/yss-cache/references/verification.md +10 -4
  31. package/template/.agents/skills/yss-cache/scripts/verify-cache-component.sh +26 -20
  32. package/template/.agents/skills/yss-distributed-id/SKILL.md +14 -58
  33. package/template/.agents/skills/yss-distributed-id/agents/openai.yaml +2 -2
  34. package/template/.agents/skills/yss-distributed-id/references/README.md +11 -47
  35. package/template/.agents/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
  36. package/template/.agents/skills/yss-mybatis/SKILL.md +14 -2
  37. package/template/.agents/skills/yss-openapi-draft-review/SKILL.md +2 -0
  38. package/template/.agents/skills/yss-openapi-governance/SKILL.md +4 -0
  39. package/template/.agents/skills/yss-product-lifecycle/SKILL.md +11 -5
  40. package/template/.agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml +68 -5
  41. package/template/.agents/skills/yss-product-lifecycle/references/orchestration.md +2 -2
  42. package/template/.agents/skills/yss-product-lifecycle/references/state-model.md +8 -8
  43. package/template/.agents/skills/yss-prototype-stage/SKILL.md +4 -2
  44. package/template/.agents/skills/yss-prototype-stage/scripts/visual-baseline-contract.mjs +2 -2
  45. package/template/.agents/skills/yss-repository/SKILL.md +1 -0
  46. package/template/.agents/skills/yss-stage-decision/SKILL.md +2 -0
  47. package/template/.agents/skills/yss-tactical-design/SKILL.md +2 -0
  48. package/template/.agents/skills/yss-technical-design/SKILL.md +2 -0
  49. package/template/.agents/skills/yss-technical-design/scripts/engineering-design.mjs +1 -1
  50. package/template/.agents/skills/yss-technical-design/scripts/validate-technical-design.mjs +11 -8
  51. package/template/.codex/skills/yss-cache/SKILL.md +6 -5
  52. package/template/.codex/skills/yss-cache/references/annotations.md +3 -1
  53. package/template/.codex/skills/yss-cache/references/architecture.md +4 -2
  54. package/template/.codex/skills/yss-cache/references/configuration.md +6 -3
  55. package/template/.codex/skills/yss-cache/references/jetcache.md +21 -0
  56. package/template/.codex/skills/yss-cache/references/redis-fallback.md +5 -3
  57. package/template/.codex/skills/yss-cache/references/verification.md +10 -4
  58. package/template/.codex/skills/yss-cache/scripts/verify-cache-component.sh +26 -20
  59. package/template/.codex/skills/yss-distributed-id/SKILL.md +14 -58
  60. package/template/.codex/skills/yss-distributed-id/agents/openai.yaml +2 -2
  61. package/template/.codex/skills/yss-distributed-id/references/README.md +11 -47
  62. package/template/.codex/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
  63. package/template/.codex/skills/yss-mybatis/SKILL.md +14 -2
  64. package/template/.codex/skills/yss-openapi-draft-review/SKILL.md +2 -0
  65. package/template/.codex/skills/yss-openapi-governance/SKILL.md +4 -0
  66. package/template/.codex/skills/yss-product-lifecycle/SKILL.md +11 -5
  67. package/template/.codex/skills/yss-product-lifecycle/references/orchestration-contract.yaml +68 -5
  68. package/template/.codex/skills/yss-product-lifecycle/references/orchestration.md +2 -2
  69. package/template/.codex/skills/yss-product-lifecycle/references/state-model.md +8 -8
  70. package/template/.codex/skills/yss-prototype-stage/SKILL.md +4 -2
  71. package/template/.codex/skills/yss-prototype-stage/scripts/visual-baseline-contract.mjs +2 -2
  72. package/template/.codex/skills/yss-repository/SKILL.md +1 -0
  73. package/template/.codex/skills/yss-stage-decision/SKILL.md +2 -0
  74. package/template/.codex/skills/yss-tactical-design/SKILL.md +2 -0
  75. package/template/.codex/skills/yss-technical-design/SKILL.md +2 -0
  76. package/template/.codex/skills/yss-technical-design/scripts/engineering-design.mjs +1 -1
  77. package/template/.codex/skills/yss-technical-design/scripts/validate-technical-design.mjs +11 -8
  78. package/template/.cursor/skills/yss-cache/SKILL.md +6 -5
  79. package/template/.cursor/skills/yss-cache/references/annotations.md +3 -1
  80. package/template/.cursor/skills/yss-cache/references/architecture.md +4 -2
  81. package/template/.cursor/skills/yss-cache/references/configuration.md +6 -3
  82. package/template/.cursor/skills/yss-cache/references/jetcache.md +21 -0
  83. package/template/.cursor/skills/yss-cache/references/redis-fallback.md +5 -3
  84. package/template/.cursor/skills/yss-cache/references/verification.md +10 -4
  85. package/template/.cursor/skills/yss-cache/scripts/verify-cache-component.sh +26 -20
  86. package/template/.cursor/skills/yss-distributed-id/SKILL.md +14 -58
  87. package/template/.cursor/skills/yss-distributed-id/agents/openai.yaml +2 -2
  88. package/template/.cursor/skills/yss-distributed-id/references/README.md +11 -47
  89. package/template/.cursor/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
  90. package/template/.cursor/skills/yss-mybatis/SKILL.md +14 -2
  91. package/template/.cursor/skills/yss-openapi-draft-review/SKILL.md +2 -0
  92. package/template/.cursor/skills/yss-openapi-governance/SKILL.md +4 -0
  93. package/template/.cursor/skills/yss-product-lifecycle/SKILL.md +11 -5
  94. package/template/.cursor/skills/yss-product-lifecycle/references/orchestration-contract.yaml +68 -5
  95. package/template/.cursor/skills/yss-product-lifecycle/references/orchestration.md +2 -2
  96. package/template/.cursor/skills/yss-product-lifecycle/references/state-model.md +8 -8
  97. package/template/.cursor/skills/yss-prototype-stage/SKILL.md +4 -2
  98. package/template/.cursor/skills/yss-prototype-stage/scripts/visual-baseline-contract.mjs +2 -2
  99. package/template/.cursor/skills/yss-repository/SKILL.md +1 -0
  100. package/template/.cursor/skills/yss-stage-decision/SKILL.md +2 -0
  101. package/template/.cursor/skills/yss-tactical-design/SKILL.md +2 -0
  102. package/template/.cursor/skills/yss-technical-design/SKILL.md +2 -0
  103. package/template/.cursor/skills/yss-technical-design/scripts/engineering-design.mjs +1 -1
  104. package/template/.cursor/skills/yss-technical-design/scripts/validate-technical-design.mjs +11 -8
  105. package/template/.pi/skills/yss-cache/SKILL.md +6 -5
  106. package/template/.pi/skills/yss-cache/references/annotations.md +3 -1
  107. package/template/.pi/skills/yss-cache/references/architecture.md +4 -2
  108. package/template/.pi/skills/yss-cache/references/configuration.md +6 -3
  109. package/template/.pi/skills/yss-cache/references/jetcache.md +21 -0
  110. package/template/.pi/skills/yss-cache/references/redis-fallback.md +5 -3
  111. package/template/.pi/skills/yss-cache/references/verification.md +10 -4
  112. package/template/.pi/skills/yss-cache/scripts/verify-cache-component.sh +26 -20
  113. package/template/.pi/skills/yss-distributed-id/SKILL.md +14 -58
  114. package/template/.pi/skills/yss-distributed-id/agents/openai.yaml +2 -2
  115. package/template/.pi/skills/yss-distributed-id/references/README.md +11 -47
  116. package/template/.pi/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
  117. package/template/.pi/skills/yss-mybatis/SKILL.md +14 -2
  118. package/template/.pi/skills/yss-openapi-draft-review/SKILL.md +2 -0
  119. package/template/.pi/skills/yss-openapi-governance/SKILL.md +4 -0
  120. package/template/.pi/skills/yss-product-lifecycle/SKILL.md +11 -5
  121. package/template/.pi/skills/yss-product-lifecycle/references/orchestration-contract.yaml +68 -5
  122. package/template/.pi/skills/yss-product-lifecycle/references/orchestration.md +2 -2
  123. package/template/.pi/skills/yss-product-lifecycle/references/state-model.md +8 -8
  124. package/template/.pi/skills/yss-prototype-stage/SKILL.md +4 -2
  125. package/template/.pi/skills/yss-prototype-stage/scripts/visual-baseline-contract.mjs +2 -2
  126. package/template/.pi/skills/yss-repository/SKILL.md +1 -0
  127. package/template/.pi/skills/yss-stage-decision/SKILL.md +2 -0
  128. package/template/.pi/skills/yss-tactical-design/SKILL.md +2 -0
  129. package/template/.pi/skills/yss-technical-design/SKILL.md +2 -0
  130. package/template/.pi/skills/yss-technical-design/scripts/engineering-design.mjs +1 -1
  131. package/template/.pi/skills/yss-technical-design/scripts/validate-technical-design.mjs +11 -8
  132. package/template/AGENTS.md +2 -0
  133. package/template/CONTEXT.md +2 -1
  134. package/template/README.md +3 -5
  135. package/template/docs/agents/issue-tracker.md +7 -0
  136. package/template/docs/agents/skill-migrations.md +4 -0
  137. package/template/docs/agents/yss-skill-registry.yaml +7 -6
  138. package/template/docs/design/README.md +0 -1
  139. package/template/docs/design/design.md +2 -2
  140. package/template/docs/design/templates/interaction-spec-template.md +1 -1
  141. package/template/docs/engineering/evidence/aliyun-artifact-resolution.json +446 -0
  142. package/template/docs/process/contract-reading.md +46 -0
  143. package/template/docs/process/lifecycle-artifact-map.md +2 -2
  144. package/template/docs/process/lifecycle-registry-baseline.json +2 -1
  145. package/template/docs/process/lifecycle-registry.yaml +10 -4
  146. package/template/docs/process/schemas/api-contract-decision-v2.schema.json +198 -0
  147. package/template/docs/process/schemas/digital-human-task-package.schema.json +8 -0
  148. package/template/docs/process/schemas/lifecycle-checkpoint.schema.json +259 -0
  149. package/template/docs/process/schemas/stage-tracking.schema.json +259 -0
  150. package/template/docs/process/stage-tracking.md +62 -0
  151. package/template/docs/process/strategic-handoff-package.md +8 -0
  152. package/template/docs/user-guide//347/224/250/346/210/267/346/211/213/345/206/214.md +2 -0
  153. package/template/docs/user-guide//347/224/250/346/210/267/346/211/213/345/206/214/347/264/242/345/274/225.md +1 -0
  154. package/template/docs/user-guide//351/230/266/346/256/265/346/216/245/345/205/245/344/270/216/344/272/247/347/211/251/345/244/215/347/224/250/344/275/277/347/224/250/346/211/213/345/206/214.md +243 -0
  155. package/template/scripts/api-contract-decision +16 -0
  156. package/template/scripts/complete-backend-delivery +17 -0
  157. package/template/scripts/contract +13 -0
  158. package/template/scripts/design-md +2 -0
  159. package/template/scripts/dispatch-slice-task +13 -0
  160. package/template/scripts/lib/api-contract-decision.mjs +45 -4
  161. package/template/scripts/lib/approval-record.mjs +1 -1
  162. package/template/scripts/lib/approved-execution-context.mjs +35 -7
  163. package/template/scripts/lib/backend-architecture.mjs +2 -2
  164. package/template/scripts/lib/backend-delivery-terminal.mjs +51 -0
  165. package/template/scripts/lib/backend-platform-provenance.mjs +1 -1
  166. package/template/scripts/lib/backend-platform.mjs +1 -1
  167. package/template/scripts/lib/backend-scaffold-prerequisites.mjs +1 -1
  168. package/template/scripts/lib/context-contract.mjs +1 -1
  169. package/template/scripts/lib/context-reconciliation.mjs +1 -1
  170. package/template/scripts/lib/contract-source.mjs +22 -0
  171. package/template/scripts/lib/contract-views.mjs +109 -0
  172. package/template/scripts/lib/delivery-preflight.mjs +3 -2
  173. package/template/scripts/lib/design-md.mjs +230 -0
  174. package/template/scripts/lib/digital-human-roles.mjs +1 -1
  175. package/template/scripts/lib/existing-backend-architecture.mjs +4 -3
  176. package/template/scripts/lib/existing-ui-baseline.mjs +4 -2
  177. package/template/scripts/lib/frontend-delivery-boundary.mjs +1 -1
  178. package/template/scripts/lib/frontend-delivery.mjs +1 -1
  179. package/template/scripts/lib/harness-execution-scope.mjs +15 -1
  180. package/template/scripts/lib/implementation-contract-compiler.mjs +4 -4
  181. package/template/scripts/lib/json-schema.mjs +19 -4
  182. package/template/scripts/lib/lifecycle-context-query.mjs +5 -1
  183. package/template/scripts/lib/lifecycle-controls.mjs +1 -1
  184. package/template/scripts/lib/lifecycle-execution-scope.mjs +92 -0
  185. package/template/scripts/lib/lifecycle-registry.mjs +2 -1
  186. package/template/scripts/lib/lifecycle-transition.mjs +21 -3
  187. package/template/scripts/lib/openapi-draft-validation.mjs +2 -1
  188. package/template/scripts/lib/skill-registry.mjs +10 -3
  189. package/template/scripts/lib/skill-supply-chain.mjs +33 -14
  190. package/template/scripts/lib/slice-contract-preparation.mjs +16 -14
  191. package/template/scripts/lib/slice-contract-views.mjs +1 -1
  192. package/template/scripts/lib/slice-contract.mjs +18 -3
  193. package/template/scripts/lib/slice-execution-preflight.mjs +7 -3
  194. package/template/scripts/lib/slice-repositories.mjs +1 -1
  195. package/template/scripts/lib/slice-task-package.mjs +3 -2
  196. package/template/scripts/lib/stage-tracking-migration.mjs +176 -0
  197. package/template/scripts/lib/stage-tracking.mjs +184 -0
  198. package/template/scripts/lib/strategic-handoff-consumption.mjs +5 -4
  199. package/template/scripts/lib/strategic-handoff-io.mjs +5 -2
  200. package/template/scripts/lib/strategic-handoff-routing.mjs +31 -0
  201. package/template/scripts/lib/strategic-handoff.mjs +4 -2
  202. package/template/scripts/lib/task-package.mjs +2 -0
  203. package/template/scripts/lib/technical-design-boundary.mjs +4 -2
  204. package/template/scripts/lib/ui-baseline.mjs +1 -1
  205. package/template/scripts/lib/user-decision.mjs +1 -1
  206. package/template/scripts/lib/validation-phase.mjs +72 -0
  207. package/template/scripts/stage-tracking +25 -0
  208. package/template/scripts/strategic-consumer-entry +47 -0
  209. package/template/scripts/verify-context-reconciliation +4 -3
  210. package/template/scripts/verify-lifecycle-checkpoint +10 -0
  211. package/template/scripts/verify-project-instance +11 -10
  212. package/template/skills-lock.json +17 -32
  213. package/template.manifest.json +18 -3
  214. package/template.snapshot.json +5 -5
  215. package/template/.agents/skills/grill-me/SKILL.md +0 -7
  216. package/template/.agents/skills/grill-me/agents/openai.yaml +0 -5
  217. package/template/.agents/skills/yss-distributed-id/assets/AutoIdInterceptor.java +0 -246
  218. package/template/.agents/skills/yss-distributed-id/assets/EnableDistributedId.java +0 -25
  219. package/template/.agents/skills/yss-distributed-id/assets/LeafConf.java +0 -36
  220. package/template/.codex/skills/grill-me/SKILL.md +0 -7
  221. package/template/.codex/skills/grill-me/agents/openai.yaml +0 -5
  222. package/template/.codex/skills/yss-distributed-id/assets/AutoIdInterceptor.java +0 -246
  223. package/template/.codex/skills/yss-distributed-id/assets/EnableDistributedId.java +0 -25
  224. package/template/.codex/skills/yss-distributed-id/assets/LeafConf.java +0 -36
  225. package/template/.cursor/skills/grill-me/SKILL.md +0 -7
  226. package/template/.cursor/skills/grill-me/agents/openai.yaml +0 -5
  227. package/template/.cursor/skills/yss-distributed-id/assets/AutoIdInterceptor.java +0 -246
  228. package/template/.cursor/skills/yss-distributed-id/assets/EnableDistributedId.java +0 -25
  229. package/template/.cursor/skills/yss-distributed-id/assets/LeafConf.java +0 -36
  230. package/template/.pi/skills/grill-me/SKILL.md +0 -7
  231. package/template/.pi/skills/grill-me/agents/openai.yaml +0 -5
  232. package/template/.pi/skills/yss-distributed-id/assets/AutoIdInterceptor.java +0 -246
  233. package/template/.pi/skills/yss-distributed-id/assets/EnableDistributedId.java +0 -25
  234. package/template/.pi/skills/yss-distributed-id/assets/LeafConf.java +0 -36
  235. package/template/docs/agents/yss-plugin-dependency-contract.md +0 -45
  236. package/template/docs/api/.gitkeep +0 -0
  237. package/template/docs/api/specs/.gitkeep +0 -0
  238. package/template/docs/architecture/.gitkeep +0 -0
  239. package/template/docs/design/facts/antdv-next/1.5.2/cli-help.txt +0 -38
  240. package/template/docs/design/facts/antdv-next/1.5.2/component-list.json +0 -434
  241. package/template/docs/design/facts/antdv-next/1.5.2/components/Alert/demo-basic.json +0 -7
  242. package/template/docs/design/facts/antdv-next/1.5.2/components/Alert/info.json +0 -246
  243. package/template/docs/design/facts/antdv-next/1.5.2/components/Alert/semantic.json +0 -40
  244. package/template/docs/design/facts/antdv-next/1.5.2/components/Alert/token.json +0 -32
  245. package/template/docs/design/facts/antdv-next/1.5.2/components/Button/demo-basic.json +0 -7
  246. package/template/docs/design/facts/antdv-next/1.5.2/components/Button/info.json +0 -190
  247. package/template/docs/design/facts/antdv-next/1.5.2/components/Button/semantic.json +0 -20
  248. package/template/docs/design/facts/antdv-next/1.5.2/components/Button/token.json +0 -256
  249. package/template/docs/design/facts/antdv-next/1.5.2/components/Input/demo-basic.json +0 -7
  250. package/template/docs/design/facts/antdv-next/1.5.2/components/Input/info.json +0 -460
  251. package/template/docs/design/facts/antdv-next/1.5.2/components/Input/semantic.json +0 -70
  252. package/template/docs/design/facts/antdv-next/1.5.2/components/Input/token.json +0 -123
  253. package/template/docs/design/facts/antdv-next/1.5.2/components/Modal/demo-basic.json +0 -7
  254. package/template/docs/design/facts/antdv-next/1.5.2/components/Modal/info.json +0 -646
  255. package/template/docs/design/facts/antdv-next/1.5.2/components/Modal/semantic.json +0 -50
  256. package/template/docs/design/facts/antdv-next/1.5.2/components/Modal/token.json +0 -130
  257. package/template/docs/design/facts/antdv-next/1.5.2/components/Select/demo-basic.json +0 -7
  258. package/template/docs/design/facts/antdv-next/1.5.2/components/Select/info.json +0 -640
  259. package/template/docs/design/facts/antdv-next/1.5.2/components/Select/semantic.json +0 -70
  260. package/template/docs/design/facts/antdv-next/1.5.2/components/Select/token.json +0 -172
  261. package/template/docs/design/facts/antdv-next/1.5.2/components/Table/demo-basic.json +0 -7
  262. package/template/docs/design/facts/antdv-next/1.5.2/components/Table/info.json +0 -1010
  263. package/template/docs/design/facts/antdv-next/1.5.2/components/Table/semantic.json +0 -70
  264. package/template/docs/design/facts/antdv-next/1.5.2/components/Table/token.json +0 -263
  265. package/template/docs/design/facts/antdv-next/1.5.2/components/Tag/demo-basic.json +0 -7
  266. package/template/docs/design/facts/antdv-next/1.5.2/components/Tag/info.json +0 -321
  267. package/template/docs/design/facts/antdv-next/1.5.2/components/Tag/semantic.json +0 -25
  268. package/template/docs/design/facts/antdv-next/1.5.2/components/Tag/token.json +0 -25
  269. package/template/docs/design/facts/antdv-next/1.5.2/design-md.json +0 -3
  270. package/template/docs/design/facts/antdv-next/1.5.2/manifest.json +0 -215
  271. package/template/docs/design/facts/antdv-next/1.5.2/resolution-probe.json +0 -5
  272. package/template/docs/design/prototypes/.gitkeep +0 -1
  273. package/template/docs/implementation/.gitkeep +0 -1
  274. package/template/docs/plan/IDEATION.md +0 -96
  275. package/template/docs/process/PDCA-SCRUM.md +0 -10
  276. package/template/docs/process/harness-executive-blueprint.md +0 -10
  277. package/template/docs/releases/.gitkeep +0 -1
  278. package/template/docs/requirements/README.md +0 -85
  279. package/template/docs/requirements/tickets/.gitkeep +0 -1
  280. package/template/docs/templates/agent-brief-template.md +0 -46
  281. package/template/docs/templates/architecture-proposal-template.md +0 -18
  282. package/template/docs/templates/implementation-plan-template.md +0 -16
  283. package/template/docs/templates/implementation-routing-template.md +0 -350
  284. package/template/docs/templates/requirement-freeze-template.md +0 -63
  285. package/template/docs/templates/risk-register-template.md +0 -11
  286. package/template/docs/templates/user-story-template.md +0 -20
  287. package/template/docs/testing/README.md +0 -110
@@ -0,0 +1,230 @@
1
+ #!/usr/bin/env node
2
+ import { createHash } from "node:crypto";
3
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
4
+ import path from "node:path";
5
+ import { spawnSync } from "node:child_process";
6
+ import { createRequire } from "node:module";
7
+ import { parseDocument } from "../vendor/yaml.mjs";
8
+
9
+ const repositoryRoot = path.resolve(new URL("../..", import.meta.url).pathname);
10
+ const designPath = path.join(repositoryRoot, "DESIGN.md");
11
+ const projectionDir = path.join(repositoryRoot, "docs/design/tokens");
12
+ const manifestPath = path.join(projectionDir, ".design-md-projection.json");
13
+ const syncMetadataPath = path.join(repositoryRoot, "docs/design/design-system-sync.yaml");
14
+ const expectedSections = ["Overview", "Colors", "Typography", "Layout", "Elevation & Depth", "Shapes", "Components", "Do's and Don'ts"];
15
+ const requiredFrontmatter = ["version", "name", "description", "colors", "typography", "rounded", "spacing", "components"];
16
+ const componentProperties = new Set(["backgroundColor", "textColor", "typography", "rounded", "padding", "size", "height", "width"]);
17
+
18
+ function sha256(value) { return createHash("sha256").update(value).digest("hex"); }
19
+ function fail(message) { throw new Error(message); }
20
+ function escapeRegExp(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); }
21
+
22
+ function resolveValue(value, frontmatter) {
23
+ const match = typeof value === "string" ? value.match(/^\{(colors|typography|rounded|spacing)\.([\w-]+)\}$/) : null;
24
+ return match ? frontmatter[match[1]]?.[match[2]] : value;
25
+ }
26
+
27
+ function projectedCssVariables(frontmatter) {
28
+ const components = frontmatter.components;
29
+ return {
30
+ "yss-color-primary-control": resolveValue(components["button-primary"].backgroundColor, frontmatter),
31
+ "yss-color-primary-control-hover": resolveValue(components["button-primary-hover"].backgroundColor, frontmatter),
32
+ "yss-color-on-primary": resolveValue(components["button-primary"].textColor, frontmatter),
33
+ "yss-control-height": resolveValue(components["button-primary"].height, frontmatter),
34
+ "yss-card-padding": resolveValue(components["card-default"].padding, frontmatter),
35
+ "yss-control-height-compact": resolveValue(components["button-compact"].height, frontmatter),
36
+ "yss-card-compact-padding": resolveValue(components["card-compact"].padding, frontmatter)
37
+ };
38
+ }
39
+
40
+ function writeCssProjection(frontmatter) {
41
+ const cssPath = path.join(projectionDir, "variables.css");
42
+ let source = readFileSync(cssPath, "utf8");
43
+ for (const [name, value] of Object.entries(projectedCssVariables(frontmatter))) {
44
+ const declaration = ` --${name}: ${value};`;
45
+ const pattern = new RegExp(`^\\s*--${escapeRegExp(name)}:.*;$`, "m");
46
+ source = pattern.test(source) ? source.replace(pattern, declaration) : source.replace(":root {\n", `:root {\n${declaration}\n`);
47
+ }
48
+ writeFileSync(cssPath, source);
49
+ }
50
+
51
+ function validateCssProjection(frontmatter) {
52
+ const source = readFileSync(path.join(projectionDir, "variables.css"), "utf8");
53
+ for (const [name, value] of Object.entries(projectedCssVariables(frontmatter))) {
54
+ if (!new RegExp(`^\\s*--${escapeRegExp(name)}: ${escapeRegExp(String(value))};$`, "m").test(source)) fail(`variables.css 的 --${name} 与 DESIGN.md 不一致`);
55
+ }
56
+ }
57
+
58
+ function readDesign(file = designPath) {
59
+ const source = readFileSync(file, "utf8");
60
+ if (!source.startsWith("---\n")) fail(`${path.relative(repositoryRoot, file)} 缺少 YAML frontmatter`);
61
+ const end = source.indexOf("\n---", 4);
62
+ if (end < 0) fail(`${path.relative(repositoryRoot, file)} frontmatter 未闭合`);
63
+ const document = parseDocument(source.slice(4, end), { uniqueKeys: true, maxAliasCount: 0 });
64
+ if (document.errors.length) fail(document.errors[0].message);
65
+ const frontmatter = document.toJS({ maxAliasCount: 0 });
66
+ for (const field of requiredFrontmatter) if (!frontmatter?.[field]) fail(`frontmatter 缺少 ${field}`);
67
+ const body = source.slice(end + 4);
68
+ const headings = [...body.matchAll(/^## ([^\n]+)$/gm)].map((match) => match[1].trim());
69
+ const actual = headings.slice(0, expectedSections.length);
70
+ if (JSON.stringify(actual) !== JSON.stringify(expectedSections)) fail(`canonical H2 章节顺序必须为: ${expectedSections.join(" → ")}`);
71
+ for (const section of expectedSections) {
72
+ if (headings.filter((heading) => heading === section).length !== 1) fail(`canonical H2 章节必须且只能出现一次: ${section}`);
73
+ }
74
+ const canonicalH1 = [...body.matchAll(/^# ([^\n]+)$/gm)].map((match) => match[1].trim()).filter((heading) => expectedSections.includes(heading));
75
+ if (canonicalH1.length > 0) fail(`canonical 正文章节必须使用 H2,不能使用 H1: ${canonicalH1.join(", ")}`);
76
+ for (const [name, component] of Object.entries(frontmatter.components)) {
77
+ if (!/^[a-z][a-z0-9-]*$/.test(name)) fail(`组件变体名称非法: ${name}`);
78
+ for (const key of Object.keys(component)) if (!componentProperties.has(key)) fail(`组件 ${name} 使用不支持的属性: ${key}`);
79
+ }
80
+ const references = [...source.matchAll(/\{(colors|typography|rounded|spacing)\.([\w-]+)\}/g)].map((match) => match[0]);
81
+ for (const reference of references) {
82
+ const [, group, token] = reference.match(/^\{([^}]+)\.([^}]+)\}$/);
83
+ if (frontmatter[group]?.[token] === undefined) fail(`悬空 token 引用: ${reference}`);
84
+ }
85
+ if (file === designPath && existsSync(syncMetadataPath)) {
86
+ const syncDocument = parseDocument(readFileSync(syncMetadataPath, "utf8"), { uniqueKeys: true, maxAliasCount: 0 });
87
+ if (syncDocument.errors.length) fail(syncDocument.errors[0].message);
88
+ const sync = syncDocument.toJS({ maxAliasCount: 0 })?.design_system_sync;
89
+ if (!sync?.baseline_sha256 || sync.baseline_sha256 !== sha256(source)) {
90
+ fail("design-system-sync.yaml 的 baseline_sha256 与 DESIGN.md 不一致,请更新跨仓同步摘要");
91
+ }
92
+ if (!Array.isArray(sync.synchronized_sections) || sync.synchronized_sections.length === 0) {
93
+ fail("design-system-sync.yaml 缺少 synchronized_sections");
94
+ }
95
+ }
96
+ return { source, frontmatter };
97
+ }
98
+
99
+ function runUpstream(args) {
100
+ const result = spawnSync("npx", ["--yes", "@google/design.md@0.4.0", ...args], { cwd: repositoryRoot, encoding: "utf8" });
101
+ if (result.error || result.status !== 0) fail(result.stderr?.trim() || result.stdout?.trim() || "design.md CLI 执行失败");
102
+ return result.stdout;
103
+ }
104
+
105
+ function projectionFiles() {
106
+ return ["theme.json", "tokens.default.json", "tokens.dark.json", "tokens.compact.json", "variables.css", "variables.dark.css"].map((file) => path.join(projectionDir, file));
107
+ }
108
+
109
+ function writeProjectionManifest() {
110
+ const { source } = readDesign();
111
+ const files = Object.fromEntries(projectionFiles().map((file) => [path.relative(repositoryRoot, file), sha256(readFileSync(file))]));
112
+ writeFileSync(manifestPath, `${JSON.stringify({ schema_version: 1, source: "DESIGN.md", source_sha256: sha256(source), files }, null, 2)}\n`);
113
+ }
114
+
115
+ function writeThemeProjection(frontmatter) {
116
+ const themeFile = path.join(projectionDir, "theme.json");
117
+ const theme = { token: {}, algorithm: "default" };
118
+ const token = theme.token || (theme.token = {});
119
+ const colors = frontmatter.colors;
120
+ const typography = frontmatter.typography;
121
+ const rounded = frontmatter.rounded;
122
+ const spacing = frontmatter.spacing;
123
+ Object.assign(token, {
124
+ colorPrimary: colors.primary,
125
+ colorSuccess: colors.success,
126
+ colorWarning: colors.warning,
127
+ colorError: colors.error,
128
+ colorInfo: colors.info ?? colors.primary,
129
+ colorTextBase: "#000000",
130
+ colorBgBase: colors.surface ?? "#ffffff",
131
+ colorBgLayout: colors["canvas-layout"] ?? "#f0f2f5",
132
+ colorText: colors.text,
133
+ colorTextSecondary: colors["text-secondary"],
134
+ colorBorder: colors.border ?? "#d9d9d9",
135
+ fontFamily: typography.body.fontFamily,
136
+ fontSize: Number.parseInt(typography.body.fontSize, 10),
137
+ borderRadius: Number.parseInt(rounded.md, 10),
138
+ sizeUnit: Number.parseInt(spacing.xxs, 10),
139
+ sizeStep: Number.parseInt(spacing.xxs, 10),
140
+ controlHeight: Number.parseInt(frontmatter.components["button-primary"].height, 10),
141
+ controlHeightSM: Number.parseInt(frontmatter.components["button-small"].height, 10),
142
+ controlHeightLG: Number.parseInt(frontmatter.components["button-large"].height, 10),
143
+ fontSizeSM: Number.parseInt(typography.caption.fontSize, 10),
144
+ borderRadiusSM: Number.parseInt(rounded.sm, 10),
145
+ borderRadiusLG: Number.parseInt(rounded.lg, 10),
146
+ padding: Number.parseInt(spacing.md, 10),
147
+ paddingSM: Number.parseInt(spacing.sm, 10),
148
+ paddingXS: Number.parseInt(spacing.xs, 10),
149
+ paddingLG: Number.parseInt(spacing.card, 10)
150
+ });
151
+ theme.components = { Card: { borderRadiusLG: token.borderRadiusLG, paddingLG: token.paddingLG } };
152
+ writeFileSync(themeFile, `${JSON.stringify(theme, null, 2)}\n`);
153
+ }
154
+
155
+ function writeAlgorithmProjections(toolchain) {
156
+ if (!toolchain) fail("export --write 需要 --antd-toolchain <独立作者工具目录>,固定 antd 6.6.4");
157
+ const require = createRequire(path.join(path.resolve(toolchain), "package.json"));
158
+ if (require("antd/package.json").version !== "6.6.4") fail("主题算法需要 antd 6.6.4");
159
+ const { theme } = require("antd");
160
+ const config = JSON.parse(readFileSync(path.join(projectionDir, "theme.json")));
161
+ const kebab = key => key.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`);
162
+ const snapshots = {};
163
+ for (const [mode, algorithm] of Object.entries({ default: theme.defaultAlgorithm, dark: theme.darkAlgorithm, compact: theme.compactAlgorithm })) {
164
+ const token = { ...config.token };
165
+ // Let the dark algorithm derive neutral colors instead of pinning light aliases.
166
+ if (mode === "dark") for (const key of ["colorBgBase", "colorTextBase", "colorBgLayout", "colorText", "colorTextSecondary", "colorBorder"]) delete token[key];
167
+ snapshots[mode] = theme.getDesignToken({ token, algorithm });
168
+ writeFileSync(path.join(projectionDir, `tokens.${mode}.json`), JSON.stringify(snapshots[mode], null, 2) + "\n");
169
+ }
170
+ const rewrite = (css, token) => {
171
+ const values = Object.fromEntries(Object.entries(token).map(([key,value]) => [kebab(key),value]));
172
+ return css.replace(/(--brand-([\w-]+):\s*)([^;]+);/g, (all, prefix, key, old) => {
173
+ const value = values[key];
174
+ if (value === undefined || typeof value === "object") return all;
175
+ const unit = typeof value === "number" ? old.trim().match(/(?:px|ms|s)$/)?.[0] || "" : "";
176
+ return `${prefix}${value}${unit};`;
177
+ });
178
+ };
179
+ const cssPath = path.join(projectionDir, "variables.css");
180
+ const css = readFileSync(cssPath, "utf8");
181
+ const darkStart = css.indexOf('[data-theme="dark"]');
182
+ writeFileSync(cssPath, darkStart < 0 ? rewrite(css, snapshots.default) : rewrite(css.slice(0, darkStart), snapshots.default) + rewrite(css.slice(darkStart), snapshots.dark));
183
+ const darkPath = path.join(projectionDir, "variables.dark.css");
184
+ writeFileSync(darkPath, rewrite(readFileSync(darkPath, "utf8"), snapshots.dark));
185
+ }
186
+
187
+ function driftCheck() {
188
+ const { source, frontmatter } = readDesign();
189
+ validateCssProjection(frontmatter);
190
+ if (!existsSync(manifestPath)) fail(`缺少 ${path.relative(repositoryRoot, manifestPath)},请先执行 export --write-manifest`);
191
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
192
+ if (manifest.source_sha256 !== sha256(source)) fail("DESIGN.md 已变化但投影未重新生成(source_sha256 漂移)");
193
+ for (const [relative, digest] of Object.entries(manifest.files || {})) {
194
+ const file = path.join(repositoryRoot, relative);
195
+ if (!existsSync(file) || sha256(readFileSync(file)) !== digest) fail(`派生文件漂移: ${relative}`);
196
+ }
197
+ }
198
+
199
+ function main() {
200
+ const [command, ...args] = process.argv.slice(2);
201
+ if (command === "lint") {
202
+ const file = args.find((arg) => !arg.startsWith("-")) || designPath;
203
+ readDesign(path.resolve(repositoryRoot, file));
204
+ process.stdout.write(`${runUpstream(["lint", file, "--format", "json"])}`);
205
+ return;
206
+ }
207
+ if (command === "diff") {
208
+ if (args.length < 2) fail("用法: design-md diff <before> <after>");
209
+ process.stdout.write(runUpstream(["diff", ...args.slice(0, 2), "--format", "json"]));
210
+ return;
211
+ }
212
+ if (command === "export") {
213
+ const format = args[0] || "dtcg";
214
+ const { frontmatter } = readDesign();
215
+ process.stdout.write(runUpstream(["export", "DESIGN.md", "--format", format]));
216
+ if (args.includes("--write")) {
217
+ const toolchain = args[args.indexOf("--antd-toolchain") + 1];
218
+ if (!args.includes("--antd-toolchain") || !toolchain) fail("缺少 --antd-toolchain");
219
+ writeThemeProjection(frontmatter);
220
+ writeAlgorithmProjections(toolchain);
221
+ writeCssProjection(frontmatter);
222
+ }
223
+ if (args.includes("--write-manifest")) writeProjectionManifest();
224
+ return;
225
+ }
226
+ if (command === "drift") { driftCheck(); process.stdout.write("DESIGN.md 投影无漂移\n"); return; }
227
+ fail("用法: design-md lint|diff|export|drift");
228
+ }
229
+
230
+ try { main(); } catch (error) { process.stderr.write(`${error.message}\n`); process.exitCode = 1; }
@@ -1,4 +1,4 @@
1
- import { readFileSync } from "node:fs";
1
+ import { readFileSync } from './validation-phase.mjs';
2
2
  import path from "node:path";
3
3
  import { parseDocument } from "../vendor/yaml.mjs";
4
4
  import { loadRegistry, ROOT } from "./lifecycle-registry.mjs";
@@ -1,6 +1,7 @@
1
1
  import {assertApprovedExecutionContext} from './approved-execution-context.mjs';
2
2
  import {createHash} from 'node:crypto';
3
- import {readFileSync,realpathSync,lstatSync,readdirSync} from 'node:fs';
3
+ import { realpathSync } from 'node:fs';
4
+ import { readFileSync, lstatSync, readdirSync } from './validation-phase.mjs';
4
5
  import path from 'node:path';
5
6
  import {execFileSync} from 'node:child_process';
6
7
  import {parseDocument} from '../vendor/yaml.mjs';
@@ -35,7 +36,7 @@ function inspectMaven(root,units){
35
36
  const script=`import json,sys,xml.etree.ElementTree as ET\nout=[]\nfor p in json.load(sys.stdin):\n r=ET.parse(p).getroot(); ns={'m':'http://maven.apache.org/POM/4.0.0'}; q=lambda s:r.find(s,ns)\n aid=q('m:artifactId'); aid=aid if aid is not None else q('artifactId')\n deps=[e.text.strip() for e in r.findall('m:dependencies/m:dependency/m:artifactId',ns)+r.findall('m:profiles/m:profile/m:dependencies/m:dependency/m:artifactId',ns)+r.findall('dependencies/dependency/artifactId') if e.text]\n modules=[e.text.strip() for e in r.findall('.//m:modules/m:module',ns)+r.findall('.//modules/module') if e.text]\n out.append({'artifact_id':aid.text.strip() if aid is not None else '', 'dependencies':deps, 'modules':modules})\nprint(json.dumps(out))`;
36
37
  try{return JSON.parse(execFileSync('python3',['-c',script],{input:JSON.stringify(units.map(u=>file(root,u.pom_ref))),encoding:'utf8',timeout:15000,maxBuffer:2*1024*1024}));}catch(error){requireThat(false,'ARCH_BUILD_INVALID',`无法读取实际 Maven 构建单元: ${error.message}`);}
37
38
  }
38
- export function verifyExistingArchitecture(identity,bindings,{root,registry,execution}={}){
39
+ export function verifyExistingArchitecture(identity,bindings,{root,registry,execution,readOnly=false}={}){
39
40
  const profile=validateExistingArchitecture(identity,registry);
40
41
  requireThat(text(root),'ARCH_EVIDENCE_REF','需要治理 root');
41
42
  const baseline=bound(root,bindings?.engineering_baseline),registration=bound(root,bindings?.repository_registration),manifest=bound(root,bindings?.manifest);
@@ -132,7 +133,7 @@ export function verifyExistingArchitecture(identity,bindings,{root,registry,exec
132
133
  }
133
134
  if(changes.length){
134
135
  requireThat(execution,'ARCH_SOURCE_STALE',`固定源码改变: ${changes.join(', ')}`);
135
- const permission=assertApprovedExecutionContext(execution,{root,architectureIdentity:identity,architectureEvidence:bindings});
136
+ const permission=assertApprovedExecutionContext(execution,{root,architectureIdentity:identity,architectureEvidence:bindings,readOnly});
136
137
  const allowed=permission.allowed_write_paths.map(x=>{if(path.isAbsolute(x)){const rel=path.relative(projectRoot,x).split(path.sep).join('/');return rel==='.'||rel===''?'.':relative(rel);}return x==='.'?'.':relative(x);});
137
138
  requireThat(changes.every(ref=>allowed.some(scope=>beneath(ref,scope))&&registration.allowed_write_paths.some(scope=>beneath(ref,scope))),'ARCH_SOURCE_OUT_OF_SCOPE','输出增量超出批准写范围');
138
139
  }
@@ -1,8 +1,10 @@
1
- import { readFileSync, statSync } from 'node:fs';
1
+ import { statSync } from 'node:fs';
2
+ import { readFileSync, withValidationPhase, inValidationPhase } from './validation-phase.mjs';
2
3
  import { ensure, hash, digest, read, safe, files, schema, treeDigest } from './strategic-handoff-io.mjs';
3
4
 
4
5
  // This is an observation bundle, not a prototype or an approval generator.
5
- export function validateExistingUiBaseline(data, { bundleRoot } = {}) {
6
+ export function validateExistingUiBaseline(data,options={}) {return inValidationPhase()?validate(data,options):withValidationPhase({root:options.bundleRoot,purpose:'existing-ui-baseline',readOnly:true},()=>validate(data,options));}
7
+ function validate(data, { bundleRoot } = {}) {
6
8
  const errors=[];
7
9
  try {
8
10
  schema(data,'docs/process/schemas/existing-ui-baseline.schema.json');
@@ -1,6 +1,6 @@
1
1
  import { normalizeSliceContract, sourceSliceContract, parseSliceYaml } from './slice-contract.mjs';
2
2
  import { loadDeliveryProfile } from './harness-execution-scope.mjs';
3
- import { existsSync } from 'node:fs';
3
+ import { existsSync } from './validation-phase.mjs';
4
4
  import path from 'node:path';
5
5
  import { spawnSync } from 'node:child_process';
6
6
  import { ROOT, read, safe, ensure, digest } from './strategic-handoff-io.mjs';
@@ -1,5 +1,5 @@
1
1
  import { uiBaselineRef, uiBaselineCaseIds, uiBaselineKind, hasConsumerRoutes } from './ui-baseline.mjs';
2
- import { existsSync, readdirSync, readFileSync } from 'node:fs';
2
+ import { existsSync, readdirSync, readFileSync } from './validation-phase.mjs';
3
3
  import path from 'node:path';
4
4
  import { spawnSync } from 'node:child_process';
5
5
  import { openBackendDelivery } from './backend-delivery.mjs';
@@ -1,4 +1,5 @@
1
- import { existsSync } from 'node:fs';
1
+ import { loadExecutionScope, assertScopeWorkUnit } from './lifecycle-execution-scope.mjs';
2
+ import { existsSync } from './validation-phase.mjs';
2
3
  import path from 'node:path';
3
4
  import { ROOT, read, safe, ensure } from './strategic-handoff-io.mjs';
4
5
 
@@ -30,6 +31,12 @@ function activeProfile(root) {
30
31
  }
31
32
 
32
33
  export function enforceHarnessTaskScope(task,{root=ROOT}={}) {
34
+ const scope=loadExecutionScope(root);
35
+ if(scope) {
36
+ assertScopeWorkUnit(task.contract?.kind==='slice-implementation'?'work-unit.slice-implementation':task.work_unit_id,{root});
37
+ ensure(task.role_id!=='role.frontend-engineer','后端职责不允许派发前端实现');
38
+ ensure(task.allowed_write_paths.every(ref=>!/^apps\/frontend(?:\/|$)/.test(ref)),'禁止写入前端工程');
39
+ }
33
40
  const profile=activeProfile(root);
34
41
  if (!profile) return;
35
42
  ensure(task.contract?.kind!=='template-maintenance','专职产品项目不得派发模板维护任务');
@@ -41,6 +48,13 @@ export function enforceHarnessTaskScope(task,{root=ROOT}={}) {
41
48
  }
42
49
 
43
50
  export function enforceHarnessSkillScope(skills,registry,{root=ROOT}={}) {
51
+ if(loadExecutionScope(root)) {
52
+ assertScopeWorkUnit('work-unit.slice-implementation',{root});
53
+ for(const id of skills) {
54
+ const impacts=registry.skills.find(skill=>skill.id===id)?.impacts||[];
55
+ ensure(!impacts.includes('frontend')||impacts.includes('backend')||impacts.includes('lifecycle'),`后端职责禁止编译生产前端技能: ${id}`);
56
+ }
57
+ }
44
58
  const profile=activeProfile(root);
45
59
  if (!profile) return;
46
60
  const side=profile.profile_id==='harness.frontend-delivery'?'frontend':'backend';
@@ -5,7 +5,7 @@ import { createApprovedExecutionContext, createApprovedRecompilationContext, ass
5
5
  import { enforceTechnicalDesign } from './technical-design-boundary.mjs';
6
6
  import { enforceHarnessSkillScope } from "./harness-execution-scope.mjs";
7
7
  import { createHash } from "node:crypto";
8
- import { readFileSync } from "node:fs";
8
+ import { readFileSync } from './validation-phase.mjs';
9
9
  import path from "node:path";
10
10
  import { parseDocument } from "../vendor/yaml.mjs";
11
11
  import { DEFAULT_REGISTRY, loadSkillRegistry, resolveSkillForNewUse } from "./skill-registry.mjs";
@@ -276,8 +276,8 @@ export function evaluateContractFreshness(contract, { registry, compilerContract
276
276
  const reasons = [];
277
277
  let trustedExecution;
278
278
  try {
279
- trustedExecution=approved_slice?createApprovedExecutionContext(approved_slice,{root,contract:all,work_unit_id}):execution;
280
- if(trustedExecution)assertApprovedExecutionContext(trustedExecution,{root,contract});
279
+ trustedExecution=approved_slice?createApprovedExecutionContext(approved_slice,{root,contract:all,work_unit_id,readOnly}):execution;
280
+ if(trustedExecution)assertApprovedExecutionContext(trustedExecution,{root,contract,readOnly});
281
281
  } catch(error) { reasons.push(error.code||'EXECUTION_APPROVAL_INVALID');trustedExecution=undefined; }
282
282
  try { enforceTechnicalDesign({ ...resolution, slice_id: contract.slice_id ?? resolution.slice_id }, { root,execution:trustedExecution,readOnly }); }
283
283
  catch { reasons.push("technical-design-stale-or-unavailable"); }
@@ -289,7 +289,7 @@ export function evaluateContractFreshness(contract, { registry, compilerContract
289
289
  try { validateArchitectureIdentity(resolution.architecture_identity, registry); }
290
290
  catch { reasons.push("architecture-identity-invalid"); }
291
291
  if (resolution.architecture_identity.schema_version === 2) {
292
- try { verifyArchitectureEvidence(resolution.architecture_identity, resolution.architecture_evidence, { root, registry, execution:trustedExecution }); }
292
+ try { verifyArchitectureEvidence(resolution.architecture_identity, resolution.architecture_evidence, { root, registry, execution:trustedExecution, readOnly }); }
293
293
  catch (error) { reasons.push(error.code ?? "architecture-evidence-stale-or-unavailable"); }
294
294
  }
295
295
  if (resolution.architecture_identity_digest !== architectureDigest(resolution.architecture_identity)) reasons.push("architecture-identity-digest-changed");
@@ -1,7 +1,8 @@
1
- import { existsSync } from "node:fs";
1
+ import { existsSync, readFileSync, validationCache } from './validation-phase.mjs';
2
2
  import { spawnSync } from "node:child_process";
3
3
  import path from "node:path";
4
4
 
5
+ const schemaCache=Symbol('schema-results');
5
6
  const validator = String.raw`
6
7
  import json
7
8
  import sys
@@ -21,8 +22,7 @@ for item in jobs:
21
22
  try:
22
23
  key = (item['schemaPath'], item['formatChecker'])
23
24
  if key not in validators:
24
- with open(item['schemaPath'], encoding='utf-8') as source:
25
- schema = json.load(source)
25
+ schema = json.loads(item['schemaText'])
26
26
  validators[key] = Draft202012Validator(schema, format_checker=FormatChecker() if key[1] else None)
27
27
  errors = list(validators[key].iter_errors(item['value']))
28
28
  if item['errorStyle'] == 'verbose':
@@ -45,10 +45,11 @@ export function validateJsonSchemas(items, { cwd = process.cwd(), timeoutMs = 30
45
45
  const effectiveSchema = path.resolve(schemaPath);
46
46
  if (!existsSync(effectiveSchema)) throw new TypeError(`缺少 ${label}: ${effectiveSchema}`);
47
47
  if (!["compact", "verbose"].includes(errorStyle)) throw new TypeError(`未知 schema errorStyle: ${errorStyle}`);
48
- return { value, schemaPath: effectiveSchema, formatChecker, errorStyle };
48
+ return { value, schemaPath: effectiveSchema, schemaText: readFileSync(effectiveSchema, 'utf8'), formatChecker, errorStyle };
49
49
  });
50
50
  // readall waits for EOF even after the complete JSON has arrived. A known UTF-8
51
51
  // byte frame lets the validator finish when a transport keeps its write end open.
52
+ const run = jobs => {
52
53
  const input = Buffer.from(JSON.stringify(jobs), "utf8");
53
54
  const result = spawnSync("python3", ["-c", validator, String(input.length)], { cwd, encoding: "utf8", input, timeout: timeoutMs, maxBuffer: 16 * 1024 * 1024 });
54
55
  if (result.error?.code === "ETIMEDOUT") throw new TypeError(`JSON_SCHEMA_TIMEOUT: schema validation exceeded ${timeoutMs}ms`);
@@ -58,6 +59,20 @@ export function validateJsonSchemas(items, { cwd = process.cwd(), timeoutMs = 30
58
59
  if (!Array.isArray(results) || results.some(item => !item || typeof item.valid !== "boolean" || typeof item.error !== "string")) throw new TypeError("JSON_SCHEMA_PROTOCOL: validator returned invalid result records");
59
60
  if (results.length !== jobs.length) throw new TypeError("schema batch 返回数量不匹配");
60
61
  return results;
62
+ };
63
+ // Batch identical jobs once and reuse only deterministic checks within this operation.
64
+ // External references retain the uncached path: their dependency closure is not local here.
65
+ const keys=jobs.map(job=>JSON.stringify([job.schemaPath,job.schemaText,job.value,job.formatChecker,job.errorStyle,path.resolve(cwd),timeoutMs]));
66
+ const pending=new Map(),cached=new Map();
67
+ for(let i=0;i<jobs.length;i++){
68
+ if(/"\$(?:ref|dynamicRef)"\s*:\s*"(?!#)/.test(jobs[i].schemaText)){pending.set(`${i}:${keys[i]}`,{job:jobs[i],indices:[i],cache:false});continue;}
69
+ const existing=validationCache(schemaCache,keys[i]);
70
+ if(existing!==undefined)cached.set(i,existing);
71
+ else {const item=pending.get(keys[i])||{job:jobs[i],indices:[],key:keys[i],cache:true};item.indices.push(i);pending.set(keys[i],item);}
72
+ }
73
+ const entries=[...pending.values()],computed=entries.length?run(entries.map(x=>x.job)):[];
74
+ entries.forEach((entry,index)=>{const result=computed[index];if(entry.cache&&!result.error.includes('Traceback (most recent call last)'))validationCache(schemaCache,entry.key,result);for(const i of entry.indices)cached.set(i,result);});
75
+ return jobs.map((_,i)=>structuredClone(cached.get(i)));
61
76
  }
62
77
 
63
78
  export function validateJsonSchema(value, schemaPath, options = {}) {
@@ -1,3 +1,4 @@
1
+ import { loadExecutionScope, assertScopeWorkUnit, scopedNextRoutes } from './lifecycle-execution-scope.mjs';
1
2
  import { createHash } from "node:crypto";
2
3
  import { readFileSync } from "node:fs";
3
4
  import path from "node:path";
@@ -67,6 +68,8 @@ export function queryLifecycleContext({ mode, stageId, workUnitId, include = []
67
68
  throw new TypeError("至少提供 --mode、--stage、--work-unit 或 --include 之一");
68
69
  }
69
70
 
71
+ const scope = loadExecutionScope();
72
+ if (workUnitId) assertScopeWorkUnit(workUnitId, { readOnly: true });
70
73
  const lifecycle = validateRegistry(loadRegistry());
71
74
  const orchestration = loadYaml(ORCHESTRATION_CONTRACT_REF, "生命周期编排合同");
72
75
  const skillRegistry = loadSkillRegistry();
@@ -91,7 +94,7 @@ export function queryLifecycleContext({ mode, stageId, workUnitId, include = []
91
94
  const evidenceIds = new Set([...gates, ...checks].flatMap((gate) => gate.evidence ?? []));
92
95
  const selected = Object.fromEntries(normalizedIncludes.map((key) => [key, orchestration[key]]));
93
96
  const transition = workUnitId ? {
94
- next: orchestration.transition_graph?.routes?.[workUnitId] ?? [],
97
+ next: scopedNextRoutes(workUnitId, orchestration.transition_graph?.routes?.[workUnitId] ?? []),
95
98
  forbidden_shortcuts: (orchestration.transition_graph?.forbidden_shortcuts ?? [])
96
99
  .filter((edge) => edge.from === workUnitId || edge.to === workUnitId),
97
100
  } : null;
@@ -129,6 +132,7 @@ export function queryLifecycleContext({ mode, stageId, workUnitId, include = []
129
132
  work_unit: workUnit,
130
133
  },
131
134
  execution: {
135
+ ...(scope ? { responsibility_scope: scope } : {}),
132
136
  mode: mode ? orchestration.modes[mode] : null,
133
137
  selected,
134
138
  ...(loadPlan ? { plan_checks: lifecycle.stages.find(stage => stage.id === 'stage.plan').spec_entry.required_checks.map(id => ({ id, status: 'pending', evidence_refs: [] })) } : {}),
@@ -1,4 +1,4 @@
1
- import { readFileSync } from 'node:fs';
1
+ import { readFileSync } from './validation-phase.mjs';
2
2
  import { createHash } from 'node:crypto';
3
3
  import path from 'node:path';
4
4
  import { parseDocument } from '../vendor/yaml.mjs';
@@ -0,0 +1,92 @@
1
+ import { existsSync, readFileSync } from './validation-phase.mjs';
2
+ import path from 'node:path';
3
+ import { spawnSync } from 'node:child_process';
4
+ import { safe } from './strategic-handoff-io.mjs';
5
+ import { ROOT } from './lifecycle-registry.mjs';
6
+ import { parseDocument } from '../vendor/yaml.mjs';
7
+
8
+ export const SCOPE_REF = '.yss-execution-scope.yaml';
9
+ export const TERMINAL_REF = '.yss-backend-delivery.json';
10
+ const read = (root, ref) => {
11
+ const file = safe(root, ref);
12
+ const doc = parseDocument(readFileSync(file, 'utf8'), { uniqueKeys: true });
13
+ if (doc.errors.length) throw new TypeError(doc.errors[0].message);
14
+ return doc.toJS({ maxAliasCount: 0 });
15
+ };
16
+ const check = (ok, message) => { if (!ok) throw new TypeError(`execution-scope-blocked: ${message}`); };
17
+
18
+ /** The project opt-in selects a policy; callers cannot supply a replacement policy. */
19
+ export function loadExecutionScope(root = ROOT) {
20
+ const marker = path.join(root, '.yss-plugin.json');
21
+ const plugin = existsSync(marker) ? read(root, '.yss-plugin.json') : null;
22
+ const file = path.join(root, SCOPE_REF);
23
+ if (!existsSync(file)) {
24
+ check(!['yss-plan-to-backend', 'yss-backend-delivery'].includes(plugin?.plugin), '插件项目缺少职责范围;迁移后才能继续');
25
+ return null;
26
+ }
27
+ const config = read(root, SCOPE_REF), identity = read(root, 'yss-project.yaml');
28
+ check(identity.schema_version === 1 && identity.repository_mode === 'project-instance', '职责范围仅适用于项目实例');
29
+ check(config.schema_version === 1 && config.scope_id === 'plan-to-backend'
30
+ && Object.keys(config).every(key => ['schema_version', 'scope_id'].includes(key)), '未知或被扩大的职责配置');
31
+ if (plugin) check(['yss-plan-to-backend', 'yss-backend-delivery'].includes(plugin.plugin) && plugin.execution_scope === config.scope_id, '插件与项目职责绑定不一致');
32
+ const contract = read(root, '.agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml');
33
+ const policy = contract.execution_scopes?.[config.scope_id];
34
+ check(policy?.terminal_work_unit === 'work-unit.backend-delivery' && Array.isArray(policy.allowed_work_units), '匹配版本的主控合同缺少职责策略');
35
+ return { ...policy, scope_id: config.scope_id };
36
+ }
37
+
38
+ export function verifyDeliveryTerminal(root = ROOT) {
39
+ check(existsSync(path.join(root, TERMINAL_REF)), '缺少正式后端交付终点记录');
40
+ const result = spawnSync(process.execPath, [path.join(root, 'scripts/complete-backend-delivery'), 'verify', '--root', root], {
41
+ cwd: root, encoding: 'utf8', input: '', timeout: 120000, maxBuffer: 8 * 1024 * 1024,
42
+ env: { ...process.env, NODE_OPTIONS: '', NODE_PATH: '' },
43
+ });
44
+ check(!result.error && result.status === 0, result.error?.message || result.stderr || '后端交付终点复验失败');
45
+ return JSON.parse(result.stdout);
46
+ }
47
+
48
+ export function assertScopeWorkUnit(workUnit, { root = ROOT, readOnly = false } = {}) {
49
+ const scope = loadExecutionScope(root);
50
+ if (!scope) { check(workUnit !== 'work-unit.backend-delivery', '后端终点需要显式职责范围'); return null; }
51
+ check(scope.allowed_work_units.includes(workUnit), `职责范围不允许 ${workUnit}`);
52
+ if (existsSync(path.join(root, TERMINAL_REF))) {
53
+ verifyDeliveryTerminal(root);
54
+ check(readOnly, '后端交付已完成;不能恢复实现或自动发布');
55
+ }
56
+ return scope;
57
+ }
58
+
59
+ export function scopedNextRoutes(workUnit, routes, { root = ROOT } = {}) {
60
+ const scope = loadExecutionScope(root);
61
+ if (!scope) return routes.filter(id => id !== 'work-unit.backend-delivery');
62
+ assertScopeWorkUnit(workUnit, { root, readOnly: true });
63
+ if (existsSync(path.join(root, TERMINAL_REF))) return [];
64
+ return (scope.route_overrides[workUnit] || routes).filter(id => scope.allowed_work_units.includes(id));
65
+ }
66
+
67
+ export function assertScopeTransition(current, next, state, { root = ROOT } = {}) {
68
+ const scope = loadExecutionScope(root);
69
+ if (!scope) { check(current !== 'work-unit.backend-delivery' && next !== 'work-unit.backend-delivery', '后端终点需要显式职责范围'); return; }
70
+ if (current === scope.terminal_work_unit && next === null) { verifyDeliveryTerminal(root); return; }
71
+ assertScopeWorkUnit(current, { root });
72
+ if (next) assertScopeWorkUnit(next, { root });
73
+ check(next !== null, '不能把中间阶段声明为后端交付完成');
74
+ assertScopeImpacts(state, { root });
75
+ }
76
+
77
+ export function assertScopeImpacts(state, { root = ROOT } = {}) {
78
+ if (!loadExecutionScope(root)) return;
79
+ check(state?.delivery_impacts?.frontend !== true, '产品 UI 设计保留,生产前端实现必须交给下游');
80
+ if (state?.ui_impact === true) {
81
+ const deferred = state.downstream_frontend;
82
+ check(deferred && ['owner', 'ticket_ref', 'verification_plan', 'target_version'].every(key => typeof deferred[key] === 'string' && deferred[key].trim()), 'UI 产品须明确下游前端责任、Ticket、验证计划和目标版本');
83
+ }
84
+ }
85
+
86
+ export function assertScopeSlice(contract, { root = ROOT } = {}) {
87
+ if (!loadExecutionScope(root)) return;
88
+ check(contract.backend?.status === 'required' && contract.frontend?.status === 'not-applicable', '本职责仅允许后端 Slice;产品 UI 设计不能混作前端实现批准');
89
+ check(!(contract.common?.impacted_areas || []).some(x => ['ui', 'frontend'].includes(x)), 'Slice 仍包含生产前端实现影响');
90
+ const roles = (contract.work_units || []).map(unit => unit.role_id);
91
+ check(!roles.includes('role.frontend-engineer'), '禁止派发前端实现');
92
+ }
@@ -1,5 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
- import { readFileSync, writeFileSync } from "node:fs";
2
+ import { writeFileSync } from 'node:fs';
3
+ import { readFileSync } from './validation-phase.mjs';
3
4
  import path from "node:path";
4
5
  import { fileURLToPath } from "node:url";
5
6
  import { parseDocument } from "../vendor/yaml.mjs";
@@ -1,3 +1,5 @@
1
+ import { assertTrackingTransition } from './stage-tracking.mjs';
2
+ import { assertScopeTransition, assertScopeImpacts, assertScopeWorkUnit, scopedNextRoutes } from './lifecycle-execution-scope.mjs';
1
3
  // Wire capability marker: profile-local readiness rules are not interchangeable.
2
4
  export const SLICE_REPOSITORY_PREPARATION_PROTOCOL = 1;
3
5
  import { createHash } from "node:crypto";
@@ -43,7 +45,8 @@ const NEXT_ROUTES = deepFreeze({
43
45
  [TICKET_DECOMPOSITION_WORK_UNIT]: [IMPLEMENTATION_WORK_UNIT],
44
46
  [IMPLEMENTATION_WORK_UNIT]: ["work-unit.frontend-implementation-verification", "work-unit.code-review"],
45
47
  "work-unit.frontend-implementation-verification": ["work-unit.code-review"],
46
- "work-unit.code-review": ["work-unit.release-and-retrospective", IMPLEMENTATION_WORK_UNIT],
48
+ "work-unit.code-review": ["work-unit.release-and-retrospective", IMPLEMENTATION_WORK_UNIT, "work-unit.backend-delivery"],
49
+ "work-unit.backend-delivery": [],
47
50
  "work-unit.release-and-retrospective": [],
48
51
  });
49
52
 
@@ -249,7 +252,11 @@ function validateTicketReference(ref, trackerKind) {
249
252
  * by `validateWorkflowExecutionResult` before this function is called.
250
253
  */
251
254
  export function validateNextRoute(currentWorkUnit, nextRoute, decisionState, options = {}) {
252
- if (currentWorkUnit === "work-unit.code-review" && nextRoute === "work-unit.release-and-retrospective") {
255
+ try { assertTrackingTransition(currentWorkUnit, nextRoute, decisionState, { root: options.root || ROOT }); }
256
+ catch (error) { return blockedResult(['stage-tracking-blocked'], [error.message]); }
257
+ try { assertScopeTransition(currentWorkUnit, nextRoute, decisionState, options); }
258
+ catch (error) { return blockedResult(['execution-scope-blocked'], [error.message]); }
259
+ if (currentWorkUnit === "work-unit.code-review" && ["work-unit.release-and-retrospective", "work-unit.backend-delivery"].includes(nextRoute)) {
253
260
  try {
254
261
  if (decisionState?.review_input?.scope_kind !== "change") throw new Error("只读基线审计不能关闭实现审查");
255
262
  validateBackendReview(decisionState, { root: options.root || ROOT });
@@ -262,7 +269,7 @@ export function validateNextRoute(currentWorkUnit, nextRoute, decisionState, opt
262
269
  try { enforceFrontendDelivery(decisionState, { root: options.root, phase: nextRoute === IMPLEMENTATION_WORK_UNIT ? "implementation" : "inputs" }); }
263
270
  catch (error) { return blockedResult(["frontend-delivery-blocked"], [error.message]); }
264
271
  }
265
- const routes = NEXT_ROUTES[currentWorkUnit];
272
+ const routes = NEXT_ROUTES[currentWorkUnit] && scopedNextRoutes(currentWorkUnit, NEXT_ROUTES[currentWorkUnit], options);
266
273
  if (!routes) return blockedResult([BLOCKING_SIGNALS.invalidRoute], ["known_current_work_unit"]);
267
274
  if (nextRoute === REPOSITORY_PREPARATION_WORK_UNIT && currentWorkUnit !== "work-unit.technical-analysis") {
268
275
  return blockedResult([BLOCKING_SIGNALS.technicalAnalysisRequired], ["work-unit.technical-analysis predecessor"]);
@@ -299,6 +306,8 @@ export function validateNextRoute(currentWorkUnit, nextRoute, decisionState, opt
299
306
  * generated and verified scaffold. Unaffected roles need an explicit reason.
300
307
  */
301
308
  export function validateImplementationRepositoriesReady(state, { exists = existsSync, read = (ref) => readFileSync(ref, "utf8"), root = ROOT } = {}) {
309
+ try { assertScopeImpacts(state, { root }); }
310
+ catch (error) { return blockedResult(['execution-scope-blocked'], [error.message]); }
302
311
  const preparation = state?.implementation_repository_preparation;
303
312
  const impacts = state?.delivery_impacts;
304
313
  const signals = [];
@@ -411,6 +420,13 @@ function validateDecisionBoundary(workUnit, state, options) {
411
420
  * `exists` is injectable so external adapters can resolve their own tracker refs.
412
421
  */
413
422
  export function validateTicketFormalization(state, { exists = existsSync, read = (ref) => readFileSync(ref, "utf8"), ...decisionOptions } = {}) {
423
+ if ((state?.tracker_kind ?? "local-markdown") === "local-markdown" && state?.vertical_slice_ticket?.ref && isReadable(state.vertical_slice_ticket.ref, exists)) {
424
+ try {
425
+ const header = read(state.vertical_slice_ticket.ref).match(/^---\r?\n([\s\S]*?)\r?\n---/);
426
+ const kind = header ? parseDocument(header[1], { uniqueKeys: true }).toJS({ maxAliasCount: 0 })?.kind : null;
427
+ if (kind === "stage-work-item") return blockedResult(["stage-work-item-not-implementable"], ["vertical-slice-ticket required"]);
428
+ } catch { return blockedResult(["ticket-content-unreadable"], ["readable vertical slice content"]); }
429
+ }
414
430
  try { enforceFrontendDelivery(state, { root: decisionOptions.root, phase: "implementation" }); }
415
431
  catch (error) { return blockedResult(["frontend-delivery-blocked"], [error.message]); }
416
432
  const repositoryResult = validateImplementationRepositoriesReady(state, { exists, read, ...decisionOptions });
@@ -532,6 +548,8 @@ export function validateTicketFormalization(state, { exists = existsSync, read =
532
548
  * Validate the complete implementation entry seam after ready-for-agent promotion.
533
549
  */
534
550
  export function validateImplementationEntry(state, options = {}) {
551
+ try { assertScopeWorkUnit(IMPLEMENTATION_WORK_UNIT, options); assertScopeImpacts(state, options); }
552
+ catch (error) { return blockedResult(['execution-scope-blocked'], [error.message]); }
535
553
  const ticketResult = validateTicketFormalization(state, options);
536
554
  if (ticketResult.result === "blocked") return ticketResult;
537
555
  if (state?.predecessor_work_unit !== TICKET_DECOMPOSITION_WORK_UNIT) {
@@ -1,5 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
- import { existsSync, lstatSync, readFileSync, realpathSync } from "node:fs";
2
+ import { realpathSync } from "node:fs";
3
+ import { existsSync, lstatSync, readFileSync } from "./validation-phase.mjs";
3
4
  import path from "node:path";
4
5
  import { fileURLToPath } from "node:url";
5
6
  import { parseAllDocuments, parseDocument } from "../vendor/yaml.mjs";