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,184 @@
1
+ import { readFileSync, existsSync, lstatSync, realpathSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { createHash } from 'node:crypto';
4
+ import { parseDocument } from '../vendor/yaml.mjs';
5
+ import { validateJsonSchema } from './json-schema.mjs';
6
+
7
+ export const TRACKER_REF = 'docs/agents/issue-tracker.md';
8
+ export const STAGE_WORK_UNITS = Object.freeze({
9
+ 'work-unit.plan-opportunity': 'stage.plan',
10
+ 'work-unit.plan-requirements': 'stage.plan',
11
+ 'work-unit.domain-strategy-design': 'stage.plan',
12
+ 'work-unit.stage-decision': 'stage.plan',
13
+ 'work-unit.spec-synthesis': 'stage.spec-architecture',
14
+ 'work-unit.prototype-design': 'stage.product-design',
15
+ });
16
+ export const sha256 = bytes => `sha256:${createHash('sha256').update(bytes).digest('hex')}`;
17
+ export function parseYaml(bytes) {
18
+ const doc = parseDocument(String(bytes), { uniqueKeys: true });
19
+ if (doc.errors.length) throw new Error(doc.errors[0].message);
20
+ return doc.toJS({ maxAliasCount: 0 });
21
+ }
22
+ export function safeTrackingPath(root, ref) {
23
+ if (typeof ref !== 'string' || !ref || path.isAbsolute(ref) || ref.includes('\\') || ref.split('/').some(p => !p || p === '.' || p === '..')) throw new Error(`tracking-path-invalid: ${ref}`);
24
+ const base = realpathSync(root);
25
+ let file = base;
26
+ for (const part of ref.split('/')) {
27
+ file = path.join(file, part);
28
+ if (existsSync(file) || (() => { try { lstatSync(file); return true; } catch { return false; } })()) {
29
+ if (lstatSync(file).isSymbolicLink()) throw new Error(`tracking-symlink-forbidden: ${ref}`);
30
+ }
31
+ }
32
+ return file;
33
+ }
34
+ export const readTracking = (root, ref) => readFileSync(safeTrackingPath(root, ref), 'utf8');
35
+ export const binding = (root, ref) => ({ ref, digest: sha256(readTracking(root, ref)) });
36
+ export function trackerConfig(root) {
37
+ const bytes = readTracking(root, TRACKER_REF);
38
+ const match = bytes.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
39
+ if (!match) throw new Error('tracking-tracker-frontmatter-required');
40
+ const config = parseYaml(match[1])?.tracker;
41
+ if (!config || !['local-markdown', 'github', 'gitlab'].includes(config.platform)) throw new Error('tracking-tracker-invalid');
42
+ if (config.lifecycle_tracking_version !== undefined && config.lifecycle_tracking_version !== 1) throw new Error('tracking-version-unsupported');
43
+ return config;
44
+ }
45
+ export function trackingProfile(root) {
46
+ const ref = 'docs/process/harness-profile.yaml';
47
+ return existsSync(safeTrackingPath(root, ref)) ? parseYaml(readTracking(root, ref)) : null;
48
+ }
49
+ export const isDesign = root => trackingProfile(root)?.profile_id === 'harness.business-ddd-strategy-handoff';
50
+ function current(root, value) {
51
+ try { return binding(root, value.ref).digest === value.digest; } catch { return false; }
52
+ }
53
+ export function trackingDrift(root, tracking) {
54
+ const changed = new Set(tracking.items.filter(item => [...item.source_refs, ...item.completion.flatMap(c => c.evidence_refs)].some(b => !current(root, b))).map(item => item.id));
55
+ let size;
56
+ do {
57
+ size = changed.size;
58
+ for (const item of tracking.items) if (item.dependencies.some(id => changed.has(id))) changed.add(item.id);
59
+ } while (size !== changed.size);
60
+ return [...changed];
61
+ }
62
+ export function refreshTracking(root, checkpoint) {
63
+ const next = structuredClone(checkpoint);
64
+ const changed = trackingDrift(root, next.stage_tracking);
65
+ for (const item of next.stage_tracking.items) {
66
+ if (changed.includes(item.id) && item.progress !== 'cancelled') {
67
+ item.progress = 'pending';
68
+ item.recheck_required = true;
69
+ // Old bindings and evidence remain historical; only explicit re-verification replaces them.
70
+ }
71
+ }
72
+ return { checkpoint: next, stale_item_ids: changed };
73
+ }
74
+ export function assertStageTracking(checkpoint, { root, checkpointRef, currentWorkUnit, nextWorkUnit, transition = false, entering = false, now = new Date() } = {}) {
75
+ const identity = parseYaml(readTracking(root, 'yss-project.yaml'));
76
+ if (identity?.schema_version !== 1 || !['template-source', 'project-instance'].includes(identity.repository_mode)) throw new Error('tracking-repository-identity-invalid');
77
+ const config = trackerConfig(root);
78
+ const tracking = checkpoint?.stage_tracking;
79
+ const relevant = STAGE_WORK_UNITS[currentWorkUnit] || STAGE_WORK_UNITS[nextWorkUnit] || ['stage.plan', 'stage.spec-architecture', 'stage.product-design'].includes(checkpoint?.stage);
80
+ if (!tracking) {
81
+ if (identity.repository_mode === 'project-instance' && config.lifecycle_tracking_version === 1 && relevant) throw new Error('stage-tracking-required');
82
+ return { status: 'not-applicable', stale_item_ids: [] };
83
+ }
84
+ validateJsonSchema(tracking, path.join(root, 'docs/process/schemas/stage-tracking.schema.json'));
85
+ const feature = checkpoint.feature_id;
86
+ if (feature !== tracking.feature_id || typeof feature !== 'string' || !feature.trim()) throw new Error('tracking-feature-mismatch');
87
+ const location = /^(docs\/\.scratch\/[a-z0-9][a-z0-9-]*\/)[^/]+\.(yaml|json)$/.exec(tracking.checkpoint_ref);
88
+ if (!location) throw new Error('tracking-checkpoint-path-invalid');
89
+ const base = location[1];
90
+ if (!tracking.checkpoint_ref.startsWith(base) || (checkpointRef && tracking.checkpoint_ref !== checkpointRef)) throw new Error('tracking-checkpoint-mismatch');
91
+ safeTrackingPath(root, tracking.checkpoint_ref);
92
+ if (isDesign(root)) {
93
+ if (tracking.entry.kind !== 'checkpoint' || tracking.entry.ref !== tracking.checkpoint_ref) throw new Error('tracking-design-parent-forbidden');
94
+ } else if (tracking.entry.kind !== 'parent-ticket' || tracking.entry.ref !== `${base}parent-ticket.md`) throw new Error('tracking-parent-required');
95
+ const entry = readTracking(root, tracking.entry.ref);
96
+ if (tracking.entry.kind === 'parent-ticket' && !entry.includes(tracking.checkpoint_ref)) throw new Error('tracking-parent-checkpoint-link-required');
97
+ const registry = parseYaml(readTracking(root, 'docs/process/lifecycle-registry.yaml'));
98
+ const stages = new Set(registry.stages.map(x => x.id));
99
+ const units = new Set(registry.work_units.map(x => x.id));
100
+ if (!stages.has(tracking.entry_stage)) throw new Error('tracking-entry-stage-invalid');
101
+ const profile = trackingProfile(root);
102
+ const allowed = profile?.lifecycle?.allowed_work_units ?? profile?.allowed_work_units;
103
+ const index = new Map();
104
+ for (const item of tracking.items) {
105
+ if (index.has(item.id)) throw new Error(`tracking-duplicate-id: ${item.id}`);
106
+ index.set(item.id, item);
107
+ if (!stages.has(item.stage) || !units.has(item.work_unit) || STAGE_WORK_UNITS[item.work_unit] !== item.stage) throw new Error(`tracking-stage-work-unit-mismatch: ${item.id}`);
108
+ if (allowed && !allowed.includes(item.work_unit)) throw new Error(`tracking-profile-work-unit-forbidden: ${item.id}`);
109
+ if (item.split_reasons.length && !item.definition_ref) throw new Error(`tracking-independent-item-required: ${item.id}`);
110
+ if (item.definition_ref) {
111
+ if (item.definition_ref !== `${base}work-items/${item.id}.md`) throw new Error('tracking-definition-path-invalid');
112
+ const definition = readTracking(root, item.definition_ref);
113
+ if (!definition.includes(`kind: stage-work-item`) || !definition.includes(`id: ${item.id}\n`) || !definition.includes(tracking.checkpoint_ref) || /ready-for-agent|^Status:|^progress:/m.test(definition)) throw new Error('tracking-definition-invalid');
114
+ if (item.definition_digest !== sha256(definition)) throw new Error('tracking-definition-drift');
115
+ }
116
+ if (item.deferred) {
117
+ if (!item.split_reasons.includes('cross-stage-deferral') || !item.definition_ref) throw new Error('tracking-deferral-independent-item-required');
118
+ if (item.progress === 'completed' || item.progress === 'cancelled') throw new Error('tracking-deferred-not-completed');
119
+ if (!Number.isFinite(Date.parse(item.deferred.resolve_by)) || Date.parse(item.deferred.resolve_by) <= now.getTime()) throw new Error('tracking-deferral-expired');
120
+ readTracking(root, item.deferred.decision_ref);
121
+ }
122
+ if (item.progress === 'cancelled' && !item.cancellation_reason) throw new Error('tracking-cancellation-reason-required');
123
+ if (item.progress === 'completed') {
124
+ if (!item.source_refs.length) throw new Error('tracking-completion-source-required');
125
+ if (item.recheck_required) throw new Error('tracking-recheck-required');
126
+ for (const criterion of item.acceptance) if (!item.completion.some(c => c.criterion === criterion && c.evidence_refs.length)) throw new Error(`tracking-acceptance-evidence-required: ${item.id}`);
127
+ for (const c of item.completion) if (!item.acceptance.includes(c.criterion)) throw new Error('tracking-unknown-acceptance-criterion');
128
+ }
129
+ }
130
+ const visited = new Set(), visiting = new Set();
131
+ function visit(id) {
132
+ if (visiting.has(id)) throw new Error('tracking-dependency-cycle');
133
+ if (visited.has(id)) return;
134
+ const item = index.get(id);
135
+ if (!item) throw new Error(`tracking-dependency-missing: ${id}`);
136
+ visiting.add(id); item.dependencies.forEach(visit); visiting.delete(id); visited.add(id);
137
+ }
138
+ index.forEach((_, id) => visit(id));
139
+ for (const item of tracking.items) {
140
+ if ((item.owner !== tracking.items[0].owner || tracking.items.some(other => other.dependencies.includes(item.id))) && !item.definition_ref) throw new Error(`tracking-independent-item-required: ${item.id}`);
141
+
142
+ if (['running', 'completed'].includes(item.progress) && item.dependencies.some(id => index.get(id).progress !== 'completed')) throw new Error(`tracking-dependency-blocked: ${item.id}`);
143
+ }
144
+ const stale = trackingDrift(root, tracking);
145
+ if (stale.some(id => ['running', 'completed'].includes(index.get(id).progress))) throw new Error(`tracking-stale-completion: ${stale.join(',')}`);
146
+ if (entering && STAGE_WORK_UNITS[nextWorkUnit] && !tracking.items.some(i => i.work_unit === nextWorkUnit)) throw new Error('tracking-entry-work-item-required');
147
+ if (transition && STAGE_WORK_UNITS[currentWorkUnit]) {
148
+ const items = tracking.items.filter(i => i.work_unit === currentWorkUnit);
149
+ if (!items.length) throw new Error('tracking-current-work-item-required');
150
+ if (items.some(i => !['completed', 'cancelled'].includes(i.progress) && !i.deferred)) throw new Error('tracking-current-work-incomplete');
151
+ if (STAGE_WORK_UNITS[currentWorkUnit] !== STAGE_WORK_UNITS[nextWorkUnit] && tracking.items.some(i => i.stage === STAGE_WORK_UNITS[currentWorkUnit] && !['completed', 'cancelled'].includes(i.progress) && !i.deferred)) throw new Error('tracking-stage-incomplete');
152
+ }
153
+ return { status: 'valid', stale_item_ids: stale };
154
+ }
155
+ export function assertTrackingTransition(currentWorkUnit, nextWorkUnit, state, { root } = {}) {
156
+ // Persisted checkpoint is authoritative. Inline WER status cannot replace it.
157
+ if (!STAGE_WORK_UNITS[currentWorkUnit] && !STAGE_WORK_UNITS[nextWorkUnit] && !state?.stage_tracking && !state?.checkpoint_ref) return { status: 'not-applicable', stale_item_ids: [] };
158
+ const repository = parseYaml(readTracking(root, 'yss-project.yaml'));
159
+ if (repository.repository_mode === 'template-source' && !state?.stage_tracking) return { status: 'not-applicable', stale_item_ids: [] };
160
+ if (!state?.checkpoint_ref) {
161
+ const identity = parseYaml(readTracking(root, 'yss-project.yaml'));
162
+ if (state?.stage_tracking || (identity.repository_mode === 'project-instance' && trackerConfig(root).lifecycle_tracking_version === 1 && (STAGE_WORK_UNITS[currentWorkUnit] || STAGE_WORK_UNITS[nextWorkUnit]))) throw new Error('tracking-persisted-checkpoint-ref-required');
163
+ return assertStageTracking(state, { root, currentWorkUnit, nextWorkUnit, transition: true });
164
+ }
165
+ const checkpoint = parseYaml(readTracking(root, state.checkpoint_ref));
166
+ if (state.feature_id && checkpoint.feature_id !== state.feature_id) throw new Error('tracking-feature-mismatch');
167
+ return assertStageTracking(checkpoint, { root, checkpointRef: state.checkpoint_ref, currentWorkUnit, nextWorkUnit, transition: true });
168
+ }
169
+
170
+ export function assertTrackingEntry(workUnit, state, { root } = {}) {
171
+ if (!STAGE_WORK_UNITS[workUnit]) return;
172
+ const repository = parseYaml(readTracking(root, 'yss-project.yaml'));
173
+ if (repository.repository_mode === 'template-source') return;
174
+ const enabled = trackerConfig(root).lifecycle_tracking_version === 1;
175
+ if (!state?.checkpoint_ref) {
176
+ if (enabled) throw new Error('tracking-entry-checkpoint-required');
177
+ return;
178
+ }
179
+ const checkpoint = parseYaml(readTracking(root, state.checkpoint_ref));
180
+ assertStageTracking(checkpoint, { root, checkpointRef: state.checkpoint_ref, nextWorkUnit: workUnit, entering: true });
181
+ const items = checkpoint.stage_tracking?.items.filter(i => i.work_unit === workUnit) || [];
182
+ const stale = new Set(checkpoint.stage_tracking ? trackingDrift(root, checkpoint.stage_tracking) : []);
183
+ if (items.length && !items.some(i => !['blocked', 'cancelled'].includes(i.progress) && !i.deferred && !i.recheck_required && !stale.has(i.id) && i.dependencies.every(id => checkpoint.stage_tracking.items.find(d => d.id === id)?.progress === 'completed'))) throw new Error('tracking-entry-no-runnable-work');
184
+ }
@@ -1,5 +1,5 @@
1
1
  import { uiBaselineSourceKeys, uiBaselineKind } from './ui-baseline.mjs';
2
- import { existsSync, readdirSync } from 'node:fs';
2
+ import { existsSync, readdirSync, inValidationPhase, acceptValidationDependencies } from './validation-phase.mjs';
3
3
  import path from 'node:path';
4
4
  import { spawnSync } from 'node:child_process';
5
5
  import { openBundle } from './strategic-handoff.mjs';
@@ -29,8 +29,9 @@ export async function verifyConsumption(data,{root=process.cwd(),sliceRef,consum
29
29
  const route=receipt.routes.find(item=>item.capability===capability);
30
30
  ensure(route&&route.activation!=='not-applicable'&&binding.route_id===route.route_id,`${capability} 未绑定当前 Handoff v4/v5 route_id`);
31
31
  }
32
- const result=spawnSync(process.execPath,[path.join(ROOT,'scripts/verify-context-reconciliation'),'--root',root,safe(root,binding.context_reconciliation_ref)],{encoding:'utf8'});
32
+ const result=spawnSync(process.execPath,[path.join(ROOT,'scripts/verify-context-reconciliation'),'--root',root,safe(root,binding.context_reconciliation_ref),...(inValidationPhase()?['--validation-dependencies']:[])],{encoding:'utf8',timeout:60000,maxBuffer:16*1024*1024});
33
33
  ensure(result.status===0,`目标术语对账未通过: ${result.error?.message || result.stderr}`);
34
+ if(inValidationPhase()){let value;try{value=JSON.parse(result.stdout);}catch{throw new TypeError('VALIDATION_RECEIPT_INVALID: Context 子进程未返回依赖');}acceptValidationDependencies(value.validation_dependencies);}
34
35
  const context=parseContextContract({root});
35
36
  const parent=safe(root,`docs/handoffs/${receipt.bundle_id}`);
36
37
  const versions=readdirSync(parent).filter(x=>/^v[1-9][0-9]*$/.test(x)&&existsSync(path.join(parent,x,'import-receipt.json'))).sort((a,b)=>Number(b.slice(1))-Number(a.slice(1)));
@@ -84,6 +85,6 @@ export async function verifyConsumption(data,{root=process.cwd(),sliceRef,consum
84
85
  }
85
86
  const blockedHere=all||(sliceRef?blocked.has(sliceRef):issues.length>0);
86
87
  return {result:blockedHere?'blocked':'verified',bundle_digest:current.manifest.bundle_digest,consumed_bundle_digest:receipt.bundle_digest,...(consumer==='frontend'?{frontend_acceptance_digest:digest(data)}:{tactical_digest:digest(data), ...(data.schema_version === 2 ? {technical_design_digest:digest(data)} : {})}),scope:sliceRef||`whole-${consumer}-design`,coverage_complete:[...known.keys()].every(id=>rows.has(id)),block_all:all,blocked_slice_refs:[...blocked].sort(),issues};
87
- },{readOnly});
88
- },{readOnly});
88
+ },{readOnly:readOnly||inValidationPhase()});
89
+ },{readOnly:readOnly||inValidationPhase()});
89
90
  }
