@gong-ym/ai-spec-auto 0.2.13 → 0.2.15

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 (631) hide show
  1. package/.agents/commands/README.md +33 -33
  2. package/.agents/commands/claude/spec-start-review.md +88 -88
  3. package/.agents/commands/codex/spec-continue.md +74 -74
  4. package/.agents/commands/codex/spec-orchestrate.md +35 -35
  5. package/.agents/commands/codex/spec-start-review.md +88 -88
  6. package/.agents/commands/codex/spec-start.md +67 -67
  7. package/.agents/commands/codex/spec-status.md +22 -22
  8. package/.agents/commands/codex/spec-stop.md +29 -29
  9. package/.agents/commands/codex/spec-update.md +40 -40
  10. package/.agents/commands/common/branch-review.md +117 -117
  11. package/.agents/commands/common/project-init.md +25 -25
  12. package/.agents/commands/common/spec-continue.md +74 -74
  13. package/.agents/commands/common/spec-orchestrate.md +35 -35
  14. package/.agents/commands/common/spec-start-review.md +82 -82
  15. package/.agents/commands/common/spec-start.md +67 -67
  16. package/.agents/commands/common/spec-status.md +22 -22
  17. package/.agents/commands/common/spec-stop.md +29 -29
  18. package/.agents/commands/common/spec-update.md +60 -40
  19. package/.agents/commands/cursor/opsx-apply.md +55 -55
  20. package/.agents/commands/cursor/opsx-archive.md +48 -48
  21. package/.agents/commands/cursor/opsx-explore.md +45 -45
  22. package/.agents/commands/cursor/opsx-propose.md +59 -59
  23. package/.agents/commands/cursor/spec-continue.md +63 -63
  24. package/.agents/commands/cursor/spec-orchestrate.md +53 -53
  25. package/.agents/commands/cursor/spec-start-review.md +78 -78
  26. package/.agents/commands/cursor/spec-start.md +59 -59
  27. package/.agents/commands/cursor/spec-status.md +30 -30
  28. package/.agents/commands/cursor/spec-stop.md +29 -29
  29. package/.agents/commands/cursor/spec-update.md +41 -41
  30. package/.agents/flows/FRONTMATTER.md +263 -263
  31. package/.agents/flows/RUN_OUTPUT.md +263 -263
  32. package/.agents/flows/common/README.md +29 -29
  33. package/.agents/flows/common/bugfix-to-verification.md +95 -95
  34. package/.agents/flows/common/change-to-architecture-review.md +89 -89
  35. package/.agents/flows/common/change-to-release.md +94 -94
  36. package/.agents/flows/common/prd-to-delivery.md +184 -184
  37. package/.agents/flows/common/requirement-to-observability.md +97 -97
  38. package/.agents/orchestration/README.md +22 -22
  39. package/.agents/orchestration/expert-dispatch-spec.md +155 -155
  40. package/.agents/orchestration/expert-executor-spec.md +84 -84
  41. package/.agents/orchestration/expert-runtime-action-spec.md +73 -73
  42. package/.agents/orchestration/runtime-state-handoff-spec.md +264 -264
  43. package/.agents/orchestration/task-anchor-spec.md +212 -212
  44. package/.agents/orchestration/task-orchestrator-adapter-payload.md +153 -153
  45. package/.agents/orchestration/task-orchestrator-bootstrap-payload.md +145 -145
  46. package/.agents/orchestration/task-orchestrator-output-extractor-spec.md +93 -93
  47. package/.agents/orchestration/task-orchestrator-run-plan-template.md +312 -312
  48. package/.agents/orchestration/task-orchestrator-runtime-hooks.md +214 -214
  49. package/.agents/registry/README.md +63 -63
  50. package/.agents/registry/flows.json +125 -125
  51. package/.agents/registry/profiles.json +101 -101
  52. package/.agents/registry/roles.json +1265 -1265
  53. package/.agents/registry/rules.json +148 -148
  54. package/.agents/registry/scenario-packages.json +123 -123
  55. package/.agents/registry/skills.json +130 -130
  56. package/.agents/roles/INDEX.md +346 -346
  57. package/.agents/roles/common/README.md +76 -76
  58. package/.agents/roles/common/archive-change.md +80 -80
  59. package/.agents/roles/common/backend-implementer.md +92 -92
  60. package/.agents/roles/common/code-guardian.md +151 -151
  61. package/.agents/roles/common/frontend-implementer.md +146 -146
  62. package/.agents/roles/common/requirement-analyst.md +138 -138
  63. package/.agents/roles/common/task-orchestrator-routing.md +301 -301
  64. package/.agents/roles/common/task-orchestrator.md +224 -224
  65. package/.agents/roles/common/tooling-implementer.md +92 -92
  66. package/.agents/roles/domains/README.md +35 -35
  67. package/.agents/roles/domains/delivery/README.md +11 -11
  68. package/.agents/roles/domains/delivery/container-specialist.md +50 -50
  69. package/.agents/roles/domains/delivery/deployment-specialist.md +50 -50
  70. package/.agents/roles/domains/delivery/pipeline-specialist.md +50 -50
  71. package/.agents/roles/domains/demand-design/README.md +16 -16
  72. package/.agents/roles/domains/demand-design/api-contract-specialist.md +52 -52
  73. package/.agents/roles/domains/demand-design/design-collaborator.md +58 -58
  74. package/.agents/roles/domains/documentation/README.md +11 -11
  75. package/.agents/roles/domains/documentation/api-doc-specialist.md +50 -50
  76. package/.agents/roles/domains/documentation/component-doc-specialist.md +49 -49
  77. package/.agents/roles/domains/documentation/technical-writing-specialist.md +48 -48
  78. package/.agents/roles/domains/engineering/README.md +17 -17
  79. package/.agents/roles/domains/engineering/architecture-advisor.md +53 -53
  80. package/.agents/roles/domains/engineering/build-specialist.md +51 -51
  81. package/.agents/roles/domains/engineering/dependency-governor.md +52 -52
  82. package/.agents/roles/domains/governance/README.md +17 -17
  83. package/.agents/roles/domains/governance/api-governance-specialist.md +51 -51
  84. package/.agents/roles/domains/governance/lint-policy-specialist.md +49 -49
  85. package/.agents/roles/domains/governance/route-governance-specialist.md +52 -52
  86. package/.agents/roles/domains/observability/README.md +11 -11
  87. package/.agents/roles/domains/observability/error-tracker.md +50 -50
  88. package/.agents/roles/domains/observability/event-instrumentation-specialist.md +51 -51
  89. package/.agents/roles/domains/observability/rum-analyst.md +50 -50
  90. package/.agents/roles/domains/performance/README.md +11 -11
  91. package/.agents/roles/domains/performance/asset-optimizer.md +50 -50
  92. package/.agents/roles/domains/performance/performance-auditor.md +56 -56
  93. package/.agents/roles/domains/performance/vitals-analyst.md +50 -50
  94. package/.agents/roles/domains/security-a11y/README.md +11 -11
  95. package/.agents/roles/domains/security-a11y/a11y-auditor.md +50 -50
  96. package/.agents/roles/domains/security-a11y/aria-specialist.md +51 -51
  97. package/.agents/roles/domains/security-a11y/security-reviewer.md +49 -49
  98. package/.agents/roles/domains/testing/README.md +12 -12
  99. package/.agents/roles/domains/testing/coverage-analyst.md +50 -50
  100. package/.agents/roles/domains/testing/e2e-test-specialist.md +51 -51
  101. package/.agents/roles/domains/testing/unit-test-specialist.md +56 -56
  102. package/.agents/roles/domains/testing/verification-reviewer.md +67 -67
  103. package/.agents/rules/README.md +87 -87
  104. package/.agents/rules/common/02-/347/274/226/347/240/201/350/247/204/350/214/203.md +45 -45
  105. package/.agents/rules/common/08-/351/200/232/347/224/250/347/272/246/346/235/237.md +63 -63
  106. package/.agents/rules/common/10-/346/226/207/346/241/243/350/247/204/350/214/203.md +101 -101
  107. package/.agents/rules/common/12-Superpowers/346/211/247/350/241/214/350/247/204/350/214/203.md +46 -46
  108. package/.agents/rules/common/14-/345/256/241/350/256/241/346/261/207/346/212/245/350/247/204/350/214/203.md +107 -107
  109. package/.agents/rules/common/15-visual-gate-wait.md +90 -90
  110. package/.agents/rules/profiles/nestjs/01-/351/241/271/347/233/256/346/246/202/350/277/260.md +27 -27
  111. package/.agents/rules/profiles/nestjs/03-/351/241/271/347/233/256/347/273/223/346/236/204.md +20 -20
  112. package/.agents/rules/profiles/nestjs/04-/346/250/241/345/235/227/347/273/223/346/236/204/350/247/204/350/214/203.md +24 -24
  113. package/.agents/rules/profiles/nestjs/05-/346/216/245/345/217/243/344/270/216/345/245/221/347/272/246/350/247/204/350/214/203.md +24 -24
  114. package/.agents/rules/profiles/nestjs/06-/346/225/260/346/215/256/350/256/277/351/227/256/350/247/204/350/214/203.md +24 -24
  115. package/.agents/rules/profiles/nestjs/07-/351/205/215/347/275/256/344/270/216/350/277/220/350/241/214/346/227/266/350/247/204/350/214/203.md +20 -20
  116. package/.agents/rules/profiles/nestjs/09-/345/274/202/345/270/270/344/270/216/346/227/245/345/277/227/350/247/204/350/214/203.md +20 -20
  117. package/.agents/rules/profiles/nestjs/11-/346/265/213/350/257/225/350/247/204/350/214/203.md +24 -24
  118. package/.agents/rules/profiles/nestjs/13-/344/273/243/347/240/201/346/240/274/345/274/217/345/214/226/344/270/216/346/243/200/346/237/245.md +20 -20
  119. package/.agents/rules/profiles/node-tooling/01-/351/241/271/347/233/256/346/246/202/350/277/260.md +30 -30
  120. package/.agents/rules/profiles/node-tooling/03-/351/241/271/347/233/256/347/273/223/346/236/204.md +37 -37
  121. package/.agents/rules/profiles/node-tooling/04-CLI/344/270/216/346/250/241/345/235/227/350/247/204/350/214/203.md +42 -42
  122. package/.agents/rules/profiles/node-tooling/05-Contract/344/270/216Schema/350/247/204/350/214/203.md +42 -42
  123. package/.agents/rules/profiles/node-tooling/06-/350/277/220/350/241/214/346/227/266/346/226/207/344/273/266/350/247/204/350/214/203.md +30 -30
  124. package/.agents/rules/profiles/node-tooling/07-/346/227/245/345/277/227/344/270/216/351/224/231/350/257/257/345/244/204/347/220/206/350/247/204/350/214/203.md +60 -60
  125. package/.agents/rules/profiles/node-tooling/09-/350/204/232/346/234/254/344/270/216/345/205/245/345/217/243/350/247/204/350/214/203.md +45 -45
  126. package/.agents/rules/profiles/node-tooling/11-/346/265/213/350/257/225/350/247/204/350/214/203.md +41 -41
  127. package/.agents/rules/profiles/node-tooling/13-/344/273/243/347/240/201/346/240/274/345/274/217/345/214/226/344/270/216/346/243/200/346/237/245.md +55 -55
  128. package/.agents/rules/profiles/react/01-/351/241/271/347/233/256/346/246/202/350/277/260.md +29 -29
  129. package/.agents/rules/profiles/react/03-/351/241/271/347/233/256/347/273/223/346/236/204.md +104 -104
  130. package/.agents/rules/profiles/react/04-/347/273/204/344/273/266/350/247/204/350/214/203.md +46 -46
  131. package/.agents/rules/profiles/react/05-API/350/247/204/350/214/203.md +67 -67
  132. package/.agents/rules/profiles/react/06-/350/267/257/347/224/261/350/247/204/350/214/203.md +54 -54
  133. package/.agents/rules/profiles/react/07-/347/212/266/346/200/201/347/256/241/347/220/206.md +226 -226
  134. package/.agents/rules/profiles/react/09-/346/240/267/345/274/217/350/247/204/350/214/203.md +71 -71
  135. package/.agents/rules/profiles/react/11-/346/265/213/350/257/225/350/247/204/350/214/203.md +80 -80
  136. package/.agents/rules/profiles/react/13-/344/273/243/347/240/201/346/240/274/345/274/217/345/214/226/344/270/216/346/243/200/346/237/245.md +159 -159
  137. package/.agents/rules/profiles/springboot/01-/351/241/271/347/233/256/346/246/202/350/277/260.md +31 -31
  138. package/.agents/rules/profiles/springboot/03-/351/241/271/347/233/256/347/273/223/346/236/204.md +37 -37
  139. package/.agents/rules/profiles/springboot/04-/345/210/206/345/261/202/350/247/204/350/214/203.md +33 -33
  140. package/.agents/rules/profiles/springboot/05-/346/216/245/345/217/243/344/270/216/345/245/221/347/272/246/350/247/204/350/214/203.md +51 -51
  141. package/.agents/rules/profiles/springboot/06-/346/225/260/346/215/256/350/256/277/351/227/256/350/247/204/350/214/203.md +34 -34
  142. package/.agents/rules/profiles/springboot/07-/351/205/215/347/275/256/344/270/216/350/277/220/350/241/214/346/227/266/350/247/204/350/214/203.md +38 -38
  143. package/.agents/rules/profiles/springboot/09-/345/274/202/345/270/270/344/270/216/346/227/245/345/277/227/350/247/204/350/214/203.md +48 -48
  144. package/.agents/rules/profiles/springboot/11-/346/265/213/350/257/225/350/247/204/350/214/203.md +43 -43
  145. package/.agents/rules/profiles/springboot/13-/344/273/243/347/240/201/346/240/274/345/274/217/345/214/226/344/270/216/346/243/200/346/237/245.md +48 -48
  146. package/.agents/rules/profiles/vue/01-/351/241/271/347/233/256/346/246/202/350/277/260.md +47 -47
  147. package/.agents/rules/profiles/vue/03-/351/241/271/347/233/256/347/273/223/346/236/204.md +106 -106
  148. package/.agents/rules/profiles/vue/04-/347/273/204/344/273/266/350/247/204/350/214/203.md +61 -61
  149. package/.agents/rules/profiles/vue/05-API/350/247/204/350/214/203.md +67 -67
  150. package/.agents/rules/profiles/vue/06-/350/267/257/347/224/261/350/247/204/350/214/203.md +69 -69
  151. package/.agents/rules/profiles/vue/07-/347/212/266/346/200/201/347/256/241/347/220/206.md +93 -93
  152. package/.agents/rules/profiles/vue/09-/346/240/267/345/274/217/350/247/204/350/214/203.md +67 -67
  153. package/.agents/rules/profiles/vue/11-/346/265/213/350/257/225/350/247/204/350/214/203.md +80 -80
  154. package/.agents/rules/profiles/vue/13-/344/273/243/347/240/201/346/240/274/345/274/217/345/214/226/344/270/216/346/243/200/346/237/245.md +159 -159
  155. package/.agents/skills/README.md +171 -171
  156. package/.agents/skills/common/archive-change/SKILL.md +180 -180
  157. package/.agents/skills/common/branch-code-reviewer/SKILL.md +533 -459
  158. package/.agents/skills/common/branch-code-reviewer/references/business-risk-guide.md +293 -293
  159. package/.agents/skills/common/branch-code-reviewer/references/html-template-guide.md +121 -121
  160. package/.agents/skills/common/config-and-secret-scan/SKILL.md +99 -99
  161. package/.agents/skills/common/create-proposal/SKILL.md +192 -192
  162. package/.agents/skills/common/create-proposal/evals/evals.json +16 -16
  163. package/.agents/skills/common/create-proposal/evals/train_queries.json +18 -18
  164. package/.agents/skills/common/create-proposal/evals/validation_queries.json +18 -18
  165. package/.agents/skills/common/create-proposal/references/interaction-spec-template.md +42 -42
  166. package/.agents/skills/common/create-test/SKILL.md +292 -292
  167. package/.agents/skills/common/dependency-impact-graph/SKILL.md +80 -80
  168. package/.agents/skills/common/execute-task/SKILL.md +206 -206
  169. package/.agents/skills/common/execute-task/evals/evals.json +16 -16
  170. package/.agents/skills/common/execute-task/evals/train_queries.json +18 -18
  171. package/.agents/skills/common/execute-task/evals/validation_queries.json +18 -18
  172. package/.agents/skills/common/find-skills/SKILL.md +144 -144
  173. package/.agents/skills/common/install-ai-spec-auto/SKILL.md +260 -260
  174. package/.agents/skills/common/install-ai-spec-auto/evals/evals.json +17 -17
  175. package/.agents/skills/common/install-ai-spec-auto/evals/train_queries.json +18 -18
  176. package/.agents/skills/common/install-ai-spec-auto/evals/validation_queries.json +18 -18
  177. package/.agents/skills/common/project-init/SKILL.md +178 -178
  178. package/.agents/skills/common/project-init/evals/evals.json +16 -16
  179. package/.agents/skills/common/project-init/evals/train_queries.json +18 -18
  180. package/.agents/skills/common/project-init/evals/validation_queries.json +18 -18
  181. package/.agents/skills/common/project-init/references/custom-rule-generation.md +89 -89
  182. package/.agents/skills/common/project-init/references/deep-scan-rules.md +67 -67
  183. package/.agents/skills/common/project-init/references/output-contracts.md +71 -71
  184. package/.agents/skills/common/project-init/references/repo-fact-gathering.md +83 -83
  185. package/.agents/skills/common/project-init/references/scope-resolution.md +76 -76
  186. package/.agents/skills/common/project-init/scripts/inspect-project.js +112 -112
  187. package/.agents/skills/common/skill-creator/LICENSE.txt +201 -201
  188. package/.agents/skills/common/skill-creator/SKILL.md +370 -370
  189. package/.agents/skills/common/skill-creator/evals/evals.json +16 -16
  190. package/.agents/skills/common/skill-creator/evals/train_queries.json +18 -18
  191. package/.agents/skills/common/skill-creator/evals/validation_queries.json +18 -18
  192. package/.agents/skills/common/skill-creator/references/output-patterns.md +82 -82
  193. package/.agents/skills/common/skill-creator/references/workflows.md +27 -27
  194. package/.agents/skills/common/skill-creator/scripts/init_skill.py +209 -209
  195. package/.agents/skills/common/skill-creator/scripts/package_skill.py +110 -110
  196. package/.agents/skills/common/skill-creator/scripts/quick_validate.py +51 -51
  197. package/.agents/skills/common/skill-optimizer/SKILL.md +102 -102
  198. package/.agents/skills/common/skill-optimizer/evals/evals.json +16 -16
  199. package/.agents/skills/common/skill-optimizer/evals/train_queries.json +18 -18
  200. package/.agents/skills/common/skill-optimizer/evals/validation_queries.json +18 -18
  201. package/.agents/skills/common/skill-optimizer/references/design-patterns.md +26 -26
  202. package/.agents/skills/common/skill-optimizer/references/review-checklist.md +22 -22
  203. package/.agents/skills/common/using-superpowers/SKILL.md +151 -151
  204. package/.agents/skills/common/wait-for-gate-signal/SKILL.md +85 -85
  205. package/.agents/skills/domains/README.md +19 -19
  206. package/.agents/skills/domains/ui-ux-pro-max/SKILL.md +58 -58
  207. package/.agents/skills/domains/web/design-analysis/SKILL.md +89 -89
  208. package/.agents/skills/domains/web/design-analysis/rules/analysis-order.md +61 -61
  209. package/.agents/skills/domains/web/design-analysis/rules/analysis-priorities.md +136 -136
  210. package/.agents/skills/domains/web/design-analysis/rules/checklist-common-misses.md +107 -107
  211. package/.agents/skills/domains/web/design-analysis/rules/implementation-common-errors.md +204 -204
  212. package/.agents/skills/domains/web/design-analysis/rules/implementation-guidelines.md +211 -211
  213. package/.agents/skills/domains/web/design-analysis/rules/output-analysis-checklist.md +247 -247
  214. package/.agents/skills/domains/web/design-analysis/rules/tools-design-guidelines.md +108 -108
  215. package/.agents/skills/domains/web/design-analysis/rules/workflow-element-extraction.md +162 -162
  216. package/.agents/skills/domains/web/design-analysis/rules/workflow-layout-map.md +131 -131
  217. package/.agents/skills/domains/web/design-analysis/rules/workflow-output-checklist.md +70 -70
  218. package/.agents/skills/domains/web/design-analysis/rules/workflow-style-summary.md +91 -91
  219. package/.agents/skills/domains/web/route-permission-map/SKILL.md +103 -103
  220. package/.agents/skills/domains/web/ui-verification/SKILL.md +114 -114
  221. package/.agents/skills/domains/web/ui-verification/evals/evals.json +16 -16
  222. package/.agents/skills/domains/web/ui-verification/evals/train_queries.json +18 -18
  223. package/.agents/skills/domains/web/ui-verification/evals/validation_queries.json +18 -18
  224. package/.agents/skills/domains/web/ui-verification/rules/comparison-content-image.md +34 -34
  225. package/.agents/skills/domains/web/ui-verification/rules/comparison-content-text.md +30 -30
  226. package/.agents/skills/domains/web/ui-verification/rules/comparison-hierarchy.md +33 -33
  227. package/.agents/skills/domains/web/ui-verification/rules/comparison-layout.md +35 -35
  228. package/.agents/skills/domains/web/ui-verification/rules/errors-alignment.md +42 -42
  229. package/.agents/skills/domains/web/ui-verification/rules/errors-button-dimensions.md +28 -28
  230. package/.agents/skills/domains/web/ui-verification/rules/errors-button-position.md +25 -25
  231. package/.agents/skills/domains/web/ui-verification/rules/errors-css-priority.md +50 -50
  232. package/.agents/skills/domains/web/ui-verification/rules/errors-flex-column-width.md +46 -46
  233. package/.agents/skills/domains/web/ui-verification/rules/errors-flex-layout.md +46 -46
  234. package/.agents/skills/domains/web/ui-verification/rules/errors-grid-container-width.md +44 -44
  235. package/.agents/skills/domains/web/ui-verification/rules/errors-page-container-width.md +39 -39
  236. package/.agents/skills/domains/web/ui-verification/rules/tools-browser-navigation.md +53 -53
  237. package/.agents/skills/domains/web/ui-verification/rules/tools-design-guidelines.md +53 -53
  238. package/.agents/skills/domains/web/ui-verification/rules/workflow-checklist.md +27 -27
  239. package/.agents/skills/domains/web/ui-verification/rules/workflow-problem-list.md +56 -56
  240. package/.agents/skills/domains/web/ui-verification/rules/workflow-reflection.md +44 -44
  241. package/.agents/skills/domains/web/ui-verification/rules/writing-alignment.md +44 -44
  242. package/.agents/skills/domains/web/ui-verification/rules/writing-element-completeness.md +63 -63
  243. package/.agents/skills/domains/web/ui-verification/rules/writing-list-layout.md +75 -75
  244. package/.agents/skills/domains/web/ui-verification/rules/writing-page-container-width.md +37 -37
  245. package/.agents/skills/domains/web/web-design-guidelines/SKILL.md +40 -40
  246. package/.agents/skills/profiles/nestjs/README.md +4 -4
  247. package/.agents/skills/profiles/node-tooling/README.md +9 -9
  248. package/.agents/skills/profiles/react/create-api/SKILL.md +145 -145
  249. package/.agents/skills/profiles/react/create-component/SKILL.md +160 -160
  250. package/.agents/skills/profiles/react/create-route/SKILL.md +168 -168
  251. package/.agents/skills/profiles/react/create-store/SKILL.md +262 -262
  252. package/.agents/skills/profiles/react/theme-variables/SKILL.md +82 -82
  253. package/.agents/skills/profiles/react/vercel-composition-patterns/AGENTS.md +899 -899
  254. package/.agents/skills/profiles/react/vercel-composition-patterns/SKILL.md +81 -81
  255. package/.agents/skills/profiles/react/vercel-composition-patterns/rules/architecture-avoid-boolean-props.md +100 -100
  256. package/.agents/skills/profiles/react/vercel-composition-patterns/rules/architecture-compound-components.md +112 -112
  257. package/.agents/skills/profiles/react/vercel-composition-patterns/rules/patterns-children-over-render-props.md +87 -87
  258. package/.agents/skills/profiles/react/vercel-composition-patterns/rules/patterns-explicit-variants.md +100 -100
  259. package/.agents/skills/profiles/react/vercel-composition-patterns/rules/state-context-interface.md +191 -191
  260. package/.agents/skills/profiles/react/vercel-composition-patterns/rules/state-decouple-implementation.md +113 -113
  261. package/.agents/skills/profiles/react/vercel-composition-patterns/rules/state-lift-state.md +125 -125
  262. package/.agents/skills/profiles/react/vercel-react-best-practices/AGENTS.md +2934 -2934
  263. package/.agents/skills/profiles/react/vercel-react-best-practices/SKILL.md +136 -136
  264. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/advanced-event-handler-refs.md +55 -55
  265. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/advanced-init-once.md +42 -42
  266. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/advanced-use-latest.md +39 -39
  267. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/async-api-routes.md +38 -38
  268. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/async-defer-await.md +80 -80
  269. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/async-dependencies.md +51 -51
  270. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/async-parallel.md +28 -28
  271. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/async-suspense-boundaries.md +99 -99
  272. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/bundle-barrel-imports.md +59 -59
  273. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/bundle-conditional.md +31 -31
  274. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/bundle-defer-third-party.md +49 -49
  275. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/bundle-dynamic-imports.md +35 -35
  276. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/bundle-preload.md +50 -50
  277. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/client-event-listeners.md +74 -74
  278. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/client-localstorage-schema.md +71 -71
  279. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/client-passive-event-listeners.md +48 -48
  280. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/client-swr-dedup.md +56 -56
  281. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/js-batch-dom-css.md +107 -107
  282. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/js-cache-function-results.md +80 -80
  283. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/js-cache-property-access.md +28 -28
  284. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/js-cache-storage.md +70 -70
  285. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/js-combine-iterations.md +32 -32
  286. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/js-early-exit.md +50 -50
  287. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/js-hoist-regexp.md +45 -45
  288. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/js-index-maps.md +37 -37
  289. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/js-length-check-first.md +49 -49
  290. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/js-min-max-loop.md +82 -82
  291. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/js-set-map-lookups.md +24 -24
  292. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/js-tosorted-immutable.md +57 -57
  293. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rendering-activity.md +26 -26
  294. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -47
  295. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rendering-conditional-render.md +40 -40
  296. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rendering-content-visibility.md +38 -38
  297. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rendering-hoist-jsx.md +46 -46
  298. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -82
  299. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md +30 -30
  300. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rendering-svg-precision.md +28 -28
  301. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rendering-usetransition-loading.md +75 -75
  302. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rerender-defer-reads.md +39 -39
  303. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rerender-dependencies.md +45 -45
  304. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md +40 -40
  305. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rerender-derived-state.md +29 -29
  306. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rerender-functional-setstate.md +74 -74
  307. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rerender-lazy-state-init.md +58 -58
  308. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rerender-memo-with-default-value.md +38 -38
  309. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rerender-memo.md +44 -44
  310. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rerender-move-effect-to-event.md +45 -45
  311. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -35
  312. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rerender-transitions.md +40 -40
  313. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md +73 -73
  314. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/server-after-nonblocking.md +73 -73
  315. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/server-auth-actions.md +96 -96
  316. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/server-cache-lru.md +41 -41
  317. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/server-cache-react.md +76 -76
  318. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/server-dedup-props.md +65 -65
  319. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/server-parallel-fetching.md +83 -83
  320. package/.agents/skills/profiles/react/vercel-react-best-practices/rules/server-serialization.md +38 -38
  321. package/.agents/skills/profiles/springboot/README.md +10 -10
  322. package/.agents/skills/profiles/vue/create-api/SKILL.md +105 -105
  323. package/.agents/skills/profiles/vue/create-component/SKILL.md +76 -76
  324. package/.agents/skills/profiles/vue/create-route/SKILL.md +141 -141
  325. package/.agents/skills/profiles/vue/create-store/SKILL.md +97 -97
  326. package/.agents/skills/profiles/vue/create-view/SKILL.md +81 -81
  327. package/.agents/skills/profiles/vue/theme-variables/SKILL.md +73 -73
  328. package/.agents/skills/profiles/vue/vue-best-practices/SKILL.md +166 -166
  329. package/.agents/skills/profiles/vue/vue-best-practices/references/animation-class-based-technique.md +254 -254
  330. package/.agents/skills/profiles/vue/vue-best-practices/references/animation-state-driven-technique.md +291 -291
  331. package/.agents/skills/profiles/vue/vue-best-practices/references/component-async.md +97 -97
  332. package/.agents/skills/profiles/vue/vue-best-practices/references/component-data-flow.md +307 -307
  333. package/.agents/skills/profiles/vue/vue-best-practices/references/component-fallthrough-attrs.md +174 -174
  334. package/.agents/skills/profiles/vue/vue-best-practices/references/component-keep-alive.md +137 -137
  335. package/.agents/skills/profiles/vue/vue-best-practices/references/component-slots.md +216 -216
  336. package/.agents/skills/profiles/vue/vue-best-practices/references/component-suspense.md +228 -228
  337. package/.agents/skills/profiles/vue/vue-best-practices/references/component-teleport.md +108 -108
  338. package/.agents/skills/profiles/vue/vue-best-practices/references/component-transition-group.md +128 -128
  339. package/.agents/skills/profiles/vue/vue-best-practices/references/component-transition.md +125 -125
  340. package/.agents/skills/profiles/vue/vue-best-practices/references/composables.md +290 -290
  341. package/.agents/skills/profiles/vue/vue-best-practices/references/directives.md +162 -162
  342. package/.agents/skills/profiles/vue/vue-best-practices/references/perf-avoid-component-abstraction-in-lists.md +159 -159
  343. package/.agents/skills/profiles/vue/vue-best-practices/references/perf-v-once-v-memo-directives.md +182 -182
  344. package/.agents/skills/profiles/vue/vue-best-practices/references/perf-virtualize-large-lists.md +187 -187
  345. package/.agents/skills/profiles/vue/vue-best-practices/references/plugins.md +166 -166
  346. package/.agents/skills/profiles/vue/vue-best-practices/references/reactivity.md +344 -344
  347. package/.agents/skills/profiles/vue/vue-best-practices/references/render-functions.md +201 -201
  348. package/.agents/skills/profiles/vue/vue-best-practices/references/sfc.md +310 -310
  349. package/.agents/skills/profiles/vue/vue-best-practices/references/state-management.md +135 -135
  350. package/.agents/skills/profiles/vue/vue-best-practices/references/updated-hook-performance.md +187 -187
  351. package/.agents/templates/common/README.md +23 -23
  352. package/.agents/templates/common/bugfix.md +22 -22
  353. package/.agents/templates/common/create-expert-package.md +458 -458
  354. package/.agents/templates/common/mock-page.md +28 -28
  355. package/.agents/templates/common/new-component.md +25 -25
  356. package/.agents/templates/common/new-page.md +31 -31
  357. package/.cursor/mcp.json +35 -35
  358. package/.qoder/mcp.json +26 -26
  359. package/bin/archive-change.js +560 -474
  360. package/bin/check-command.js +62 -62
  361. package/bin/cli.js +0 -0
  362. package/bin/command-template-renderer.js +40 -40
  363. package/bin/context-command.js +102 -102
  364. package/bin/demo-runtime-smoke.js +760 -760
  365. package/bin/execution-semantics.js +821 -821
  366. package/bin/executor-command.js +93 -93
  367. package/bin/expert-dispatch.js +334 -334
  368. package/bin/expert-executor.js +1148 -1148
  369. package/bin/guard-command.js +52 -52
  370. package/bin/hub-command.js +876 -876
  371. package/bin/ide-command.js +242 -242
  372. package/bin/init-command.js +193 -193
  373. package/bin/install-workflow.js +35 -3
  374. package/bin/manifest-export.js +34 -34
  375. package/bin/profile-registry.js +90 -90
  376. package/bin/protocol-workflow.js +452 -446
  377. package/bin/repair-command.js +161 -161
  378. package/bin/repo-map.js +177 -177
  379. package/bin/report-command.js +236 -236
  380. package/bin/runtime-bootstrap.js +428 -428
  381. package/bin/runtime-embedded.js +101 -101
  382. package/bin/runtime-fallback.js +106 -106
  383. package/bin/runtime-launcher.js +116 -116
  384. package/bin/runtime-paths.js +177 -177
  385. package/bin/runtime-registry.js +289 -289
  386. package/bin/runtime-state.js +2541 -2541
  387. package/bin/scan.js +96 -96
  388. package/bin/self-upgrade.js +206 -206
  389. package/bin/skill-spec-validator.js +457 -457
  390. package/bin/spec-command.js +366 -366
  391. package/bin/superpowers.js +384 -384
  392. package/bin/sync-command.js +59 -59
  393. package/bin/sync.js +1904 -1904
  394. package/bin/task-orchestrator-adapter.js +341 -341
  395. package/bin/task-orchestrator-extractor.js +274 -274
  396. package/bin/task-orchestrator-runner.js +1208 -1208
  397. package/bin/telemetry/README.md +66 -66
  398. package/bin/telemetry/aspect.js +153 -153
  399. package/bin/telemetry/collect.js +67 -67
  400. package/bin/telemetry/config.js +114 -114
  401. package/bin/telemetry/defaults.json +5 -5
  402. package/bin/telemetry/healthcheck.js +195 -195
  403. package/bin/telemetry/identity.js +53 -53
  404. package/bin/telemetry/index.js +25 -25
  405. package/bin/telemetry/reporter.js +83 -83
  406. package/bin/telemetry/safe.js +39 -39
  407. package/bin/validate-registry.js +740 -740
  408. package/bin/visual-bridge-config.js +117 -117
  409. package/bin/visual-bridge.js +287 -287
  410. package/bin/visual-command.js +432 -432
  411. package/bin/worktree-command.js +194 -194
  412. package/configs/common/.editorconfig +15 -15
  413. package/configs/common/.husky/commit-msg +4 -4
  414. package/configs/common/.husky/pre-commit +4 -4
  415. package/configs/common/.lintstagedrc +11 -11
  416. package/configs/common/.prettierignore +11 -11
  417. package/configs/common/.prettierrc.json +11 -11
  418. package/configs/common/.stylelintignore +14 -14
  419. package/configs/common/.stylelintrc.json +21 -21
  420. package/configs/common/commitlint.config.js +3 -3
  421. package/configs/profiles/nestjs/.gitkeep +1 -1
  422. package/configs/profiles/node-tooling/.gitkeep +1 -1
  423. package/configs/profiles/react/.eslintignore +6 -6
  424. package/configs/profiles/react/.eslintrc.js +16 -16
  425. package/configs/profiles/react/.stylelintrc.json +18 -18
  426. package/configs/profiles/springboot/.gitkeep +1 -1
  427. package/configs/profiles/vue/.eslintignore +6 -6
  428. package/configs/profiles/vue/.eslintrc.cjs +17 -17
  429. package/contracts/README.md +28 -28
  430. package/contracts/fixtures/asset-package.fixture.json +26 -26
  431. package/contracts/fixtures/asset-usage-feedback.fixture.json +14 -14
  432. package/contracts/fixtures/evidence-report.fixture.json +28 -28
  433. package/contracts/fixtures/manifest.fixture.json +20 -20
  434. package/contracts/fixtures/run-event.fixture.json +15 -15
  435. package/contracts/schemas/asset-package.schema.json +76 -76
  436. package/contracts/schemas/asset-usage-feedback.schema.json +57 -57
  437. package/contracts/schemas/evidence-report.schema.json +60 -60
  438. package/contracts/schemas/manifest.schema.json +63 -63
  439. package/contracts/schemas/run-event.schema.json +72 -72
  440. package/install.ps1 +35 -35
  441. package/install.sh +17 -17
  442. package/internal/ai-protocol-workflow.js +5824 -5600
  443. package/internal/hub-client.js +98 -98
  444. package/internal/hub-sync-selection.js +69 -69
  445. package/internal/visual-hooks/README.md +481 -481
  446. package/internal/visual-hooks/config-loader.js +218 -218
  447. package/internal/visual-hooks/control-puller.js +206 -206
  448. package/internal/visual-hooks/gate-signal.js +150 -150
  449. package/internal/visual-hooks/inbox-consumer.js +469 -469
  450. package/internal/visual-hooks/index.js +197 -197
  451. package/internal/visual-hooks/push-client.js +189 -189
  452. package/internal/visual-hooks/receipt-pusher.js +176 -176
  453. package/internal/visual-hooks/runtime-state-pusher.js +128 -128
  454. package/openspec/config.yaml.template +52 -52
  455. package/openspec/schemas/expert-delivery/schema.yaml +68 -68
  456. package/openspec/schemas/expert-delivery/templates/checklist.md +39 -39
  457. package/openspec/schemas/expert-delivery/templates/design.md +61 -61
  458. package/openspec/schemas/expert-delivery/templates/iterations.md +25 -25
  459. package/openspec/schemas/expert-delivery/templates/proposal.md +45 -45
  460. package/openspec/schemas/expert-delivery/templates/spec.md +29 -29
  461. package/openspec/schemas/expert-delivery/templates/tasks.md +24 -24
  462. package/package.json +1 -1
  463. package/scripts/acceptance-zero-intrusion.sh +168 -168
  464. package/scripts/hub-sync-assets.config.example.json +296 -296
  465. package/scripts/hub-sync-assets.js +2038 -2038
  466. package/scripts/local-verify.sh +280 -280
  467. package/scripts/post-publish-auto-fix-check.js +404 -404
  468. package/scripts/post-publish-verify.sh +175 -175
  469. package/scripts/setup-cursor-manual-test.sh +107 -107
  470. package/scripts/setup-cursor-spec-archive-test.sh +111 -111
  471. package/scripts/setup-visual-integration.sh +225 -225
  472. package/scripts/test-integration.sh +176 -176
  473. package/scripts/update-test-project.sh +93 -93
  474. package/scripts/upload-four-web.sh +57 -57
  475. package/scripts/verify-install-ps1-bom.js +26 -26
  476. package/src/agent/agent-context.js +259 -259
  477. package/src/agent/agent-profile.js +185 -185
  478. package/src/agent/agent-templates.js +161 -161
  479. package/src/agent/agent-types.js +108 -108
  480. package/src/agent/collaboration-protocol.js +333 -333
  481. package/src/agent/conflict-handler.js +364 -364
  482. package/src/agent/file-permission.js +121 -121
  483. package/src/agent/index.js +38 -38
  484. package/src/agent/permission-audit.js +151 -151
  485. package/src/agent/review-repair-loop.js +270 -270
  486. package/src/agent/tool-permission.js +101 -101
  487. package/src/asset/asset-dependency.js +322 -322
  488. package/src/asset/asset-feedback.js +350 -350
  489. package/src/asset/asset-fork.js +300 -300
  490. package/src/asset/asset-install.js +278 -278
  491. package/src/asset/asset-installer.js +497 -497
  492. package/src/asset/asset-lifecycle.js +324 -324
  493. package/src/asset/asset-manager.js +245 -245
  494. package/src/asset/asset-package-manager.js +349 -349
  495. package/src/asset/asset-package.js +186 -186
  496. package/src/asset/asset-quality.js +262 -262
  497. package/src/asset/asset-registry.js +387 -387
  498. package/src/asset/asset-version.js +293 -293
  499. package/src/asset/index.js +86 -86
  500. package/src/cache/agent-profile-cache.js +59 -59
  501. package/src/cache/asset-cache.js +63 -63
  502. package/src/cache/global-cache.js +61 -61
  503. package/src/cache/manifest-cache.js +30 -30
  504. package/src/check/check-service.js +32 -32
  505. package/src/config/config-layer.js +343 -343
  506. package/src/config/config-loader.js +60 -60
  507. package/src/config/defaults.js +49 -49
  508. package/src/connectors/hub/asset-package.js +72 -72
  509. package/src/connectors/hub/asset-usage-feedback.js +46 -46
  510. package/src/connectors/hub/hub-connector.js +44 -44
  511. package/src/connectors/hub/index.js +21 -21
  512. package/src/connectors/visual/evidence-report.js +49 -49
  513. package/src/connectors/visual/index.js +15 -15
  514. package/src/connectors/visual/queue.js +41 -41
  515. package/src/connectors/visual/run-event.js +81 -81
  516. package/src/connectors/visual/visual-connector.js +77 -77
  517. package/src/context/context-budget.js +59 -59
  518. package/src/context/context-builder.js +285 -285
  519. package/src/context/context-loader.js +116 -116
  520. package/src/context/context-planner.js +158 -158
  521. package/src/context/types.js +96 -96
  522. package/src/contracts/index.js +63 -63
  523. package/src/executor/executor-registry.js +78 -78
  524. package/src/executor/executor-result-parser.js +44 -44
  525. package/src/executor/executor-runner.js +141 -141
  526. package/src/executor/executor-selector.js +139 -139
  527. package/src/executor/executor-timeout.js +36 -36
  528. package/src/executor/providers/base-provider-utils.js +189 -189
  529. package/src/executor/providers/claude-code-executor-provider.js +128 -128
  530. package/src/executor/providers/codex-executor-provider.js +126 -126
  531. package/src/executor/providers/cursor-executor-provider.js +99 -99
  532. package/src/executor/types.js +137 -137
  533. package/src/git/branch-manager.js +71 -71
  534. package/src/git/dirty-checker.js +43 -43
  535. package/src/git/dirty-strategy-handler.js +29 -29
  536. package/src/git/git-command.js +37 -37
  537. package/src/git/git-repository-detector.js +45 -45
  538. package/src/git/multi-repo-worktree-planner.js +88 -88
  539. package/src/git/policy.js +19 -19
  540. package/src/git/strategies/block-dirty-strategy.js +34 -34
  541. package/src/git/strategies/ignore-dirty-strategy.js +33 -33
  542. package/src/git/strategies/patch-snapshot-strategy.js +53 -53
  543. package/src/git/strategies/wip-commit-strategy.js +38 -38
  544. package/src/git/types.js +71 -71
  545. package/src/git/worktree-manager.js +85 -85
  546. package/src/governance/asset-review.js +351 -351
  547. package/src/governance/audit-log.js +368 -368
  548. package/src/governance/gray-release.js +312 -312
  549. package/src/governance/index.js +31 -31
  550. package/src/governance/policy-types.js +56 -56
  551. package/src/governance/rbac-types.js +171 -171
  552. package/src/governance/rbac.js +382 -382
  553. package/src/governance/rollback.js +360 -360
  554. package/src/governance/security-policy.js +354 -354
  555. package/src/hook/hook-config-writer.js +125 -125
  556. package/src/hub/hub-client.js +186 -186
  557. package/src/hub/hub-config.js +39 -39
  558. package/src/hub/project-facts.js +31 -31
  559. package/src/hub/runtime-feedback-reporter.js +55 -55
  560. package/src/ide/adapters/adapter-protocol.js +385 -385
  561. package/src/ide/adapters/claude-adapter.js +419 -419
  562. package/src/ide/adapters/codex-adapter.js +60 -60
  563. package/src/ide/adapters/cursor-adapter.js +484 -484
  564. package/src/ide/adapters/index.js +24 -24
  565. package/src/ide/anchors/markdown-anchor-writer.js +152 -152
  566. package/src/ide/ide-service.js +270 -270
  567. package/src/ide/ide-types.js +94 -94
  568. package/src/ide/links/link-mode-resolver.js +160 -160
  569. package/src/ide/registry/ide-registry-builder.js +165 -165
  570. package/src/incident/incident-writer.js +47 -47
  571. package/src/incident/types.js +22 -22
  572. package/src/init/ide-linker.js +126 -126
  573. package/src/init/ide-pointer-injector.js +75 -75
  574. package/src/init/init-applier.js +197 -197
  575. package/src/init/init-plan.js +294 -294
  576. package/src/init/init-service.js +65 -65
  577. package/src/init/manifest-installer.js +302 -302
  578. package/src/init/types.js +26 -26
  579. package/src/project/config-writer.js +83 -83
  580. package/src/project/context-index-writer.js +82 -82
  581. package/src/project/json-utils.js +72 -72
  582. package/src/project/local-state-writer.js +50 -50
  583. package/src/project/lock-file-writer.js +98 -98
  584. package/src/project/manifest-writer.js +126 -126
  585. package/src/project/policy-config-writer.js +91 -91
  586. package/src/project/project-config-writer.js +74 -74
  587. package/src/project/project-files.js +39 -39
  588. package/src/project/registry-index-writer.js +43 -43
  589. package/src/project/workspace-config-writer.js +63 -63
  590. package/src/run/index.js +11 -11
  591. package/src/run/run-id.js +32 -32
  592. package/src/run/run-service.js +269 -269
  593. package/src/run/run-store.js +80 -80
  594. package/src/scanner/aggregator/detection-aggregator.js +23 -23
  595. package/src/scanner/boundary/boundary-resolver.js +229 -229
  596. package/src/scanner/detectors/detector-registry.js +44 -44
  597. package/src/scanner/detectors/fastapi-detector.js +46 -46
  598. package/src/scanner/detectors/go-detector.js +46 -46
  599. package/src/scanner/detectors/nestjs-detector.js +57 -57
  600. package/src/scanner/detectors/nextjs-detector.js +52 -52
  601. package/src/scanner/detectors/react-vite-detector.js +52 -52
  602. package/src/scanner/detectors/react-webpack-detector.js +57 -57
  603. package/src/scanner/detectors/springboot-detector.js +46 -46
  604. package/src/scanner/detectors/springcloud-detector.js +46 -46
  605. package/src/scanner/detectors/springmvc-detector.js +46 -46
  606. package/src/scanner/detectors/vue-vite-detector.js +52 -52
  607. package/src/scanner/engine.js +72 -72
  608. package/src/scanner/facts/fact-extractor.js +211 -211
  609. package/src/scanner/types.js +30 -30
  610. package/src/security/asset-tamper-checker.js +188 -188
  611. package/src/security/checksum.js +40 -40
  612. package/src/spec/spec-writer.js +302 -302
  613. package/src/state-machine/circuit-breaker.js +112 -112
  614. package/src/state-machine/escape-hatch.js +49 -49
  615. package/src/state-machine/stage-runner.js +281 -281
  616. package/src/state-machine/state-machine.js +24 -24
  617. package/src/state-machine/transition-guard.js +36 -36
  618. package/src/state-machine/types.js +37 -37
  619. package/src/sync/sync-service.js +192 -192
  620. package/src/visual/agent-visual.js +142 -142
  621. package/src/visual/event-gateway.js +357 -357
  622. package/src/visual/event-mapper.js +128 -128
  623. package/src/visual/hook-dashboard.js +216 -216
  624. package/src/visual/index.js +27 -27
  625. package/src/visual/metrics.js +287 -287
  626. package/src/visual/privacy-filter.js +100 -100
  627. package/src/visual/risk-board.js +252 -252
  628. package/src/visual/timeline.js +245 -245
  629. package/src/visual/visual-client.js +94 -94
  630. package/src/visual/visual-config.js +40 -40
  631. package/src/visual/visual-reporter.js +88 -88
