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
@@ -1,4 +1,4 @@
1
- import { existsSync, readFileSync } from "node:fs";
1
+ import { existsSync, readFileSync } from './validation-phase.mjs';
2
2
  import path from "node:path";
3
3
  import { parseDocument } from "../vendor/yaml.mjs";
4
4
  import { nestedSkillPaths, OBSOLETE, PROJECTION_ROOTS, ROOT, unregisteredNestedSkillPaths } from "./skill-supply-chain.mjs";
@@ -65,10 +65,17 @@ export function loadSkillRegistry(filePath = DEFAULT_REGISTRY) {
65
65
  export function resolveSkillForNewUse(registry, requestedId) {
66
66
  requireString(requestedId, "requested skill id");
67
67
  if (OBSOLETE.has(requestedId)) {
68
+ const replacementSkill = requestedId === "grill-me" ? "grilling" : null;
68
69
  throw new SkillLifecycleError(
69
70
  SKILL_LIFECYCLE_FAILURE_CODES.retired,
70
- `${requestedId} 已硬退役;按迁移 tombstone 重新路由`,
71
- { skill: requestedId, migration_ref: "docs/agents/skill-migrations.md" }
71
+ replacementSkill
72
+ ? `${requestedId} 已硬退役;请使用 ${replacementSkill}`
73
+ : `${requestedId} 已硬退役;按迁移 tombstone 重新路由`,
74
+ {
75
+ skill: requestedId,
76
+ migration_ref: "docs/agents/skill-migrations.md",
77
+ ...(replacementSkill ? { replacement_skill: replacementSkill } : {})
78
+ }
72
79
  );
73
80
  }
74
81
  const aliases = new Map((registry.skills ?? []).flatMap((skill) => (skill.aliases ?? []).map((alias) => [alias, skill.id])));
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import { cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
2
+ import { cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, rmSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
3
3
  import { spawnSync } from "node:child_process";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
@@ -10,7 +10,7 @@ const LOCK_PATH = path.join(ROOT, "skills-lock.json");
10
10
  const YSS_UI_MANIFEST_PATH = path.join(SOURCE_ROOT, ".yss-skills-manifest.json");
11
11
  const STRATEGIC_DESIGN_MANIFEST_PATH = path.join(SOURCE_ROOT, ".strategic-design-skills-manifest.json");
12
12
  export const PROJECTION_ROOTS = [".codex/skills", ".cursor/skills", ".pi/skills"];
13
- export const OBSOLETE = new Set(["yss-antd-design", "yss-antdv-next-design", "to-" + "prd", "to-" + "issues", "design-an-interface", "qa", "request-refactor-plan", "ubiquitous-language", "edit-article", "obsidian-vault", "writing-great-skills", "code-review-process", "yss-domain-modeling", "yss-dir", "yss-duckdb", "yss-file", "yss-filerunner", "yss-db2mybatis", "yss-mail", "yss-mapper-dynamic", "yss-quality", "yss-sql-condition", "yss-sql-tpl", "yss-valuation", "yss-variable", "yss-openapi", "web-design-engineer", "web-video-presentation", "wireframe-prototype", "wizard", "git-guardrails-claude-code", "claude-handoff", "batch-grill-me", "product-design-prototype", "research", "ask-matt", "dispatching-parallel-agents", "loop-me", "migrate-to-shoehorn", "prototype-page-acceptance", "scaffold-exercises", "setup-pre-commit", "setup-ts-deep-modules", "teach", "writing-beats", "writing-fragments", "writing-shape", "yss-microapp-commit", "yss-page-module-development", "yss-dictionary", "yss-jdbc", "yss-log", "yss-taskflow", "yss-backend-scaffold-application", "yss-backend-scaffold-domain", "yss-backend-scaffold-infrastructure", "yss-backend-scaffold-web", "yss-router", "yss-source-index"]);
13
+ export const OBSOLETE = new Set(["yss-antd-design", "yss-antdv-next-design", "to-" + "prd", "to-" + "issues", "design-an-interface", "qa", "request-refactor-plan", "ubiquitous-language", "edit-article", "obsidian-vault", "writing-great-skills", "code-review-process", "yss-domain-modeling", "yss-dir", "yss-duckdb", "yss-file", "yss-filerunner", "yss-db2mybatis", "yss-mail", "yss-mapper-dynamic", "yss-quality", "yss-sql-condition", "yss-sql-tpl", "yss-valuation", "yss-variable", "yss-openapi", "web-design-engineer", "web-video-presentation", "wireframe-prototype", "wizard", "git-guardrails-claude-code", "claude-handoff", "batch-grill-me", "grill-me", "product-design-prototype", "research", "ask-matt", "dispatching-parallel-agents", "loop-me", "migrate-to-shoehorn", "prototype-page-acceptance", "scaffold-exercises", "setup-pre-commit", "setup-ts-deep-modules", "teach", "writing-beats", "writing-fragments", "writing-shape", "yss-microapp-commit", "yss-page-module-development", "yss-dictionary", "yss-jdbc", "yss-log", "yss-taskflow", "yss-backend-scaffold-application", "yss-backend-scaffold-domain", "yss-backend-scaffold-infrastructure", "yss-backend-scaffold-web", "yss-router", "yss-source-index"]);
14
14
  for (const id of ["component-selection-imports", "page-form-module", "page-list-module", "page-skeleton", "vue3-best-practices", "yss-use-table-height", "yss-use-tree-height", "yss-components", "yss-mvc-scaffold-generator", "yss-mvc-design", "yss-mvc-data-analysis-project-initializer", "yss-backend-scaffold-parent", "yss-backend-scaffold-adapter", "yss-application-layer-reference", "yss-domain-layer-reference", "yss-infrastructure-layer-reference", "yss-web-layer-reference"]) OBSOLETE.add(id);
15
15
  export function obsoleteCanonicalResidues(names, obsolete = OBSOLETE) {
16
16
  return names.filter((name) => obsolete.has(name)).sort();
@@ -49,6 +49,21 @@ export function treeHash(directory) {
49
49
  return digest.digest("hex");
50
50
  }
51
51
 
52
+ // treeHash is the published, normalized Skill digest. A no-op copy needs the
53
+ // stricter filesystem comparison so modes, links and raw line endings survive.
54
+ function projectionMatchesSource(source, target) {
55
+ const sourceInfo = lstatSafe(source);
56
+ const targetInfo = lstatSafe(target);
57
+ if (!sourceInfo || !targetInfo || (sourceInfo.mode & 0o7777) !== (targetInfo.mode & 0o7777)) return false;
58
+ if (sourceInfo.isSymbolicLink()) return targetInfo.isSymbolicLink() && readlinkSync(source) === readlinkSync(target);
59
+ if (sourceInfo.isFile()) return targetInfo.isFile() && readFileSync(source).equals(readFileSync(target));
60
+ if (!sourceInfo.isDirectory() || !targetInfo.isDirectory()) return false;
61
+ const sourceNames = readdirSync(source).sort();
62
+ const targetNames = readdirSync(target).sort();
63
+ return sourceNames.length === targetNames.length && sourceNames.every((name, index) =>
64
+ name === targetNames[index] && projectionMatchesSource(path.join(source, name), path.join(target, name)));
65
+ }
66
+
52
67
  /**
53
68
  * 校验并返回 YSS UI 前端 skills 与 MCP 的上游集成合同。
54
69
  *
@@ -156,13 +171,12 @@ export function unlockedCanonicalEntries(names, allowedNames, hasSkillMd = () =>
156
171
  const allowed = new Set(allowedNames);
157
172
  return names.filter((name) => !allowed.has(name) && !OBSOLETE.has(name) && hasSkillMd(name)).sort();
158
173
  }
159
- export function syncSkills({ check = false } = {}) {
174
+ export function syncSkills({ check = false, hashCache = new Map() } = {}) {
160
175
  let trackedSet;
161
176
  const tracked = ref => (trackedSet ??= trackedPaths()).has(ref);
162
- const sourceHashes = new Map();
163
177
  const sourceHash = source => {
164
- if (!sourceHashes.has(source)) sourceHashes.set(source, treeHash(source));
165
- return sourceHashes.get(source);
178
+ if (!hashCache.has(source)) hashCache.set(source, treeHash(source));
179
+ return hashCache.get(source);
166
180
  };
167
181
  const lock = parseLock();
168
182
  const shared = sharedFromLock(lock);
@@ -192,10 +206,12 @@ export function syncSkills({ check = false } = {}) {
192
206
  else if (sourceHash(source) !== treeHash(target)) drift.push(`projection drift: ${relative(target)}`);
193
207
  } else if (info?.isSymbolicLink() && existsSync(target) && realpathSync(target) === realpathSync(source)) {
194
208
  continue;
209
+ } else if (info?.isDirectory() && tracked(relative(target)) && projectionMatchesSource(source, target)) {
210
+ continue;
195
211
  } else {
196
212
  rmSync(target, { recursive: true, force: true });
197
213
  if (!info || !tracked(relative(target))) symlinkSync(path.relative(projection, source), target, "dir");
198
- else cpSync(source, target, { recursive: true, preserveTimestamps: true });
214
+ else cpSync(source, target, { recursive: true, preserveTimestamps: true, verbatimSymlinks: true });
199
215
  }
200
216
  }
201
217
  for (const name of OBSOLETE) {
@@ -230,11 +246,12 @@ function sourceRevisionMap(oldLock, arguments_) {
230
246
  }
231
247
  return sources;
232
248
  }
233
- function metadata(name, skillPath, directory, previous, canonical = false, sources = {}, upstreamRoot = null) {
249
+ function metadata(name, skillPath, directory, previous, canonical = false, sources = {}, upstreamRoot = null, hashCache = new Map()) {
234
250
  const old = previous[name] ?? {};
235
251
  let recordedPath = old.skillPath ?? skillPath;
236
252
  if (canonical && /^\.(codex|cursor|pi)\/skills\//.test(recordedPath)) recordedPath = skillPath;
237
- const result = { source: old.source ?? "project", sourceType: old.sourceType ?? "local", skillPath: recordedPath, effectiveHash: treeHash(directory) };
253
+ if (!hashCache.has(directory)) hashCache.set(directory, treeHash(directory));
254
+ const result = { source: old.source ?? "project", sourceType: old.sourceType ?? "local", skillPath: recordedPath, effectiveHash: hashCache.get(directory) };
238
255
  const sourceInfo = sources[result.source];
239
256
  if (sourceInfo?.revision) result.sourceRevision = sourceInfo.revision;
240
257
  let upstreamDirectory = null;
@@ -249,7 +266,7 @@ function metadata(name, skillPath, directory, previous, canonical = false, sourc
249
266
  }
250
267
  return result;
251
268
  }
252
- export function updateSkillLock(arguments_ = process.argv.slice(2)) {
269
+ export function updateSkillLock(arguments_ = process.argv.slice(2), { hashCache = new Map() } = {}) {
253
270
  const oldLock = parseLock(); const previous = priorMetadata(oldLock); const sources = sourceRevisionMap(oldLock, arguments_); const upstreamRoot = option(arguments_, "upstream-root");
254
271
  const yssUiManifest = loadYssUiSkillManifest();
255
272
  sources[yssUiManifest.source] = { ...(sources[yssUiManifest.source] ?? {}), revision: yssUiManifest.source_revision };
@@ -269,7 +286,7 @@ export function updateSkillLock(arguments_ = process.argv.slice(2)) {
269
286
  if (unlocked.length) throw new TypeError(`发现未登记到 skills-lock.json 的已跟踪共享 skills: ${unlocked.join(", ")}\n确认新增后运行 scripts/update-skill-lock --add=<skill-name>`);
270
287
  const targets = [".agents/skills", ...PROJECTION_ROOTS];
271
288
  const shared = Object.fromEntries(sharedNames.map((name) => {
272
- const item = metadata(name, `.agents/skills/${name}/SKILL.md`, path.join(SOURCE_ROOT, name), previous, true, sources, upstreamRoot);
289
+ const item = metadata(name, `.agents/skills/${name}/SKILL.md`, path.join(SOURCE_ROOT, name), previous, true, sources, upstreamRoot, hashCache);
273
290
  const yssUiSkill = yssUiSkills.get(name);
274
291
  if (yssUiSkill) {
275
292
  item.source = yssUiManifest.source;
@@ -300,10 +317,12 @@ export function updateSkillLock(arguments_ = process.argv.slice(2)) {
300
317
  if (!names.length) continue;
301
318
  const absentPlatform = names.filter((name) => !lstatSafe(path.join(location, name))?.isDirectory());
302
319
  if (absentPlatform.length) throw new TypeError(`锁文件声明的平台 skills 缺少内容 (${root}): ${absentPlatform.join(", ")}`);
303
- platform[root] = Object.fromEntries(names.map((name) => { const item = metadata(name, `${root}/${name}/SKILL.md`, path.join(location, name), previous, false, sources, upstreamRoot); item.targets = [root]; return [name, item]; }));
320
+ platform[root] = Object.fromEntries(names.map((name) => { const item = metadata(name, `${root}/${name}/SKILL.md`, path.join(location, name), previous, false, sources, upstreamRoot, hashCache); item.targets = [root]; return [name, item]; }));
304
321
  }
305
322
  const manifest = { version: 3, generatedBy: "scripts/update-skill-lock", canonicalRoot: ".agents/skills", projectionRoots: PROJECTION_ROOTS, sources, skills: { shared, platform } };
306
323
  const rendered = `${JSON.stringify(manifest, null, 2)}\n`;
307
- if (arguments_.includes("--check")) { if (!existsSync(LOCK_PATH) || readFileSync(LOCK_PATH, "utf8") !== rendered) throw new TypeError("skills-lock.json is stale; run scripts/update-skill-lock"); return "skills-lock.json matches distributed skills"; }
308
- writeFileSync(LOCK_PATH, rendered); return `updated skills-lock.json with ${sharedNames.length} shared skills and ${Object.values(platform).reduce((sum, group) => sum + Object.keys(group).length, 0)} platform skills`;
324
+ const current = existsSync(LOCK_PATH) ? readFileSync(LOCK_PATH, "utf8") : null;
325
+ if (arguments_.includes("--check")) { if (current !== rendered) throw new TypeError("skills-lock.json is stale; run scripts/update-skill-lock"); return "skills-lock.json matches distributed skills"; }
326
+ if (current !== rendered) writeFileSync(LOCK_PATH, rendered);
327
+ return `updated skills-lock.json with ${sharedNames.length} shared skills and ${Object.values(platform).reduce((sum, group) => sum + Object.keys(group).length, 0)} platform skills`;
309
328
  }
@@ -1,7 +1,8 @@
1
+ import {readContractSource,contractSourceMetadata} from './contract-source.mjs';
1
2
  import {sliceRepositories} from './slice-repositories.mjs';
2
3
  import { sliceCheckApplicability } from './slice-applicability.mjs';
3
4
  import path from 'node:path';
4
- import fs from 'node:fs';
5
+ import fs, {withValidationPhase} from './validation-phase.mjs';
5
6
  import { stringify } from '../vendor/yaml.mjs';
6
7
  import { safe, hash, digest, write } from './strategic-handoff-io.mjs';
7
8
  import { compileDefaultImplementationContract, digestDocument, loadCompilerContract } from './implementation-contract-compiler.mjs';
@@ -9,11 +10,7 @@ import { normalizeSliceContract, parseSliceYaml, readSliceSources, resolveSliceB
9
10
 
10
11
  const object=value=>value && typeof value==='object' && !Array.isArray(value);
11
12
  const same=(a,b)=>digest(a)===digest(b);
12
- function metadata(text) {
13
- const front=/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(text);
14
- if(front)return parseSliceYaml(front[1]);
15
- return (/^\s*[{[]/.test(text)||/^[A-Za-z_][\w-]*:\s/m.test(text)&&!/^#/m.test(text))?parseSliceYaml(text):{};
16
- }
13
+ const metadata=contractSourceMetadata;
17
14
  function mergeFacts(left,right,path,report) {
18
15
  if(left===undefined)return structuredClone(right);
19
16
  if(right===undefined)return structuredClone(left);
@@ -43,7 +40,15 @@ function lifecycleSources(checkpoint,report) {
43
40
  return result;
44
41
  }
45
42
  /** sources select existing assets; refinements contain ONLY new slice facts, never an approved contract. */
46
- export function prepareSliceImplementationContract({root=process.cwd(),ticket_ref,checkpoint_ref,sources={},refinements={},approved_slice}={}) {
43
+ export function prepareSliceImplementationContract(options={}) {
44
+ let result;
45
+ try{return withValidationPhase({root:options.root,purpose:'slice-prepare',slice_id:options.ticket_ref,readOnly:true,signal:options.signal},()=>result=prepare(options));}
46
+ catch(error){
47
+ if(!result||!['VALIDATION_INPUT_CHANGED','VALIDATION_CANCELLED'].includes(error.code))throw error;
48
+ result.slice_contract.status='blocked';result.report.blockers.push({code:error.code==='VALIDATION_INPUT_CHANGED'?'SLICE_INPUT_CONFLICT':error.code,field:'slice',source_ref:null,reason:`${error.message.includes('compiler-contract.yaml')?'编译规则来源冲突':'生成期间来源变化'}: ${error.message}`,responsibility:'professional-review',recovery:'重新读取当前来源并准备新草案'});return result;
49
+ }
50
+ }
51
+ function prepare({root=process.cwd(),ticket_ref,checkpoint_ref,sources={},refinements={},approved_slice}={}) {
47
52
  const started=performance.now();
48
53
  const report={read_only:true,blockers:[],sources:{},provenance:[],reason_chains:{},checks:[]};
49
54
  const snapshots=new Map();
@@ -52,6 +57,7 @@ export function prepareSliceImplementationContract({root=process.cwd(),ticket_re
52
57
  try {
53
58
  let ticketText='',ticket={},saved={};
54
59
  try {ticketText=read(ticket_ref).toString('utf8');ticket=metadata(ticketText);}catch(error){report.blockers.push({code:'SLICE_SOURCE_UNREADABLE',field:'basis.ticket',source_ref:ticket_ref,reason:error.message});}
60
+ if (ticket.kind === 'stage-work-item' || /\/work-items\//.test(ticket_ref || '')) report.blockers.push({code:'STAGE_WORK_ITEM_NOT_IMPLEMENTABLE',field:'basis.ticket',reason:'stage-work-item 不能作为实现 Ticket'});
55
61
  if(checkpoint_ref)try {saved=parseSliceYaml(read(checkpoint_ref));}catch(error){report.blockers.push({code:'SLICE_SOURCE_UNREADABLE',field:'checkpoint',source_ref:checkpoint_ref,reason:error.message});}
56
62
  const fromTicket=ticket.slice_implementation || {};
57
63
  const facts=mergeFacts(saved.slice_implementation || {},fromTicket,'slice',report);
@@ -73,13 +79,9 @@ export function prepareSliceImplementationContract({root=process.cwd(),ticket_re
73
79
  try {
74
80
  if(typeof value==='string' && Object.hasOwn(selected,value)) {basis[key]=value;continue;}
75
81
  const binding=typeof value==='string'?{ref:value}:value;
76
- const bytes=read(binding.ref),rawDigest=hash(bytes);
77
- if(binding.digest&&binding.digest!==rawDigest)throw new TypeError(`stale: 来源 ${key}`);
78
- const data=metadata(bytes.toString('utf8'));
79
- const sourceVersion=data.requirement_version??data.version??data.contract_version;
80
- if(binding.version!==undefined&&sourceVersion!==undefined&&String(binding.version)!==String(sourceVersion))throw new TypeError(`来源版本冲突: ${key}`);
81
- const version=binding.version||data.requirement_version||data.version||data.contract_version;
82
- basis[key]={ref:binding.ref,digest:rawDigest,...(version?{version:String(version)}:{}),...(binding.approval_ref?{approval_ref:binding.approval_ref}:{})};
82
+ const source=readContractSource(binding,{root,read});
83
+ const rawDigest=source.binding.digest;
84
+ basis[key]=source.binding;
83
85
  if(seen.has(binding.ref)) {
84
86
  const previous=seen.get(binding.ref);
85
87
  if(!same(basis[key],basis[previous]))throw new TypeError(`来源绑定冲突: ${key}/${previous}`);
@@ -1,4 +1,4 @@
1
- import fs from 'node:fs';
1
+ import fs from './validation-phase.mjs';
2
2
  import { safe, hash, digest } from './strategic-handoff-io.mjs';
3
3
  import { selectSliceWorkUnit, readSliceContract, readSliceSources, sliceAcceptanceText, parseSliceYaml, sourceSliceContract } from './slice-contract.mjs';
4
4
 
@@ -1,11 +1,12 @@
1
1
  import {validateExistingUiBaseline} from './existing-ui-baseline.mjs';
2
2
  import {sliceRepositories} from './slice-repositories.mjs';
3
3
  import { sliceCheckApplicability } from './slice-applicability.mjs';
4
- import fs from 'node:fs';
4
+ import fs, {validationMemo} from './validation-phase.mjs';
5
5
  import path from 'node:path';
6
6
  import { parseDocument } from '../vendor/yaml.mjs';
7
7
  import { safe, hash, digest, schema } from './strategic-handoff-io.mjs';
8
8
 
9
+ const parseCache=Symbol('slice-yaml');
9
10
  const originals = new WeakMap();
10
11
  const sourceSnapshots = new WeakMap();
11
12
  const nonempty = value => typeof value === 'string' && value.trim();
@@ -13,7 +14,8 @@ const unique = values => [...new Set(values)];
13
14
  const fail = message => { throw new TypeError(`slice-contract-invalid: ${message}`); };
14
15
  const requireThat = (ok, message) => { if (!ok) fail(message); };
15
16
  export const sourceSliceContract = value => originals.get(value) || value?.slice_contract || value;
16
- export function parseSliceYaml(bytes) {
17
+ export function parseSliceYaml(bytes) {return validationMemo(parseCache,String(bytes),()=>parseSliceYamlFresh(bytes));}
18
+ function parseSliceYamlFresh(bytes) {
17
19
  const doc = parseDocument(String(bytes), { uniqueKeys: true, intAsBigInt: true });
18
20
  requireThat(!doc.errors.length, `YAML 无效: ${doc.errors[0]?.message}`);
19
21
  const value = doc.toJS({ maxAliasCount: 0 });
@@ -99,10 +101,23 @@ export function sliceAcceptanceText(contract, sources) {
99
101
  export function normalizeSliceContract(document, options = {}) {
100
102
  const raw = sourceSliceContract(document);
101
103
  requireThat(raw && [2,3].includes(raw.schema_version), 'Slice Implementation Contract schema v1 已停止支持;需要 schema v2 或 v3');
102
- if (raw.schema_version === 2) return structuredClone(raw);
104
+ if (raw.schema_version === 2) {
105
+ const ref = raw.lifecycle_refs?.ticket;
106
+ if (ref) {
107
+ requireThat(!/\/work-items\//.test(ref), 'stage-work-item 不能作为实现 Ticket');
108
+ const file = safe(options.root || process.cwd(), ref, { missing: true });
109
+ if (fs.existsSync(file)) {
110
+ const header = fs.readFileSync(file, 'utf8').match(/^---\r?\n([\s\S]*?)\r?\n---/);
111
+ requireThat(!header || parseSliceYaml(header[1])?.kind !== 'stage-work-item', 'stage-work-item 不能作为实现 Ticket');
112
+ }
113
+ }
114
+ return structuredClone(raw);
115
+ }
103
116
  schema(raw,'docs/process/schemas/slice-implementation-contract-v3.schema.json');
104
117
  const sources=readSliceSources(raw,options), refs=Object.fromEntries(Object.entries(sources).map(([key,item])=>[key,item.ref]));
105
118
  requireKeys(refs,['spec','ticket','engineering_baseline','implementation_repository','build_architecture_checklist'],'依据');
119
+ const ticketHeader = sources.ticket.text.match(/^---\r?\n([\s\S]*?)\r?\n---/);
120
+ requireThat(!/\/work-items\//.test(refs.ticket) && (!ticketHeader || parseSliceYaml(ticketHeader[1])?.kind !== 'stage-work-item'), 'stage-work-item 不能作为实现 Ticket');
106
121
  if(!raw.applicability.checks)requireKeys(refs,['architecture_review'],'依据');
107
122
  else {
108
123
  requireKeys(refs,['lifecycle_registry','process_tailoring'],'适用性依据');
@@ -1,10 +1,14 @@
1
+ import { withValidationPhase } from './validation-phase.mjs';
1
2
  import { readSliceContract } from './slice-contract.mjs';
2
3
  import { createApprovedExecutionContext } from './approved-execution-context.mjs';
3
4
  import { evaluateContractFreshness, loadCompilerContract } from './implementation-contract-compiler.mjs';
4
5
  import { loadSkillRegistry } from './skill-registry.mjs';
5
6
 
6
7
  /** Readability, structure and execution approval are different levels of evidence. */
7
- export function inspectSliceContract(ref, { root = process.cwd(), approval_ref, work_unit_id } = {}) {
8
+ export function inspectSliceContract(ref, options = {}) {
9
+ return withValidationPhase({root:options.root,purpose:'slice-inspect',slice_id:ref,work_unit_id:options.work_unit_id,readOnly:true,signal:options.signal},()=>inspect(ref,options));
10
+ }
11
+ function inspect(ref, { root = process.cwd(), approval_ref, work_unit_id } = {}) {
8
12
  const loaded = readSliceContract(ref, { root });
9
13
  const { contract, binding } = loaded;
10
14
  const checks = { readable: 'passed', structure: 'passed', approval: 'not-checked', freshness: 'not-checked' };
@@ -28,9 +32,9 @@ export function inspectSliceContract(ref, { root = process.cwd(), approval_ref,
28
32
  if (approval_ref && !blockers.length) {
29
33
  try {
30
34
  const approved_slice = { ...binding, approval_ref };
31
- const context = createApprovedExecutionContext(approved_slice, { root, contract, work_unit_id });
35
+ const context = createApprovedExecutionContext(approved_slice, { root, contract, work_unit_id, readOnly: true });
32
36
  checks.approval = 'passed';
33
- const current = evaluateContractFreshness(contract, { root, registry: loadSkillRegistry(), compilerContract: loadCompilerContract(), approved_slice, work_unit_id }, context);
37
+ const current = evaluateContractFreshness(contract, { root, registry: loadSkillRegistry(), compilerContract: loadCompilerContract(), approved_slice, work_unit_id, readOnly: true }, context);
34
38
  checks.freshness = current.freshness === 'current' ? 'passed' : 'failed';
35
39
  blockers.push(...current.reasons);
36
40
  } catch (error) {
@@ -1,4 +1,4 @@
1
- import fs from 'node:fs';
1
+ import fs from './validation-phase.mjs';
2
2
  import * as lifecycle from './lifecycle-transition.mjs';
3
3
  import path from 'node:path';
4
4
  import {digest,schema} from './strategic-handoff-io.mjs';
@@ -1,5 +1,5 @@
1
1
  import {enforceHarnessTaskScope} from './harness-execution-scope.mjs';
2
- import { readFileSync } from 'node:fs';
2
+ import { readFileSync, withValidationPhase } from './validation-phase.mjs';
3
3
  import { ROOT, hash, safe, digest } from './strategic-handoff-io.mjs';
4
4
  import { normalizeSliceContract, withinSlicePath, parseSliceYaml } from './slice-contract.mjs';
5
5
  import { createApprovedExecutionContext, assertApprovedExecutionContext } from './approved-execution-context.mjs';
@@ -14,7 +14,8 @@ function generateTaskPackageDefaults(roleId,overrides,{rolesDoc}={}) {
14
14
  }
15
15
 
16
16
  /** Compile dispatch from the approved YAML. Only runtime identity and task ID come from the caller. */
17
- export function compileSliceTaskPackage(binding,{root=ROOT,work_unit_id,task_id,actor_id,runtime_id,execution_state='Worker',rolesDoc,user_decisions}={}) {
17
+ export function compileSliceTaskPackage(binding,options={}) {return withValidationPhase({root:options.root||ROOT,purpose:'slice-dispatch',slice_id:binding.ref,work_unit_id:options.work_unit_id,readOnly:false,signal:options.signal},()=>compile(binding,options));}
18
+ function compile(binding,{root=ROOT,work_unit_id,task_id,actor_id,runtime_id,execution_state='Worker',rolesDoc,user_decisions}={}) {
18
19
  const actualRoles=parseSliceYaml(readFileSync(safe(root,'docs/agents/digital-human-roles.yaml')));
19
20
  if(rolesDoc&&digest(rolesDoc)!==digest(actualRoles))fail('调用方不能替换接收端角色能力');
20
21
  rolesDoc=actualRoles;
@@ -0,0 +1,176 @@
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync, unlinkSync, statSync, openSync, closeSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { parseDocument, stringify } from '../vendor/yaml.mjs';
4
+ import { TRACKER_REF, STAGE_WORK_UNITS, sha256, safeTrackingPath, readTracking, parseYaml, trackerConfig, isDesign, binding, assertStageTracking, refreshTracking } from './stage-tracking.mjs';
5
+
6
+ const descriptor = (root, ref) => { const p = safeTrackingPath(root, ref); return existsSync(p) ? { digest: sha256(readFileSync(p)), mode: statSync(p).mode & 0o777 } : null; };
7
+ const json = x => `${JSON.stringify(x, null, 2)}\n`;
8
+ function featureFrom(ref) {
9
+ const match = /^docs\/\.scratch\/([a-z0-9][a-z0-9-]*)\/[^/]+\.(yaml|json)$/.exec(ref);
10
+ if (!match) throw new Error('tracking-checkpoint-path-invalid');
11
+ return match[1];
12
+ }
13
+ function identity(root) {
14
+ const value = parseYaml(readTracking(root, 'yss-project.yaml'));
15
+ if (value?.schema_version !== 1 || value.repository_mode !== 'project-instance') throw new Error('stage-tracking-project-instance-required');
16
+ }
17
+ function enabledTracker(bytes) {
18
+ const match = bytes.match(/^---\r?\n([\s\S]*?)\r?\n---/);
19
+ if (!match) throw new Error('tracking-tracker-frontmatter-required');
20
+ const doc = parseDocument(match[1], { uniqueKeys: true });
21
+ doc.setIn(['tracker', 'lifecycle_tracking_version'], 1);
22
+ return `---\n${doc.toString()}---${bytes.slice(match[0].length)}`;
23
+ }
24
+ function definition(item, checkpointRef) {
25
+ const fields = { kind: item.kind, id: item.id, checkpoint_ref: checkpointRef };
26
+ return `---\n${stringify(fields)}---\n# ${item.title}\n\n负责人:${item.owner}\n\n${item.scope}\n\n## 验收\n\n${item.acceptance.map(x => `- ${x}`).join('\n')}\n\n当前进度、依赖与完成证据以 ${checkpointRef} 中的 ${item.id} 为准。\n`;
27
+ }
28
+ export function checkTracking(root, checkpointRef) {
29
+ identity(root);
30
+ const feature = featureFrom(checkpointRef);
31
+ const config = trackerConfig(root);
32
+ if (!existsSync(safeTrackingPath(root, checkpointRef))) return { status: 'missing-checkpoint', feature_id: feature, enabled: config.lifecycle_tracking_version === 1 };
33
+ const checkpoint = parseYaml(readTracking(root, checkpointRef));
34
+ try {
35
+ const result = assertStageTracking(checkpoint, { root, checkpointRef });
36
+ return { ...result, feature_id: checkpoint.feature_id || feature, enabled: config.lifecycle_tracking_version === 1, migration_required: !checkpoint.stage_tracking };
37
+ } catch (error) { return { status: 'blocked', feature_id: feature, enabled: config.lifecycle_tracking_version === 1, error: error.message, migration_required: !checkpoint.stage_tracking }; }
38
+ }
39
+ export function planTracking(root, { checkpoint_ref, items = [], refresh = false } = {}) {
40
+ root = path.resolve(root); identity(root);
41
+ const feature = featureFrom(checkpoint_ref);
42
+ trackerConfig(root);
43
+ const changes = [], observed = {}, gaps = [];
44
+ function watch(ref) { observed[ref] = descriptor(root, ref); }
45
+ function change(ref, after) {
46
+ watch(ref);
47
+ if (!observed[ref] || sha256(after) !== observed[ref].digest) changes.push({ ref, before: observed[ref], after, after_digest: sha256(after) });
48
+ }
49
+ for (const ref of ['yss-project.yaml', 'CONTEXT.md', TRACKER_REF, 'docs/process/lifecycle-registry.yaml', 'docs/process/schemas/stage-tracking.schema.json', 'docs/process/harness-profile.yaml', checkpoint_ref]) watch(ref);
50
+ const exists = observed[checkpoint_ref] !== null;
51
+ let checkpoint;
52
+ if (exists) checkpoint = parseYaml(readTracking(root, checkpoint_ref));
53
+ else {
54
+ const template = 'docs/process/templates/lifecycle-checkpoint-template.yaml'; watch(template);
55
+ checkpoint = parseYaml(readTracking(root, template));
56
+ checkpoint.feature_id = feature;
57
+ checkpoint.mode = 'orchestrate'; checkpoint.status = 'running';
58
+ checkpoint.stage = items[0]?.stage || 'stage.plan';
59
+ checkpoint.next_work_unit = items[0]?.work_unit || 'work-unit.plan-requirements';
60
+ }
61
+ checkpoint.feature_id ??= feature;
62
+ const design = isDesign(root);
63
+ checkpoint.stage_tracking ??= { schema_version: 1, feature_id: checkpoint.feature_id, checkpoint_ref, entry_stage: checkpoint.stage,
64
+ entry: { kind: design ? 'checkpoint' : 'parent-ticket', ref: design ? checkpoint_ref : `docs/.scratch/${feature}/parent-ticket.md` }, items: [] };
65
+ if (refresh) checkpoint = refreshTracking(root, checkpoint).checkpoint;
66
+ const tracking = checkpoint.stage_tracking;
67
+ const ids = new Set(tracking.items.map(x => x.id));
68
+ for (const seed of items) {
69
+ if (ids.has(seed.id)) {
70
+ // Resume never overwrites a live item with an older task definition.
71
+ const existing = tracking.items.find(x => x.id === seed.id);
72
+ for (const key of ['title', 'owner', 'scope', 'stage', 'work_unit', 'acceptance', 'dependencies', 'split_reasons']) if (JSON.stringify(seed[key] ?? existing[key]) !== JSON.stringify(existing[key])) throw new Error(`tracking-existing-item-conflict: ${seed.id}.${key}`);
73
+ continue;
74
+ }
75
+ if (seed.progress && seed.progress !== 'pending') throw new Error('tracking-migration-cannot-infer-completion');
76
+ const item = { id: seed.id, kind: 'stage-work-item', title: seed.title, stage: seed.stage, work_unit: seed.work_unit, owner: seed.owner, scope: seed.scope,
77
+ acceptance: seed.acceptance, dependencies: seed.dependencies || [], source_refs: (seed.source_refs || []).map(ref => binding(root, typeof ref === 'string' ? ref : ref.ref)),
78
+ progress: 'pending', split_reasons: seed.split_reasons || [], completion: [] };
79
+ if (seed.deferred) { item.deferred = seed.deferred; if (!item.split_reasons.includes('cross-stage-deferral')) item.split_reasons.push('cross-stage-deferral'); }
80
+ tracking.items.push(item); ids.add(item.id);
81
+ }
82
+ for (const item of tracking.items) {
83
+ if (tracking.items.some(other => other.dependencies.includes(item.id)) && !item.split_reasons.includes('blocks-other-work')) item.split_reasons.push('blocks-other-work');
84
+ if (tracking.items[0]?.owner && item.owner !== tracking.items[0].owner && !item.split_reasons.includes('different-owner')) item.split_reasons.push('different-owner');
85
+ }
86
+ if (!tracking.items.length) gaps.push('需要明确当前阶段工作项及负责人、范围和验收条件;不从历史文件推断完成');
87
+ if (!tracking.items.some(i => i.stage === checkpoint.stage) && ['stage.plan', 'stage.spec-architecture', 'stage.product-design'].includes(checkpoint.stage)) gaps.push('当前阶段缺少工作项');
88
+ for (const item of tracking.items) {
89
+ for (const key of ['id', 'title', 'owner', 'scope', 'stage', 'work_unit']) if (typeof item[key] !== 'string' || !item[key].trim()) gaps.push(`${item.id || '?'} 缺少 ${key}`);
90
+ if (!item.acceptance?.length) gaps.push(`${item.id || '?'} 缺少验收条件`);
91
+ for (const source of [...item.source_refs, ...item.completion.flatMap(x => x.evidence_refs)]) watch(source.ref);
92
+ if (item.deferred) watch(item.deferred.decision_ref);
93
+ if (item.split_reasons?.length && !gaps.length) {
94
+ const ref = `docs/.scratch/${feature}/work-items/${item.id}.md`;
95
+ if (!item.definition_ref) {
96
+ if (descriptor(root, ref)) throw new Error(`tracking-unowned-file-conflict: ${ref}`);
97
+ item.definition_ref = ref;
98
+ const content = definition(item, checkpoint_ref);
99
+ item.definition_digest = sha256(content); change(ref, content);
100
+ } else watch(item.definition_ref);
101
+ }
102
+ }
103
+ const entryRef = tracking.entry.ref;
104
+ if (!design) {
105
+ watch(entryRef);
106
+ const link = `\n阶段工作进度与证据:${checkpoint_ref}\n`;
107
+ if (!observed[entryRef]) change(entryRef, `# ${feature}\n\nStatus: needs-triage\n${link}${trackerConfig(root).platform !== 'local-markdown' ? `\npublication: pending\npending_publication_to: ${trackerConfig(root).platform}\n` : ''}`);
108
+ else if (!readTracking(root, entryRef).includes(checkpoint_ref)) change(entryRef, readTracking(root, entryRef) + link);
109
+ }
110
+ const mapRef = `docs/.scratch/${feature}/map.md`; watch(mapRef);
111
+ if (!observed[mapRef]) change(mapRef, `---\ncheckpoint_ref: ${checkpoint_ref}\n---\n# ${feature}\n\n阶段工作与证据见 ${checkpoint_ref}。\n`);
112
+ else if (!readTracking(root, mapRef).includes(checkpoint_ref)) change(mapRef, readTracking(root, mapRef) + `\n阶段工作与证据:${checkpoint_ref}\n`);
113
+ change(checkpoint_ref, checkpoint_ref.endsWith('.json') ? json(checkpoint) : stringify(checkpoint));
114
+ change(TRACKER_REF, enabledTracker(readTracking(root, TRACKER_REF)));
115
+ const input = { checkpoint_ref, items, refresh };
116
+ const payload = { schema_version: 1, root, input, gaps, observed, changes };
117
+ return { ...payload, plan_id: sha256(JSON.stringify(payload)).slice(7) };
118
+ }
119
+ export function applyTracking(root, plan, { afterWrite } = {}) {
120
+ root = path.resolve(root); identity(root);
121
+ if (plan.root !== root || plan.schema_version !== 1 || !/^[a-f0-9]{64}$/.test(plan.plan_id)) throw new Error('tracking-plan-identity-invalid');
122
+ const { plan_id, ...payload } = plan;
123
+ if (sha256(JSON.stringify(payload)).slice(7) !== plan_id) throw new Error('tracking-plan-digest-mismatch');
124
+ const feature = featureFrom(plan.input.checkpoint_ref);
125
+ const transactionRef = `docs/.scratch/${feature}/verification/stage-tracking-migrations/${plan_id}`;
126
+ const receiptRef = `${transactionRef}/receipt.json`;
127
+ if (existsSync(safeTrackingPath(root, receiptRef))) {
128
+ const receipt = JSON.parse(readTracking(root, receiptRef));
129
+ if (receipt.plan_id !== plan_id) throw new Error('tracking-receipt-conflict');
130
+ return { status: 'unchanged', plan_id, receipt_ref: receiptRef };
131
+ }
132
+ if (plan.gaps.length) throw new Error(`tracking-migration-needs-info: ${plan.gaps.join('; ')}`);
133
+ const fresh = planTracking(root, plan.input);
134
+ if (JSON.stringify(fresh) !== JSON.stringify(plan)) throw new Error('tracking-plan-stale');
135
+ if (!plan.changes.length) return { status: 'unchanged', plan_id };
136
+ const transactionPath = safeTrackingPath(root, transactionRef);
137
+ if (existsSync(transactionPath)) throw new Error(`tracking-transaction-needs-recovery: ${transactionRef}`);
138
+ mkdirSync(transactionPath, { recursive: true });
139
+ const backups = plan.changes.map(c => ({ ref: c.ref, before: c.before, bytes: c.before ? readFileSync(safeTrackingPath(root, c.ref)).toString('base64') : null }));
140
+ writeFileSync(path.join(transactionPath, 'backup.json'), json(backups), { flag: 'wx' });
141
+ writeFileSync(path.join(transactionPath, 'plan.json'), json(plan), { flag: 'wx' });
142
+ const written = [], temporary = new Set();
143
+ try {
144
+ for (const [ref, before] of Object.entries(plan.observed)) if (JSON.stringify(descriptor(root, ref)) !== JSON.stringify(before)) throw new Error('tracking-concurrent-change');
145
+ for (const change of plan.changes) {
146
+ const file = safeTrackingPath(root, change.ref);
147
+ if (JSON.stringify(descriptor(root, change.ref)) !== JSON.stringify(change.before)) throw new Error('tracking-concurrent-change');
148
+ mkdirSync(path.dirname(file), { recursive: true });
149
+ const temp = `${file}.stage-tracking-${plan_id}`;
150
+ const fd = openSync(temp, 'wx', change.before?.mode || 0o644);
151
+ temporary.add(temp);
152
+ try { writeFileSync(fd, change.after); } finally { closeSync(fd); }
153
+ renameSync(temp, file); temporary.delete(temp); written.push(change);
154
+ afterWrite?.(change, written.length);
155
+ }
156
+ for (const [ref, before] of Object.entries(plan.observed)) {
157
+ const changed = written.find(x => x.ref === ref);
158
+ const actual = descriptor(root, ref);
159
+ if (changed ? actual?.digest !== changed.after_digest : JSON.stringify(actual) !== JSON.stringify(before)) throw new Error('tracking-concurrent-change');
160
+ }
161
+ const cp = parseYaml(readTracking(root, plan.input.checkpoint_ref));
162
+ assertStageTracking(cp, { root, checkpointRef: plan.input.checkpoint_ref });
163
+ writeFileSync(safeTrackingPath(root, receiptRef), json({ plan_id, status: 'applied', changed_refs: written.map(x => x.ref) }), { flag: 'wx' });
164
+ return { status: 'applied', plan_id, receipt_ref: receiptRef, changed_refs: written.map(x => x.ref) };
165
+ } catch (error) {
166
+ const conflicts = [];
167
+ for (const temp of temporary) { try { unlinkSync(temp); } catch (cleanupError) { if (cleanupError.code !== 'ENOENT') conflicts.push(temp); } }
168
+ for (const change of written.reverse()) {
169
+ if (descriptor(root, change.ref)?.digest !== change.after_digest) { conflicts.push(change.ref); continue; }
170
+ const backup = backups.find(x => x.ref === change.ref), file = safeTrackingPath(root, change.ref);
171
+ if (backup.bytes === null) unlinkSync(file); else writeFileSync(file, Buffer.from(backup.bytes, 'base64'), { mode: backup.before.mode });
172
+ }
173
+ writeFileSync(path.join(transactionPath, 'failure.json'), json({ error: error.message, recovery_conflicts: conflicts }));
174
+ throw new Error(`${error.message}; tracking-rollback-${conflicts.length ? `conflict:${conflicts.join(',')}` : 'complete'}`);
175
+ }
176
+ }