@@ -1,5 +1,6 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { existsSync, lstatSync, readdirSync, readFileSync, mkdirSync, writeFileSync, realpathSync } from 'node:fs';
2
+ import { mkdirSync, writeFileSync, realpathSync } from 'node:fs';
3
+ import { existsSync, lstatSync, readdirSync, readFileSync, validationMemo } from './validation-phase.mjs';
3
4
  import path from 'node:path';
4
5
  import { AsyncLocalStorage } from 'node:async_hooks';
5
6
  import { spawnSync } from 'node:child_process';
@@ -9,6 +10,7 @@ import { parseDocument } from '../vendor/yaml.mjs';
9
10
  export const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
10
11
  // A v5 package keeps the source glossary as a snapshot, never a nested CONTEXT.md.
11
12
  // Only this fixed alias is supported, isolated to the current asynchronous reader.
13
+ const parseCache=Symbol('handoff-yaml');
12
14
  const sourceContextSnapshots = new AsyncLocalStorage();
13
15
  export function withSourceContextSnapshot(root, action) {
14
16
  const roots = new Set(sourceContextSnapshots.getStore() || []);
@@ -20,7 +22,8 @@ export const canonical = value => Array.isArray(value) ? value.map(canonical) :
20
22
  export const hash = bytes => `sha256:${createHash('sha256').update(bytes).digest('hex')}`;
21
23
  export const digest = value => hash(JSON.stringify(canonical(value)));
22
24
  export const json = value => `${JSON.stringify(canonical(value), null, 2)}\n`;
23
- export function parse(bytes) {
25
+ export function parse(bytes) {return validationMemo(parseCache,String(bytes),()=>parseFresh(bytes));}
26
+ function parseFresh(bytes) {
24
27
  const doc = parseDocument(String(bytes), { uniqueKeys: true, maxAliasCount: 0 });
25
28
  ensure(!doc.errors.length, `YAML/JSON 无效: ${doc.errors[0]?.message}`);
26
29
  return doc.toJS({ maxAliasCount: 0 });
@@ -0,0 +1,31 @@
1
+ import { existsSync } from './validation-phase.mjs';
2
+ import path from 'node:path';
3
+ import { read, safe, ensure } from './strategic-handoff-io.mjs';
4
+
5
+ // Wire work-unit IDs belong to the immutable source package. Only target routing is adapted.
6
+ export function consumerEntry(root, capability, sourceWorkUnit) {
7
+ const scope = existsSync(path.join(root, '.yss-execution-scope.yaml')) ? read(safe(root, '.yss-execution-scope.yaml')) : null;
8
+ const profile = existsSync(path.join(root, 'docs/process/harness-profile.yaml')) ? read(safe(root, 'docs/process/harness-profile.yaml')) : null;
9
+ const contractRef = scope || !profile ? '.agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml' : '.agents/skills/harness-orchestrator/references/orchestration-contract.yaml';
10
+ const contract = read(safe(root, contractRef));
11
+ const target = scope?.scope_id || profile?.profile_id || 'yss-full-lifecycle';
12
+ const policy = contract.consumer_entry_routes?.[target]?.[capability];
13
+ ensure(policy && policy.source_work_unit === sourceWorkUnit, `consumer-entry-unmapped: ${target}/${capability}/${sourceWorkUnit}`);
14
+ const registry = read(safe(root, 'docs/process/lifecycle-registry.yaml'));
15
+ ensure(registry.work_units.some(unit => unit.id === policy.target_work_unit), 'consumer-entry-unknown-target');
16
+ if (scope) {
17
+ ensure(scope.schema_version === 1, 'consumer-entry-invalid-scope');
18
+ ensure(contract.execution_scopes?.[scope.scope_id]?.allowed_work_units?.includes(policy.target_work_unit), 'consumer-entry-outside-scope');
19
+ }
20
+ if (profile) ensure(profile.lifecycle.allowed_work_units.includes(policy.target_work_unit), 'consumer-entry-outside-profile');
21
+ return { capability, source_work_unit: sourceWorkUnit, target_work_unit: policy.target_work_unit, target_profile: target };
22
+ }
23
+
24
+ export function scopedConsumerCapabilities(root, fallback) {
25
+ if (!existsSync(path.join(root, '.yss-execution-scope.yaml'))) return fallback;
26
+ const scope = read(safe(root, '.yss-execution-scope.yaml'));
27
+ const contract = read(safe(root, '.agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml'));
28
+ const capabilities = contract.execution_scopes?.[scope.scope_id]?.consumer_capabilities;
29
+ ensure(scope.schema_version === 1 && Array.isArray(capabilities) && capabilities.length, 'consumer-scope-capabilities-missing');
30
+ return capabilities;
31
+ }
@@ -1,4 +1,6 @@
1
- import { existsSync, lstatSync, readFileSync, mkdirSync, mkdtempSync, renameSync, rmSync, cpSync } from 'node:fs';
1
+ import { scopedConsumerCapabilities } from './strategic-handoff-routing.mjs';
2
+ import { existsSync, lstatSync, readFileSync } from './validation-phase.mjs';
3
+ import { mkdirSync, mkdtempSync, renameSync, rmSync, cpSync } from 'node:fs';
2
4
  import path from 'node:path';
3
5
  import { tmpdir } from 'node:os';
4
6
  import { ensure, canonical, digest, hash, json, parse, read, safe, relative, files, write, project, schema, schemaBatch, archive, ROOT, sourceApprovalPolicy, withSourceContextSnapshot } from './strategic-handoff-io.mjs';
@@ -432,7 +434,7 @@ export async function importBundle({bundle,targetRoot}) {
432
434
  }else{
433
435
  const profileRef=path.join(target,'docs/process/harness-profile.yaml');
434
436
  const profile=existsSync(profileRef)?read(profileRef):null;
435
- const capabilities=profile?.handoff?.consumer_capabilities||CAPABILITIES;
437
+ const capabilities=scopedConsumerCapabilities(target,profile?.handoff?.consumer_capabilities||CAPABILITIES);
436
438
  ensure(Array.isArray(capabilities)&&capabilities.length&&capabilities.every(capability=>CAPABILITIES.includes(capability)),'目标 profile 未声明受支持的消费者能力');
437
439
  const selected=b.handoff.consumer_routes.filter(route=>capabilities.includes(route.capability));
438
440
  ensure(selected.length===capabilities.length,'目标 profile 的消费者能力在 Handoff v4 中缺失');
@@ -1,3 +1,4 @@
1
+ import { assertTrackingEntry } from './stage-tracking.mjs';
1
2
  import { normalizeSliceContract } from './slice-contract.mjs';
2
3
  import { assertSliceV3TaskPackage } from './slice-task-package.mjs';
3
4
  export { compileSliceTaskPackage, assertSliceV3TaskPackage } from './slice-task-package.mjs';
@@ -68,6 +69,7 @@ function validateSkillSource(value, registry) {
68
69
  }
69
70
 
70
71
  function validateCommon(value, registry, lifecycle) {
72
+ if (value.allowed_write_paths?.length && value.contract.kind === "lifecycle-work-unit") assertTrackingEntry(value.work_unit_id, value, { root: ROOT });
71
73
  if (value.work_unit_id === 'work-unit.spec-synthesis') assertPlanSpecEntry(value);
72
74
  const workUnit = lifecycle.work_units.find((item) => item.id === value.work_unit_id);
73
75
  if (!workUnit && value.contract.kind !== "slice-implementation") fail(`未知 work_unit_id: ${value.work_unit_id}`);
@@ -1,6 +1,6 @@
1
1
  import { assertApprovedExecutionContext, approvedExecutionBinding } from './approved-execution-context.mjs';
2
2
  import path from 'node:path';
3
- import { readFileSync } from 'node:fs';
3
+ import { readFileSync, inValidationPhase, acceptValidationDependencies } from './validation-phase.mjs';
4
4
  import { spawnSync } from 'node:child_process';
5
5
  import { ROOT, read, safe, ensure, hash } from './strategic-handoff-io.mjs';
6
6
 
@@ -22,11 +22,13 @@ export function enforceTechnicalDesign(state = {}, { root = ROOT, execution, rea
22
22
  if (binding.legacy_ddd === true) args.push('--legacy-ddd');
23
23
  if(readOnly)args.push('--read-only');
24
24
  if(execution){
25
- assertApprovedExecutionContext(execution,{root,technicalDesign:data,architectureIdentity:state.architecture_identity,sliceId:state.slice_id});
25
+ assertApprovedExecutionContext(execution,{root,technicalDesign:data,architectureIdentity:state.architecture_identity,sliceId:state.slice_id,readOnly});
26
26
  const approved=approvedExecutionBinding(execution);
27
27
  if(approved.work_unit_id)args.push('--work-unit',approved.work_unit_id);
28
28
  args.push('--approved-slice',approved.ref,'--approved-slice-digest',approved.digest,'--approved-slice-approval',approved.approval_ref);
29
29
  }
30
+ if(inValidationPhase())args.push('--validation-dependencies');
30
31
  const verified = spawnSync(process.execPath, args, { cwd: root, encoding: 'utf8', timeout: 60000, maxBuffer: 2 * 1024 * 1024 });
31
32
  ensure(verified.status === 0, `技术设计不可消费: ${verified.error?.message || verified.stderr}`);
33
+ if(inValidationPhase()){let output;try{output=JSON.parse(verified.stdout);}catch{throw new TypeError('VALIDATION_RECEIPT_INVALID: 技术设计子进程未返回依赖');}const dependencies=output.validation_dependencies;const required=[file,...(execution?[path.resolve(root,approvedExecutionBinding(execution).ref),path.resolve(root,approvedExecutionBinding(execution).approval_ref),path.resolve(root,'docs/agents/digital-human-roles.yaml')]:[])];ensure(required.every(ref=>dependencies?.files?.some(row=>row.file===ref)),'VALIDATION_RECEIPT_INVALID: 技术设计依赖报告缺少必要原始资产');acceptValidationDependencies(dependencies);}
32
34
  }
@@ -1,4 +1,4 @@
1
- import { readFileSync } from 'node:fs';
1
+ import { readFileSync } from './validation-phase.mjs';
2
2
  import { ensure, read, safe, hash } from './strategic-handoff-io.mjs';
3
3
  import { validateVisualBaseline } from '../../.agents/skills/yss-prototype-stage/scripts/visual-baseline-contract.mjs';
4
4
  import { validateExistingUiBaseline } from './existing-ui-baseline.mjs';
@@ -1,6 +1,6 @@
1
1
  import { normalizeSliceContract } from './slice-contract.mjs';
2
2
  import { createHash } from "node:crypto";
3
- import { readFileSync } from "node:fs";
3
+ import { readFileSync } from './validation-phase.mjs';
4
4
  import path from "node:path";
5
5
  import { parseDocument } from "../vendor/yaml.mjs";
6
6
  import { ROOT } from "./lifecycle-registry.mjs";
@@ -0,0 +1,72 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import {fileURLToPath} from 'node:url';
4
+ import {createHash} from 'node:crypto';
5
+ import {AsyncLocalStorage} from 'node:async_hooks';
6
+
7
+ // Only a live local call owns these snapshots. Neither receipts nor cache data confer approval.
8
+ const phases=new AsyncLocalStorage();
9
+ const checksum=bytes=>createHash('sha256').update(bytes).digest('hex');
10
+ const filename=value=>path.resolve(value instanceof URL?fileURLToPath(value):value);
11
+ const error=(code,detail)=>Object.assign(new TypeError(`${code}: ${detail}`),{code});
12
+ const current=()=>{const phase=phases.getStore();if(phase&&!phase.active)throw error('VALIDATION_PHASE_EXPIRED','验证阶段已经结束');if(phase?.signal?.aborted)throw error('VALIDATION_CANCELLED','验证阶段已取消');return phase;};
13
+ const statShape=stat=>({type:stat.isSymbolicLink()?'link':stat.isDirectory()?'directory':stat.isFile()?'file':'other',mode:Number(stat.mode),ino:String(stat.ino),dev:String(stat.dev)});
14
+ function observe(file,kind,read){const phase=current();if(!phase)return read();const key=`${kind}\0${file}`;if(!phase.observations.has(key))phase.observations.set(key,{file,kind,value:read()});return phase.observations.get(key).value;}
15
+ function pathIdentity(file){let part=path.parse(file).root;for(const name of path.relative(part,file).split(path.sep).filter(Boolean)){part=path.join(part,name);const nameRef=part;observe(nameRef,'stat',()=>{try{return statShape(fs.lstatSync(nameRef));}catch(e){if(e.code==='ENOENT')return null;throw e;}});}}
16
+ export function readFileSync(file,options){
17
+ const phase=current();if(!phase||typeof file==='number'||(typeof options==='object'&&options?.flag&&options.flag!=='r'))return fs.readFileSync(file,options);
18
+ const ref=filename(file);pathIdentity(ref);
19
+ if(!phase.files.has(ref))phase.files.set(ref,Buffer.from(fs.readFileSync(ref)));
20
+ const bytes=phase.files.get(ref),encoding=typeof options==='string'?options:options?.encoding;
21
+ return encoding?bytes.toString(encoding):Buffer.from(bytes);
22
+ }
23
+ export async function readFile(file,options){return readFileSync(file,options);}
24
+ export async function readdir(file,options){return readdirSync(file,options);}
25
+ export function existsSync(file){const phase=current();if(!phase||typeof file==='number')return fs.existsSync(file);const ref=filename(file);pathIdentity(ref);return observe(ref,'exists',()=>fs.existsSync(ref));}
26
+ export function lstatSync(file,options){const result=fs.lstatSync(file,options);const phase=current();if(phase&&result){const ref=filename(file);observe(ref,'stat',()=>statShape(result));}return result;}
27
+ export function readdirSync(file,options){
28
+ const result=fs.readdirSync(file,options),phase=current();
29
+ if(phase){const ref=filename(file);pathIdentity(ref);
30
+ // Record the enumeration actually returned to the consumer, not a second read.
31
+ const encoding=typeof options==='string'?options:options?.encoding||'utf8';
32
+ const names=result.map(entry=>{const name=typeof entry==='string'||Buffer.isBuffer(entry)?entry:entry.name;return Buffer.isBuffer(name)?name.toString():Buffer.from(name,encoding==='buffer'?'utf8':encoding).toString();}).sort();
33
+ const recorded=observe(ref,'directory',()=>names);
34
+ if(JSON.stringify(recorded)!==JSON.stringify(names))throw error('VALIDATION_INPUT_CHANGED',ref);
35
+ }
36
+ return result;
37
+ }
38
+ function actualObservation(row){if(row.kind==='exists')return fs.existsSync(row.file);if(row.kind==='directory')return fs.readdirSync(row.file).sort();if(row.kind==='stat'){try{return statShape(fs.lstatSync(row.file));}catch(e){if(e.code==='ENOENT')return null;throw e;}}throw error('VALIDATION_RECEIPT_INVALID','未知依赖类型');}
39
+ function receipt(phase){return{schema_version:1,files:[...phase.files].map(([file,bytes])=>({file,digest:checksum(bytes)})),observations:[...phase.observations.values()]};}
40
+ function checkReceipt(value){
41
+ if(value?.schema_version!==1||!Array.isArray(value.files)||!Array.isArray(value.observations))throw error('VALIDATION_RECEIPT_INVALID','缺少受控校验进程的依赖');
42
+ for(const row of value.files){if(!path.isAbsolute(row.file)||!/^[a-f0-9]{64}$/.test(row.digest))throw error('VALIDATION_RECEIPT_INVALID','非法文件依赖');try{if(checksum(fs.readFileSync(row.file))!==row.digest)throw Error('changed');}catch{throw error('VALIDATION_INPUT_CHANGED',row.file);}}
43
+ for(const row of value.observations){if(!path.isAbsolute(row.file))throw error('VALIDATION_RECEIPT_INVALID','非法路径依赖');try{if(JSON.stringify(actualObservation(row))!==JSON.stringify(row.value))throw Error('changed');}catch{throw error('VALIDATION_INPUT_CHANGED',row.file);}}
44
+ }
45
+ function mergedReceipt(phase){
46
+ const files=new Map(),observations=new Map();
47
+ for(const part of [receipt(phase),...phase.children]){
48
+ if(part?.schema_version!==1||!Array.isArray(part.files)||!Array.isArray(part.observations))throw error('VALIDATION_RECEIPT_INVALID','缺少受控校验进程的依赖');
49
+ for(const row of part.files){if(typeof row.file!=='string'||!path.isAbsolute(row.file)||!/^[a-f0-9]{64}$/.test(row.digest))throw error('VALIDATION_RECEIPT_INVALID','非法文件依赖');if(files.has(row.file)&&files.get(row.file).digest!==row.digest)throw error('VALIDATION_INPUT_CHANGED',row.file);files.set(row.file,row);}
50
+ for(const row of part.observations){if(typeof row.file!=='string'||!path.isAbsolute(row.file)||!['stat','exists','directory'].includes(row.kind))throw error('VALIDATION_RECEIPT_INVALID','非法路径依赖');const key=`${row.kind}\0${row.file}`;if(observations.has(key)&&JSON.stringify(observations.get(key).value)!==JSON.stringify(row.value))throw error('VALIDATION_INPUT_CHANGED',row.file);observations.set(key,row);}
51
+ }
52
+ return{schema_version:1,files:[...files.values()],observations:[...observations.values()]};
53
+ }
54
+ export function acceptValidationDependencies(value){
55
+ const phase=current();if(!phase)throw error('VALIDATION_PHASE_REQUIRED','子进程依赖必须交回本次验证阶段');
56
+ // Compare parent/child observations now; verify their union against disk before returning.
57
+ phase.children.push(structuredClone(value));mergedReceipt(phase);
58
+ }
59
+ export function validationDependencies(){const phase=current();if(!phase)throw error('VALIDATION_PHASE_REQUIRED','没有活动阶段');return structuredClone(mergedReceipt(phase));}
60
+ export function validationCache(namespace,key,...values){const phase=current();if(!phase)return undefined;const cache=phase.memo.get(namespace)||new Map();phase.memo.set(namespace,cache);if(values.length)cache.set(key,structuredClone(values[0]));return structuredClone(cache.get(key));}
61
+ export function validationMemo(namespace,key,compute){const phase=current();if(!phase)return compute();const cache=phase.memo.get(namespace)||new Map();phase.memo.set(namespace,cache);if(!cache.has(key))cache.set(key,structuredClone(compute()));return structuredClone(cache.get(key));}
62
+ export function validationPhaseToken(){return current()?.token;}
63
+ export function inValidationPhase(){return !!current();}
64
+ export function withValidationPhase(binding,action){
65
+ const parent=current(),identity=JSON.stringify({root:path.resolve(binding.root||process.cwd()),purpose:binding.purpose,unit:binding.work_unit_id||null,slice:binding.slice_id||null,readOnly:binding.readOnly===true});
66
+ if(binding.signal?.aborted)throw error('VALIDATION_CANCELLED','验证阶段已取消');
67
+ if(parent?.identity===identity&&(!binding.signal||binding.signal===parent.signal))return action();
68
+ const phase={identity,token:Object.freeze({}),active:true,files:new Map(),observations:new Map(),memo:new Map(),children:[],signal:binding.signal};
69
+ const finish=value=>{try{current();const dependencies=mergedReceipt(phase);checkReceipt(dependencies);if(parent){parent.children.push(dependencies);mergedReceipt(parent);}return value;}finally{phase.active=false;}};
70
+ return phases.run(phase,()=>{try{const value=action();if(value&&typeof value.then==='function')return value.then(finish,error=>{phase.active=false;throw error;});return finish(value);}catch(error){phase.active=false;throw error;}});
71
+ }
72
+ export default {...fs,readFileSync,existsSync,lstatSync,readdirSync};
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ import path from 'node:path';
3
+ import { readFileSync } from 'node:fs';
4
+ import { parseArgs } from 'node:util';
5
+ import { parseYaml } from './lib/stage-tracking.mjs';
6
+ import { checkTracking, planTracking, applyTracking } from './lib/stage-tracking-migration.mjs';
7
+ try {
8
+ const { values, positionals } = parseArgs({ allowPositionals: true, strict: true, options: { root: { type: 'string' }, checkpoint: { type: 'string' }, items: { type: 'string' }, plan: { type: 'string' }, refresh: { type: 'boolean', default: false } } });
9
+ const root = path.resolve(values.root || path.resolve(import.meta.dirname, '..'));
10
+ const command = positionals[0];
11
+ if (positionals.length !== 1 || !['check', 'plan', 'apply'].includes(command)) throw new Error('用法: stage-tracking check|plan --root <项目> --checkpoint <相对路径> [--items <yaml>] [--refresh];apply --root <项目> --plan <json>');
12
+ let result;
13
+ if (command === 'apply') {
14
+ if (!values.plan || values.checkpoint || values.items || values.refresh) throw new Error('apply 只接受 --root 和 --plan');
15
+ result = applyTracking(root, JSON.parse(readFileSync(values.plan, 'utf8')));
16
+ } else {
17
+ if (!values.checkpoint || values.plan) throw new Error('check/plan 需要 --checkpoint');
18
+ if (command === 'check' && (values.items || values.refresh)) throw new Error('check 不接受写入意图参数');
19
+ const input = values.items ? parseYaml(readFileSync(values.items, 'utf8')) : [];
20
+ if (!Array.isArray(input)) throw new Error('--items 必须是工作项数组');
21
+ result = command === 'check' ? checkTracking(root, values.checkpoint) : planTracking(root, { checkpoint_ref: values.checkpoint, items: input, refresh: values.refresh });
22
+ if (result.status === 'blocked' || (result.status === 'missing-checkpoint' && result.enabled)) process.exitCode = 1;
23
+ }
24
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
25
+ } catch (error) { process.stderr.write(`${error.message}\n`); process.exitCode = 1; }
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ import { parseArgs } from 'node:util';
3
+ import path from 'node:path';
4
+ import { readFileSync, existsSync } from 'node:fs';
5
+ import { openBundle, openDelivery, importBundle } from './lib/strategic-handoff.mjs';
6
+ import { read, safe, ensure, hash, schema, project } from './lib/strategic-handoff-io.mjs';
7
+ import { consumerEntry } from './lib/strategic-handoff-routing.mjs';
8
+
9
+ try {
10
+ const { values } = parseArgs({ options: { root: { type: 'string' }, receipt: { type: 'string' }, bundle: { type: 'string' } } });
11
+ const root = project(values.root);
12
+ ensure(Boolean(values.receipt) !== Boolean(values.bundle), 'provide-receipt-or-bundle');
13
+ const inspect = b => {
14
+ ensure(b.handoff.schema_version === 5, 'design-entry-requires-handoff-v5');
15
+ const route = b.handoff.consumer_routes.find(item => item.capability === 'backend-technical-design');
16
+ ensure(route && route.activation !== 'not-applicable', 'backend-consumer-route-not-active');
17
+ return { ...consumerEntry(root, route.capability, route.entry_work_unit), route_id: route.route_id,
18
+ bundle_id: b.manifest.bundle_id, version: b.manifest.version, bundle_digest: b.manifest.bundle_digest };
19
+ };
20
+ let receiptRef = values.receipt;
21
+ if (values.bundle) {
22
+ const input = path.resolve(values.bundle);
23
+ // Validate the destination route before any import writes.
24
+ if (existsSync(path.join(input, 'delivery-record.json'))) await openDelivery(input, inspect);
25
+ else await openBundle(input, inspect);
26
+ receiptRef = (await importBundle({ bundle: input, targetRoot: root })).receipt_ref;
27
+ }
28
+ const receipt = read(safe(root, receiptRef));
29
+ ensure([2, 3].includes(receipt.schema_version), 'design-entry-receipt-version');
30
+ schema(receipt, receipt.schema_version === 3 ? 'docs/process/schemas/strategic-handoff-import-receipt-v3.schema.json' : 'docs/process/schemas/strategic-handoff-import-receipt.schema.json');
31
+ const base = `docs/handoffs/${receipt.bundle_id}/${receipt.version}`;
32
+ ensure(receiptRef === `${base}/import-receipt.json` && receipt.package_ref === `${base}/package`, 'design-entry-receipt-path');
33
+ const entry = await openBundle(safe(root, receipt.package_ref), inspect);
34
+ ensure(entry.bundle_id === receipt.bundle_id && entry.version === receipt.version && entry.bundle_digest === receipt.bundle_digest, 'design-entry-receipt-drift');
35
+ const route = receipt.routes.find(item => item.capability === entry.capability);
36
+ ensure(route?.route_id === entry.route_id && route.activation !== 'not-applicable', 'design-entry-receipt-route');
37
+ if (receipt.schema_version === 3) {
38
+ ensure(receipt.source_delivery_record_ref === `${base}/source-delivery-record.json`, 'design-entry-delivery-path');
39
+ const bytes = readFileSync(safe(root, receipt.source_delivery_record_ref));
40
+ ensure(hash(bytes) === receipt.source_delivery_record_sha256, 'design-entry-delivery-drift');
41
+ const record = JSON.parse(bytes);
42
+ ensure(record.bundle_digest === receipt.bundle_digest && record.handoff.id === receipt.bundle_id && record.handoff.version === receipt.version, 'design-entry-delivery-binding');
43
+ }
44
+ console.log(JSON.stringify({ result: values.bundle ? 'design-imported' : 'design-inputs-verified', ...entry,
45
+ import_receipt_ref: receiptRef, next_work_unit: entry.target_work_unit, ready_for_agent: false,
46
+ stage_verification_required: true, pending: ['target-context-reconciliation', 'engineering-design-and-consumption', 'slice-contract-approval'] }, null, 2));
47
+ } catch (error) { console.error(JSON.stringify({ result: 'blocked', error: error.message, ready_for_agent: false })); process.exitCode = 1; }
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
+ import {withValidationPhase,validationDependencies} from "./lib/validation-phase.mjs";
2
3
  import { parseArgs } from "node:util";
3
4
  import { verifyContextReconciliation } from "./lib/context-reconciliation.mjs";
4
5
  try {
5
- const { values, positionals } = parseArgs({ options: { root: { type: "string", default: process.cwd() } }, allowPositionals: true, strict: true });
6
+ const { values, positionals } = parseArgs({ options: { "validation-dependencies": {type:"boolean",default:false}, root: { type: "string", default: process.cwd() } }, allowPositionals: true, strict: true });
6
7
  if (positionals.length !== 1) throw new TypeError("用法: scripts/verify-context-reconciliation [--root <repo>] <record.yaml|json>");
7
- verifyContextReconciliation(positionals[0], { root: values.root });
8
- process.stdout.write("Context reconciliation 验证通过\n");
8
+ const result=withValidationPhase({root:values.root,purpose:"context-reconciliation",readOnly:true},()=>{verifyContextReconciliation(positionals[0], { root: values.root });return values["validation-dependencies"]?{result:"verified",validation_dependencies:validationDependencies()}:null;});
9
+ process.stdout.write(result?JSON.stringify(result)+"\n":"Context reconciliation 验证通过\n");
9
10
  } catch (error) {
10
11
  process.stderr.write(`Context reconciliation 验证失败: ${error.message}\n`);
11
12
  process.exitCode = 1;
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { assertStageTracking } from './lib/stage-tracking.mjs';
2
3
  import { readFileSync } from "node:fs";
3
4
  import { spawnSync } from "node:child_process";
4
5
  import path from "node:path";
@@ -6,6 +7,7 @@ import { parseArgs } from "node:util";
6
7
  import { fileURLToPath } from "node:url";
7
8
  import { parseDocument } from "./vendor/yaml.mjs";
8
9
  import { assertCheckpointApprovals, assertCheckpointUserDecisions } from "./lib/approval-record.mjs";
10
+ import { loadExecutionScope, assertScopeWorkUnit, verifyDeliveryTerminal } from './lib/lifecycle-execution-scope.mjs';
9
11
  import { assertPlanCheckpoint } from "./lib/plan-spec-entry.mjs";
10
12
 
11
13
  const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
@@ -32,6 +34,14 @@ try {
32
34
  const value = document.toJS({ maxAliasCount: 0 });
33
35
  const result = spawnSync("python3", ["-c", validator, schemaPath], { cwd: root, encoding: "utf8", input: JSON.stringify(value) });
34
36
  if (result.status !== 0) throw new TypeError(`${result.stdout}${result.stderr}`.trim());
37
+ if(loadExecutionScope(root)) {
38
+ if(value.status==='completed') {
39
+ if(value.next_work_unit!==null)throw new TypeError('后端终点不得继续其他工作单元');
40
+ verifyDeliveryTerminal(root);
41
+ } else if(value.next_work_unit) assertScopeWorkUnit(value.next_work_unit,{root,readOnly:value.mode==='audit'||value.mode==='route'});
42
+ else if(value.mode==='resume'||value.mode==='orchestrate') assertScopeWorkUnit('work-unit.entry-triage',{root});
43
+ }
44
+ assertStageTracking(value, { root, checkpointRef: path.relative(root, sourcePath), nextWorkUnit: value.next_work_unit, entering: value.mode === 'resume' || value.mode === 'orchestrate' });
35
45
  assertCheckpointApprovals(value, sourcePath);
36
46
  assertPlanCheckpoint(value);
37
47
  assertCheckpointUserDecisions(value);
@@ -5,6 +5,7 @@ import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { parseDocument } from "./vendor/yaml.mjs";
7
7
  import { loadRegistry, validateRegistry } from "./lib/lifecycle-registry.mjs";
8
+ import { syncSkills, updateSkillLock } from "./lib/skill-supply-chain.mjs";
8
9
 
9
10
  const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
10
11
  const identityPath = path.join(root, "yss-project.yaml");
@@ -16,18 +17,18 @@ if (identity?.schema_version !== 1 || identity?.repository_mode !== "project-ins
16
17
  throw new TypeError("verify-project-instance 仅适用于合法 project-instance");
17
18
  }
18
19
 
19
- const commands = [
20
- ["scripts/sync-skills", ["--check"]],
21
- ["scripts/update-skill-lock", ["--check"]],
22
- ["scripts/verify-context-contract", ["--root", ".", "--json"]],
23
- ];
24
- for (const [command, args] of commands) {
20
+ const commands = ["scripts/sync-skills", "scripts/update-skill-lock", "scripts/verify-context-contract"];
21
+ for (const command of commands) {
25
22
  const executable = path.join(root, command);
26
23
  if (!existsSync(executable)) throw new TypeError(`缺少项目实例校验入口: ${command}`);
27
- const result = spawnSync(executable, args, { cwd: root, encoding: "utf8" });
28
- if (result.status !== 0) {
29
- throw new TypeError(result.stderr.trim() || result.stdout.trim() || `${command} 校验失败`);
30
- }
24
+ }
25
+ const hashCache = new Map();
26
+ syncSkills({ check: true, hashCache });
27
+ updateSkillLock(["--check"], { hashCache });
28
+ const command = "scripts/verify-context-contract";
29
+ const result = spawnSync(path.join(root, command), ["--root", ".", "--json"], { cwd: root, encoding: "utf8" });
30
+ if (result.status !== 0) {
31
+ throw new TypeError(result.stderr.trim() || result.stdout.trim() || `${command} 校验失败`);
31
32
  }
32
33
  validateRegistry(loadRegistry());
33
34
  process.stdout.write("项目实例校验通过\n");