package/bin/sync.js CHANGED
@@ -1,1904 +1,1904 @@
1
- #!/usr/bin/env node
2
- const crypto = require('crypto');
3
- const fs = require('fs');
4
- const os = require('os');
5
- const path = require('path');
6
- const { spawnSync } = require('child_process');
7
- const {
8
- formatSupportedProfiles,
9
- getProfileEntries,
10
- readProfilesRegistry,
11
- resolveProfileId,
12
- } = require('./profile-registry');
13
- const {
14
- normalizeSuperpowersManifest,
15
- buildSuperpowersState,
16
- writeSuperpowersState,
17
- shouldExposeSkillToIde,
18
- SUPERPOWERS_STATE_REL_PATH,
19
- upsertManagedAgentsBlock,
20
- } = require('./superpowers');
21
- const {
22
- normalizeVisualBridgeManifest,
23
- buildVisualBridgeState,
24
- writeVisualBridgeState,
25
- readVisualBridgeState,
26
- VISUAL_BRIDGE_STATE_REL_PATH,
27
- } = require('./visual-bridge-config');
28
- const { readRenderedCommandTemplate } = require('./command-template-renderer');
29
-
30
- const SUPPORTED_IDES = ['cursor', 'claude', 'codex', 'opencode', 'trae', 'qoder'];
31
- const DEFAULT_IDES = ['cursor', 'claude'];
32
- const ALL_IDES = [...SUPPORTED_IDES];
33
- const DEFAULT_REMOTE_MANIFEST_TIMEOUT_MS = 15000;
34
-
35
- function printUsage(profilesRegistry = null) {
36
- const profileHint = profilesRegistry
37
- ? formatSupportedProfiles(profilesRegistry)
38
- : 'see .agents/registry/profiles.json';
39
- console.log(`Usage:
40
- ai-spec-auto sync [target] --manifest <manifest.json|url> [options]
41
-
42
- Options:
43
- --manifest <file|url> Local manifest JSON file path or remote manifest URL
44
- --profile <profile> Override profile from manifest (${profileHint})
45
- --ide <preset> Override ides (default | all | cursor | claude | codex | qoder | comma-separated)
46
- --superpowers Force enable superpowers(超能力桥接)
47
- --no-superpowers Force disable superpowers(超能力桥接)
48
- --hub-origin <origin> Hub origin for supplement fetch when manifest is local
49
- --no-hub-fetch Disable Hub supplement fetch for missing assets
50
- --out <file> Write the normalized manifest to a file
51
- --json Print JSON output only
52
- --dry-run Resolve only, do not write files
53
- --force Reserved for future conflict handling
54
- --help Show this help
55
- `);
56
- }
57
-
58
- function parseArgs(argv) {
59
- const args = [...argv];
60
- const options = {
61
- target: '.',
62
- json: false,
63
- pretty: true,
64
- dryRun: false,
65
- force: false,
66
- hubFetch: true,
67
- out: '',
68
- };
69
-
70
- while (args.length > 0) {
71
- const arg = args.shift();
72
- if (!arg.startsWith('-') && options.target === '.') {
73
- options.target = arg;
74
- continue;
75
- }
76
-
77
- switch (arg) {
78
- case '--manifest':
79
- options.manifest = requireArg(arg, args);
80
- break;
81
- case '--profile':
82
- options.profile = requireArg(arg, args);
83
- break;
84
- case '--ide':
85
- options.ide = requireArg(arg, args);
86
- break;
87
- case '--superpowers':
88
- options.superpowers = true;
89
- break;
90
- case '--no-superpowers':
91
- options.superpowers = false;
92
- break;
93
- case '--json':
94
- options.json = true;
95
- options.pretty = false;
96
- break;
97
- case '--hub-origin':
98
- options.hubOrigin = requireArg(arg, args);
99
- break;
100
- case '--no-hub-fetch':
101
- options.hubFetch = false;
102
- break;
103
- case '--out':
104
- options.out = requireArg(arg, args);
105
- break;
106
- case '--dry-run':
107
- options.dryRun = true;
108
- break;
109
- case '--force':
110
- options.force = true;
111
- break;
112
- case '--pretty':
113
- options.pretty = true;
114
- options.json = false;
115
- break;
116
- case '--help':
117
- case '-h':
118
- options.help = true;
119
- break;
120
- default:
121
- throw new Error(`Unknown argument: ${arg}`);
122
- }
123
- }
124
-
125
- return options;
126
- }
127
-
128
- function requireArg(flag, args) {
129
- const next = args.shift();
130
- if (!next || next.startsWith('--')) {
131
- throw new Error(`选项 ${flag} 需要一个参数值`);
132
- }
133
- return next;
134
- }
135
-
136
- function isHttpUrl(value) {
137
- return /^https?:\/\//i.test(String(value || ''));
138
- }
139
-
140
- function getSourceDir() {
141
- if (process.env.ENGINEERED_SPEC_LOCAL) {
142
- return process.env.ENGINEERED_SPEC_LOCAL;
143
- }
144
- return path.join(__dirname, '..');
145
- }
146
-
147
- function toPosix(value) {
148
- return value.split(path.sep).join('/');
149
- }
150
-
151
- function targetRel(targetDir, filePath) {
152
- return toPosix(path.relative(targetDir, filePath));
153
- }
154
-
155
- function ensureDir(dirPath) {
156
- fs.mkdirSync(dirPath, { recursive: true });
157
- }
158
-
159
- function readJsonFile(filePath, label) {
160
- const raw = fs.readFileSync(filePath, 'utf8');
161
- try {
162
- return JSON.parse(raw);
163
- } catch (error) {
164
- throw new Error(`${label} is not valid JSON: ${filePath}`);
165
- }
166
- }
167
-
168
- function readRegistryJson(sourceDir, fileName, rootKey) {
169
- const filePath = path.join(sourceDir, '.agents/registry', fileName);
170
- if (!fs.existsSync(filePath)) {
171
- throw new Error(`Registry file not found: ${filePath}`);
172
- }
173
- const data = readJsonFile(filePath, `Registry ${fileName}`);
174
- if (!data || typeof data !== 'object' || !data[rootKey] || typeof data[rootKey] !== 'object') {
175
- throw new Error(`Registry ${fileName} is missing root key "${rootKey}"`);
176
- }
177
- return data[rootKey];
178
- }
179
-
180
- function normalizeList(value) {
181
- if (!value) return [];
182
- if (Array.isArray(value)) {
183
- return [...new Set(value.map((item) => String(item).trim()).filter(Boolean))];
184
- }
185
- return [...new Set(String(value).split(',').map((item) => item.trim()).filter(Boolean))];
186
- }
187
-
188
- function normalizeIdes(value) {
189
- const raw = typeof value === 'string' ? value.trim() : value;
190
- if (!raw || (Array.isArray(raw) && raw.length === 0)) {
191
- return [...DEFAULT_IDES];
192
- }
193
- if (raw === 'default') {
194
- return [...DEFAULT_IDES];
195
- }
196
- if (raw === 'all') {
197
- return [...ALL_IDES];
198
- }
199
-
200
- const items = normalizeList(raw);
201
- const unknown = items.filter((item) => !SUPPORTED_IDES.includes(item));
202
- if (unknown.length > 0) {
203
- throw new Error(`Unsupported ides: ${unknown.join(', ')}`);
204
- }
205
- return items;
206
- }
207
-
208
- function parseLocalPreferences(value) {
209
- if (!value || typeof value !== 'object' || Array.isArray(value)) {
210
- return null;
211
- }
212
- if (!Object.prototype.hasOwnProperty.call(value, 'project_init')) {
213
- return null;
214
- }
215
- const projectInit = value.project_init;
216
- if (!projectInit || typeof projectInit !== 'object' || Array.isArray(projectInit)) {
217
- return { project_init: { custom_rules: [] } };
218
- }
219
- return {
220
- project_init: {
221
- custom_rules: normalizeList(projectInit.custom_rules),
222
- },
223
- };
224
- }
225
-
226
- function walkFiles(rootDir, predicate) {
227
- const results = [];
228
- if (!fs.existsSync(rootDir)) {
229
- return results;
230
- }
231
- const stack = [rootDir];
232
- while (stack.length > 0) {
233
- const current = stack.pop();
234
- const entries = fs.readdirSync(current, { withFileTypes: true });
235
- for (const entry of entries) {
236
- if (entry.name === '.DS_Store') {
237
- continue;
238
- }
239
- const fullPath = path.join(current, entry.name);
240
- if (entry.isDirectory()) {
241
- stack.push(fullPath);
242
- continue;
243
- }
244
- if (!predicate || predicate(fullPath, entry)) {
245
- results.push(fullPath);
246
- }
247
- }
248
- }
249
- return results.sort();
250
- }
251
-
252
- function readSkillCatalog(sourceDir, metadataMap = new Map()) {
253
- const skillRegistry = readRegistryJson(sourceDir, 'skills.json', 'skills');
254
- const profilesRegistry = readProfilesRegistry(sourceDir);
255
- const catalog = {
256
- common: new Map(),
257
- profiles: Object.fromEntries(
258
- Object.keys(getProfileEntries(profilesRegistry)).map((profileId) => [profileId, new Map()])
259
- ),
260
- domains: new Map(),
261
- };
262
- const profileDirMap = Object.entries(getProfileEntries(profilesRegistry)).map(([profileId, entry]) => ({
263
- profileId,
264
- skillsDir: String(entry?.skills_dir || '').trim(),
265
- }));
266
-
267
- const skillsRoot = path.join(sourceDir, '.agents/skills');
268
- const skillFiles = walkFiles(skillsRoot, (filePath) => filePath.endsWith('/SKILL.md'));
269
- for (const filePath of skillFiles) {
270
- const rel = toPosix(path.relative(sourceDir, filePath));
271
- const dirRel = toPosix(path.dirname(rel));
272
- const id = path.basename(path.dirname(filePath));
273
- const entry = {
274
- id,
275
- sourceDirRel: dirRel,
276
- sourceFileRel: rel,
277
- domains: normalizeList(skillRegistry[id]?.domains),
278
- sourceRoot: sourceDir,
279
- sourceType: metadataMap.get(id)?.__sourceType || 'local',
280
- sourceRef: metadataMap.get(id)?.__sourceRef || `local://${dirRel}`,
281
- sourceOrigin: metadataMap.get(id)?.__sourceOrigin || null,
282
- version: metadataMap.get(id)?.__version || 'workspace',
283
- hubSlug: metadataMap.get(id)?.__hubSlug || null,
284
- };
285
-
286
- if (rel.startsWith('.agents/skills/common/')) {
287
- catalog.common.set(id, entry);
288
- } else if (rel.startsWith('.agents/skills/domains/')) {
289
- catalog.domains.set(id, entry);
290
- } else {
291
- const matchedProfile = profileDirMap.find((item) => item.skillsDir && rel.startsWith(`${item.skillsDir}/`));
292
- if (matchedProfile) {
293
- catalog.profiles[matchedProfile.profileId].set(id, entry);
294
- }
295
- }
296
- }
297
-
298
- return catalog;
299
- }
300
-
301
- function loadSyncRegistry(sourceDir) {
302
- const roles = readJsonFile(path.join(sourceDir, '.agents/registry/roles.json'), 'Registry roles.json');
303
- const flowsPath = path.join(sourceDir, '.agents/registry/flows.json');
304
- const flows = fs.existsSync(flowsPath)
305
- ? readJsonFile(flowsPath, 'Registry flows.json')
306
- : { version: 1, support_files: [], flows: {} };
307
- const scenarioPackagesPath = path.join(sourceDir, '.agents/registry/scenario-packages.json');
308
- const scenarioPackages = fs.existsSync(scenarioPackagesPath)
309
- ? readJsonFile(scenarioPackagesPath, 'Registry scenario-packages.json')
310
- : { version: 1, scenario_packages: {} };
311
- return {
312
- roles: {
313
- ...roles,
314
- __sourceRoot: sourceDir,
315
- },
316
- rules: buildRuleRegistryForSource(readRegistryJson(sourceDir, 'rules.json', 'rules'), sourceDir, new Map()),
317
- flows: {
318
- ...flows,
319
- __sourceRoot: sourceDir,
320
- },
321
- scenarioPackages: {
322
- ...scenarioPackages,
323
- __sourceRoot: sourceDir,
324
- },
325
- };
326
- }
327
-
328
- function readRoleCatalog(roleRegistry) {
329
- const catalog = new Map();
330
- for (const [id, entry] of Object.entries(roleRegistry.roles || {})) {
331
- if (!entry || typeof entry !== 'object' || !entry.source) {
332
- continue;
333
- }
334
- catalog.set(id, {
335
- id,
336
- name: entry.name || id,
337
- status: entry.status || 'unknown',
338
- domains: normalizeList(entry.domains),
339
- sourceRel: entry.source,
340
- sourceRoot: roleRegistry.__sourceRoot,
341
- sourceType: entry.__sourceType || 'local',
342
- sourceRef: entry.__sourceRef || `local://${entry.source}`,
343
- sourceOrigin: entry.__sourceOrigin || null,
344
- version: entry.__version || 'workspace',
345
- hubSlug: entry.__hubSlug || null,
346
- });
347
- }
348
- return catalog;
349
- }
350
-
351
- function readRuleCatalog(ruleRegistry) {
352
- const catalog = new Map();
353
- for (const [id, entry] of Object.entries(ruleRegistry || {})) {
354
- if (!entry || typeof entry !== 'object') {
355
- continue;
356
- }
357
- if (!entry.source && !entry.sourceByProfile) {
358
- continue;
359
- }
360
- catalog.set(id, {
361
- id,
362
- source: entry.source || null,
363
- sourceByProfile: entry.sourceByProfile || null,
364
- domains: normalizeList(entry.domains),
365
- sourceRoot: entry.__sourceRoot,
366
- sourceType: entry.__sourceType || 'local',
367
- sourceRef: entry.__sourceRef || (entry.source ? `local://${entry.source}` : 'local://profiled-rule'),
368
- sourceOrigin: entry.__sourceOrigin || null,
369
- version: entry.__version || 'workspace',
370
- hubSlug: entry.__hubSlug || null,
371
- });
372
- }
373
- return catalog;
374
- }
375
-
376
- function readFlowCatalog(flowRegistry) {
377
- const catalog = new Map();
378
- for (const [id, entry] of Object.entries(flowRegistry.flows || {})) {
379
- if (!entry || typeof entry !== 'object' || !entry.source) {
380
- continue;
381
- }
382
- catalog.set(id, {
383
- id,
384
- name: entry.name || id,
385
- status: entry.status || 'unknown',
386
- sourceRel: entry.source,
387
- sourceRoot: flowRegistry.__sourceRoot,
388
- sourceType: entry.__sourceType || 'local',
389
- sourceRef: entry.__sourceRef || `local://${entry.source}`,
390
- sourceOrigin: entry.__sourceOrigin || null,
391
- version: entry.__version || 'workspace',
392
- });
393
- }
394
- return catalog;
395
- }
396
-
397
- function resolveSkill(id, profile, catalog) {
398
- return (
399
- catalog.profiles[profile]?.get(id) ||
400
- catalog.common.get(id) ||
401
- catalog.domains.get(id) ||
402
- null
403
- );
404
- }
405
-
406
- function normalizeLegacyProfileScopedSkillId(skillId, profile, catalog, warnings) {
407
- const normalized = String(skillId || '').trim();
408
- if (!normalized) {
409
- return normalized;
410
- }
411
-
412
- const match = normalized.match(/^(.*?)-(react|vue)$/);
413
- if (!match) {
414
- return normalized;
415
- }
416
-
417
- const [, baseId, scopedProfile] = match;
418
- if (!baseId) {
419
- return normalized;
420
- }
421
-
422
- const resolvedBase = resolveSkill(baseId, profile, catalog);
423
- if (!resolvedBase) {
424
- return normalized;
425
- }
426
-
427
- if (scopedProfile !== profile) {
428
- warnings.push(`Skill id "${normalized}" uses legacy profile suffix "${scopedProfile}" but target profile is "${profile}"; normalized to "${baseId}".`);
429
- } else {
430
- warnings.push(`Skill id "${normalized}" uses legacy profile suffix; normalized to "${baseId}".`);
431
- }
432
- return baseId;
433
- }
434
-
435
- const LEGACY_RULE_ID_ALIASES = {
436
- 'react-project-overview': 'project-overview',
437
- 'vue-project-overview': 'project-overview',
438
- 'react-project-structure': 'project-structure',
439
- 'vue-project-structure': 'project-structure',
440
- 'react-component-guidelines': 'component-standard',
441
- 'vue-component-guidelines': 'component-standard',
442
- 'react-routing-guidelines': 'route-standard',
443
- 'vue-routing-guidelines': 'route-standard',
444
- 'react-state-management': 'store-standard',
445
- 'vue-state-management': 'store-standard',
446
- 'react-style-guidelines': 'style-standard',
447
- 'vue-style-guidelines': 'style-standard',
448
- 'api-guidelines': 'api-standard',
449
- 'coding-guidelines': 'coding-standard',
450
- 'general-constraints': 'generic-constraints',
451
- 'documentation-guidelines': 'doc-standard',
452
- 'testing-guidelines': 'test-standard',
453
- 'superpowers-execution-guidelines': 'superpowers-standard',
454
- 'code-formatting-and-checks': 'format-check-standard',
455
- 'audit-reporting-guidelines': 'audit-report-standard',
456
- };
457
-
458
- function normalizeLegacyRuleId(ruleId, profile, ruleRegistry, warnings) {
459
- const normalized = String(ruleId || '').trim();
460
- if (!normalized) {
461
- return normalized;
462
- }
463
-
464
- const canonicalId = LEGACY_RULE_ID_ALIASES[normalized];
465
- if (!canonicalId) {
466
- return normalized;
467
- }
468
-
469
- const resolvedCanonical = resolveRule(canonicalId, profile, ruleRegistry);
470
- if (!resolvedCanonical) {
471
- return normalized;
472
- }
473
-
474
- warnings.push(`Rule id "${normalized}" uses legacy manifest alias; normalized to "${canonicalId}".`);
475
- return canonicalId;
476
- }
477
-
478
- function resolveRule(id, profile, ruleRegistry) {
479
- const entry = ruleRegistry.get(id);
480
- if (!entry) {
481
- return null;
482
- }
483
- if (entry.sourceByProfile) {
484
- const source = entry.sourceByProfile[profile];
485
- if (!source) {
486
- return null;
487
- }
488
- return { ...entry, id, sourceRel: source, domains: entry.domains || [] };
489
- }
490
- return { ...entry, id, sourceRel: entry.source, domains: entry.domains || [] };
491
- }
492
-
493
- function normalizeManifest(rawManifest, existingManifest, options, profilesRegistry) {
494
- const rawProfile = options.profile || rawManifest?.profile || existingManifest?.profile || null;
495
- const resolvedProfile = resolveProfileId(profilesRegistry, rawProfile);
496
- const rawLocalPreferences = parseLocalPreferences(rawManifest?.local_preferences);
497
- const existingLocalPreferences = parseLocalPreferences(existingManifest?.local_preferences);
498
- const normalizedSuperpowers = normalizeSuperpowersManifest(
499
- options.superpowers === undefined
500
- ? rawManifest?.superpowers
501
- : { ...(rawManifest?.superpowers || existingManifest?.superpowers || {}), enabled: options.superpowers },
502
- existingManifest?.superpowers,
503
- );
504
- const normalizedVisualBridge = normalizeVisualBridgeManifest(
505
- rawManifest?.visual_bridge,
506
- existingManifest?.visual_bridge,
507
- );
508
- const manifest = {
509
- schema_version: Number(rawManifest?.schema_version || existingManifest?.schema_version || 1),
510
- manifest_type: rawManifest?.manifest_type || existingManifest?.manifest_type || 'hub-install',
511
- name: rawManifest?.name || existingManifest?.name || null,
512
- description: rawManifest?.description || existingManifest?.description || null,
513
- version: rawManifest?.version || existingManifest?.version || null,
514
- profile: resolvedProfile,
515
- ides: normalizeIdes(options.ide || rawManifest?.ides || existingManifest?.ides || 'default'),
516
- scenario_packages: normalizeList(rawManifest?.scenario_packages || existingManifest?.scenario_packages),
517
- roles: normalizeList(rawManifest?.roles || existingManifest?.roles),
518
- skills: normalizeList(rawManifest?.skills || existingManifest?.skills),
519
- rules: normalizeList(rawManifest?.rules || existingManifest?.rules),
520
- entry_role: rawManifest?.entry_role || existingManifest?.entry_role || null,
521
- tags: normalizeList(rawManifest?.tags || existingManifest?.tags),
522
- constraints: rawManifest?.constraints || existingManifest?.constraints || null,
523
- notes: normalizeList(rawManifest?.notes || existingManifest?.notes),
524
- sources: Array.isArray(rawManifest?.sources) ? rawManifest.sources : Array.isArray(existingManifest?.sources) ? existingManifest.sources : [],
525
- };
526
- if (normalizedSuperpowers) {
527
- manifest.superpowers = normalizedSuperpowers;
528
- }
529
- if (normalizedVisualBridge) {
530
- manifest.visual_bridge = normalizedVisualBridge;
531
- }
532
- const localPreferences = rawLocalPreferences !== null ? rawLocalPreferences : existingLocalPreferences;
533
- if (localPreferences) {
534
- manifest.local_preferences = localPreferences;
535
- }
536
-
537
- if (!manifest.profile) {
538
- if (!rawProfile) {
539
- throw new Error('Manifest is missing profile(技术栈)');
540
- }
541
- throw new Error(`Unsupported profile: ${rawProfile}. Supported profiles: ${formatSupportedProfiles(profilesRegistry)}`);
542
- }
543
-
544
- return manifest;
545
- }
546
-
547
- async function loadManifestInput(manifestInput, timeoutMs = DEFAULT_REMOTE_MANIFEST_TIMEOUT_MS) {
548
- if (isHttpUrl(manifestInput)) {
549
- const controller = new AbortController();
550
- const timer = setTimeout(() => controller.abort(), timeoutMs);
551
- try {
552
- const response = await fetch(manifestInput, {
553
- signal: controller.signal,
554
- headers: {
555
- accept: 'application/json',
556
- },
557
- });
558
- if (!response.ok) {
559
- throw new Error(`Remote manifest request failed with status ${response.status} ${response.statusText}: ${manifestInput}`);
560
- }
561
- const rawText = await response.text();
562
- try {
563
- return {
564
- manifestSource: manifestInput,
565
- rawManifest: JSON.parse(rawText),
566
- };
567
- } catch (error) {
568
- throw new Error(`Remote manifest is not valid JSON: ${manifestInput}`);
569
- }
570
- } catch (error) {
571
- if (error.name === 'AbortError') {
572
- throw new Error(`Remote manifest request timed out after ${timeoutMs}ms: ${manifestInput}`);
573
- }
574
- if (error.message && error.message.startsWith('Remote manifest')) {
575
- throw error;
576
- }
577
- throw new Error(`Failed to fetch remote manifest: ${manifestInput} (${error.message})`);
578
- } finally {
579
- clearTimeout(timer);
580
- }
581
- }
582
-
583
- const manifestPath = path.resolve(manifestInput);
584
- if (!fs.existsSync(manifestPath)) {
585
- throw new Error(`Manifest file not found: ${manifestPath}`);
586
- }
587
-
588
- return {
589
- manifestSource: manifestPath,
590
- rawManifest: readJsonFile(manifestPath, 'Manifest'),
591
- };
592
- }
593
-
594
- function sha256Json(value) {
595
- return `sha256:${crypto.createHash('sha256').update(JSON.stringify(value)).digest('hex')}`;
596
- }
597
-
598
- function hashDirectory(dirPath) {
599
- if (!fs.existsSync(dirPath)) {
600
- return null;
601
- }
602
- const hash = crypto.createHash('sha256');
603
- const files = walkFiles(dirPath, () => true);
604
- for (const filePath of files) {
605
- const rel = toPosix(path.relative(dirPath, filePath));
606
- hash.update(rel);
607
- hash.update(fs.readFileSync(filePath));
608
- }
609
- return hash.digest('hex');
610
- }
611
-
612
- function unique(values) {
613
- return [...new Set(values.filter(Boolean))];
614
- }
615
-
616
- function resolveManifest(manifest, catalogs, options = {}) {
617
- const allowMissing = options.allowMissing === true;
618
- const warnings = [];
619
- const roleIds = new Set(manifest.roles);
620
- const skillIds = new Set(manifest.skills);
621
- const ruleIds = new Set(manifest.rules);
622
- for (const scenarioId of manifest.scenario_packages || []) {
623
- const scenarioEntry = catalogs.scenarioPackages?.get(scenarioId);
624
- if (!scenarioEntry) {
625
- continue;
626
- }
627
- for (const roleId of scenarioEntry.roles || []) roleIds.add(roleId);
628
- for (const skillId of scenarioEntry.skills || []) skillIds.add(skillId);
629
- for (const ruleId of scenarioEntry.rules || []) ruleIds.add(ruleId);
630
- if (manifest.superpowers?.enabled && ['frontend-basic', 'bugfix-to-verification'].includes(scenarioId)) {
631
- ruleIds.add('superpowers-standard');
632
- }
633
- }
634
- const domains = new Set();
635
- const missing = {
636
- roles: [],
637
- skills: [],
638
- rules: [],
639
- };
640
-
641
- const resolvedRoles = [];
642
- for (const roleId of roleIds) {
643
- const entry = catalogs.roles.get(roleId);
644
- if (!entry) {
645
- if (allowMissing) {
646
- missing.roles.push(roleId);
647
- continue;
648
- }
649
- throw new Error(`Unknown role(专家角色) id: ${roleId}`);
650
- }
651
- resolvedRoles.push(entry);
652
- for (const domain of entry.domains || []) domains.add(domain);
653
- }
654
-
655
- const resolvedSkills = [];
656
- for (const skillId of skillIds) {
657
- const normalizedSkillId = normalizeLegacyProfileScopedSkillId(skillId, manifest.profile, catalogs.skills, warnings);
658
- const entry = resolveSkill(normalizedSkillId, manifest.profile, catalogs.skills);
659
- if (!entry) {
660
- if (allowMissing) {
661
- missing.skills.push(skillId);
662
- continue;
663
- }
664
- throw new Error(`Unknown skill(技能) id for profile "${manifest.profile}": ${skillId}`);
665
- }
666
- resolvedSkills.push(entry);
667
- for (const domain of entry.domains || []) domains.add(domain);
668
- }
669
-
670
- const resolvedRules = [];
671
- for (const ruleId of ruleIds) {
672
- const normalizedRuleId = normalizeLegacyRuleId(ruleId, manifest.profile, catalogs.rules, warnings);
673
- const entry = resolveRule(normalizedRuleId, manifest.profile, catalogs.rules);
674
- if (!entry) {
675
- if (allowMissing) {
676
- missing.rules.push(ruleId);
677
- continue;
678
- }
679
- throw new Error(`Unknown rule(规则) id for profile "${manifest.profile}": ${ruleId}`);
680
- }
681
- resolvedRules.push(entry);
682
- for (const domain of entry.domains || []) domains.add(domain);
683
- }
684
-
685
- const installedFlows = [...catalogs.flows.values()]
686
- .filter((entry) => entry.status === 'active')
687
- .map((entry) => entry.id);
688
-
689
- if (!manifest.entry_role) {
690
- manifest.entry_role = resolvedRoles.some((entry) => entry.id === 'task-orchestrator')
691
- ? 'task-orchestrator'
692
- : resolvedRoles[0]?.id || null;
693
- }
694
-
695
- if (!allowMissing && manifest.entry_role && !resolvedRoles.some((entry) => entry.id === manifest.entry_role)) {
696
- throw new Error(`entry_role(默认入口角色) is not included in resolved roles: ${manifest.entry_role}`);
697
- }
698
-
699
- return {
700
- warnings,
701
- missing,
702
- resolved: {
703
- domains: unique([...domains]),
704
- installed_flows: installedFlows,
705
- roles: resolvedRoles,
706
- skills: resolvedSkills,
707
- rules: resolvedRules,
708
- },
709
- };
710
- }
711
-
712
- function hasMissingAssets(resolvedResult) {
713
- return resolvedResult.missing.roles.length > 0 ||
714
- resolvedResult.missing.skills.length > 0 ||
715
- resolvedResult.missing.rules.length > 0;
716
- }
717
-
718
- function normalizeOrigin(value) {
719
- if (!value) return null;
720
- try {
721
- return new URL(String(value)).origin;
722
- } catch (error) {
723
- throw new Error(`Invalid hub origin: ${value}`);
724
- }
725
- }
726
-
727
- function resolveHubOrigin(options, manifestSource) {
728
- if (options.hubFetch === false) {
729
- return null;
730
- }
731
- if (options.hubOrigin) {
732
- return normalizeOrigin(options.hubOrigin);
733
- }
734
- if (isHttpUrl(manifestSource)) {
735
- return new URL(manifestSource).origin;
736
- }
737
- return null;
738
- }
739
-
740
- function mergeRegistryEntries(localEntries, remoteEntries) {
741
- return {
742
- ...(remoteEntries || {}),
743
- ...(localEntries || {}),
744
- };
745
- }
746
-
747
- function mergeSupportFiles(localFiles, remoteFiles) {
748
- return unique([...(remoteFiles || []), ...(localFiles || [])]);
749
- }
750
-
751
- function buildRoleRegistryForSource(rawRegistry, sourceRoot, metadataMap) {
752
- return {
753
- version: rawRegistry.version || 1,
754
- support_files: [...(rawRegistry.support_files || [])],
755
- __sourceRoot: sourceRoot,
756
- roles: Object.fromEntries(
757
- Object.entries(rawRegistry.roles || {}).map(([id, entry]) => {
758
- const meta = metadataMap.get(id);
759
- return [id, {
760
- ...entry,
761
- ...(meta || {}),
762
- }];
763
- }),
764
- ),
765
- };
766
- }
767
-
768
- function buildRuleRegistryForSource(rawRegistry, sourceRoot, metadataMap) {
769
- return Object.fromEntries(
770
- Object.entries(rawRegistry || {}).map(([id, entry]) => {
771
- const meta = metadataMap.get(id);
772
- return [id, {
773
- ...entry,
774
- __sourceRoot: sourceRoot,
775
- ...(meta || {}),
776
- }];
777
- }),
778
- );
779
- }
780
-
781
- function buildFlowRegistryForSource(rawRegistry, sourceRoot) {
782
- return {
783
- version: rawRegistry.version || 1,
784
- support_files: [...(rawRegistry.support_files || [])],
785
- __sourceRoot: sourceRoot,
786
- flows: Object.fromEntries(
787
- Object.entries(rawRegistry.flows || {}).map(([id, entry]) => [id, {
788
- ...entry,
789
- }]),
790
- ),
791
- };
792
- }
793
-
794
- function createAssetMetadataMap(items, kind, requestUrl, origin) {
795
- const map = new Map();
796
- for (const item of items || []) {
797
- const id = String(item.registryId || '').trim();
798
- if (!id) continue;
799
- map.set(id, {
800
- __sourceType: 'hub',
801
- __sourceRef: `${requestUrl}#${kind}:${id}`,
802
- __sourceOrigin: origin,
803
- __version: String(item.version || 'published'),
804
- __hubSlug: item.hubSlug || null,
805
- });
806
- }
807
- return map;
808
- }
809
-
810
- function mergeMapCatalog(localMap, remoteMap) {
811
- return new Map([
812
- ...remoteMap.entries(),
813
- ...localMap.entries(),
814
- ]);
815
- }
816
-
817
- function mergeSkillCatalog(localCatalog, remoteCatalog) {
818
- const profiles = {};
819
- const profileIds = unique([
820
- ...Object.keys(localCatalog.profiles || {}),
821
- ...Object.keys(remoteCatalog.profiles || {}),
822
- ]);
823
- for (const profileId of profileIds) {
824
- profiles[profileId] = mergeMapCatalog(
825
- localCatalog.profiles?.[profileId] || new Map(),
826
- remoteCatalog.profiles?.[profileId] || new Map(),
827
- );
828
- }
829
-
830
- return {
831
- common: mergeMapCatalog(localCatalog.common, remoteCatalog.common),
832
- profiles,
833
- domains: mergeMapCatalog(localCatalog.domains, remoteCatalog.domains),
834
- };
835
- }
836
-
837
- function extractZipArchive(zipPath, destDir) {
838
- let result;
839
- if (process.platform === 'win32') {
840
- result = spawnSync('powershell', [
841
- '-NoProfile',
842
- '-Command',
843
- `Expand-Archive -LiteralPath '${zipPath.replace(/'/g, "''")}' -DestinationPath '${destDir.replace(/'/g, "''")}' -Force`,
844
- ], {
845
- encoding: 'utf8',
846
- });
847
- } else {
848
- result = spawnSync('unzip', ['-qq', zipPath, '-d', destDir], {
849
- encoding: 'utf8',
850
- });
851
- }
852
-
853
- if (result.error) {
854
- throw new Error(`Failed to extract Hub supplement zip: ${result.error.message}`);
855
- }
856
- if (result.status !== 0) {
857
- throw new Error(`Failed to extract Hub supplement zip: ${(result.stderr || result.stdout || '').trim() || 'unknown unzip error'}`);
858
- }
859
- }
860
-
861
- function readOptionalJson(filePath, label) {
862
- if (!fs.existsSync(filePath)) {
863
- return null;
864
- }
865
- return readJsonFile(filePath, label);
866
- }
867
-
868
- async function fetchHubSupplement(origin, manifest, missing, timeoutMs) {
869
- const requestUrl = `${origin.replace(/\/$/, '')}/api/install/supplement-export`;
870
- const payload = {
871
- profile: manifest.profile,
872
- ides: manifest.ides,
873
- roles: missing.roles,
874
- skills: missing.skills,
875
- rules: missing.rules,
876
- };
877
- const controller = new AbortController();
878
- const timer = setTimeout(() => controller.abort(), timeoutMs);
879
- try {
880
- const response = await fetch(requestUrl, {
881
- method: 'POST',
882
- signal: controller.signal,
883
- headers: {
884
- accept: 'application/zip',
885
- 'content-type': 'application/json',
886
- },
887
- body: JSON.stringify(payload),
888
- });
889
- if (!response.ok) {
890
- const text = await response.text().catch(() => '');
891
- throw new Error(`Hub supplement request failed with status ${response.status} ${response.statusText}${text ? `: ${text}` : ''}`);
892
- }
893
-
894
- const bytes = Buffer.from(await response.arrayBuffer());
895
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ai-spec-hub-supplement-'));
896
- const zipPath = path.join(tempDir, 'supplement.zip');
897
- const extractDir = path.join(tempDir, 'bundle');
898
- ensureDir(extractDir);
899
- fs.writeFileSync(zipPath, bytes);
900
- extractZipArchive(zipPath, extractDir);
901
-
902
- const report = readOptionalJson(path.join(extractDir, 'export-report.json'), 'Hub supplement report') || {
903
- warnings: [],
904
- assets: { roles: [], skills: [], rules: [] },
905
- };
906
- return {
907
- origin,
908
- requestUrl,
909
- tempDir,
910
- extractDir,
911
- report,
912
- };
913
- } catch (error) {
914
- if (error.name === 'AbortError') {
915
- throw new Error(`Hub supplement request timed out after ${timeoutMs}ms: ${requestUrl}`);
916
- }
917
- if (error.message && error.message.startsWith('Hub supplement')) {
918
- throw error;
919
- }
920
- throw new Error(`Failed to fetch Hub supplement: ${requestUrl} (${error.message})`);
921
- } finally {
922
- clearTimeout(timer);
923
- }
924
- }
925
-
926
- function buildSupplementState(supplement) {
927
- const supplementRoot = supplement.extractDir;
928
- const supplementRegistry = loadSyncRegistry(supplementRoot);
929
- const roleMetadata = createAssetMetadataMap(supplement.report.assets?.roles, 'role', supplement.requestUrl, supplement.origin);
930
- const skillMetadata = createAssetMetadataMap(supplement.report.assets?.skills, 'skill', supplement.requestUrl, supplement.origin);
931
- const ruleMetadata = createAssetMetadataMap(supplement.report.assets?.rules, 'rule', supplement.requestUrl, supplement.origin);
932
-
933
- const roleRegistry = buildRoleRegistryForSource(supplementRegistry.roles, supplementRoot, roleMetadata);
934
- const flowRegistry = buildFlowRegistryForSource(supplementRegistry.flows, supplementRoot);
935
- const ruleRegistry = buildRuleRegistryForSource(supplementRegistry.rules, supplementRoot, ruleMetadata);
936
-
937
- return {
938
- registry: {
939
- roles: roleRegistry,
940
- rules: ruleRegistry,
941
- flows: flowRegistry,
942
- },
943
- catalogs: {
944
- roles: readRoleCatalog(roleRegistry),
945
- skills: readSkillCatalog(supplementRoot, skillMetadata),
946
- rules: readRuleCatalog(ruleRegistry),
947
- flows: readFlowCatalog(flowRegistry),
948
- },
949
- warnings: normalizeList(supplement.report.warnings),
950
- };
951
- }
952
-
953
- function mergePreparedState(prepared, supplement, supplementState) {
954
- const mergedRoleRegistry = {
955
- ...prepared.registry.roles,
956
- support_files: mergeSupportFiles(prepared.registry.roles.support_files, supplementState.registry.roles.support_files),
957
- roles: mergeRegistryEntries(prepared.registry.roles.roles, supplementState.registry.roles.roles),
958
- };
959
- const mergedRuleRegistry = mergeRegistryEntries(prepared.registry.rules, supplementState.registry.rules);
960
- const mergedFlowRegistry = {
961
- ...prepared.registry.flows,
962
- support_files: mergeSupportFiles(prepared.registry.flows.support_files, supplementState.registry.flows.support_files),
963
- flows: mergeRegistryEntries(prepared.registry.flows.flows, supplementState.registry.flows.flows),
964
- };
965
-
966
- return {
967
- ...prepared,
968
- registry: {
969
- roles: mergedRoleRegistry,
970
- rules: mergedRuleRegistry,
971
- flows: mergedFlowRegistry,
972
- },
973
- catalogs: {
974
- roles: mergeMapCatalog(prepared.catalogs.roles, supplementState.catalogs.roles),
975
- skills: mergeSkillCatalog(prepared.catalogs.skills, supplementState.catalogs.skills),
976
- rules: mergeMapCatalog(prepared.catalogs.rules, supplementState.catalogs.rules),
977
- flows: prepared.catalogs.flows,
978
- },
979
- supplements: [...(prepared.supplements || []), supplement],
980
- };
981
- }
982
-
983
- function readExistingManifest(targetDir) {
984
- const manifestPath = path.join(targetDir, '.ai-spec/manifest.json');
985
- if (!fs.existsSync(manifestPath)) {
986
- return null;
987
- }
988
- return readJsonFile(manifestPath, 'Existing manifest');
989
- }
990
-
991
- function writeJsonTracked(targetDir, filePath, value, changes) {
992
- const content = `${JSON.stringify(value, null, 2)}\n`;
993
- return writeTextTracked(targetDir, filePath, content, changes);
994
- }
995
-
996
- function writeTextTracked(targetDir, filePath, content, changes) {
997
- ensureDir(path.dirname(filePath));
998
- const rel = targetRel(targetDir, filePath);
999
- if (!fs.existsSync(filePath)) {
1000
- fs.writeFileSync(filePath, content, 'utf8');
1001
- changes.created.push(rel);
1002
- return;
1003
- }
1004
- const current = fs.readFileSync(filePath, 'utf8');
1005
- if (current === content) {
1006
- if (!changes.skipped.includes(rel)) {
1007
- changes.skipped.push(rel);
1008
- }
1009
- return;
1010
- }
1011
- fs.writeFileSync(filePath, content, 'utf8');
1012
- changes.updated.push(rel);
1013
- }
1014
-
1015
- function copyFileTracked(sourceDir, targetDir, sourceRel, destRel, changes) {
1016
- const sourcePath = path.join(sourceDir, sourceRel);
1017
- const destPath = path.join(targetDir, destRel);
1018
- const content = fs.readFileSync(sourcePath);
1019
- ensureDir(path.dirname(destPath));
1020
- const rel = targetRel(targetDir, destPath);
1021
-
1022
- if (!fs.existsSync(destPath)) {
1023
- fs.writeFileSync(destPath, content);
1024
- changes.created.push(rel);
1025
- return;
1026
- }
1027
-
1028
- const current = fs.readFileSync(destPath);
1029
- if (Buffer.compare(current, content) === 0) {
1030
- if (!changes.skipped.includes(rel)) {
1031
- changes.skipped.push(rel);
1032
- }
1033
- return;
1034
- }
1035
-
1036
- fs.writeFileSync(destPath, content);
1037
- changes.updated.push(rel);
1038
- }
1039
-
1040
- function copyRenderedCommandTracked(sourceDir, targetDir, sourceRel, destRel, changes) {
1041
- const sourcePath = path.join(sourceDir, sourceRel);
1042
- const destPath = path.join(targetDir, destRel);
1043
- const content = readRenderedCommandTemplate(sourcePath, {
1044
- forceLocalProtocol: process.env.ENGINEERED_SPEC_FORCE_LOCAL_CLI === '1',
1045
- });
1046
- ensureDir(path.dirname(destPath));
1047
- const rel = targetRel(targetDir, destPath);
1048
-
1049
- if (!fs.existsSync(destPath)) {
1050
- fs.writeFileSync(destPath, content, 'utf8');
1051
- changes.created.push(rel);
1052
- return;
1053
- }
1054
-
1055
- const current = fs.readFileSync(destPath, 'utf8');
1056
- if (current === content) {
1057
- if (!changes.skipped.includes(rel)) {
1058
- changes.skipped.push(rel);
1059
- }
1060
- return;
1061
- }
1062
-
1063
- fs.writeFileSync(destPath, content, 'utf8');
1064
- changes.updated.push(rel);
1065
- }
1066
-
1067
- function copyFileIfMissingTracked(sourceDir, targetDir, sourceRel, destRel, changes) {
1068
- const sourcePath = path.join(sourceDir, sourceRel);
1069
- const destPath = path.join(targetDir, destRel);
1070
- const rel = targetRel(targetDir, destPath);
1071
- if (fs.existsSync(destPath)) {
1072
- if (!changes.skipped.includes(rel)) {
1073
- changes.skipped.push(rel);
1074
- }
1075
- return;
1076
- }
1077
- const content = fs.readFileSync(sourcePath);
1078
- ensureDir(path.dirname(destPath));
1079
- fs.writeFileSync(destPath, content);
1080
- changes.created.push(rel);
1081
- }
1082
-
1083
- function copyDirectoryTracked(sourceDir, targetDir, sourceDirRel, destDirRel, changes) {
1084
- const sourcePath = path.join(sourceDir, sourceDirRel);
1085
- const destPath = path.join(targetDir, destDirRel);
1086
- const existsBefore = fs.existsSync(destPath);
1087
- const sourceHash = hashDirectory(sourcePath);
1088
- const destHash = existsBefore ? hashDirectory(destPath) : null;
1089
- const rel = targetRel(targetDir, destPath);
1090
-
1091
- if (existsBefore && sourceHash && destHash && sourceHash === destHash) {
1092
- if (!changes.skipped.includes(rel)) {
1093
- changes.skipped.push(rel);
1094
- }
1095
- return;
1096
- }
1097
-
1098
- fs.rmSync(destPath, { recursive: true, force: true });
1099
- ensureDir(path.dirname(destPath));
1100
- fs.cpSync(sourcePath, destPath, { recursive: true });
1101
- if (existsBefore) {
1102
- if (!changes.updated.includes(rel)) {
1103
- changes.updated.push(rel);
1104
- }
1105
- } else {
1106
- if (!changes.created.includes(rel)) {
1107
- changes.created.push(rel);
1108
- }
1109
- }
1110
- }
1111
-
1112
- function isManagedPruneAsset(asset) {
1113
- if (!asset || typeof asset !== 'object') return false;
1114
- const rel = String(asset.local_path || '').trim();
1115
- if (!rel) return false;
1116
- if (rel === '.agents' || rel.startsWith('.agents/roles/') || rel.startsWith('.agents/skills/') || rel.startsWith('.agents/rules/')) {
1117
- return true;
1118
- }
1119
- if (rel === SUPERPOWERS_STATE_REL_PATH) {
1120
- return true;
1121
- }
1122
- if (rel === VISUAL_BRIDGE_STATE_REL_PATH) {
1123
- return true;
1124
- }
1125
- if (/^\.(claude|cursor|codex|opencode|trae)\/rules$/.test(rel)) {
1126
- return true;
1127
- }
1128
- if (/^\.(claude|cursor|codex|opencode|trae)\/skills\/[^/]+$/.test(rel)) {
1129
- return true;
1130
- }
1131
- if (/^\.(claude|cursor|codex|opencode|trae)\/commands\/[^/]+\.md$/.test(rel)) {
1132
- return true;
1133
- }
1134
- if (/^\.codex\/commands\/[^/]+\.md$/.test(rel)) {
1135
- return true;
1136
- }
1137
- return false;
1138
- }
1139
-
1140
- function readPreviousSources(targetDir) {
1141
- const sourcesPath = path.join(targetDir, '.ai-spec', 'sources.json');
1142
- if (!fs.existsSync(sourcesPath)) {
1143
- return null;
1144
- }
1145
- return readJsonFile(sourcesPath, 'Existing sources');
1146
- }
1147
-
1148
- function collectManagedPathsFromSources(sources) {
1149
- const managed = new Set();
1150
- for (const asset of Array.isArray(sources?.assets) ? sources.assets : []) {
1151
- if (!isManagedPruneAsset(asset)) {
1152
- continue;
1153
- }
1154
- managed.add(asset.local_path);
1155
- }
1156
- return managed;
1157
- }
1158
-
1159
- function sortPathsForRemoval(paths) {
1160
- return [...paths].sort((left, right) => {
1161
- const leftDepth = left.split('/').length;
1162
- const rightDepth = right.split('/').length;
1163
- if (leftDepth !== rightDepth) {
1164
- return rightDepth - leftDepth;
1165
- }
1166
- return right.localeCompare(left);
1167
- });
1168
- }
1169
-
1170
- function cleanupEmptyIdeDirs(targetDir, changes) {
1171
- for (const ide of ALL_IDES) {
1172
- const ideDir = path.join(targetDir, `.${ide}`);
1173
- if (!fs.existsSync(ideDir)) {
1174
- continue;
1175
- }
1176
- for (const child of ['commands', 'skills']) {
1177
- const childDir = path.join(ideDir, child);
1178
- if (fs.existsSync(childDir) && fs.readdirSync(childDir).filter((entry) => entry !== '.DS_Store').length === 0) {
1179
- removePathTracked(targetDir, childDir, changes);
1180
- }
1181
- }
1182
- const remaining = fs.readdirSync(ideDir).filter((entry) => entry !== '.DS_Store');
1183
- if (remaining.length === 0) {
1184
- removePathTracked(targetDir, ideDir, changes);
1185
- }
1186
- }
1187
- }
1188
-
1189
- function pruneManagedAssets(targetDir, previousSources, currentSources, changes) {
1190
- const previousPaths = collectManagedPathsFromSources(previousSources);
1191
- const currentPaths = collectManagedPathsFromSources(currentSources);
1192
- const stalePaths = [...previousPaths].filter((item) => !currentPaths.has(item));
1193
- for (const rel of sortPathsForRemoval(stalePaths)) {
1194
- removePathTracked(targetDir, path.join(targetDir, rel), changes);
1195
- }
1196
- cleanupEmptyIdeDirs(targetDir, changes);
1197
- }
1198
-
1199
- function ensureSymlinkTracked(targetDir, linkPath, linkTarget, changes) {
1200
- ensureDir(path.dirname(linkPath));
1201
- const rel = targetRel(targetDir, linkPath);
1202
- let existedBefore = false;
1203
-
1204
- try {
1205
- const stat = fs.lstatSync(linkPath);
1206
- existedBefore = true;
1207
- if (stat.isSymbolicLink()) {
1208
- const currentTarget = fs.readlinkSync(linkPath);
1209
- if (currentTarget === linkTarget) {
1210
- if (!changes.skipped.includes(rel)) {
1211
- changes.skipped.push(rel);
1212
- }
1213
- return;
1214
- }
1215
- fs.unlinkSync(linkPath);
1216
- } else {
1217
- fs.rmSync(linkPath, { recursive: true, force: true });
1218
- }
1219
- } catch (error) {
1220
- existedBefore = false;
1221
- }
1222
-
1223
- fs.symlinkSync(linkTarget, linkPath);
1224
- if (existedBefore) {
1225
- if (!changes.updated.includes(rel)) {
1226
- changes.updated.push(rel);
1227
- }
1228
- } else {
1229
- if (!changes.created.includes(rel)) {
1230
- changes.created.push(rel);
1231
- }
1232
- }
1233
- }
1234
-
1235
- function removePathTracked(targetDir, targetPath, changes) {
1236
- const rel = targetRel(targetDir, targetPath);
1237
- try {
1238
- const stat = fs.lstatSync(targetPath);
1239
- if (stat.isSymbolicLink()) {
1240
- fs.unlinkSync(targetPath);
1241
- } else {
1242
- fs.rmSync(targetPath, { recursive: true, force: true });
1243
- }
1244
- if (!changes.updated.includes(rel) && !changes.created.includes(rel)) {
1245
- changes.updated.push(rel);
1246
- }
1247
- } catch (error) {
1248
- // Already absent; nothing to do.
1249
- }
1250
- }
1251
-
1252
- function installRoles(targetDir, resolvedRoles, roleRegistry, changes) {
1253
- for (const supportFile of roleRegistry.support_files || []) {
1254
- copyFileTracked(roleRegistry.__sourceRoot, targetDir, supportFile, supportFile, changes);
1255
- }
1256
-
1257
- const copiedDomainReadmes = new Set();
1258
- for (const role of resolvedRoles) {
1259
- copyFileTracked(role.sourceRoot, targetDir, role.sourceRel, role.sourceRel, changes);
1260
- const domainReadme = role.sourceRel.match(/^\.agents\/roles\/domains\/([^/]+)\//);
1261
- if (domainReadme && role.sourceType === 'local') {
1262
- const domainReadmeRel = `.agents/roles/domains/${domainReadme[1]}/README.md`;
1263
- if (!copiedDomainReadmes.has(domainReadmeRel) && fs.existsSync(path.join(role.sourceRoot, domainReadmeRel))) {
1264
- copyFileTracked(role.sourceRoot, targetDir, domainReadmeRel, domainReadmeRel, changes);
1265
- copiedDomainReadmes.add(domainReadmeRel);
1266
- }
1267
- }
1268
- }
1269
- }
1270
-
1271
- function installSkills(targetDir, resolvedSkills, changes) {
1272
- if (resolvedSkills.some((item) => item.sourceType === 'local')) {
1273
- const localRoot = resolvedSkills.find((item) => item.sourceType === 'local')?.sourceRoot;
1274
- if (localRoot && fs.existsSync(path.join(localRoot, '.agents/skills/README.md'))) {
1275
- copyFileTracked(localRoot, targetDir, '.agents/skills/README.md', '.agents/skills/README.md', changes);
1276
- }
1277
- }
1278
- for (const skill of resolvedSkills) {
1279
- copyDirectoryTracked(skill.sourceRoot, targetDir, skill.sourceDirRel, `.agents/skills/${skill.id}`, changes);
1280
- }
1281
- }
1282
-
1283
- function installRules(targetDir, resolvedRules, changes) {
1284
- const localRoot = resolvedRules.find((item) => item.sourceType === 'local')?.sourceRoot;
1285
- if (localRoot && fs.existsSync(path.join(localRoot, '.agents/rules/README.md'))) {
1286
- copyFileTracked(localRoot, targetDir, '.agents/rules/README.md', '.agents/rules/README.md', changes);
1287
- }
1288
- for (const rule of resolvedRules) {
1289
- const destRel = getInstalledRulePath(rule);
1290
- copyFileTracked(rule.sourceRoot, targetDir, rule.sourceRel, destRel, changes);
1291
- }
1292
- }
1293
-
1294
- function getInstalledRulePath(rule) {
1295
- const baseName = path.basename(rule.sourceRel || '');
1296
- if (baseName && baseName !== 'RULE.md') {
1297
- return `.agents/rules/${baseName}`;
1298
- }
1299
- return `.agents/rules/${rule.id}.md`;
1300
- }
1301
-
1302
- function installFlows(targetDir, catalogs, flowRegistry, changes) {
1303
- for (const supportFile of flowRegistry.support_files || []) {
1304
- copyFileTracked(flowRegistry.__sourceRoot, targetDir, supportFile, supportFile, changes);
1305
- }
1306
- for (const flow of catalogs.flows.values()) {
1307
- copyFileTracked(flow.sourceRoot, targetDir, flow.sourceRel, flow.sourceRel, changes);
1308
- }
1309
- }
1310
-
1311
- function installIdeAssets(sourceDir, targetDir, ides, resolvedSkills, changes, superpowersEnabled = false) {
1312
- const commandsDir = path.join(sourceDir, '.agents/commands/common');
1313
- const commandFiles = fs.existsSync(commandsDir)
1314
- ? fs.readdirSync(commandsDir).filter((name) => name.endsWith('.md')).sort()
1315
- : [];
1316
-
1317
- for (const ide of ides) {
1318
- const ideDir = path.join(targetDir, `.${ide}`);
1319
- ensureDir(ideDir);
1320
- ensureSymlinkTracked(targetDir, path.join(ideDir, 'rules'), '../.agents/rules', changes);
1321
- ensureDir(path.join(ideDir, 'skills'));
1322
-
1323
- for (const skill of resolvedSkills) {
1324
- const linkPath = path.join(ideDir, 'skills', skill.id);
1325
- if (!shouldExposeSkillToIde(skill.id, superpowersEnabled)) {
1326
- removePathTracked(targetDir, linkPath, changes);
1327
- continue;
1328
- }
1329
- const linkTarget = `../../.agents/skills/${skill.id}`;
1330
- ensureSymlinkTracked(targetDir, linkPath, linkTarget, changes);
1331
- }
1332
-
1333
- for (const fileName of commandFiles) {
1334
- copyRenderedCommandTracked(sourceDir, targetDir, `.agents/commands/common/${fileName}`, `.${ide}/commands/${fileName}`, changes);
1335
- }
1336
-
1337
- const ideCommandsDir = path.join(sourceDir, '.agents/commands', ide);
1338
- const ideCommandFiles = fs.existsSync(ideCommandsDir)
1339
- ? fs.readdirSync(ideCommandsDir).filter((name) => name.endsWith('.md')).sort()
1340
- : [];
1341
-
1342
- for (const fileName of ideCommandFiles) {
1343
- copyRenderedCommandTracked(sourceDir, targetDir, `.agents/commands/${ide}/${fileName}`, `.${ide}/commands/${fileName}`, changes);
1344
- }
1345
-
1346
- if (ide === 'cursor') {
1347
- const sourceMcp = path.join(sourceDir, '.cursor/mcp.json');
1348
- if (fs.existsSync(sourceMcp)) {
1349
- copyFileIfMissingTracked(sourceDir, targetDir, '.cursor/mcp.json', '.cursor/mcp.json', changes);
1350
- }
1351
- }
1352
- }
1353
- }
1354
-
1355
- function buildLock(manifest, targetDir, manifestSource, resolved, cliVersion) {
1356
- return {
1357
- schema_version: 1,
1358
- lock_type: 'local-install-lock',
1359
- generated_at: new Date().toISOString(),
1360
- target: {
1361
- path: targetRel(targetDir, targetDir) || '.',
1362
- profile: manifest.profile,
1363
- ides: manifest.ides,
1364
- },
1365
- source: {
1366
- manifest: manifestSource,
1367
- manifest_type: manifest.manifest_type,
1368
- },
1369
- request: {
1370
- scenario_packages: manifest.scenario_packages,
1371
- roles: manifest.roles,
1372
- skills: manifest.skills,
1373
- rules: manifest.rules,
1374
- superpowers: manifest.superpowers || null,
1375
- },
1376
- resolved: {
1377
- domains: resolved.domains,
1378
- installed_flows: resolved.installed_flows,
1379
- roles: resolved.roles.map((item) => item.id),
1380
- skills: resolved.skills.map((item) => item.id),
1381
- rules: resolved.rules.map((item) => item.id),
1382
- },
1383
- assets: {
1384
- roles: resolved.roles.map((item) => ({
1385
- id: item.id,
1386
- version: item.version || 'workspace',
1387
- source_type: item.sourceType || 'local',
1388
- ...(item.hubSlug ? { hub_slug: item.hubSlug } : {}),
1389
- })),
1390
- skills: resolved.skills.map((item) => ({
1391
- id: item.id,
1392
- version: item.version || 'workspace',
1393
- source_type: item.sourceType || 'local',
1394
- ...(item.hubSlug ? { hub_slug: item.hubSlug } : {}),
1395
- })),
1396
- rules: resolved.rules.map((item) => ({
1397
- id: item.id,
1398
- version: item.version || 'workspace',
1399
- source_type: item.sourceType || 'local',
1400
- ...(item.hubSlug ? { hub_slug: item.hubSlug } : {}),
1401
- })),
1402
- flows: resolved.installed_flows.map((id) => ({ id, version: 'workspace' })),
1403
- superpowers: manifest.superpowers
1404
- ? {
1405
- enabled: manifest.superpowers.enabled,
1406
- preferred_mode: manifest.superpowers.preferred_mode,
1407
- codex_entry: manifest.superpowers.codex_entry,
1408
- }
1409
- : null,
1410
- visual_bridge: manifest.visual_bridge
1411
- ? {
1412
- enabled: manifest.visual_bridge.enabled,
1413
- server_url: manifest.visual_bridge.server_url,
1414
- workspace_id: manifest.visual_bridge.workspace_id,
1415
- agent_id: manifest.visual_bridge.agent_id,
1416
- push_on_runtime_state: manifest.visual_bridge.push_on_runtime_state,
1417
- push_on_sync: manifest.visual_bridge.push_on_sync,
1418
- fail_open: manifest.visual_bridge.fail_open,
1419
- }
1420
- : null,
1421
- },
1422
- installer: {
1423
- command: 'ai-spec-auto sync',
1424
- cli_version: cliVersion,
1425
- mode: 'normal',
1426
- },
1427
- integrity: {
1428
- manifest_hash: sha256Json(manifest),
1429
- resolved_hash: sha256Json({
1430
- domains: resolved.domains,
1431
- installed_flows: resolved.installed_flows,
1432
- roles: resolved.roles.map((item) => item.id),
1433
- skills: resolved.skills.map((item) => item.id),
1434
- rules: resolved.rules.map((item) => item.id),
1435
- superpowers: manifest.superpowers || null,
1436
- }),
1437
- },
1438
- status: 'success',
1439
- };
1440
- }
1441
-
1442
- function buildSources(manifest, manifestSource, resolved, sourceDir) {
1443
- const assets = [];
1444
-
1445
- assets.push({
1446
- kind: 'superpowers-config',
1447
- id: 'project-superpowers',
1448
- source_type: 'local',
1449
- source_ref: `local://${SUPERPOWERS_STATE_REL_PATH}`,
1450
- local_path: SUPERPOWERS_STATE_REL_PATH,
1451
- });
1452
- if (manifest.visual_bridge) {
1453
- assets.push({
1454
- kind: 'visual-bridge-config',
1455
- id: 'project-visual-bridge',
1456
- source_type: 'local',
1457
- source_ref: `local://${VISUAL_BRIDGE_STATE_REL_PATH}`,
1458
- local_path: VISUAL_BRIDGE_STATE_REL_PATH,
1459
- });
1460
- }
1461
-
1462
- for (const role of resolved.roles) {
1463
- assets.push({
1464
- kind: 'role',
1465
- id: role.id,
1466
- source_type: role.sourceType || 'local',
1467
- source_ref: role.sourceRef || `local://${role.sourceRel}`,
1468
- local_path: role.sourceRel,
1469
- ...(role.hubSlug ? { hub_slug: role.hubSlug } : {}),
1470
- ...(role.version ? { version: role.version } : {}),
1471
- });
1472
- }
1473
-
1474
- for (const skill of resolved.skills) {
1475
- assets.push({
1476
- kind: 'skill',
1477
- id: skill.id,
1478
- source_type: skill.sourceType || 'local',
1479
- source_ref: skill.sourceRef || `local://${skill.sourceDirRel}`,
1480
- local_path: `.agents/skills/${skill.id}`,
1481
- ...(skill.hubSlug ? { hub_slug: skill.hubSlug } : {}),
1482
- ...(skill.version ? { version: skill.version } : {}),
1483
- });
1484
- }
1485
-
1486
- for (const rule of resolved.rules) {
1487
- assets.push({
1488
- kind: 'rule',
1489
- id: rule.id,
1490
- source_type: rule.sourceType || 'local',
1491
- source_ref: rule.sourceRef || `local://${rule.sourceRel}`,
1492
- local_path: getInstalledRulePath(rule),
1493
- ...(rule.hubSlug ? { hub_slug: rule.hubSlug } : {}),
1494
- ...(rule.version ? { version: rule.version } : {}),
1495
- });
1496
- }
1497
-
1498
- for (const flowId of resolved.installed_flows) {
1499
- assets.push({
1500
- kind: 'flow',
1501
- id: flowId,
1502
- source_type: 'local',
1503
- source_ref: `local://.agents/flows/common/${flowId}.md`,
1504
- local_path: `.agents/flows/common/${flowId}.md`,
1505
- });
1506
- }
1507
-
1508
- for (const ide of manifest.ides || []) {
1509
- assets.push({
1510
- kind: 'ide-rule-link',
1511
- id: `${ide}:rules`,
1512
- source_type: 'local',
1513
- source_ref: `local://.${ide}/rules`,
1514
- local_path: `.${ide}/rules`,
1515
- });
1516
- }
1517
-
1518
- for (const ide of manifest.ides || []) {
1519
- for (const skill of resolved.skills) {
1520
- if (!shouldExposeSkillToIde(skill.id, manifest.superpowers?.enabled)) {
1521
- continue;
1522
- }
1523
- assets.push({
1524
- kind: 'ide-skill-link',
1525
- id: `${ide}:${skill.id}`,
1526
- source_type: 'local',
1527
- source_ref: `local://.${ide}/skills/${skill.id}`,
1528
- local_path: `.${ide}/skills/${skill.id}`,
1529
- });
1530
- if (skill.id === 'using-superpowers') {
1531
- assets.push({
1532
- kind: 'ide-superpowers-entry',
1533
- id: `${ide}:${skill.id}`,
1534
- source_type: 'local',
1535
- source_ref: `local://.${ide}/skills/${skill.id}`,
1536
- local_path: `.${ide}/skills/${skill.id}`,
1537
- });
1538
- }
1539
- }
1540
- }
1541
-
1542
- for (const ide of manifest.ides || []) {
1543
- const commonCommandsDir = path.join(sourceDir, '.agents', 'commands', 'common');
1544
- const ideCommandsDir = path.join(sourceDir, '.agents', 'commands', ide);
1545
- const commandFiles = unique([
1546
- ...walkFiles(commonCommandsDir, (filePath) => filePath.endsWith('.md')).map((filePath) => path.basename(filePath)),
1547
- ...walkFiles(ideCommandsDir, (filePath) => filePath.endsWith('.md')).map((filePath) => path.basename(filePath)),
1548
- ]);
1549
- for (const fileName of commandFiles) {
1550
- assets.push({
1551
- kind: 'ide-command-template',
1552
- id: `${ide}:${fileName}`,
1553
- source_type: 'local',
1554
- source_ref: `local://.${ide}/commands/${fileName}`,
1555
- local_path: `.${ide}/commands/${fileName}`,
1556
- });
1557
- }
1558
- }
1559
-
1560
- if (manifest.superpowers?.enabled && (manifest.ides || []).includes('codex')) {
1561
- assets.push({
1562
- kind: 'codex-agents-bridge',
1563
- id: 'codex:agents-md',
1564
- source_type: 'local',
1565
- source_ref: 'local://AGENTS.md',
1566
- local_path: 'AGENTS.md',
1567
- });
1568
- }
1569
-
1570
- return {
1571
- schema_version: 1,
1572
- sources_type: 'local-install-sources',
1573
- generated_at: new Date().toISOString(),
1574
- manifest: {
1575
- type: manifest.manifest_type,
1576
- source: manifestSource,
1577
- },
1578
- registries: [
1579
- {
1580
- type: 'local-workspace',
1581
- name: 'ai-spec-auto-local',
1582
- path: sourceDir,
1583
- },
1584
- ...unique(
1585
- [...resolved.roles, ...resolved.skills, ...resolved.rules]
1586
- .map((item) => item.sourceOrigin || '')
1587
- .filter(Boolean),
1588
- ).map((origin) => ({
1589
- type: 'hub-supplement',
1590
- name: 'hub-supplement',
1591
- source: origin,
1592
- })),
1593
- ],
1594
- assets,
1595
- };
1596
- }
1597
-
1598
- function printPretty(result, isDryRun) {
1599
- const noun = isDryRun ? 'sync-plan(同步计划)' : 'sync-result(同步结果)';
1600
- console.log(`${noun}: ${result.status}`);
1601
- console.log(`target(目标项目): ${result.target.path}`);
1602
- console.log(`profile(技术栈): ${result.target.profile}`);
1603
- console.log(`ides(IDE 列表): ${result.target.ides.join(', ')}`);
1604
- console.log(`roles(专家角色): ${result.resolved.roles.join(', ') || '(none)'}`);
1605
- console.log(`skills(技能): ${result.resolved.skills.join(', ') || '(none)'}`);
1606
- console.log(`rules(规则): ${result.resolved.rules.join(', ') || '(none)'}`);
1607
- console.log(`domains(能力域): ${result.resolved.domains.join(', ') || '(none)'}`);
1608
- if (Array.isArray(result.resolved.installed_flows)) {
1609
- console.log(`installed_flows(已安装流程模板): ${result.resolved.installed_flows.join(', ') || '(none)'}`);
1610
- }
1611
- if (result.changes) {
1612
- console.log(`created(新建): ${result.changes.created.length}`);
1613
- console.log(`updated(更新): ${result.changes.updated.length}`);
1614
- console.log(`skipped(跳过): ${result.changes.skipped.length}`);
1615
- console.log(`conflicts(冲突): ${result.changes.conflicts.length}`);
1616
- }
1617
- if (result.warnings.length > 0) {
1618
- console.log(`warnings(警告):`);
1619
- for (const warning of result.warnings) {
1620
- console.log(`- ${warning}`);
1621
- }
1622
- }
1623
- if (result.errors.length > 0) {
1624
- console.log(`errors(错误):`);
1625
- for (const error of result.errors) {
1626
- console.log(`- ${error}`);
1627
- }
1628
- }
1629
- }
1630
-
1631
- function buildPlan(targetDir, manifestSource, manifest, resolvedResult) {
1632
- return {
1633
- schema_version: 1,
1634
- kind: 'sync-plan',
1635
- status: 'planned',
1636
- target: {
1637
- path: targetDir,
1638
- profile: manifest.profile,
1639
- ides: manifest.ides,
1640
- },
1641
- source: {
1642
- manifest: manifestSource,
1643
- manifest_type: manifest.manifest_type,
1644
- },
1645
- request: {
1646
- scenario_packages: manifest.scenario_packages,
1647
- roles: manifest.roles,
1648
- skills: manifest.skills,
1649
- rules: manifest.rules,
1650
- },
1651
- resolved: {
1652
- domains: resolvedResult.resolved.domains,
1653
- installed_flows: resolvedResult.resolved.installed_flows,
1654
- roles: resolvedResult.resolved.roles.map((item) => item.id),
1655
- skills: resolvedResult.resolved.skills.map((item) => item.id),
1656
- rules: resolvedResult.resolved.rules.map((item) => item.id),
1657
- },
1658
- warnings: resolvedResult.warnings,
1659
- errors: [],
1660
- };
1661
- }
1662
-
1663
- function dedupeChanges(changes) {
1664
- return {
1665
- created: unique(changes.created),
1666
- updated: unique(changes.updated.filter((item) => !changes.created.includes(item))),
1667
- skipped: unique(changes.skipped.filter((item) => !changes.created.includes(item) && !changes.updated.includes(item))),
1668
- conflicts: unique(changes.conflicts),
1669
- };
1670
- }
1671
-
1672
- function buildResult(prepared, changes = null) {
1673
- return {
1674
- schema_version: 1,
1675
- kind: changes ? 'sync-result' : 'sync-plan',
1676
- status: changes ? 'success' : 'planned',
1677
- target: {
1678
- path: prepared.targetDir,
1679
- profile: prepared.manifest.profile,
1680
- ides: prepared.manifest.ides,
1681
- },
1682
- source: {
1683
- manifest: prepared.manifestSource,
1684
- manifest_type: prepared.manifest.manifest_type,
1685
- },
1686
- request: {
1687
- scenario_packages: prepared.manifest.scenario_packages,
1688
- roles: prepared.manifest.roles,
1689
- skills: prepared.manifest.skills,
1690
- rules: prepared.manifest.rules,
1691
- },
1692
- resolved: {
1693
- domains: prepared.resolvedResult.resolved.domains,
1694
- installed_flows: prepared.resolvedResult.resolved.installed_flows,
1695
- roles: prepared.resolvedResult.resolved.roles.map((item) => item.id),
1696
- skills: prepared.resolvedResult.resolved.skills.map((item) => item.id),
1697
- rules: prepared.resolvedResult.resolved.rules.map((item) => item.id),
1698
- },
1699
- ...(changes
1700
- ? {
1701
- changes,
1702
- artifacts: {
1703
- manifest: '.ai-spec/manifest.json',
1704
- lock: '.ai-spec/lock.json',
1705
- sources: '.ai-spec/sources.json',
1706
- },
1707
- }
1708
- : {}),
1709
- warnings: prepared.resolvedResult.warnings,
1710
- errors: [],
1711
- };
1712
- }
1713
-
1714
- async function prepareSync(options) {
1715
- const sourceDir = getSourceDir();
1716
- const profilesRegistry = readProfilesRegistry(sourceDir);
1717
- const registryValidation = require('./validate-registry').validateRegistry(sourceDir);
1718
- if (registryValidation.status !== 'success') {
1719
- throw new Error(`Registry validation failed with ${registryValidation.errors.length} error(s). Run "ai-spec-auto validate-registry" for details.`);
1720
- }
1721
-
1722
- const targetDir = path.resolve(options.target || '.');
1723
- const cliVersion = require(path.join(sourceDir, 'package.json')).version || '0.0.0';
1724
- const manifestInput = options.manifest
1725
- ? options.manifest
1726
- : path.join(targetDir, '.ai-spec/manifest.json');
1727
-
1728
- if (!manifestInput) {
1729
- throw new Error('sync(同步) requires --manifest(安装清单) or an existing .ai-spec/manifest.json');
1730
- }
1731
-
1732
- const requestedTimeout = Number(process.env.AI_SPEC_REMOTE_MANIFEST_TIMEOUT_MS || DEFAULT_REMOTE_MANIFEST_TIMEOUT_MS);
1733
- const timeoutMs = Number.isFinite(requestedTimeout) && requestedTimeout > 0
1734
- ? requestedTimeout
1735
- : DEFAULT_REMOTE_MANIFEST_TIMEOUT_MS;
1736
- const { manifestSource, rawManifest } = await loadManifestInput(manifestInput, timeoutMs);
1737
- const existingManifest = readExistingManifest(targetDir);
1738
- const manifest = normalizeManifest(rawManifest, existingManifest, options, profilesRegistry);
1739
- const registry = loadSyncRegistry(sourceDir);
1740
- const catalogs = {
1741
- roles: readRoleCatalog(registry.roles),
1742
- skills: readSkillCatalog(sourceDir),
1743
- rules: readRuleCatalog(registry.rules),
1744
- flows: readFlowCatalog(registry.flows),
1745
- scenarioPackages: new Map(Object.entries(registry.scenarioPackages?.scenario_packages || {})),
1746
- };
1747
- let prepared = {
1748
- options,
1749
- sourceDir,
1750
- profilesRegistry,
1751
- targetDir,
1752
- cliVersion,
1753
- manifestInput,
1754
- manifestSource,
1755
- rawManifest,
1756
- existingManifest,
1757
- manifest,
1758
- registry,
1759
- catalogs,
1760
- supplements: [],
1761
- };
1762
- const preResolved = resolveManifest(manifest, catalogs, { allowMissing: true });
1763
- const hubOrigin = resolveHubOrigin(options, manifestSource);
1764
-
1765
- if (hasMissingAssets(preResolved) && hubOrigin) {
1766
- const supplement = await fetchHubSupplement(
1767
- hubOrigin,
1768
- manifest,
1769
- preResolved.missing,
1770
- timeoutMs,
1771
- );
1772
- const supplementState = buildSupplementState(supplement);
1773
- prepared = mergePreparedState(prepared, supplement, supplementState);
1774
- }
1775
-
1776
- const resolvedResult = resolveManifest(prepared.manifest, prepared.catalogs);
1777
-
1778
- return {
1779
- ...prepared,
1780
- resolvedResult: {
1781
- ...resolvedResult,
1782
- warnings: unique([
1783
- ...resolvedResult.warnings,
1784
- ...preResolved.warnings,
1785
- ...prepared.supplements.flatMap((item) => normalizeList(item.report?.warnings)),
1786
- ]),
1787
- },
1788
- };
1789
- }
1790
-
1791
- async function runSync(options, preparedState = null) {
1792
- const prepared = preparedState || await prepareSync(options);
1793
- try {
1794
- if (options.dryRun) {
1795
- return buildResult(prepared, null);
1796
- }
1797
-
1798
- const changes = {
1799
- created: [],
1800
- updated: [],
1801
- skipped: [],
1802
- conflicts: [],
1803
- };
1804
-
1805
- installRoles(prepared.targetDir, prepared.resolvedResult.resolved.roles, prepared.registry.roles, changes);
1806
- installSkills(prepared.targetDir, prepared.resolvedResult.resolved.skills, changes);
1807
- installRules(prepared.targetDir, prepared.resolvedResult.resolved.rules, changes);
1808
- installFlows(prepared.targetDir, prepared.catalogs, prepared.registry.flows, changes);
1809
- installIdeAssets(
1810
- prepared.sourceDir,
1811
- prepared.targetDir,
1812
- prepared.manifest.ides,
1813
- prepared.resolvedResult.resolved.skills,
1814
- changes,
1815
- Boolean(prepared.manifest.superpowers?.enabled),
1816
- );
1817
-
1818
- const aiSpecDir = path.join(prepared.targetDir, '.ai-spec');
1819
- ensureDir(aiSpecDir);
1820
-
1821
- const manifestOutPath = path.join(aiSpecDir, 'manifest.json');
1822
- const lockOutPath = path.join(aiSpecDir, 'lock.json');
1823
- const sourcesOutPath = path.join(aiSpecDir, 'sources.json');
1824
- const previousSources = readPreviousSources(prepared.targetDir);
1825
-
1826
- writeJsonTracked(prepared.targetDir, manifestOutPath, prepared.manifest, changes);
1827
- const superpowersState = buildSuperpowersState({
1828
- targetDir: prepared.targetDir,
1829
- enabled: Boolean(prepared.manifest.superpowers?.enabled),
1830
- manifestConfig: prepared.manifest.superpowers || null,
1831
- ides: prepared.manifest.ides,
1832
- env: process.env,
1833
- cliVersion: prepared.cliVersion,
1834
- source: 'sync',
1835
- });
1836
- writeSuperpowersState(prepared.targetDir, superpowersState);
1837
- const visualBridgeState = buildVisualBridgeState({
1838
- targetDir: prepared.targetDir,
1839
- manifestConfig: prepared.manifest.visual_bridge || null,
1840
- previousState: readVisualBridgeState(prepared.targetDir),
1841
- cliVersion: prepared.cliVersion,
1842
- source: 'sync',
1843
- });
1844
- if (visualBridgeState) {
1845
- writeVisualBridgeState(prepared.targetDir, visualBridgeState);
1846
- }
1847
- const lock = buildLock(prepared.manifest, prepared.targetDir, prepared.manifestSource, prepared.resolvedResult.resolved, prepared.cliVersion);
1848
- writeJsonTracked(prepared.targetDir, lockOutPath, lock, changes);
1849
- const sources = buildSources(prepared.manifest, prepared.manifestSource, prepared.resolvedResult.resolved, prepared.sourceDir);
1850
- if (previousSources) {
1851
- pruneManagedAssets(prepared.targetDir, previousSources, sources, changes);
1852
- }
1853
- writeJsonTracked(prepared.targetDir, sourcesOutPath, sources, changes);
1854
- upsertManagedAgentsBlock(
1855
- prepared.targetDir,
1856
- Boolean(prepared.manifest.superpowers?.enabled) && prepared.manifest.ides.includes('codex'),
1857
- );
1858
-
1859
- return buildResult(prepared, dedupeChanges(changes));
1860
- } finally {
1861
- for (const supplement of prepared.supplements || []) {
1862
- try {
1863
- fs.rmSync(supplement.tempDir, { recursive: true, force: true });
1864
- } catch (error) {
1865
- // Cleanup failure should not change sync result.
1866
- }
1867
- }
1868
- }
1869
- }
1870
-
1871
- async function main(argv) {
1872
- try {
1873
- const options = parseArgs(argv);
1874
- let profilesRegistry = null;
1875
- try {
1876
- profilesRegistry = readProfilesRegistry(getSourceDir());
1877
- } catch (error) {
1878
- if (!options.help) {
1879
- throw error;
1880
- }
1881
- }
1882
- if (options.help) {
1883
- printUsage(profilesRegistry);
1884
- return 0;
1885
- }
1886
-
1887
- const result = await runSync(options);
1888
- if (options.json) {
1889
- process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
1890
- } else {
1891
- printPretty(result, options.dryRun);
1892
- }
1893
- return 0;
1894
- } catch (error) {
1895
- console.error(`sync(同步) failed: ${error.message}`);
1896
- return 1;
1897
- }
1898
- }
1899
-
1900
- module.exports = { parseArgs, prepareSync, runSync, main };
1901
-
1902
- if (require.main === module) {
1903
- process.exit(main(process.argv.slice(2)));
1904
- }
1
+ #!/usr/bin/env node
2
+ const crypto = require('crypto');
3
+ const fs = require('fs');
4
+ const os = require('os');
5
+ const path = require('path');
6
+ const { spawnSync } = require('child_process');
7
+ const {
8
+ formatSupportedProfiles,
9
+ getProfileEntries,
10
+ readProfilesRegistry,
11
+ resolveProfileId,
12
+ } = require('./profile-registry');
13
+ const {
14
+ normalizeSuperpowersManifest,
15
+ buildSuperpowersState,
16
+ writeSuperpowersState,
17
+ shouldExposeSkillToIde,
18
+ SUPERPOWERS_STATE_REL_PATH,
19
+ upsertManagedAgentsBlock,
20
+ } = require('./superpowers');
21
+ const {
22
+ normalizeVisualBridgeManifest,
23
+ buildVisualBridgeState,
24
+ writeVisualBridgeState,
25
+ readVisualBridgeState,
26
+ VISUAL_BRIDGE_STATE_REL_PATH,
27
+ } = require('./visual-bridge-config');
28
+ const { readRenderedCommandTemplate } = require('./command-template-renderer');
29
+
30
+ const SUPPORTED_IDES = ['cursor', 'claude', 'codex', 'opencode', 'trae', 'qoder'];
31
+ const DEFAULT_IDES = ['cursor', 'claude'];
32
+ const ALL_IDES = [...SUPPORTED_IDES];
33
+ const DEFAULT_REMOTE_MANIFEST_TIMEOUT_MS = 15000;
34
+
35
+ function printUsage(profilesRegistry = null) {
36
+ const profileHint = profilesRegistry
37
+ ? formatSupportedProfiles(profilesRegistry)
38
+ : 'see .agents/registry/profiles.json';
39
+ console.log(`Usage:
40
+ ai-spec-auto sync [target] --manifest <manifest.json|url> [options]
41
+
42
+ Options:
43
+ --manifest <file|url> Local manifest JSON file path or remote manifest URL
44
+ --profile <profile> Override profile from manifest (${profileHint})
45
+ --ide <preset> Override ides (default | all | cursor | claude | codex | qoder | comma-separated)
46
+ --superpowers Force enable superpowers(超能力桥接)
47
+ --no-superpowers Force disable superpowers(超能力桥接)
48
+ --hub-origin <origin> Hub origin for supplement fetch when manifest is local
49
+ --no-hub-fetch Disable Hub supplement fetch for missing assets
50
+ --out <file> Write the normalized manifest to a file
51
+ --json Print JSON output only
52
+ --dry-run Resolve only, do not write files
53
+ --force Reserved for future conflict handling
54
+ --help Show this help
55
+ `);
56
+ }
57
+
58
+ function parseArgs(argv) {
59
+ const args = [...argv];
60
+ const options = {
61
+ target: '.',
62
+ json: false,
63
+ pretty: true,
64
+ dryRun: false,
65
+ force: false,
66
+ hubFetch: true,
67
+ out: '',
68
+ };
69
+
70
+ while (args.length > 0) {
71
+ const arg = args.shift();
72
+ if (!arg.startsWith('-') && options.target === '.') {
73
+ options.target = arg;
74
+ continue;
75
+ }
76
+
77
+ switch (arg) {
78
+ case '--manifest':
79
+ options.manifest = requireArg(arg, args);
80
+ break;
81
+ case '--profile':
82
+ options.profile = requireArg(arg, args);
83
+ break;
84
+ case '--ide':
85
+ options.ide = requireArg(arg, args);
86
+ break;
87
+ case '--superpowers':
88
+ options.superpowers = true;
89
+ break;
90
+ case '--no-superpowers':
91
+ options.superpowers = false;
92
+ break;
93
+ case '--json':
94
+ options.json = true;
95
+ options.pretty = false;
96
+ break;
97
+ case '--hub-origin':
98
+ options.hubOrigin = requireArg(arg, args);
99
+ break;
100
+ case '--no-hub-fetch':
101
+ options.hubFetch = false;
102
+ break;
103
+ case '--out':
104
+ options.out = requireArg(arg, args);
105
+ break;
106
+ case '--dry-run':
107
+ options.dryRun = true;
108
+ break;
109
+ case '--force':
110
+ options.force = true;
111
+ break;
112
+ case '--pretty':
113
+ options.pretty = true;
114
+ options.json = false;
115
+ break;
116
+ case '--help':
117
+ case '-h':
118
+ options.help = true;
119
+ break;
120
+ default:
121
+ throw new Error(`Unknown argument: ${arg}`);
122
+ }
123
+ }
124
+
125
+ return options;
126
+ }
127
+
128
+ function requireArg(flag, args) {
129
+ const next = args.shift();
130
+ if (!next || next.startsWith('--')) {
131
+ throw new Error(`选项 ${flag} 需要一个参数值`);
132
+ }
133
+ return next;
134
+ }
135
+
136
+ function isHttpUrl(value) {
137
+ return /^https?:\/\//i.test(String(value || ''));
138
+ }
139
+
140
+ function getSourceDir() {
141
+ if (process.env.ENGINEERED_SPEC_LOCAL) {
142
+ return process.env.ENGINEERED_SPEC_LOCAL;
143
+ }
144
+ return path.join(__dirname, '..');
145
+ }
146
+
147
+ function toPosix(value) {
148
+ return value.split(path.sep).join('/');
149
+ }
150
+
151
+ function targetRel(targetDir, filePath) {
152
+ return toPosix(path.relative(targetDir, filePath));
153
+ }
154
+
155
+ function ensureDir(dirPath) {
156
+ fs.mkdirSync(dirPath, { recursive: true });
157
+ }
158
+
159
+ function readJsonFile(filePath, label) {
160
+ const raw = fs.readFileSync(filePath, 'utf8');
161
+ try {
162
+ return JSON.parse(raw);
163
+ } catch (error) {
164
+ throw new Error(`${label} is not valid JSON: ${filePath}`);
165
+ }
166
+ }
167
+
168
+ function readRegistryJson(sourceDir, fileName, rootKey) {
169
+ const filePath = path.join(sourceDir, '.agents/registry', fileName);
170
+ if (!fs.existsSync(filePath)) {
171
+ throw new Error(`Registry file not found: ${filePath}`);
172
+ }
173
+ const data = readJsonFile(filePath, `Registry ${fileName}`);
174
+ if (!data || typeof data !== 'object' || !data[rootKey] || typeof data[rootKey] !== 'object') {
175
+ throw new Error(`Registry ${fileName} is missing root key "${rootKey}"`);
176
+ }
177
+ return data[rootKey];
178
+ }
179
+
180
+ function normalizeList(value) {
181
+ if (!value) return [];
182
+ if (Array.isArray(value)) {
183
+ return [...new Set(value.map((item) => String(item).trim()).filter(Boolean))];
184
+ }
185
+ return [...new Set(String(value).split(',').map((item) => item.trim()).filter(Boolean))];
186
+ }
187
+
188
+ function normalizeIdes(value) {
189
+ const raw = typeof value === 'string' ? value.trim() : value;
190
+ if (!raw || (Array.isArray(raw) && raw.length === 0)) {
191
+ return [...DEFAULT_IDES];
192
+ }
193
+ if (raw === 'default') {
194
+ return [...DEFAULT_IDES];
195
+ }
196
+ if (raw === 'all') {
197
+ return [...ALL_IDES];
198
+ }
199
+
200
+ const items = normalizeList(raw);
201
+ const unknown = items.filter((item) => !SUPPORTED_IDES.includes(item));
202
+ if (unknown.length > 0) {
203
+ throw new Error(`Unsupported ides: ${unknown.join(', ')}`);
204
+ }
205
+ return items;
206
+ }
207
+
208
+ function parseLocalPreferences(value) {
209
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
210
+ return null;
211
+ }
212
+ if (!Object.prototype.hasOwnProperty.call(value, 'project_init')) {
213
+ return null;
214
+ }
215
+ const projectInit = value.project_init;
216
+ if (!projectInit || typeof projectInit !== 'object' || Array.isArray(projectInit)) {
217
+ return { project_init: { custom_rules: [] } };
218
+ }
219
+ return {
220
+ project_init: {
221
+ custom_rules: normalizeList(projectInit.custom_rules),
222
+ },
223
+ };
224
+ }
225
+
226
+ function walkFiles(rootDir, predicate) {
227
+ const results = [];
228
+ if (!fs.existsSync(rootDir)) {
229
+ return results;
230
+ }
231
+ const stack = [rootDir];
232
+ while (stack.length > 0) {
233
+ const current = stack.pop();
234
+ const entries = fs.readdirSync(current, { withFileTypes: true });
235
+ for (const entry of entries) {
236
+ if (entry.name === '.DS_Store') {
237
+ continue;
238
+ }
239
+ const fullPath = path.join(current, entry.name);
240
+ if (entry.isDirectory()) {
241
+ stack.push(fullPath);
242
+ continue;
243
+ }
244
+ if (!predicate || predicate(fullPath, entry)) {
245
+ results.push(fullPath);
246
+ }
247
+ }
248
+ }
249
+ return results.sort();
250
+ }
251
+
252
+ function readSkillCatalog(sourceDir, metadataMap = new Map()) {
253
+ const skillRegistry = readRegistryJson(sourceDir, 'skills.json', 'skills');
254
+ const profilesRegistry = readProfilesRegistry(sourceDir);
255
+ const catalog = {
256
+ common: new Map(),
257
+ profiles: Object.fromEntries(
258
+ Object.keys(getProfileEntries(profilesRegistry)).map((profileId) => [profileId, new Map()])
259
+ ),
260
+ domains: new Map(),
261
+ };
262
+ const profileDirMap = Object.entries(getProfileEntries(profilesRegistry)).map(([profileId, entry]) => ({
263
+ profileId,
264
+ skillsDir: String(entry?.skills_dir || '').trim(),
265
+ }));
266
+
267
+ const skillsRoot = path.join(sourceDir, '.agents/skills');
268
+ const skillFiles = walkFiles(skillsRoot, (filePath) => filePath.endsWith('/SKILL.md'));
269
+ for (const filePath of skillFiles) {
270
+ const rel = toPosix(path.relative(sourceDir, filePath));
271
+ const dirRel = toPosix(path.dirname(rel));
272
+ const id = path.basename(path.dirname(filePath));
273
+ const entry = {
274
+ id,
275
+ sourceDirRel: dirRel,
276
+ sourceFileRel: rel,
277
+ domains: normalizeList(skillRegistry[id]?.domains),
278
+ sourceRoot: sourceDir,
279
+ sourceType: metadataMap.get(id)?.__sourceType || 'local',
280
+ sourceRef: metadataMap.get(id)?.__sourceRef || `local://${dirRel}`,
281
+ sourceOrigin: metadataMap.get(id)?.__sourceOrigin || null,
282
+ version: metadataMap.get(id)?.__version || 'workspace',
283
+ hubSlug: metadataMap.get(id)?.__hubSlug || null,
284
+ };
285
+
286
+ if (rel.startsWith('.agents/skills/common/')) {
287
+ catalog.common.set(id, entry);
288
+ } else if (rel.startsWith('.agents/skills/domains/')) {
289
+ catalog.domains.set(id, entry);
290
+ } else {
291
+ const matchedProfile = profileDirMap.find((item) => item.skillsDir && rel.startsWith(`${item.skillsDir}/`));
292
+ if (matchedProfile) {
293
+ catalog.profiles[matchedProfile.profileId].set(id, entry);
294
+ }
295
+ }
296
+ }
297
+
298
+ return catalog;
299
+ }
300
+
301
+ function loadSyncRegistry(sourceDir) {
302
+ const roles = readJsonFile(path.join(sourceDir, '.agents/registry/roles.json'), 'Registry roles.json');
303
+ const flowsPath = path.join(sourceDir, '.agents/registry/flows.json');
304
+ const flows = fs.existsSync(flowsPath)
305
+ ? readJsonFile(flowsPath, 'Registry flows.json')
306
+ : { version: 1, support_files: [], flows: {} };
307
+ const scenarioPackagesPath = path.join(sourceDir, '.agents/registry/scenario-packages.json');
308
+ const scenarioPackages = fs.existsSync(scenarioPackagesPath)
309
+ ? readJsonFile(scenarioPackagesPath, 'Registry scenario-packages.json')
310
+ : { version: 1, scenario_packages: {} };
311
+ return {
312
+ roles: {
313
+ ...roles,
314
+ __sourceRoot: sourceDir,
315
+ },
316
+ rules: buildRuleRegistryForSource(readRegistryJson(sourceDir, 'rules.json', 'rules'), sourceDir, new Map()),
317
+ flows: {
318
+ ...flows,
319
+ __sourceRoot: sourceDir,
320
+ },
321
+ scenarioPackages: {
322
+ ...scenarioPackages,
323
+ __sourceRoot: sourceDir,
324
+ },
325
+ };
326
+ }
327
+
328
+ function readRoleCatalog(roleRegistry) {
329
+ const catalog = new Map();
330
+ for (const [id, entry] of Object.entries(roleRegistry.roles || {})) {
331
+ if (!entry || typeof entry !== 'object' || !entry.source) {
332
+ continue;
333
+ }
334
+ catalog.set(id, {
335
+ id,
336
+ name: entry.name || id,
337
+ status: entry.status || 'unknown',
338
+ domains: normalizeList(entry.domains),
339
+ sourceRel: entry.source,
340
+ sourceRoot: roleRegistry.__sourceRoot,
341
+ sourceType: entry.__sourceType || 'local',
342
+ sourceRef: entry.__sourceRef || `local://${entry.source}`,
343
+ sourceOrigin: entry.__sourceOrigin || null,
344
+ version: entry.__version || 'workspace',
345
+ hubSlug: entry.__hubSlug || null,
346
+ });
347
+ }
348
+ return catalog;
349
+ }
350
+
351
+ function readRuleCatalog(ruleRegistry) {
352
+ const catalog = new Map();
353
+ for (const [id, entry] of Object.entries(ruleRegistry || {})) {
354
+ if (!entry || typeof entry !== 'object') {
355
+ continue;
356
+ }
357
+ if (!entry.source && !entry.sourceByProfile) {
358
+ continue;
359
+ }
360
+ catalog.set(id, {
361
+ id,
362
+ source: entry.source || null,
363
+ sourceByProfile: entry.sourceByProfile || null,
364
+ domains: normalizeList(entry.domains),
365
+ sourceRoot: entry.__sourceRoot,
366
+ sourceType: entry.__sourceType || 'local',
367
+ sourceRef: entry.__sourceRef || (entry.source ? `local://${entry.source}` : 'local://profiled-rule'),
368
+ sourceOrigin: entry.__sourceOrigin || null,
369
+ version: entry.__version || 'workspace',
370
+ hubSlug: entry.__hubSlug || null,
371
+ });
372
+ }
373
+ return catalog;
374
+ }
375
+
376
+ function readFlowCatalog(flowRegistry) {
377
+ const catalog = new Map();
378
+ for (const [id, entry] of Object.entries(flowRegistry.flows || {})) {
379
+ if (!entry || typeof entry !== 'object' || !entry.source) {
380
+ continue;
381
+ }
382
+ catalog.set(id, {
383
+ id,
384
+ name: entry.name || id,
385
+ status: entry.status || 'unknown',
386
+ sourceRel: entry.source,
387
+ sourceRoot: flowRegistry.__sourceRoot,
388
+ sourceType: entry.__sourceType || 'local',
389
+ sourceRef: entry.__sourceRef || `local://${entry.source}`,
390
+ sourceOrigin: entry.__sourceOrigin || null,
391
+ version: entry.__version || 'workspace',
392
+ });
393
+ }
394
+ return catalog;
395
+ }
396
+
397
+ function resolveSkill(id, profile, catalog) {
398
+ return (
399
+ catalog.profiles[profile]?.get(id) ||
400
+ catalog.common.get(id) ||
401
+ catalog.domains.get(id) ||
402
+ null
403
+ );
404
+ }
405
+
406
+ function normalizeLegacyProfileScopedSkillId(skillId, profile, catalog, warnings) {
407
+ const normalized = String(skillId || '').trim();
408
+ if (!normalized) {
409
+ return normalized;
410
+ }
411
+
412
+ const match = normalized.match(/^(.*?)-(react|vue)$/);
413
+ if (!match) {
414
+ return normalized;
415
+ }
416
+
417
+ const [, baseId, scopedProfile] = match;
418
+ if (!baseId) {
419
+ return normalized;
420
+ }
421
+
422
+ const resolvedBase = resolveSkill(baseId, profile, catalog);
423
+ if (!resolvedBase) {
424
+ return normalized;
425
+ }
426
+
427
+ if (scopedProfile !== profile) {
428
+ warnings.push(`Skill id "${normalized}" uses legacy profile suffix "${scopedProfile}" but target profile is "${profile}"; normalized to "${baseId}".`);
429
+ } else {
430
+ warnings.push(`Skill id "${normalized}" uses legacy profile suffix; normalized to "${baseId}".`);
431
+ }
432
+ return baseId;
433
+ }
434
+
435
+ const LEGACY_RULE_ID_ALIASES = {
436
+ 'react-project-overview': 'project-overview',
437
+ 'vue-project-overview': 'project-overview',
438
+ 'react-project-structure': 'project-structure',
439
+ 'vue-project-structure': 'project-structure',
440
+ 'react-component-guidelines': 'component-standard',
441
+ 'vue-component-guidelines': 'component-standard',
442
+ 'react-routing-guidelines': 'route-standard',
443
+ 'vue-routing-guidelines': 'route-standard',
444
+ 'react-state-management': 'store-standard',
445
+ 'vue-state-management': 'store-standard',
446
+ 'react-style-guidelines': 'style-standard',
447
+ 'vue-style-guidelines': 'style-standard',
448
+ 'api-guidelines': 'api-standard',
449
+ 'coding-guidelines': 'coding-standard',
450
+ 'general-constraints': 'generic-constraints',
451
+ 'documentation-guidelines': 'doc-standard',
452
+ 'testing-guidelines': 'test-standard',
453
+ 'superpowers-execution-guidelines': 'superpowers-standard',
454
+ 'code-formatting-and-checks': 'format-check-standard',
455
+ 'audit-reporting-guidelines': 'audit-report-standard',
456
+ };
457
+
458
+ function normalizeLegacyRuleId(ruleId, profile, ruleRegistry, warnings) {
459
+ const normalized = String(ruleId || '').trim();
460
+ if (!normalized) {
461
+ return normalized;
462
+ }
463
+
464
+ const canonicalId = LEGACY_RULE_ID_ALIASES[normalized];
465
+ if (!canonicalId) {
466
+ return normalized;
467
+ }
468
+
469
+ const resolvedCanonical = resolveRule(canonicalId, profile, ruleRegistry);
470
+ if (!resolvedCanonical) {
471
+ return normalized;
472
+ }
473
+
474
+ warnings.push(`Rule id "${normalized}" uses legacy manifest alias; normalized to "${canonicalId}".`);
475
+ return canonicalId;
476
+ }
477
+
478
+ function resolveRule(id, profile, ruleRegistry) {
479
+ const entry = ruleRegistry.get(id);
480
+ if (!entry) {
481
+ return null;
482
+ }
483
+ if (entry.sourceByProfile) {
484
+ const source = entry.sourceByProfile[profile];
485
+ if (!source) {
486
+ return null;
487
+ }
488
+ return { ...entry, id, sourceRel: source, domains: entry.domains || [] };
489
+ }
490
+ return { ...entry, id, sourceRel: entry.source, domains: entry.domains || [] };
491
+ }
492
+
493
+ function normalizeManifest(rawManifest, existingManifest, options, profilesRegistry) {
494
+ const rawProfile = options.profile || rawManifest?.profile || existingManifest?.profile || null;
495
+ const resolvedProfile = resolveProfileId(profilesRegistry, rawProfile);
496
+ const rawLocalPreferences = parseLocalPreferences(rawManifest?.local_preferences);
497
+ const existingLocalPreferences = parseLocalPreferences(existingManifest?.local_preferences);
498
+ const normalizedSuperpowers = normalizeSuperpowersManifest(
499
+ options.superpowers === undefined
500
+ ? rawManifest?.superpowers
501
+ : { ...(rawManifest?.superpowers || existingManifest?.superpowers || {}), enabled: options.superpowers },
502
+ existingManifest?.superpowers,
503
+ );
504
+ const normalizedVisualBridge = normalizeVisualBridgeManifest(
505
+ rawManifest?.visual_bridge,
506
+ existingManifest?.visual_bridge,
507
+ );
508
+ const manifest = {
509
+ schema_version: Number(rawManifest?.schema_version || existingManifest?.schema_version || 1),
510
+ manifest_type: rawManifest?.manifest_type || existingManifest?.manifest_type || 'hub-install',
511
+ name: rawManifest?.name || existingManifest?.name || null,
512
+ description: rawManifest?.description || existingManifest?.description || null,
513
+ version: rawManifest?.version || existingManifest?.version || null,
514
+ profile: resolvedProfile,
515
+ ides: normalizeIdes(options.ide || rawManifest?.ides || existingManifest?.ides || 'default'),
516
+ scenario_packages: normalizeList(rawManifest?.scenario_packages || existingManifest?.scenario_packages),
517
+ roles: normalizeList(rawManifest?.roles || existingManifest?.roles),
518
+ skills: normalizeList(rawManifest?.skills || existingManifest?.skills),
519
+ rules: normalizeList(rawManifest?.rules || existingManifest?.rules),
520
+ entry_role: rawManifest?.entry_role || existingManifest?.entry_role || null,
521
+ tags: normalizeList(rawManifest?.tags || existingManifest?.tags),
522
+ constraints: rawManifest?.constraints || existingManifest?.constraints || null,
523
+ notes: normalizeList(rawManifest?.notes || existingManifest?.notes),
524
+ sources: Array.isArray(rawManifest?.sources) ? rawManifest.sources : Array.isArray(existingManifest?.sources) ? existingManifest.sources : [],
525
+ };
526
+ if (normalizedSuperpowers) {
527
+ manifest.superpowers = normalizedSuperpowers;
528
+ }
529
+ if (normalizedVisualBridge) {
530
+ manifest.visual_bridge = normalizedVisualBridge;
531
+ }
532
+ const localPreferences = rawLocalPreferences !== null ? rawLocalPreferences : existingLocalPreferences;
533
+ if (localPreferences) {
534
+ manifest.local_preferences = localPreferences;
535
+ }
536
+
537
+ if (!manifest.profile) {
538
+ if (!rawProfile) {
539
+ throw new Error('Manifest is missing profile(技术栈)');
540
+ }
541
+ throw new Error(`Unsupported profile: ${rawProfile}. Supported profiles: ${formatSupportedProfiles(profilesRegistry)}`);
542
+ }
543
+
544
+ return manifest;
545
+ }
546
+
547
+ async function loadManifestInput(manifestInput, timeoutMs = DEFAULT_REMOTE_MANIFEST_TIMEOUT_MS) {
548
+ if (isHttpUrl(manifestInput)) {
549
+ const controller = new AbortController();
550
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
551
+ try {
552
+ const response = await fetch(manifestInput, {
553
+ signal: controller.signal,
554
+ headers: {
555
+ accept: 'application/json',
556
+ },
557
+ });
558
+ if (!response.ok) {
559
+ throw new Error(`Remote manifest request failed with status ${response.status} ${response.statusText}: ${manifestInput}`);
560
+ }
561
+ const rawText = await response.text();
562
+ try {
563
+ return {
564
+ manifestSource: manifestInput,
565
+ rawManifest: JSON.parse(rawText),
566
+ };
567
+ } catch (error) {
568
+ throw new Error(`Remote manifest is not valid JSON: ${manifestInput}`);
569
+ }
570
+ } catch (error) {
571
+ if (error.name === 'AbortError') {
572
+ throw new Error(`Remote manifest request timed out after ${timeoutMs}ms: ${manifestInput}`);
573
+ }
574
+ if (error.message && error.message.startsWith('Remote manifest')) {
575
+ throw error;
576
+ }
577
+ throw new Error(`Failed to fetch remote manifest: ${manifestInput} (${error.message})`);
578
+ } finally {
579
+ clearTimeout(timer);
580
+ }
581
+ }
582
+
583
+ const manifestPath = path.resolve(manifestInput);
584
+ if (!fs.existsSync(manifestPath)) {
585
+ throw new Error(`Manifest file not found: ${manifestPath}`);
586
+ }
587
+
588
+ return {
589
+ manifestSource: manifestPath,
590
+ rawManifest: readJsonFile(manifestPath, 'Manifest'),
591
+ };
592
+ }
593
+
594
+ function sha256Json(value) {
595
+ return `sha256:${crypto.createHash('sha256').update(JSON.stringify(value)).digest('hex')}`;
596
+ }
597
+
598
+ function hashDirectory(dirPath) {
599
+ if (!fs.existsSync(dirPath)) {
600
+ return null;
601
+ }
602
+ const hash = crypto.createHash('sha256');
603
+ const files = walkFiles(dirPath, () => true);
604
+ for (const filePath of files) {
605
+ const rel = toPosix(path.relative(dirPath, filePath));
606
+ hash.update(rel);
607
+ hash.update(fs.readFileSync(filePath));
608
+ }
609
+ return hash.digest('hex');
610
+ }
611
+
612
+ function unique(values) {
613
+ return [...new Set(values.filter(Boolean))];
614
+ }
615
+
616
+ function resolveManifest(manifest, catalogs, options = {}) {
617
+ const allowMissing = options.allowMissing === true;
618
+ const warnings = [];
619
+ const roleIds = new Set(manifest.roles);
620
+ const skillIds = new Set(manifest.skills);
621
+ const ruleIds = new Set(manifest.rules);
622
+ for (const scenarioId of manifest.scenario_packages || []) {
623
+ const scenarioEntry = catalogs.scenarioPackages?.get(scenarioId);
624
+ if (!scenarioEntry) {
625
+ continue;
626
+ }
627
+ for (const roleId of scenarioEntry.roles || []) roleIds.add(roleId);
628
+ for (const skillId of scenarioEntry.skills || []) skillIds.add(skillId);
629
+ for (const ruleId of scenarioEntry.rules || []) ruleIds.add(ruleId);
630
+ if (manifest.superpowers?.enabled && ['frontend-basic', 'bugfix-to-verification'].includes(scenarioId)) {
631
+ ruleIds.add('superpowers-standard');
632
+ }
633
+ }
634
+ const domains = new Set();
635
+ const missing = {
636
+ roles: [],
637
+ skills: [],
638
+ rules: [],
639
+ };
640
+
641
+ const resolvedRoles = [];
642
+ for (const roleId of roleIds) {
643
+ const entry = catalogs.roles.get(roleId);
644
+ if (!entry) {
645
+ if (allowMissing) {
646
+ missing.roles.push(roleId);
647
+ continue;
648
+ }
649
+ throw new Error(`Unknown role(专家角色) id: ${roleId}`);
650
+ }
651
+ resolvedRoles.push(entry);
652
+ for (const domain of entry.domains || []) domains.add(domain);
653
+ }
654
+
655
+ const resolvedSkills = [];
656
+ for (const skillId of skillIds) {
657
+ const normalizedSkillId = normalizeLegacyProfileScopedSkillId(skillId, manifest.profile, catalogs.skills, warnings);
658
+ const entry = resolveSkill(normalizedSkillId, manifest.profile, catalogs.skills);
659
+ if (!entry) {
660
+ if (allowMissing) {
661
+ missing.skills.push(skillId);
662
+ continue;
663
+ }
664
+ throw new Error(`Unknown skill(技能) id for profile "${manifest.profile}": ${skillId}`);
665
+ }
666
+ resolvedSkills.push(entry);
667
+ for (const domain of entry.domains || []) domains.add(domain);
668
+ }
669
+
670
+ const resolvedRules = [];
671
+ for (const ruleId of ruleIds) {
672
+ const normalizedRuleId = normalizeLegacyRuleId(ruleId, manifest.profile, catalogs.rules, warnings);
673
+ const entry = resolveRule(normalizedRuleId, manifest.profile, catalogs.rules);
674
+ if (!entry) {
675
+ if (allowMissing) {
676
+ missing.rules.push(ruleId);
677
+ continue;
678
+ }
679
+ throw new Error(`Unknown rule(规则) id for profile "${manifest.profile}": ${ruleId}`);
680
+ }
681
+ resolvedRules.push(entry);
682
+ for (const domain of entry.domains || []) domains.add(domain);
683
+ }
684
+
685
+ const installedFlows = [...catalogs.flows.values()]
686
+ .filter((entry) => entry.status === 'active')
687
+ .map((entry) => entry.id);
688
+
689
+ if (!manifest.entry_role) {
690
+ manifest.entry_role = resolvedRoles.some((entry) => entry.id === 'task-orchestrator')
691
+ ? 'task-orchestrator'
692
+ : resolvedRoles[0]?.id || null;
693
+ }
694
+
695
+ if (!allowMissing && manifest.entry_role && !resolvedRoles.some((entry) => entry.id === manifest.entry_role)) {
696
+ throw new Error(`entry_role(默认入口角色) is not included in resolved roles: ${manifest.entry_role}`);
697
+ }
698
+
699
+ return {
700
+ warnings,
701
+ missing,
702
+ resolved: {
703
+ domains: unique([...domains]),
704
+ installed_flows: installedFlows,
705
+ roles: resolvedRoles,
706
+ skills: resolvedSkills,
707
+ rules: resolvedRules,
708
+ },
709
+ };
710
+ }
711
+
712
+ function hasMissingAssets(resolvedResult) {
713
+ return resolvedResult.missing.roles.length > 0 ||
714
+ resolvedResult.missing.skills.length > 0 ||
715
+ resolvedResult.missing.rules.length > 0;
716
+ }
717
+
718
+ function normalizeOrigin(value) {
719
+ if (!value) return null;
720
+ try {
721
+ return new URL(String(value)).origin;
722
+ } catch (error) {
723
+ throw new Error(`Invalid hub origin: ${value}`);
724
+ }
725
+ }
726
+
727
+ function resolveHubOrigin(options, manifestSource) {
728
+ if (options.hubFetch === false) {
729
+ return null;
730
+ }
731
+ if (options.hubOrigin) {
732
+ return normalizeOrigin(options.hubOrigin);
733
+ }
734
+ if (isHttpUrl(manifestSource)) {
735
+ return new URL(manifestSource).origin;
736
+ }
737
+ return null;
738
+ }
739
+
740
+ function mergeRegistryEntries(localEntries, remoteEntries) {
741
+ return {
742
+ ...(remoteEntries || {}),
743
+ ...(localEntries || {}),
744
+ };
745
+ }
746
+
747
+ function mergeSupportFiles(localFiles, remoteFiles) {
748
+ return unique([...(remoteFiles || []), ...(localFiles || [])]);
749
+ }
750
+
751
+ function buildRoleRegistryForSource(rawRegistry, sourceRoot, metadataMap) {
752
+ return {
753
+ version: rawRegistry.version || 1,
754
+ support_files: [...(rawRegistry.support_files || [])],
755
+ __sourceRoot: sourceRoot,
756
+ roles: Object.fromEntries(
757
+ Object.entries(rawRegistry.roles || {}).map(([id, entry]) => {
758
+ const meta = metadataMap.get(id);
759
+ return [id, {
760
+ ...entry,
761
+ ...(meta || {}),
762
+ }];
763
+ }),
764
+ ),
765
+ };
766
+ }
767
+
768
+ function buildRuleRegistryForSource(rawRegistry, sourceRoot, metadataMap) {
769
+ return Object.fromEntries(
770
+ Object.entries(rawRegistry || {}).map(([id, entry]) => {
771
+ const meta = metadataMap.get(id);
772
+ return [id, {
773
+ ...entry,
774
+ __sourceRoot: sourceRoot,
775
+ ...(meta || {}),
776
+ }];
777
+ }),
778
+ );
779
+ }
780
+
781
+ function buildFlowRegistryForSource(rawRegistry, sourceRoot) {
782
+ return {
783
+ version: rawRegistry.version || 1,
784
+ support_files: [...(rawRegistry.support_files || [])],
785
+ __sourceRoot: sourceRoot,
786
+ flows: Object.fromEntries(
787
+ Object.entries(rawRegistry.flows || {}).map(([id, entry]) => [id, {
788
+ ...entry,
789
+ }]),
790
+ ),
791
+ };
792
+ }
793
+
794
+ function createAssetMetadataMap(items, kind, requestUrl, origin) {
795
+ const map = new Map();
796
+ for (const item of items || []) {
797
+ const id = String(item.registryId || '').trim();
798
+ if (!id) continue;
799
+ map.set(id, {
800
+ __sourceType: 'hub',
801
+ __sourceRef: `${requestUrl}#${kind}:${id}`,
802
+ __sourceOrigin: origin,
803
+ __version: String(item.version || 'published'),
804
+ __hubSlug: item.hubSlug || null,
805
+ });
806
+ }
807
+ return map;
808
+ }
809
+
810
+ function mergeMapCatalog(localMap, remoteMap) {
811
+ return new Map([
812
+ ...remoteMap.entries(),
813
+ ...localMap.entries(),
814
+ ]);
815
+ }
816
+
817
+ function mergeSkillCatalog(localCatalog, remoteCatalog) {
818
+ const profiles = {};
819
+ const profileIds = unique([
820
+ ...Object.keys(localCatalog.profiles || {}),
821
+ ...Object.keys(remoteCatalog.profiles || {}),
822
+ ]);
823
+ for (const profileId of profileIds) {
824
+ profiles[profileId] = mergeMapCatalog(
825
+ localCatalog.profiles?.[profileId] || new Map(),
826
+ remoteCatalog.profiles?.[profileId] || new Map(),
827
+ );
828
+ }
829
+
830
+ return {
831
+ common: mergeMapCatalog(localCatalog.common, remoteCatalog.common),
832
+ profiles,
833
+ domains: mergeMapCatalog(localCatalog.domains, remoteCatalog.domains),
834
+ };
835
+ }
836
+
837
+ function extractZipArchive(zipPath, destDir) {
838
+ let result;
839
+ if (process.platform === 'win32') {
840
+ result = spawnSync('powershell', [
841
+ '-NoProfile',
842
+ '-Command',
843
+ `Expand-Archive -LiteralPath '${zipPath.replace(/'/g, "''")}' -DestinationPath '${destDir.replace(/'/g, "''")}' -Force`,
844
+ ], {
845
+ encoding: 'utf8',
846
+ });
847
+ } else {
848
+ result = spawnSync('unzip', ['-qq', zipPath, '-d', destDir], {
849
+ encoding: 'utf8',
850
+ });
851
+ }
852
+
853
+ if (result.error) {
854
+ throw new Error(`Failed to extract Hub supplement zip: ${result.error.message}`);
855
+ }
856
+ if (result.status !== 0) {
857
+ throw new Error(`Failed to extract Hub supplement zip: ${(result.stderr || result.stdout || '').trim() || 'unknown unzip error'}`);
858
+ }
859
+ }
860
+
861
+ function readOptionalJson(filePath, label) {
862
+ if (!fs.existsSync(filePath)) {
863
+ return null;
864
+ }
865
+ return readJsonFile(filePath, label);
866
+ }
867
+
868
+ async function fetchHubSupplement(origin, manifest, missing, timeoutMs) {
869
+ const requestUrl = `${origin.replace(/\/$/, '')}/api/install/supplement-export`;
870
+ const payload = {
871
+ profile: manifest.profile,
872
+ ides: manifest.ides,
873
+ roles: missing.roles,
874
+ skills: missing.skills,
875
+ rules: missing.rules,
876
+ };
877
+ const controller = new AbortController();
878
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
879
+ try {
880
+ const response = await fetch(requestUrl, {
881
+ method: 'POST',
882
+ signal: controller.signal,
883
+ headers: {
884
+ accept: 'application/zip',
885
+ 'content-type': 'application/json',
886
+ },
887
+ body: JSON.stringify(payload),
888
+ });
889
+ if (!response.ok) {
890
+ const text = await response.text().catch(() => '');
891
+ throw new Error(`Hub supplement request failed with status ${response.status} ${response.statusText}${text ? `: ${text}` : ''}`);
892
+ }
893
+
894
+ const bytes = Buffer.from(await response.arrayBuffer());
895
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ai-spec-hub-supplement-'));
896
+ const zipPath = path.join(tempDir, 'supplement.zip');
897
+ const extractDir = path.join(tempDir, 'bundle');
898
+ ensureDir(extractDir);
899
+ fs.writeFileSync(zipPath, bytes);
900
+ extractZipArchive(zipPath, extractDir);
901
+
902
+ const report = readOptionalJson(path.join(extractDir, 'export-report.json'), 'Hub supplement report') || {
903
+ warnings: [],
904
+ assets: { roles: [], skills: [], rules: [] },
905
+ };
906
+ return {
907
+ origin,
908
+ requestUrl,
909
+ tempDir,
910
+ extractDir,
911
+ report,
912
+ };
913
+ } catch (error) {
914
+ if (error.name === 'AbortError') {
915
+ throw new Error(`Hub supplement request timed out after ${timeoutMs}ms: ${requestUrl}`);
916
+ }
917
+ if (error.message && error.message.startsWith('Hub supplement')) {
918
+ throw error;
919
+ }
920
+ throw new Error(`Failed to fetch Hub supplement: ${requestUrl} (${error.message})`);
921
+ } finally {
922
+ clearTimeout(timer);
923
+ }
924
+ }
925
+
926
+ function buildSupplementState(supplement) {
927
+ const supplementRoot = supplement.extractDir;
928
+ const supplementRegistry = loadSyncRegistry(supplementRoot);
929
+ const roleMetadata = createAssetMetadataMap(supplement.report.assets?.roles, 'role', supplement.requestUrl, supplement.origin);
930
+ const skillMetadata = createAssetMetadataMap(supplement.report.assets?.skills, 'skill', supplement.requestUrl, supplement.origin);
931
+ const ruleMetadata = createAssetMetadataMap(supplement.report.assets?.rules, 'rule', supplement.requestUrl, supplement.origin);
932
+
933
+ const roleRegistry = buildRoleRegistryForSource(supplementRegistry.roles, supplementRoot, roleMetadata);
934
+ const flowRegistry = buildFlowRegistryForSource(supplementRegistry.flows, supplementRoot);
935
+ const ruleRegistry = buildRuleRegistryForSource(supplementRegistry.rules, supplementRoot, ruleMetadata);
936
+
937
+ return {
938
+ registry: {
939
+ roles: roleRegistry,
940
+ rules: ruleRegistry,
941
+ flows: flowRegistry,
942
+ },
943
+ catalogs: {
944
+ roles: readRoleCatalog(roleRegistry),
945
+ skills: readSkillCatalog(supplementRoot, skillMetadata),
946
+ rules: readRuleCatalog(ruleRegistry),
947
+ flows: readFlowCatalog(flowRegistry),
948
+ },
949
+ warnings: normalizeList(supplement.report.warnings),
950
+ };
951
+ }
952
+
953
+ function mergePreparedState(prepared, supplement, supplementState) {
954
+ const mergedRoleRegistry = {
955
+ ...prepared.registry.roles,
956
+ support_files: mergeSupportFiles(prepared.registry.roles.support_files, supplementState.registry.roles.support_files),
957
+ roles: mergeRegistryEntries(prepared.registry.roles.roles, supplementState.registry.roles.roles),
958
+ };
959
+ const mergedRuleRegistry = mergeRegistryEntries(prepared.registry.rules, supplementState.registry.rules);
960
+ const mergedFlowRegistry = {
961
+ ...prepared.registry.flows,
962
+ support_files: mergeSupportFiles(prepared.registry.flows.support_files, supplementState.registry.flows.support_files),
963
+ flows: mergeRegistryEntries(prepared.registry.flows.flows, supplementState.registry.flows.flows),
964
+ };
965
+
966
+ return {
967
+ ...prepared,
968
+ registry: {
969
+ roles: mergedRoleRegistry,
970
+ rules: mergedRuleRegistry,
971
+ flows: mergedFlowRegistry,
972
+ },
973
+ catalogs: {
974
+ roles: mergeMapCatalog(prepared.catalogs.roles, supplementState.catalogs.roles),
975
+ skills: mergeSkillCatalog(prepared.catalogs.skills, supplementState.catalogs.skills),
976
+ rules: mergeMapCatalog(prepared.catalogs.rules, supplementState.catalogs.rules),
977
+ flows: prepared.catalogs.flows,
978
+ },
979
+ supplements: [...(prepared.supplements || []), supplement],
980
+ };
981
+ }
982
+
983
+ function readExistingManifest(targetDir) {
984
+ const manifestPath = path.join(targetDir, '.ai-spec/manifest.json');
985
+ if (!fs.existsSync(manifestPath)) {
986
+ return null;
987
+ }
988
+ return readJsonFile(manifestPath, 'Existing manifest');
989
+ }
990
+
991
+ function writeJsonTracked(targetDir, filePath, value, changes) {
992
+ const content = `${JSON.stringify(value, null, 2)}\n`;
993
+ return writeTextTracked(targetDir, filePath, content, changes);
994
+ }
995
+
996
+ function writeTextTracked(targetDir, filePath, content, changes) {
997
+ ensureDir(path.dirname(filePath));
998
+ const rel = targetRel(targetDir, filePath);
999
+ if (!fs.existsSync(filePath)) {
1000
+ fs.writeFileSync(filePath, content, 'utf8');
1001
+ changes.created.push(rel);
1002
+ return;
1003
+ }
1004
+ const current = fs.readFileSync(filePath, 'utf8');
1005
+ if (current === content) {
1006
+ if (!changes.skipped.includes(rel)) {
1007
+ changes.skipped.push(rel);
1008
+ }
1009
+ return;
1010
+ }
1011
+ fs.writeFileSync(filePath, content, 'utf8');
1012
+ changes.updated.push(rel);
1013
+ }
1014
+
1015
+ function copyFileTracked(sourceDir, targetDir, sourceRel, destRel, changes) {
1016
+ const sourcePath = path.join(sourceDir, sourceRel);
1017
+ const destPath = path.join(targetDir, destRel);
1018
+ const content = fs.readFileSync(sourcePath);
1019
+ ensureDir(path.dirname(destPath));
1020
+ const rel = targetRel(targetDir, destPath);
1021
+
1022
+ if (!fs.existsSync(destPath)) {
1023
+ fs.writeFileSync(destPath, content);
1024
+ changes.created.push(rel);
1025
+ return;
1026
+ }
1027
+
1028
+ const current = fs.readFileSync(destPath);
1029
+ if (Buffer.compare(current, content) === 0) {
1030
+ if (!changes.skipped.includes(rel)) {
1031
+ changes.skipped.push(rel);
1032
+ }
1033
+ return;
1034
+ }
1035
+
1036
+ fs.writeFileSync(destPath, content);
1037
+ changes.updated.push(rel);
1038
+ }
1039
+
1040
+ function copyRenderedCommandTracked(sourceDir, targetDir, sourceRel, destRel, changes) {
1041
+ const sourcePath = path.join(sourceDir, sourceRel);
1042
+ const destPath = path.join(targetDir, destRel);
1043
+ const content = readRenderedCommandTemplate(sourcePath, {
1044
+ forceLocalProtocol: process.env.ENGINEERED_SPEC_FORCE_LOCAL_CLI === '1',
1045
+ });
1046
+ ensureDir(path.dirname(destPath));
1047
+ const rel = targetRel(targetDir, destPath);
1048
+
1049
+ if (!fs.existsSync(destPath)) {
1050
+ fs.writeFileSync(destPath, content, 'utf8');
1051
+ changes.created.push(rel);
1052
+ return;
1053
+ }
1054
+
1055
+ const current = fs.readFileSync(destPath, 'utf8');
1056
+ if (current === content) {
1057
+ if (!changes.skipped.includes(rel)) {
1058
+ changes.skipped.push(rel);
1059
+ }
1060
+ return;
1061
+ }
1062
+
1063
+ fs.writeFileSync(destPath, content, 'utf8');
1064
+ changes.updated.push(rel);
1065
+ }
1066
+
1067
+ function copyFileIfMissingTracked(sourceDir, targetDir, sourceRel, destRel, changes) {
1068
+ const sourcePath = path.join(sourceDir, sourceRel);
1069
+ const destPath = path.join(targetDir, destRel);
1070
+ const rel = targetRel(targetDir, destPath);
1071
+ if (fs.existsSync(destPath)) {
1072
+ if (!changes.skipped.includes(rel)) {
1073
+ changes.skipped.push(rel);
1074
+ }
1075
+ return;
1076
+ }
1077
+ const content = fs.readFileSync(sourcePath);
1078
+ ensureDir(path.dirname(destPath));
1079
+ fs.writeFileSync(destPath, content);
1080
+ changes.created.push(rel);
1081
+ }
1082
+
1083
+ function copyDirectoryTracked(sourceDir, targetDir, sourceDirRel, destDirRel, changes) {
1084
+ const sourcePath = path.join(sourceDir, sourceDirRel);
1085
+ const destPath = path.join(targetDir, destDirRel);
1086
+ const existsBefore = fs.existsSync(destPath);
1087
+ const sourceHash = hashDirectory(sourcePath);
1088
+ const destHash = existsBefore ? hashDirectory(destPath) : null;
1089
+ const rel = targetRel(targetDir, destPath);
1090
+
1091
+ if (existsBefore && sourceHash && destHash && sourceHash === destHash) {
1092
+ if (!changes.skipped.includes(rel)) {
1093
+ changes.skipped.push(rel);
1094
+ }
1095
+ return;
1096
+ }
1097
+
1098
+ fs.rmSync(destPath, { recursive: true, force: true });
1099
+ ensureDir(path.dirname(destPath));
1100
+ fs.cpSync(sourcePath, destPath, { recursive: true });
1101
+ if (existsBefore) {
1102
+ if (!changes.updated.includes(rel)) {
1103
+ changes.updated.push(rel);
1104
+ }
1105
+ } else {
1106
+ if (!changes.created.includes(rel)) {
1107
+ changes.created.push(rel);
1108
+ }
1109
+ }
1110
+ }
1111
+
1112
+ function isManagedPruneAsset(asset) {
1113
+ if (!asset || typeof asset !== 'object') return false;
1114
+ const rel = String(asset.local_path || '').trim();
1115
+ if (!rel) return false;
1116
+ if (rel === '.agents' || rel.startsWith('.agents/roles/') || rel.startsWith('.agents/skills/') || rel.startsWith('.agents/rules/')) {
1117
+ return true;
1118
+ }
1119
+ if (rel === SUPERPOWERS_STATE_REL_PATH) {
1120
+ return true;
1121
+ }
1122
+ if (rel === VISUAL_BRIDGE_STATE_REL_PATH) {
1123
+ return true;
1124
+ }
1125
+ if (/^\.(claude|cursor|codex|opencode|trae)\/rules$/.test(rel)) {
1126
+ return true;
1127
+ }
1128
+ if (/^\.(claude|cursor|codex|opencode|trae)\/skills\/[^/]+$/.test(rel)) {
1129
+ return true;
1130
+ }
1131
+ if (/^\.(claude|cursor|codex|opencode|trae)\/commands\/[^/]+\.md$/.test(rel)) {
1132
+ return true;
1133
+ }
1134
+ if (/^\.codex\/commands\/[^/]+\.md$/.test(rel)) {
1135
+ return true;
1136
+ }
1137
+ return false;
1138
+ }
1139
+
1140
+ function readPreviousSources(targetDir) {
1141
+ const sourcesPath = path.join(targetDir, '.ai-spec', 'sources.json');
1142
+ if (!fs.existsSync(sourcesPath)) {
1143
+ return null;
1144
+ }
1145
+ return readJsonFile(sourcesPath, 'Existing sources');
1146
+ }
1147
+
1148
+ function collectManagedPathsFromSources(sources) {
1149
+ const managed = new Set();
1150
+ for (const asset of Array.isArray(sources?.assets) ? sources.assets : []) {
1151
+ if (!isManagedPruneAsset(asset)) {
1152
+ continue;
1153
+ }
1154
+ managed.add(asset.local_path);
1155
+ }
1156
+ return managed;
1157
+ }
1158
+
1159
+ function sortPathsForRemoval(paths) {
1160
+ return [...paths].sort((left, right) => {
1161
+ const leftDepth = left.split('/').length;
1162
+ const rightDepth = right.split('/').length;
1163
+ if (leftDepth !== rightDepth) {
1164
+ return rightDepth - leftDepth;
1165
+ }
1166
+ return right.localeCompare(left);
1167
+ });
1168
+ }
1169
+
1170
+ function cleanupEmptyIdeDirs(targetDir, changes) {
1171
+ for (const ide of ALL_IDES) {
1172
+ const ideDir = path.join(targetDir, `.${ide}`);
1173
+ if (!fs.existsSync(ideDir)) {
1174
+ continue;
1175
+ }
1176
+ for (const child of ['commands', 'skills']) {
1177
+ const childDir = path.join(ideDir, child);
1178
+ if (fs.existsSync(childDir) && fs.readdirSync(childDir).filter((entry) => entry !== '.DS_Store').length === 0) {
1179
+ removePathTracked(targetDir, childDir, changes);
1180
+ }
1181
+ }
1182
+ const remaining = fs.readdirSync(ideDir).filter((entry) => entry !== '.DS_Store');
1183
+ if (remaining.length === 0) {
1184
+ removePathTracked(targetDir, ideDir, changes);
1185
+ }
1186
+ }
1187
+ }
1188
+
1189
+ function pruneManagedAssets(targetDir, previousSources, currentSources, changes) {
1190
+ const previousPaths = collectManagedPathsFromSources(previousSources);
1191
+ const currentPaths = collectManagedPathsFromSources(currentSources);
1192
+ const stalePaths = [...previousPaths].filter((item) => !currentPaths.has(item));
1193
+ for (const rel of sortPathsForRemoval(stalePaths)) {
1194
+ removePathTracked(targetDir, path.join(targetDir, rel), changes);
1195
+ }
1196
+ cleanupEmptyIdeDirs(targetDir, changes);
1197
+ }
1198
+
1199
+ function ensureSymlinkTracked(targetDir, linkPath, linkTarget, changes) {
1200
+ ensureDir(path.dirname(linkPath));
1201
+ const rel = targetRel(targetDir, linkPath);
1202
+ let existedBefore = false;
1203
+
1204
+ try {
1205
+ const stat = fs.lstatSync(linkPath);
1206
+ existedBefore = true;
1207
+ if (stat.isSymbolicLink()) {
1208
+ const currentTarget = fs.readlinkSync(linkPath);
1209
+ if (currentTarget === linkTarget) {
1210
+ if (!changes.skipped.includes(rel)) {
1211
+ changes.skipped.push(rel);
1212
+ }
1213
+ return;
1214
+ }
1215
+ fs.unlinkSync(linkPath);
1216
+ } else {
1217
+ fs.rmSync(linkPath, { recursive: true, force: true });
1218
+ }
1219
+ } catch (error) {
1220
+ existedBefore = false;
1221
+ }
1222
+
1223
+ fs.symlinkSync(linkTarget, linkPath);
1224
+ if (existedBefore) {
1225
+ if (!changes.updated.includes(rel)) {
1226
+ changes.updated.push(rel);
1227
+ }
1228
+ } else {
1229
+ if (!changes.created.includes(rel)) {
1230
+ changes.created.push(rel);
1231
+ }
1232
+ }
1233
+ }
1234
+
1235
+ function removePathTracked(targetDir, targetPath, changes) {
1236
+ const rel = targetRel(targetDir, targetPath);
1237
+ try {
1238
+ const stat = fs.lstatSync(targetPath);
1239
+ if (stat.isSymbolicLink()) {
1240
+ fs.unlinkSync(targetPath);
1241
+ } else {
1242
+ fs.rmSync(targetPath, { recursive: true, force: true });
1243
+ }
1244
+ if (!changes.updated.includes(rel) && !changes.created.includes(rel)) {
1245
+ changes.updated.push(rel);
1246
+ }
1247
+ } catch (error) {
1248
+ // Already absent; nothing to do.
1249
+ }
1250
+ }
1251
+
1252
+ function installRoles(targetDir, resolvedRoles, roleRegistry, changes) {
1253
+ for (const supportFile of roleRegistry.support_files || []) {
1254
+ copyFileTracked(roleRegistry.__sourceRoot, targetDir, supportFile, supportFile, changes);
1255
+ }
1256
+
1257
+ const copiedDomainReadmes = new Set();
1258
+ for (const role of resolvedRoles) {
1259
+ copyFileTracked(role.sourceRoot, targetDir, role.sourceRel, role.sourceRel, changes);
1260
+ const domainReadme = role.sourceRel.match(/^\.agents\/roles\/domains\/([^/]+)\//);
1261
+ if (domainReadme && role.sourceType === 'local') {
1262
+ const domainReadmeRel = `.agents/roles/domains/${domainReadme[1]}/README.md`;
1263
+ if (!copiedDomainReadmes.has(domainReadmeRel) && fs.existsSync(path.join(role.sourceRoot, domainReadmeRel))) {
1264
+ copyFileTracked(role.sourceRoot, targetDir, domainReadmeRel, domainReadmeRel, changes);
1265
+ copiedDomainReadmes.add(domainReadmeRel);
1266
+ }
1267
+ }
1268
+ }
1269
+ }
1270
+
1271
+ function installSkills(targetDir, resolvedSkills, changes) {
1272
+ if (resolvedSkills.some((item) => item.sourceType === 'local')) {
1273
+ const localRoot = resolvedSkills.find((item) => item.sourceType === 'local')?.sourceRoot;
1274
+ if (localRoot && fs.existsSync(path.join(localRoot, '.agents/skills/README.md'))) {
1275
+ copyFileTracked(localRoot, targetDir, '.agents/skills/README.md', '.agents/skills/README.md', changes);
1276
+ }
1277
+ }
1278
+ for (const skill of resolvedSkills) {
1279
+ copyDirectoryTracked(skill.sourceRoot, targetDir, skill.sourceDirRel, `.agents/skills/${skill.id}`, changes);
1280
+ }
1281
+ }
1282
+
1283
+ function installRules(targetDir, resolvedRules, changes) {
1284
+ const localRoot = resolvedRules.find((item) => item.sourceType === 'local')?.sourceRoot;
1285
+ if (localRoot && fs.existsSync(path.join(localRoot, '.agents/rules/README.md'))) {
1286
+ copyFileTracked(localRoot, targetDir, '.agents/rules/README.md', '.agents/rules/README.md', changes);
1287
+ }
1288
+ for (const rule of resolvedRules) {
1289
+ const destRel = getInstalledRulePath(rule);
1290
+ copyFileTracked(rule.sourceRoot, targetDir, rule.sourceRel, destRel, changes);
1291
+ }
1292
+ }
1293
+
1294
+ function getInstalledRulePath(rule) {
1295
+ const baseName = path.basename(rule.sourceRel || '');
1296
+ if (baseName && baseName !== 'RULE.md') {
1297
+ return `.agents/rules/${baseName}`;
1298
+ }
1299
+ return `.agents/rules/${rule.id}.md`;
1300
+ }
1301
+
1302
+ function installFlows(targetDir, catalogs, flowRegistry, changes) {
1303
+ for (const supportFile of flowRegistry.support_files || []) {
1304
+ copyFileTracked(flowRegistry.__sourceRoot, targetDir, supportFile, supportFile, changes);
1305
+ }
1306
+ for (const flow of catalogs.flows.values()) {
1307
+ copyFileTracked(flow.sourceRoot, targetDir, flow.sourceRel, flow.sourceRel, changes);
1308
+ }
1309
+ }
1310
+
1311
+ function installIdeAssets(sourceDir, targetDir, ides, resolvedSkills, changes, superpowersEnabled = false) {
1312
+ const commandsDir = path.join(sourceDir, '.agents/commands/common');
1313
+ const commandFiles = fs.existsSync(commandsDir)
1314
+ ? fs.readdirSync(commandsDir).filter((name) => name.endsWith('.md')).sort()
1315
+ : [];
1316
+
1317
+ for (const ide of ides) {
1318
+ const ideDir = path.join(targetDir, `.${ide}`);
1319
+ ensureDir(ideDir);
1320
+ ensureSymlinkTracked(targetDir, path.join(ideDir, 'rules'), '../.agents/rules', changes);
1321
+ ensureDir(path.join(ideDir, 'skills'));
1322
+
1323
+ for (const skill of resolvedSkills) {
1324
+ const linkPath = path.join(ideDir, 'skills', skill.id);
1325
+ if (!shouldExposeSkillToIde(skill.id, superpowersEnabled)) {
1326
+ removePathTracked(targetDir, linkPath, changes);
1327
+ continue;
1328
+ }
1329
+ const linkTarget = `../../.agents/skills/${skill.id}`;
1330
+ ensureSymlinkTracked(targetDir, linkPath, linkTarget, changes);
1331
+ }
1332
+
1333
+ for (const fileName of commandFiles) {
1334
+ copyRenderedCommandTracked(sourceDir, targetDir, `.agents/commands/common/${fileName}`, `.${ide}/commands/${fileName}`, changes);
1335
+ }
1336
+
1337
+ const ideCommandsDir = path.join(sourceDir, '.agents/commands', ide);
1338
+ const ideCommandFiles = fs.existsSync(ideCommandsDir)
1339
+ ? fs.readdirSync(ideCommandsDir).filter((name) => name.endsWith('.md')).sort()
1340
+ : [];
1341
+
1342
+ for (const fileName of ideCommandFiles) {
1343
+ copyRenderedCommandTracked(sourceDir, targetDir, `.agents/commands/${ide}/${fileName}`, `.${ide}/commands/${fileName}`, changes);
1344
+ }
1345
+
1346
+ if (ide === 'cursor') {
1347
+ const sourceMcp = path.join(sourceDir, '.cursor/mcp.json');
1348
+ if (fs.existsSync(sourceMcp)) {
1349
+ copyFileIfMissingTracked(sourceDir, targetDir, '.cursor/mcp.json', '.cursor/mcp.json', changes);
1350
+ }
1351
+ }
1352
+ }
1353
+ }
1354
+
1355
+ function buildLock(manifest, targetDir, manifestSource, resolved, cliVersion) {
1356
+ return {
1357
+ schema_version: 1,
1358
+ lock_type: 'local-install-lock',
1359
+ generated_at: new Date().toISOString(),
1360
+ target: {
1361
+ path: targetRel(targetDir, targetDir) || '.',
1362
+ profile: manifest.profile,
1363
+ ides: manifest.ides,
1364
+ },
1365
+ source: {
1366
+ manifest: manifestSource,
1367
+ manifest_type: manifest.manifest_type,
1368
+ },
1369
+ request: {
1370
+ scenario_packages: manifest.scenario_packages,
1371
+ roles: manifest.roles,
1372
+ skills: manifest.skills,
1373
+ rules: manifest.rules,
1374
+ superpowers: manifest.superpowers || null,
1375
+ },
1376
+ resolved: {
1377
+ domains: resolved.domains,
1378
+ installed_flows: resolved.installed_flows,
1379
+ roles: resolved.roles.map((item) => item.id),
1380
+ skills: resolved.skills.map((item) => item.id),
1381
+ rules: resolved.rules.map((item) => item.id),
1382
+ },
1383
+ assets: {
1384
+ roles: resolved.roles.map((item) => ({
1385
+ id: item.id,
1386
+ version: item.version || 'workspace',
1387
+ source_type: item.sourceType || 'local',
1388
+ ...(item.hubSlug ? { hub_slug: item.hubSlug } : {}),
1389
+ })),
1390
+ skills: resolved.skills.map((item) => ({
1391
+ id: item.id,
1392
+ version: item.version || 'workspace',
1393
+ source_type: item.sourceType || 'local',
1394
+ ...(item.hubSlug ? { hub_slug: item.hubSlug } : {}),
1395
+ })),
1396
+ rules: resolved.rules.map((item) => ({
1397
+ id: item.id,
1398
+ version: item.version || 'workspace',
1399
+ source_type: item.sourceType || 'local',
1400
+ ...(item.hubSlug ? { hub_slug: item.hubSlug } : {}),
1401
+ })),
1402
+ flows: resolved.installed_flows.map((id) => ({ id, version: 'workspace' })),
1403
+ superpowers: manifest.superpowers
1404
+ ? {
1405
+ enabled: manifest.superpowers.enabled,
1406
+ preferred_mode: manifest.superpowers.preferred_mode,
1407
+ codex_entry: manifest.superpowers.codex_entry,
1408
+ }
1409
+ : null,
1410
+ visual_bridge: manifest.visual_bridge
1411
+ ? {
1412
+ enabled: manifest.visual_bridge.enabled,
1413
+ server_url: manifest.visual_bridge.server_url,
1414
+ workspace_id: manifest.visual_bridge.workspace_id,
1415
+ agent_id: manifest.visual_bridge.agent_id,
1416
+ push_on_runtime_state: manifest.visual_bridge.push_on_runtime_state,
1417
+ push_on_sync: manifest.visual_bridge.push_on_sync,
1418
+ fail_open: manifest.visual_bridge.fail_open,
1419
+ }
1420
+ : null,
1421
+ },
1422
+ installer: {
1423
+ command: 'ai-spec-auto sync',
1424
+ cli_version: cliVersion,
1425
+ mode: 'normal',
1426
+ },
1427
+ integrity: {
1428
+ manifest_hash: sha256Json(manifest),
1429
+ resolved_hash: sha256Json({
1430
+ domains: resolved.domains,
1431
+ installed_flows: resolved.installed_flows,
1432
+ roles: resolved.roles.map((item) => item.id),
1433
+ skills: resolved.skills.map((item) => item.id),
1434
+ rules: resolved.rules.map((item) => item.id),
1435
+ superpowers: manifest.superpowers || null,
1436
+ }),
1437
+ },
1438
+ status: 'success',
1439
+ };
1440
+ }
1441
+
1442
+ function buildSources(manifest, manifestSource, resolved, sourceDir) {
1443
+ const assets = [];
1444
+
1445
+ assets.push({
1446
+ kind: 'superpowers-config',
1447
+ id: 'project-superpowers',
1448
+ source_type: 'local',
1449
+ source_ref: `local://${SUPERPOWERS_STATE_REL_PATH}`,
1450
+ local_path: SUPERPOWERS_STATE_REL_PATH,
1451
+ });
1452
+ if (manifest.visual_bridge) {
1453
+ assets.push({
1454
+ kind: 'visual-bridge-config',
1455
+ id: 'project-visual-bridge',
1456
+ source_type: 'local',
1457
+ source_ref: `local://${VISUAL_BRIDGE_STATE_REL_PATH}`,
1458
+ local_path: VISUAL_BRIDGE_STATE_REL_PATH,
1459
+ });
1460
+ }
1461
+
1462
+ for (const role of resolved.roles) {
1463
+ assets.push({
1464
+ kind: 'role',
1465
+ id: role.id,
1466
+ source_type: role.sourceType || 'local',
1467
+ source_ref: role.sourceRef || `local://${role.sourceRel}`,
1468
+ local_path: role.sourceRel,
1469
+ ...(role.hubSlug ? { hub_slug: role.hubSlug } : {}),
1470
+ ...(role.version ? { version: role.version } : {}),
1471
+ });
1472
+ }
1473
+
1474
+ for (const skill of resolved.skills) {
1475
+ assets.push({
1476
+ kind: 'skill',
1477
+ id: skill.id,
1478
+ source_type: skill.sourceType || 'local',
1479
+ source_ref: skill.sourceRef || `local://${skill.sourceDirRel}`,
1480
+ local_path: `.agents/skills/${skill.id}`,
1481
+ ...(skill.hubSlug ? { hub_slug: skill.hubSlug } : {}),
1482
+ ...(skill.version ? { version: skill.version } : {}),
1483
+ });
1484
+ }
1485
+
1486
+ for (const rule of resolved.rules) {
1487
+ assets.push({
1488
+ kind: 'rule',
1489
+ id: rule.id,
1490
+ source_type: rule.sourceType || 'local',
1491
+ source_ref: rule.sourceRef || `local://${rule.sourceRel}`,
1492
+ local_path: getInstalledRulePath(rule),
1493
+ ...(rule.hubSlug ? { hub_slug: rule.hubSlug } : {}),
1494
+ ...(rule.version ? { version: rule.version } : {}),
1495
+ });
1496
+ }
1497
+
1498
+ for (const flowId of resolved.installed_flows) {
1499
+ assets.push({
1500
+ kind: 'flow',
1501
+ id: flowId,
1502
+ source_type: 'local',
1503
+ source_ref: `local://.agents/flows/common/${flowId}.md`,
1504
+ local_path: `.agents/flows/common/${flowId}.md`,
1505
+ });
1506
+ }
1507
+
1508
+ for (const ide of manifest.ides || []) {
1509
+ assets.push({
1510
+ kind: 'ide-rule-link',
1511
+ id: `${ide}:rules`,
1512
+ source_type: 'local',
1513
+ source_ref: `local://.${ide}/rules`,
1514
+ local_path: `.${ide}/rules`,
1515
+ });
1516
+ }
1517
+
1518
+ for (const ide of manifest.ides || []) {
1519
+ for (const skill of resolved.skills) {
1520
+ if (!shouldExposeSkillToIde(skill.id, manifest.superpowers?.enabled)) {
1521
+ continue;
1522
+ }
1523
+ assets.push({
1524
+ kind: 'ide-skill-link',
1525
+ id: `${ide}:${skill.id}`,
1526
+ source_type: 'local',
1527
+ source_ref: `local://.${ide}/skills/${skill.id}`,
1528
+ local_path: `.${ide}/skills/${skill.id}`,
1529
+ });
1530
+ if (skill.id === 'using-superpowers') {
1531
+ assets.push({
1532
+ kind: 'ide-superpowers-entry',
1533
+ id: `${ide}:${skill.id}`,
1534
+ source_type: 'local',
1535
+ source_ref: `local://.${ide}/skills/${skill.id}`,
1536
+ local_path: `.${ide}/skills/${skill.id}`,
1537
+ });
1538
+ }
1539
+ }
1540
+ }
1541
+
1542
+ for (const ide of manifest.ides || []) {
1543
+ const commonCommandsDir = path.join(sourceDir, '.agents', 'commands', 'common');
1544
+ const ideCommandsDir = path.join(sourceDir, '.agents', 'commands', ide);
1545
+ const commandFiles = unique([
1546
+ ...walkFiles(commonCommandsDir, (filePath) => filePath.endsWith('.md')).map((filePath) => path.basename(filePath)),
1547
+ ...walkFiles(ideCommandsDir, (filePath) => filePath.endsWith('.md')).map((filePath) => path.basename(filePath)),
1548
+ ]);
1549
+ for (const fileName of commandFiles) {
1550
+ assets.push({
1551
+ kind: 'ide-command-template',
1552
+ id: `${ide}:${fileName}`,
1553
+ source_type: 'local',
1554
+ source_ref: `local://.${ide}/commands/${fileName}`,
1555
+ local_path: `.${ide}/commands/${fileName}`,
1556
+ });
1557
+ }
1558
+ }
1559
+
1560
+ if (manifest.superpowers?.enabled && (manifest.ides || []).includes('codex')) {
1561
+ assets.push({
1562
+ kind: 'codex-agents-bridge',
1563
+ id: 'codex:agents-md',
1564
+ source_type: 'local',
1565
+ source_ref: 'local://AGENTS.md',
1566
+ local_path: 'AGENTS.md',
1567
+ });
1568
+ }
1569
+
1570
+ return {
1571
+ schema_version: 1,
1572
+ sources_type: 'local-install-sources',
1573
+ generated_at: new Date().toISOString(),
1574
+ manifest: {
1575
+ type: manifest.manifest_type,
1576
+ source: manifestSource,
1577
+ },
1578
+ registries: [
1579
+ {
1580
+ type: 'local-workspace',
1581
+ name: 'ai-spec-auto-local',
1582
+ path: sourceDir,
1583
+ },
1584
+ ...unique(
1585
+ [...resolved.roles, ...resolved.skills, ...resolved.rules]
1586
+ .map((item) => item.sourceOrigin || '')
1587
+ .filter(Boolean),
1588
+ ).map((origin) => ({
1589
+ type: 'hub-supplement',
1590
+ name: 'hub-supplement',
1591
+ source: origin,
1592
+ })),
1593
+ ],
1594
+ assets,
1595
+ };
1596
+ }
1597
+
1598
+ function printPretty(result, isDryRun) {
1599
+ const noun = isDryRun ? 'sync-plan(同步计划)' : 'sync-result(同步结果)';
1600
+ console.log(`${noun}: ${result.status}`);
1601
+ console.log(`target(目标项目): ${result.target.path}`);
1602
+ console.log(`profile(技术栈): ${result.target.profile}`);
1603
+ console.log(`ides(IDE 列表): ${result.target.ides.join(', ')}`);
1604
+ console.log(`roles(专家角色): ${result.resolved.roles.join(', ') || '(none)'}`);
1605
+ console.log(`skills(技能): ${result.resolved.skills.join(', ') || '(none)'}`);
1606
+ console.log(`rules(规则): ${result.resolved.rules.join(', ') || '(none)'}`);
1607
+ console.log(`domains(能力域): ${result.resolved.domains.join(', ') || '(none)'}`);
1608
+ if (Array.isArray(result.resolved.installed_flows)) {
1609
+ console.log(`installed_flows(已安装流程模板): ${result.resolved.installed_flows.join(', ') || '(none)'}`);
1610
+ }
1611
+ if (result.changes) {
1612
+ console.log(`created(新建): ${result.changes.created.length}`);
1613
+ console.log(`updated(更新): ${result.changes.updated.length}`);
1614
+ console.log(`skipped(跳过): ${result.changes.skipped.length}`);
1615
+ console.log(`conflicts(冲突): ${result.changes.conflicts.length}`);
1616
+ }
1617
+ if (result.warnings.length > 0) {
1618
+ console.log(`warnings(警告):`);
1619
+ for (const warning of result.warnings) {
1620
+ console.log(`- ${warning}`);
1621
+ }
1622
+ }
1623
+ if (result.errors.length > 0) {
1624
+ console.log(`errors(错误):`);
1625
+ for (const error of result.errors) {
1626
+ console.log(`- ${error}`);
1627
+ }
1628
+ }
1629
+ }
1630
+
1631
+ function buildPlan(targetDir, manifestSource, manifest, resolvedResult) {
1632
+ return {
1633
+ schema_version: 1,
1634
+ kind: 'sync-plan',
1635
+ status: 'planned',
1636
+ target: {
1637
+ path: targetDir,
1638
+ profile: manifest.profile,
1639
+ ides: manifest.ides,
1640
+ },
1641
+ source: {
1642
+ manifest: manifestSource,
1643
+ manifest_type: manifest.manifest_type,
1644
+ },
1645
+ request: {
1646
+ scenario_packages: manifest.scenario_packages,
1647
+ roles: manifest.roles,
1648
+ skills: manifest.skills,
1649
+ rules: manifest.rules,
1650
+ },
1651
+ resolved: {
1652
+ domains: resolvedResult.resolved.domains,
1653
+ installed_flows: resolvedResult.resolved.installed_flows,
1654
+ roles: resolvedResult.resolved.roles.map((item) => item.id),
1655
+ skills: resolvedResult.resolved.skills.map((item) => item.id),
1656
+ rules: resolvedResult.resolved.rules.map((item) => item.id),
1657
+ },
1658
+ warnings: resolvedResult.warnings,
1659
+ errors: [],
1660
+ };
1661
+ }
1662
+
1663
+ function dedupeChanges(changes) {
1664
+ return {
1665
+ created: unique(changes.created),
1666
+ updated: unique(changes.updated.filter((item) => !changes.created.includes(item))),
1667
+ skipped: unique(changes.skipped.filter((item) => !changes.created.includes(item) && !changes.updated.includes(item))),
1668
+ conflicts: unique(changes.conflicts),
1669
+ };
1670
+ }
1671
+
1672
+ function buildResult(prepared, changes = null) {
1673
+ return {
1674
+ schema_version: 1,
1675
+ kind: changes ? 'sync-result' : 'sync-plan',
1676
+ status: changes ? 'success' : 'planned',
1677
+ target: {
1678
+ path: prepared.targetDir,
1679
+ profile: prepared.manifest.profile,
1680
+ ides: prepared.manifest.ides,
1681
+ },
1682
+ source: {
1683
+ manifest: prepared.manifestSource,
1684
+ manifest_type: prepared.manifest.manifest_type,
1685
+ },
1686
+ request: {
1687
+ scenario_packages: prepared.manifest.scenario_packages,
1688
+ roles: prepared.manifest.roles,
1689
+ skills: prepared.manifest.skills,
1690
+ rules: prepared.manifest.rules,
1691
+ },
1692
+ resolved: {
1693
+ domains: prepared.resolvedResult.resolved.domains,
1694
+ installed_flows: prepared.resolvedResult.resolved.installed_flows,
1695
+ roles: prepared.resolvedResult.resolved.roles.map((item) => item.id),
1696
+ skills: prepared.resolvedResult.resolved.skills.map((item) => item.id),
1697
+ rules: prepared.resolvedResult.resolved.rules.map((item) => item.id),
1698
+ },
1699
+ ...(changes
1700
+ ? {
1701
+ changes,
1702
+ artifacts: {
1703
+ manifest: '.ai-spec/manifest.json',
1704
+ lock: '.ai-spec/lock.json',
1705
+ sources: '.ai-spec/sources.json',
1706
+ },
1707
+ }
1708
+ : {}),
1709
+ warnings: prepared.resolvedResult.warnings,
1710
+ errors: [],
1711
+ };
1712
+ }
1713
+
1714
+ async function prepareSync(options) {
1715
+ const sourceDir = getSourceDir();
1716
+ const profilesRegistry = readProfilesRegistry(sourceDir);
1717
+ const registryValidation = require('./validate-registry').validateRegistry(sourceDir);
1718
+ if (registryValidation.status !== 'success') {
1719
+ throw new Error(`Registry validation failed with ${registryValidation.errors.length} error(s). Run "ai-spec-auto validate-registry" for details.`);
1720
+ }
1721
+
1722
+ const targetDir = path.resolve(options.target || '.');
1723
+ const cliVersion = require(path.join(sourceDir, 'package.json')).version || '0.0.0';
1724
+ const manifestInput = options.manifest
1725
+ ? options.manifest
1726
+ : path.join(targetDir, '.ai-spec/manifest.json');
1727
+
1728
+ if (!manifestInput) {
1729
+ throw new Error('sync(同步) requires --manifest(安装清单) or an existing .ai-spec/manifest.json');
1730
+ }
1731
+
1732
+ const requestedTimeout = Number(process.env.AI_SPEC_REMOTE_MANIFEST_TIMEOUT_MS || DEFAULT_REMOTE_MANIFEST_TIMEOUT_MS);
1733
+ const timeoutMs = Number.isFinite(requestedTimeout) && requestedTimeout > 0
1734
+ ? requestedTimeout
1735
+ : DEFAULT_REMOTE_MANIFEST_TIMEOUT_MS;
1736
+ const { manifestSource, rawManifest } = await loadManifestInput(manifestInput, timeoutMs);
1737
+ const existingManifest = readExistingManifest(targetDir);
1738
+ const manifest = normalizeManifest(rawManifest, existingManifest, options, profilesRegistry);
1739
+ const registry = loadSyncRegistry(sourceDir);
1740
+ const catalogs = {
1741
+ roles: readRoleCatalog(registry.roles),
1742
+ skills: readSkillCatalog(sourceDir),
1743
+ rules: readRuleCatalog(registry.rules),
1744
+ flows: readFlowCatalog(registry.flows),
1745
+ scenarioPackages: new Map(Object.entries(registry.scenarioPackages?.scenario_packages || {})),
1746
+ };
1747
+ let prepared = {
1748
+ options,
1749
+ sourceDir,
1750
+ profilesRegistry,
1751
+ targetDir,
1752
+ cliVersion,
1753
+ manifestInput,
1754
+ manifestSource,
1755
+ rawManifest,
1756
+ existingManifest,
1757
+ manifest,
1758
+ registry,
1759
+ catalogs,
1760
+ supplements: [],
1761
+ };
1762
+ const preResolved = resolveManifest(manifest, catalogs, { allowMissing: true });
1763
+ const hubOrigin = resolveHubOrigin(options, manifestSource);
1764
+
1765
+ if (hasMissingAssets(preResolved) && hubOrigin) {
1766
+ const supplement = await fetchHubSupplement(
1767
+ hubOrigin,
1768
+ manifest,
1769
+ preResolved.missing,
1770
+ timeoutMs,
1771
+ );
1772
+ const supplementState = buildSupplementState(supplement);
1773
+ prepared = mergePreparedState(prepared, supplement, supplementState);
1774
+ }
1775
+
1776
+ const resolvedResult = resolveManifest(prepared.manifest, prepared.catalogs);
1777
+
1778
+ return {
1779
+ ...prepared,
1780
+ resolvedResult: {
1781
+ ...resolvedResult,
1782
+ warnings: unique([
1783
+ ...resolvedResult.warnings,
1784
+ ...preResolved.warnings,
1785
+ ...prepared.supplements.flatMap((item) => normalizeList(item.report?.warnings)),
1786
+ ]),
1787
+ },
1788
+ };
1789
+ }
1790
+
1791
+ async function runSync(options, preparedState = null) {
1792
+ const prepared = preparedState || await prepareSync(options);
1793
+ try {
1794
+ if (options.dryRun) {
1795
+ return buildResult(prepared, null);
1796
+ }
1797
+
1798
+ const changes = {
1799
+ created: [],
1800
+ updated: [],
1801
+ skipped: [],
1802
+ conflicts: [],
1803
+ };
1804
+
1805
+ installRoles(prepared.targetDir, prepared.resolvedResult.resolved.roles, prepared.registry.roles, changes);
1806
+ installSkills(prepared.targetDir, prepared.resolvedResult.resolved.skills, changes);
1807
+ installRules(prepared.targetDir, prepared.resolvedResult.resolved.rules, changes);
1808
+ installFlows(prepared.targetDir, prepared.catalogs, prepared.registry.flows, changes);
1809
+ installIdeAssets(
1810
+ prepared.sourceDir,
1811
+ prepared.targetDir,
1812
+ prepared.manifest.ides,
1813
+ prepared.resolvedResult.resolved.skills,
1814
+ changes,
1815
+ Boolean(prepared.manifest.superpowers?.enabled),
1816
+ );
1817
+
1818
+ const aiSpecDir = path.join(prepared.targetDir, '.ai-spec');
1819
+ ensureDir(aiSpecDir);
1820
+
1821
+ const manifestOutPath = path.join(aiSpecDir, 'manifest.json');
1822
+ const lockOutPath = path.join(aiSpecDir, 'lock.json');
1823
+ const sourcesOutPath = path.join(aiSpecDir, 'sources.json');
1824
+ const previousSources = readPreviousSources(prepared.targetDir);
1825
+
1826
+ writeJsonTracked(prepared.targetDir, manifestOutPath, prepared.manifest, changes);
1827
+ const superpowersState = buildSuperpowersState({
1828
+ targetDir: prepared.targetDir,
1829
+ enabled: Boolean(prepared.manifest.superpowers?.enabled),
1830
+ manifestConfig: prepared.manifest.superpowers || null,
1831
+ ides: prepared.manifest.ides,
1832
+ env: process.env,
1833
+ cliVersion: prepared.cliVersion,
1834
+ source: 'sync',
1835
+ });
1836
+ writeSuperpowersState(prepared.targetDir, superpowersState);
1837
+ const visualBridgeState = buildVisualBridgeState({
1838
+ targetDir: prepared.targetDir,
1839
+ manifestConfig: prepared.manifest.visual_bridge || null,
1840
+ previousState: readVisualBridgeState(prepared.targetDir),
1841
+ cliVersion: prepared.cliVersion,
1842
+ source: 'sync',
1843
+ });
1844
+ if (visualBridgeState) {
1845
+ writeVisualBridgeState(prepared.targetDir, visualBridgeState);
1846
+ }
1847
+ const lock = buildLock(prepared.manifest, prepared.targetDir, prepared.manifestSource, prepared.resolvedResult.resolved, prepared.cliVersion);
1848
+ writeJsonTracked(prepared.targetDir, lockOutPath, lock, changes);
1849
+ const sources = buildSources(prepared.manifest, prepared.manifestSource, prepared.resolvedResult.resolved, prepared.sourceDir);
1850
+ if (previousSources) {
1851
+ pruneManagedAssets(prepared.targetDir, previousSources, sources, changes);
1852
+ }
1853
+ writeJsonTracked(prepared.targetDir, sourcesOutPath, sources, changes);
1854
+ upsertManagedAgentsBlock(
1855
+ prepared.targetDir,
1856
+ Boolean(prepared.manifest.superpowers?.enabled) && prepared.manifest.ides.includes('codex'),
1857
+ );
1858
+
1859
+ return buildResult(prepared, dedupeChanges(changes));
1860
+ } finally {
1861
+ for (const supplement of prepared.supplements || []) {
1862
+ try {
1863
+ fs.rmSync(supplement.tempDir, { recursive: true, force: true });
1864
+ } catch (error) {
1865
+ // Cleanup failure should not change sync result.
1866
+ }
1867
+ }
1868
+ }
1869
+ }
1870
+
1871
+ async function main(argv) {
1872
+ try {
1873
+ const options = parseArgs(argv);
1874
+ let profilesRegistry = null;
1875
+ try {
1876
+ profilesRegistry = readProfilesRegistry(getSourceDir());
1877
+ } catch (error) {
1878
+ if (!options.help) {
1879
+ throw error;
1880
+ }
1881
+ }
1882
+ if (options.help) {
1883
+ printUsage(profilesRegistry);
1884
+ return 0;
1885
+ }
1886
+
1887
+ const result = await runSync(options);
1888
+ if (options.json) {
1889
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
1890
+ } else {
1891
+ printPretty(result, options.dryRun);
1892
+ }
1893
+ return 0;
1894
+ } catch (error) {
1895
+ console.error(`sync(同步) failed: ${error.message}`);
1896
+ return 1;
1897
+ }
1898
+ }
1899
+
1900
+ module.exports = { parseArgs, prepareSync, runSync, main };
1901
+
1902
+ if (require.main === module) {
1903
+ process.exit(main(process.argv.slice(2)));
1904
+ }