@mmerterden/multi-agent-pipeline 8.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (817) hide show
  1. package/CHANGELOG.md +2623 -0
  2. package/LICENSE +21 -0
  3. package/README.md +852 -0
  4. package/docs/FIGMA_PIPELINE.md +138 -0
  5. package/docs/GENERICITY-REVIEW.md +277 -0
  6. package/docs/STABILITY-FIX-PLAN.md +168 -0
  7. package/docs/adr/0001-three-model-triage.md +81 -0
  8. package/docs/adr/0002-instruction-driven-flag.md +62 -0
  9. package/docs/adr/0003-unified-shared-skills.md +55 -0
  10. package/docs/adr/0004-zero-dependency-philosophy.md +60 -0
  11. package/docs/adr/0005-lazy-phase-docs.md +68 -0
  12. package/docs/adr/0006-skills-core-external-split.md +52 -0
  13. package/docs/adr/0007-multi-tool-adapter-framework.md +110 -0
  14. package/docs/adr/0008-installer-modularization-and-secret-leak-defense.md +98 -0
  15. package/docs/adr/README.md +33 -0
  16. package/docs/architecture.md +181 -0
  17. package/docs/best-practices.md +93 -0
  18. package/docs/features.md +274 -0
  19. package/docs/performance.md +116 -0
  20. package/docs/recovery-guide.md +479 -0
  21. package/index.js +76 -0
  22. package/install/_adapters.mjs +69 -0
  23. package/install/_common.mjs +150 -0
  24. package/install/_copilot-instructions.mjs +32 -0
  25. package/install/_dev-only-files.mjs +23 -0
  26. package/install/_platform-filter.mjs +132 -0
  27. package/install/_telemetry.mjs +79 -0
  28. package/install/claude.mjs +332 -0
  29. package/install/copilot.mjs +254 -0
  30. package/install/index.mjs +179 -0
  31. package/install/templates/copilot-instructions.md +319 -0
  32. package/install.js +24 -0
  33. package/package.json +78 -0
  34. package/pipeline/adapters/_base.mjs +288 -0
  35. package/pipeline/adapters/copilot-chat.mjs +158 -0
  36. package/pipeline/adapters/cursor.mjs +187 -0
  37. package/pipeline/agents/android-architect.md +42 -0
  38. package/pipeline/agents/backend-architect.md +43 -0
  39. package/pipeline/agents/code-reviewer.md +57 -0
  40. package/pipeline/agents/dev-critic.md +148 -0
  41. package/pipeline/agents/explorer.md +34 -0
  42. package/pipeline/agents/ios-architect.md +41 -0
  43. package/pipeline/agents/security-auditor.md +98 -0
  44. package/pipeline/agents/task-clarifier.md +113 -0
  45. package/pipeline/claude-md-template.md +55 -0
  46. package/pipeline/commands/archive-guard.md +45 -0
  47. package/pipeline/commands/deploy.md +54 -0
  48. package/pipeline/commands/figma-to-swiftui.md +295 -0
  49. package/pipeline/commands/multi-agent/_account-picker.md +90 -0
  50. package/pipeline/commands/multi-agent/_dev-context.md +111 -0
  51. package/pipeline/commands/multi-agent/_input-parser.md +43 -0
  52. package/pipeline/commands/multi-agent/_repo-picker.md +76 -0
  53. package/pipeline/commands/multi-agent/autopilot.md +116 -0
  54. package/pipeline/commands/multi-agent/channels.md +465 -0
  55. package/pipeline/commands/multi-agent/delete.md +66 -0
  56. package/pipeline/commands/multi-agent/dev-autopilot.md +120 -0
  57. package/pipeline/commands/multi-agent/dev-local-autopilot.md +110 -0
  58. package/pipeline/commands/multi-agent/dev-local.md +105 -0
  59. package/pipeline/commands/multi-agent/dev.md +246 -0
  60. package/pipeline/commands/multi-agent/diff-explain.md +68 -0
  61. package/pipeline/commands/multi-agent/help.md +422 -0
  62. package/pipeline/commands/multi-agent/issue.md +79 -0
  63. package/pipeline/commands/multi-agent/jira.md +132 -0
  64. package/pipeline/commands/multi-agent/kill.md +38 -0
  65. package/pipeline/commands/multi-agent/language.md +94 -0
  66. package/pipeline/commands/multi-agent/local-autopilot.md +139 -0
  67. package/pipeline/commands/multi-agent/local.md +117 -0
  68. package/pipeline/commands/multi-agent/log.md +25 -0
  69. package/pipeline/commands/multi-agent/manual-test.md +43 -0
  70. package/pipeline/commands/multi-agent/purge.md +39 -0
  71. package/pipeline/commands/multi-agent/refactor.md +188 -0
  72. package/pipeline/commands/multi-agent/refs/android-guide.md +250 -0
  73. package/pipeline/commands/multi-agent/refs/audit-guide.md +240 -0
  74. package/pipeline/commands/multi-agent/refs/backend-guide.md +135 -0
  75. package/pipeline/commands/multi-agent/refs/channels/confluence.md +153 -0
  76. package/pipeline/commands/multi-agent/refs/channels/issue-comment.md +141 -0
  77. package/pipeline/commands/multi-agent/refs/channels/jira.md +127 -0
  78. package/pipeline/commands/multi-agent/refs/channels/pr-review-actions.md +135 -0
  79. package/pipeline/commands/multi-agent/refs/channels/pr.md +139 -0
  80. package/pipeline/commands/multi-agent/refs/channels/wiki.md +66 -0
  81. package/pipeline/commands/multi-agent/refs/component-dispatch.md +92 -0
  82. package/pipeline/commands/multi-agent/refs/cross-cli-contract.md +326 -0
  83. package/pipeline/commands/multi-agent/refs/frontend-guide.md +136 -0
  84. package/pipeline/commands/multi-agent/refs/issue-jira-triad.md +104 -0
  85. package/pipeline/commands/multi-agent/refs/keychain.md +80 -0
  86. package/pipeline/commands/multi-agent/refs/knowledge.md +112 -0
  87. package/pipeline/commands/multi-agent/refs/multi-repo-integration-build.md +207 -0
  88. package/pipeline/commands/multi-agent/refs/phases/log-format.md +89 -0
  89. package/pipeline/commands/multi-agent/refs/phases/modes.md +156 -0
  90. package/pipeline/commands/multi-agent/refs/phases/operations.md +91 -0
  91. package/pipeline/commands/multi-agent/refs/phases/phase-0-init.md +481 -0
  92. package/pipeline/commands/multi-agent/refs/phases/phase-1-analysis.md +264 -0
  93. package/pipeline/commands/multi-agent/refs/phases/phase-2-planning.md +278 -0
  94. package/pipeline/commands/multi-agent/refs/phases/phase-3-dev.md +364 -0
  95. package/pipeline/commands/multi-agent/refs/phases/phase-4-review.md +378 -0
  96. package/pipeline/commands/multi-agent/refs/phases/phase-5-test.md +129 -0
  97. package/pipeline/commands/multi-agent/refs/phases/phase-6-commit.md +339 -0
  98. package/pipeline/commands/multi-agent/refs/phases/phase-7-report.md +361 -0
  99. package/pipeline/commands/multi-agent/refs/phases.md +187 -0
  100. package/pipeline/commands/multi-agent/refs/progress-contract.md +155 -0
  101. package/pipeline/commands/multi-agent/refs/rules.md +189 -0
  102. package/pipeline/commands/multi-agent/refs/swiftui-guide.md +254 -0
  103. package/pipeline/commands/multi-agent/refs/tracker-contract.md +256 -0
  104. package/pipeline/commands/multi-agent/refs/wiki-capture.md +109 -0
  105. package/pipeline/commands/multi-agent/resume.md +28 -0
  106. package/pipeline/commands/multi-agent/review.md +228 -0
  107. package/pipeline/commands/multi-agent/scan.md +74 -0
  108. package/pipeline/commands/multi-agent/search.md +97 -0
  109. package/pipeline/commands/multi-agent/setup.md +767 -0
  110. package/pipeline/commands/multi-agent/stack.md +48 -0
  111. package/pipeline/commands/multi-agent/status.md +38 -0
  112. package/pipeline/commands/multi-agent/sync.md +319 -0
  113. package/pipeline/commands/multi-agent/test.md +39 -0
  114. package/pipeline/commands/multi-agent/update.md +88 -0
  115. package/pipeline/commands/multi-agent.md +293 -0
  116. package/pipeline/commands/security-review.md +6 -0
  117. package/pipeline/commands/sim-test.md +256 -0
  118. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-1-analysis.json +25 -0
  119. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-2-plan.json +30 -0
  120. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-4-review.json +20 -0
  121. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-4-triage.json +15 -0
  122. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/metadata.json +14 -0
  123. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/task.json +12 -0
  124. package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-1-analysis.json +29 -0
  125. package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-2-plan.json +43 -0
  126. package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-4-review.json +35 -0
  127. package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-4-triage.json +35 -0
  128. package/pipeline/eval/golden-tasks/02-android-feature-compose/metadata.json +14 -0
  129. package/pipeline/eval/golden-tasks/02-android-feature-compose/task.json +12 -0
  130. package/pipeline/eval/golden-tasks/README.md +65 -0
  131. package/pipeline/eval/triage/01-empty-findings/expected.json +6 -0
  132. package/pipeline/eval/triage/01-empty-findings/input.json +5 -0
  133. package/pipeline/eval/triage/01-empty-findings/notes.md +7 -0
  134. package/pipeline/eval/triage/02-real-blocker/expected.json +15 -0
  135. package/pipeline/eval/triage/02-real-blocker/input.json +14 -0
  136. package/pipeline/eval/triage/02-real-blocker/notes.md +7 -0
  137. package/pipeline/eval/triage/03-out-of-scope-defer/expected.json +18 -0
  138. package/pipeline/eval/triage/03-out-of-scope-defer/input.json +14 -0
  139. package/pipeline/eval/triage/03-out-of-scope-defer/notes.md +10 -0
  140. package/pipeline/eval/triage/04-false-positive-reject/expected.json +18 -0
  141. package/pipeline/eval/triage/04-false-positive-reject/input.json +14 -0
  142. package/pipeline/eval/triage/04-false-positive-reject/notes.md +10 -0
  143. package/pipeline/eval/triage/05-mixed-classification/expected.json +43 -0
  144. package/pipeline/eval/triage/05-mixed-classification/input.json +38 -0
  145. package/pipeline/eval/triage/05-mixed-classification/notes.md +17 -0
  146. package/pipeline/eval/triage/06-severity-mismatch/expected.json +15 -0
  147. package/pipeline/eval/triage/06-severity-mismatch/input.json +14 -0
  148. package/pipeline/eval/triage/06-severity-mismatch/notes.md +9 -0
  149. package/pipeline/eval/triage/07-duplicate-reviewers/expected.json +27 -0
  150. package/pipeline/eval/triage/07-duplicate-reviewers/input.json +22 -0
  151. package/pipeline/eval/triage/07-duplicate-reviewers/notes.md +9 -0
  152. package/pipeline/eval/triage/08-style-misclassified/expected.json +18 -0
  153. package/pipeline/eval/triage/08-style-misclassified/input.json +14 -0
  154. package/pipeline/eval/triage/08-style-misclassified/notes.md +9 -0
  155. package/pipeline/eval/triage/09-cascading-finding/expected.json +23 -0
  156. package/pipeline/eval/triage/09-cascading-finding/input.json +22 -0
  157. package/pipeline/eval/triage/09-cascading-finding/notes.md +9 -0
  158. package/pipeline/eval/triage/10-deferred-crossref/expected.json +18 -0
  159. package/pipeline/eval/triage/10-deferred-crossref/input.json +14 -0
  160. package/pipeline/eval/triage/10-deferred-crossref/notes.md +9 -0
  161. package/pipeline/eval/triage/11-vercel-token-leak-blocker/expected.json +27 -0
  162. package/pipeline/eval/triage/11-vercel-token-leak-blocker/input.json +22 -0
  163. package/pipeline/eval/triage/11-vercel-token-leak-blocker/notes.md +14 -0
  164. package/pipeline/eval/triage/README.md +54 -0
  165. package/pipeline/lib/account-resolver.sh +204 -0
  166. package/pipeline/lib/channels-multi-repo.sh +218 -0
  167. package/pipeline/lib/context-link-extractor.sh +192 -0
  168. package/pipeline/lib/credential-store-resolver.sh +57 -0
  169. package/pipeline/lib/credential-store.sh +226 -0
  170. package/pipeline/lib/fetch-confluence.sh +358 -0
  171. package/pipeline/lib/fetch-crashlytics.sh +314 -0
  172. package/pipeline/lib/fetch-fortify.sh +321 -0
  173. package/pipeline/lib/fetch-swagger.sh +270 -0
  174. package/pipeline/lib/issue-fetcher.sh +333 -0
  175. package/pipeline/lib/multi-repo-pipeline.sh +252 -0
  176. package/pipeline/lib/plan-todos.sh +284 -0
  177. package/pipeline/lib/post-pr-review.sh +374 -0
  178. package/pipeline/lib/repo-cache.sh +231 -0
  179. package/pipeline/lib/review-watch.sh +244 -0
  180. package/pipeline/lib/shadow-git.sh +222 -0
  181. package/pipeline/lib/submodule-detector.sh +177 -0
  182. package/pipeline/lib/vercel-deploy.sh +170 -0
  183. package/pipeline/preferences-template.json +132 -0
  184. package/pipeline/rules/app-store-guidelines.md +59 -0
  185. package/pipeline/rules/code-review.md +27 -0
  186. package/pipeline/rules/code-style.md +37 -0
  187. package/pipeline/rules/debugging.md +24 -0
  188. package/pipeline/rules/figma-pipeline.md +190 -0
  189. package/pipeline/rules/git-conventions.md +29 -0
  190. package/pipeline/rules/kotlin-android.md +92 -0
  191. package/pipeline/rules/performance.md +23 -0
  192. package/pipeline/rules/security.md +39 -0
  193. package/pipeline/rules/swiftui-qa.md +32 -0
  194. package/pipeline/rules/tdd.md +25 -0
  195. package/pipeline/rules/testing.md +37 -0
  196. package/pipeline/schemas/agent-state.schema.json +273 -0
  197. package/pipeline/schemas/analysis-output.schema.json +59 -0
  198. package/pipeline/schemas/clarify-output.schema.json +74 -0
  199. package/pipeline/schemas/dev-critic-output.schema.json +104 -0
  200. package/pipeline/schemas/diff-risk.schema.json +78 -0
  201. package/pipeline/schemas/figma-project-config.schema.json +372 -0
  202. package/pipeline/schemas/migrations/README.md +73 -0
  203. package/pipeline/schemas/migrations/figma-config-1.0.0-to-2.0.0.mjs +112 -0
  204. package/pipeline/schemas/migrations/prefs-2.0.0-to-2.1.0.mjs +75 -0
  205. package/pipeline/schemas/migrations/prefs-2.1.0-to-2.2.0.mjs +64 -0
  206. package/pipeline/schemas/migrations/prefs-2.2.0-to-2.3.0.mjs +36 -0
  207. package/pipeline/schemas/migrations/state-2.0.0-to-2.1.0.mjs +34 -0
  208. package/pipeline/schemas/plan-todos.schema.json +62 -0
  209. package/pipeline/schemas/planning-output.schema.json +57 -0
  210. package/pipeline/schemas/prefs.schema.json +1137 -0
  211. package/pipeline/schemas/reviewer-output.schema.json +55 -0
  212. package/pipeline/schemas/test-gap.schema.json +64 -0
  213. package/pipeline/schemas/token-budget.json +17 -0
  214. package/pipeline/schemas/triage-corpus.schema.json +31 -0
  215. package/pipeline/schemas/triage-output.schema.json +115 -0
  216. package/pipeline/scripts/.last-figma-sync-plan.json +23 -0
  217. package/pipeline/scripts/README-figma-smokes.md +34 -0
  218. package/pipeline/scripts/README.md +104 -0
  219. package/pipeline/scripts/aggregate-metrics.mjs +310 -0
  220. package/pipeline/scripts/audit-log-rotate.sh +61 -0
  221. package/pipeline/scripts/audit-log.sh +69 -0
  222. package/pipeline/scripts/benchmark-phase-0.sh +128 -0
  223. package/pipeline/scripts/build-skills-index.mjs +139 -0
  224. package/pipeline/scripts/classify-plan-safety.mjs +177 -0
  225. package/pipeline/scripts/cost-table.json +27 -0
  226. package/pipeline/scripts/diff-explain.mjs +276 -0
  227. package/pipeline/scripts/diff-risk-score.mjs +328 -0
  228. package/pipeline/scripts/eval-golden-tasks-live.mjs +294 -0
  229. package/pipeline/scripts/eval-golden-tasks.mjs +223 -0
  230. package/pipeline/scripts/eval-triage.mjs +171 -0
  231. package/pipeline/scripts/figma-placeholder-map.json +191 -0
  232. package/pipeline/scripts/fixtures/diff-risk-android.diff +40 -0
  233. package/pipeline/scripts/fixtures/diff-risk-ios.diff +48 -0
  234. package/pipeline/scripts/fixtures/install-layout.tsv +16 -0
  235. package/pipeline/scripts/fixtures/test-gap-node.diff +30 -0
  236. package/pipeline/scripts/fixtures/test-gap-python.diff +32 -0
  237. package/pipeline/scripts/gen-mode-dispatch.mjs +170 -0
  238. package/pipeline/scripts/gen-skills-index.mjs +90 -0
  239. package/pipeline/scripts/github-ssh-setup.sh +103 -0
  240. package/pipeline/scripts/import-figma-skills.sh +253 -0
  241. package/pipeline/scripts/keychain-save.sh +74 -0
  242. package/pipeline/scripts/keychain.py +294 -0
  243. package/pipeline/scripts/log-metric.sh +98 -0
  244. package/pipeline/scripts/match-skills.mjs +167 -0
  245. package/pipeline/scripts/memory-load.sh +46 -0
  246. package/pipeline/scripts/memory-save.sh +76 -0
  247. package/pipeline/scripts/migrate-prefs.mjs +390 -0
  248. package/pipeline/scripts/migrate-state.mjs +215 -0
  249. package/pipeline/scripts/output-quality-check.sh +125 -0
  250. package/pipeline/scripts/phase-banner.sh +158 -0
  251. package/pipeline/scripts/phase-tracker.sh +548 -0
  252. package/pipeline/scripts/pre-commit-check.sh +69 -0
  253. package/pipeline/scripts/pre-push-check.sh +77 -0
  254. package/pipeline/scripts/render-agent-log-cost.sh +149 -0
  255. package/pipeline/scripts/render-cost-summary.sh +137 -0
  256. package/pipeline/scripts/render-work-summary.sh +195 -0
  257. package/pipeline/scripts/repo-map.mjs +367 -0
  258. package/pipeline/scripts/run-aggregator.mjs +298 -0
  259. package/pipeline/scripts/scan-skills.sh +332 -0
  260. package/pipeline/scripts/search-logs.sh +291 -0
  261. package/pipeline/scripts/sign-skills.sh +67 -0
  262. package/pipeline/scripts/smoke-adapters.sh +207 -0
  263. package/pipeline/scripts/smoke-add-detail.sh +137 -0
  264. package/pipeline/scripts/smoke-agent-log-cost.sh +183 -0
  265. package/pipeline/scripts/smoke-agent-model-routing.sh +87 -0
  266. package/pipeline/scripts/smoke-bitbucket-contract.sh +223 -0
  267. package/pipeline/scripts/smoke-channels-flow.sh +130 -0
  268. package/pipeline/scripts/smoke-ci-workflows.sh +88 -0
  269. package/pipeline/scripts/smoke-clarify.sh +148 -0
  270. package/pipeline/scripts/smoke-commands-skills-parity.sh +87 -0
  271. package/pipeline/scripts/smoke-compliance-skills.sh +119 -0
  272. package/pipeline/scripts/smoke-cost-summary.sh +139 -0
  273. package/pipeline/scripts/smoke-cross-cli-behavior.sh +198 -0
  274. package/pipeline/scripts/smoke-cross-phase-cohesion.sh +128 -0
  275. package/pipeline/scripts/smoke-delete-flow.sh +151 -0
  276. package/pipeline/scripts/smoke-dev-critic.sh +144 -0
  277. package/pipeline/scripts/smoke-diff-explain.sh +128 -0
  278. package/pipeline/scripts/smoke-diff-risk.sh +161 -0
  279. package/pipeline/scripts/smoke-dynamic-skill-loading.sh +160 -0
  280. package/pipeline/scripts/smoke-eval-live.sh +136 -0
  281. package/pipeline/scripts/smoke-existing-discovery-gate.sh +71 -0
  282. package/pipeline/scripts/smoke-figma-android-parity.sh +148 -0
  283. package/pipeline/scripts/smoke-figma-config-schema.sh +144 -0
  284. package/pipeline/scripts/smoke-figma-credential-store.sh +105 -0
  285. package/pipeline/scripts/smoke-figma-cross-cli-inventory.sh +177 -0
  286. package/pipeline/scripts/smoke-figma-dispatch.sh +123 -0
  287. package/pipeline/scripts/smoke-figma-skill-import.sh +174 -0
  288. package/pipeline/scripts/smoke-figma-sync.sh +149 -0
  289. package/pipeline/scripts/smoke-identity-isolation.sh +70 -0
  290. package/pipeline/scripts/smoke-install-layout.sh +241 -0
  291. package/pipeline/scripts/smoke-install-leak-gate.sh +125 -0
  292. package/pipeline/scripts/smoke-issue-comment-template.sh +86 -0
  293. package/pipeline/scripts/smoke-issue-jira-triad.sh +120 -0
  294. package/pipeline/scripts/smoke-keychain.sh +158 -0
  295. package/pipeline/scripts/smoke-language-axis.sh +109 -0
  296. package/pipeline/scripts/smoke-lib-scripts.sh +395 -0
  297. package/pipeline/scripts/smoke-migrate-state.sh +102 -0
  298. package/pipeline/scripts/smoke-mode-dispatch-drift.sh +158 -0
  299. package/pipeline/scripts/smoke-multi-repo-integration.sh +116 -0
  300. package/pipeline/scripts/smoke-multi-repo-worktree.sh +61 -0
  301. package/pipeline/scripts/smoke-no-token-prompt.sh +69 -0
  302. package/pipeline/scripts/smoke-pat-audit.sh +107 -0
  303. package/pipeline/scripts/smoke-per-repo-memory.sh +156 -0
  304. package/pipeline/scripts/smoke-personal-data.sh +82 -0
  305. package/pipeline/scripts/smoke-phase-0-multi-repo.sh +170 -0
  306. package/pipeline/scripts/smoke-phase-6-multi.sh +79 -0
  307. package/pipeline/scripts/smoke-phase-banner.sh +101 -0
  308. package/pipeline/scripts/smoke-phase-tracker.sh +255 -0
  309. package/pipeline/scripts/smoke-phase0-bridge-contract.sh +241 -0
  310. package/pipeline/scripts/smoke-phase4-triage.sh +142 -0
  311. package/pipeline/scripts/smoke-plan-approval-gate.sh +71 -0
  312. package/pipeline/scripts/smoke-plan-safety.sh +139 -0
  313. package/pipeline/scripts/smoke-plan-todos.sh +193 -0
  314. package/pipeline/scripts/smoke-pr-review-actions.sh +152 -0
  315. package/pipeline/scripts/smoke-pre-commit.sh +138 -0
  316. package/pipeline/scripts/smoke-pref-migration.sh +224 -0
  317. package/pipeline/scripts/smoke-prefs-language.sh +134 -0
  318. package/pipeline/scripts/smoke-progress-contract.sh +118 -0
  319. package/pipeline/scripts/smoke-push-retry.sh +75 -0
  320. package/pipeline/scripts/smoke-readme-counts.sh +120 -0
  321. package/pipeline/scripts/smoke-repo-map.sh +300 -0
  322. package/pipeline/scripts/smoke-review-watch.sh +134 -0
  323. package/pipeline/scripts/smoke-run-aggregator.sh +216 -0
  324. package/pipeline/scripts/smoke-schema-validation.sh +173 -0
  325. package/pipeline/scripts/smoke-search.sh +187 -0
  326. package/pipeline/scripts/smoke-shadow-git.sh +175 -0
  327. package/pipeline/scripts/smoke-skill-authoring.sh +142 -0
  328. package/pipeline/scripts/smoke-skill-language.sh +83 -0
  329. package/pipeline/scripts/smoke-skill-manifest.sh +138 -0
  330. package/pipeline/scripts/smoke-skill-scan.sh +198 -0
  331. package/pipeline/scripts/smoke-stack-swap.sh +132 -0
  332. package/pipeline/scripts/smoke-subagent-validators.sh +105 -0
  333. package/pipeline/scripts/smoke-sync-delegation.sh +74 -0
  334. package/pipeline/scripts/smoke-sync-parity.sh +92 -0
  335. package/pipeline/scripts/smoke-tasklist-ordering.sh +111 -0
  336. package/pipeline/scripts/smoke-telemetry.sh +147 -0
  337. package/pipeline/scripts/smoke-test-gap.sh +183 -0
  338. package/pipeline/scripts/smoke-token-budget.sh +67 -0
  339. package/pipeline/scripts/smoke-tracker-contract.sh +129 -0
  340. package/pipeline/scripts/smoke-tracker-tokens-invocation.sh +65 -0
  341. package/pipeline/scripts/smoke-triage-memory.sh +174 -0
  342. package/pipeline/scripts/smoke-url-enrichment.sh +70 -0
  343. package/pipeline/scripts/smoke-validator-contradiction.sh +67 -0
  344. package/pipeline/scripts/smoke-vercel-deploy-redact.sh +129 -0
  345. package/pipeline/scripts/smoke-wiki-integration.sh +146 -0
  346. package/pipeline/scripts/smoke-work-summary.sh +163 -0
  347. package/pipeline/scripts/smoke-worktree-path-convention.sh +86 -0
  348. package/pipeline/scripts/smoke-write-state.sh +115 -0
  349. package/pipeline/scripts/stack-swap.sh +182 -0
  350. package/pipeline/scripts/sync-figma-source.sh +228 -0
  351. package/pipeline/scripts/sync-parity-check.sh +135 -0
  352. package/pipeline/scripts/test-gap-rules/android.json +25 -0
  353. package/pipeline/scripts/test-gap-rules/ios.json +29 -0
  354. package/pipeline/scripts/test-gap-rules/node.json +17 -0
  355. package/pipeline/scripts/test-gap-rules/python.json +19 -0
  356. package/pipeline/scripts/test-gap-scan.mjs +343 -0
  357. package/pipeline/scripts/token-budget-report.mjs +145 -0
  358. package/pipeline/scripts/triage-memory.mjs +258 -0
  359. package/pipeline/scripts/ui-tree-dumper.swift +122 -0
  360. package/pipeline/scripts/uninstall.mjs +331 -0
  361. package/pipeline/scripts/update-issue-progress.sh +146 -0
  362. package/pipeline/scripts/validate-analysis.mjs +132 -0
  363. package/pipeline/scripts/validate-diff-risk.mjs +117 -0
  364. package/pipeline/scripts/validate-planning.mjs +180 -0
  365. package/pipeline/scripts/validate-reviewer.mjs +131 -0
  366. package/pipeline/scripts/validate-schemas.mjs +88 -0
  367. package/pipeline/scripts/validate-test-gap.mjs +90 -0
  368. package/pipeline/scripts/validate-triage.mjs +175 -0
  369. package/pipeline/scripts/verify-skills.sh +126 -0
  370. package/pipeline/scripts/write-state.mjs +175 -0
  371. package/pipeline/skills/.skill-manifest.json +779 -0
  372. package/pipeline/skills/.skills-index.json +1771 -0
  373. package/pipeline/skills/figma-android/README.md +36 -0
  374. package/pipeline/skills/figma-android/figma-component-code-connect/SKILL.md +62 -0
  375. package/pipeline/skills/figma-android/figma-component-implement/SKILL.md +158 -0
  376. package/pipeline/skills/figma-android/figma-component-test/SKILL.md +120 -0
  377. package/pipeline/skills/figma-android/figma-component-wiki/SKILL.md +35 -0
  378. package/pipeline/skills/figma-android/figma-to-component/SKILL.md +124 -0
  379. package/pipeline/skills/figma-common/README.md +57 -0
  380. package/pipeline/skills/figma-common/figma-cli-iterate/SKILL.md +277 -0
  381. package/pipeline/skills/figma-common/figma-cli-iterate-mend/SKILL.md +498 -0
  382. package/pipeline/skills/figma-common/figma-cli-lean-iterate/SKILL.md +283 -0
  383. package/pipeline/skills/figma-common/figma-cli-skip/SKILL.md +362 -0
  384. package/pipeline/skills/figma-common/figma-commit/COMMON_REBASE.md +206 -0
  385. package/pipeline/skills/figma-common/figma-commit/REVIEW.md +337 -0
  386. package/pipeline/skills/figma-common/figma-commit/SKILL.md +211 -0
  387. package/pipeline/skills/figma-common/figma-component-confluence-sync/SKILL.md +218 -0
  388. package/pipeline/skills/figma-common/figma-component-start/SKILL.md +246 -0
  389. package/pipeline/skills/figma-common/figma-component-status-update/SKILL.md +73 -0
  390. package/pipeline/skills/figma-common/figma-fix/SKILL.md +316 -0
  391. package/pipeline/skills/figma-common/figma-form-integration/SKILL.md +542 -0
  392. package/pipeline/skills/figma-common/figma-issue/SKILL.md +745 -0
  393. package/pipeline/skills/figma-common/figma-iterate/SKILL.md +203 -0
  394. package/pipeline/skills/figma-common/figma-iteration-commit/SKILL.md +1015 -0
  395. package/pipeline/skills/figma-common/figma-mend/SKILL.md +331 -0
  396. package/pipeline/skills/figma-common/figma-price-integration/SKILL.md +398 -0
  397. package/pipeline/skills/figma-common/figma-remote-mcp-auth/SKILL.md +104 -0
  398. package/pipeline/skills/figma-common/figma-review/SKILL.md +395 -0
  399. package/pipeline/skills/figma-common/figma-setup/SKILL.md +514 -0
  400. package/pipeline/skills/figma-common/figma-setup/scripts/fetch-mcp-token.py +592 -0
  401. package/pipeline/skills/figma-common/figma-skip/SKILL.md +129 -0
  402. package/pipeline/skills/figma-common/figma-ui-patterns/SKILL.md +104 -0
  403. package/pipeline/skills/figma-common/figma-utility/SKILL.md +274 -0
  404. package/pipeline/skills/figma-common/figma-utility/scripts/figma-utility.py +808 -0
  405. package/pipeline/skills/figma-common/figma-validate/SKILL.md +633 -0
  406. package/pipeline/skills/figma-common/performance-iteration-commit-all/SKILL.md +711 -0
  407. package/pipeline/skills/figma-common/performance-review-next/SKILL.md +233 -0
  408. package/pipeline/skills/figma-common/performance-start/SKILL.md +425 -0
  409. package/pipeline/skills/figma-common/performance-swiftui/SKILL.md +706 -0
  410. package/pipeline/skills/figma-common/performance-tour/SKILL.md +418 -0
  411. package/pipeline/skills/figma-ios/REVIEW_CHECKLIST.md +67 -0
  412. package/pipeline/skills/figma-ios/figma-component-code-connect/SKILL.md +178 -0
  413. package/pipeline/skills/figma-ios/figma-component-implement/SKILL.md +184 -0
  414. package/pipeline/skills/figma-ios/figma-component-test/SKILL.md +219 -0
  415. package/pipeline/skills/figma-ios/figma-component-wiki/SKILL.md +274 -0
  416. package/pipeline/skills/figma-ios/figma-to-component/SKILL.md +401 -0
  417. package/pipeline/skills/figma-ios/figma-to-component/halt-return-protocol.md +57 -0
  418. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-0-init.md +307 -0
  419. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-1-gathering.md +119 -0
  420. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-1.5-existing-discovery.md +174 -0
  421. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-2-orchestrator.md +333 -0
  422. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-2a-testing-identifiers.md +368 -0
  423. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-2b-localization.md +393 -0
  424. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-2c-accessibility.md +617 -0
  425. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-2d-analytics.md +352 -0
  426. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3-orchestrator.md +337 -0
  427. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3a-location.md +206 -0
  428. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3b-tokens.md +235 -0
  429. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3c-nested.md +214 -0
  430. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3d-patterns.md +871 -0
  431. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3e-assets.md +156 -0
  432. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3f-utilities.md +175 -0
  433. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3g-property-coverage.md +176 -0
  434. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3h-variant-config.md +333 -0
  435. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-4-orchestrator.md +412 -0
  436. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-4a-configuration.md +336 -0
  437. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-4b-view.md +695 -0
  438. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-4c-documentation.md +332 -0
  439. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-4d-preview.md +380 -0
  440. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-4e-modifiers.md +262 -0
  441. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-5-orchestrator.md +482 -0
  442. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-5a-viewinspector.md +274 -0
  443. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-5b-snapshot.md +636 -0
  444. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-5c-unit.md +142 -0
  445. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-6-code-connect.md +547 -0
  446. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-7-wiki.md +39 -0
  447. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-7a-confluence-generate.md +659 -0
  448. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-7a-wiki-generate.md +580 -0
  449. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-8-cleanup.md +51 -0
  450. package/pipeline/skills/figma-ios/figma-to-component/reference/accessibility.md +129 -0
  451. package/pipeline/skills/figma-ios/figma-to-component/reference/analytics-events.md +64 -0
  452. package/pipeline/skills/figma-ios/figma-to-component/reference/code-connect.md +531 -0
  453. package/pipeline/skills/figma-ios/figma-to-component/reference/confluence-api.md +89 -0
  454. package/pipeline/skills/figma-ios/figma-to-component/reference/confluence-xhtml.md +155 -0
  455. package/pipeline/skills/figma-ios/figma-to-component/reference/figma-to-swiftui-effects.md +196 -0
  456. package/pipeline/skills/figma-ios/figma-to-component/reference/halt-return-protocol.md +57 -0
  457. package/pipeline/skills/figma-ios/figma-to-component/reference/localization-naming.md +89 -0
  458. package/pipeline/skills/figma-ios/figma-to-component/reference/macros.md +227 -0
  459. package/pipeline/skills/figma-ios/figma-to-component/reference/missing-tokens.md +157 -0
  460. package/pipeline/skills/figma-ios/figma-to-component/reference/orchestrator-discipline.md +90 -0
  461. package/pipeline/skills/figma-ios/figma-to-component/reference/registry.md +116 -0
  462. package/pipeline/skills/figma-ios/figma-to-component/reference/remote-mcp-script.md +153 -0
  463. package/pipeline/skills/figma-ios/figma-to-component/reference/rest-api-script.md +130 -0
  464. package/pipeline/skills/figma-ios/figma-to-component/reference/scripts-inventory.md +218 -0
  465. package/pipeline/skills/figma-ios/figma-to-component/reference/snapshot-testing.md +188 -0
  466. package/pipeline/skills/figma-ios/figma-to-component/reference/subcomponent-graph.md +93 -0
  467. package/pipeline/skills/figma-ios/figma-to-component/reference/testing-identifiers-naming.md +98 -0
  468. package/pipeline/skills/figma-ios/figma-to-component/reference/tools.md +261 -0
  469. package/pipeline/skills/figma-ios/figma-to-component/reference/viewinspector.md +147 -0
  470. package/pipeline/skills/figma-ios/figma-to-component/reference/wiki-to-confluence-mapping.md +182 -0
  471. package/pipeline/skills/figma-ios/figma-to-component/scripts/apply-author-login-map.py +185 -0
  472. package/pipeline/skills/figma-ios/figma-to-component/scripts/backfill-status.py +609 -0
  473. package/pipeline/skills/figma-ios/figma-to-component/scripts/build-author-registry.py +332 -0
  474. package/pipeline/skills/figma-ios/figma-to-component/scripts/bulk-sync-issues.py +261 -0
  475. package/pipeline/skills/figma-ios/figma-to-component/scripts/code-connect-data-gather.py +184 -0
  476. package/pipeline/skills/figma-ios/figma-to-component/scripts/code-connect-publish.sh +188 -0
  477. package/pipeline/skills/figma-ios/figma-to-component/scripts/confluence-component-status-upload.py +768 -0
  478. package/pipeline/skills/figma-ios/figma-to-component/scripts/confluence-component-status.py +191 -0
  479. package/pipeline/skills/figma-ios/figma-to-component/scripts/confluence-data-gather.py +420 -0
  480. package/pipeline/skills/figma-ios/figma-to-component/scripts/confluence-page-ids.json +94 -0
  481. package/pipeline/skills/figma-ios/figma-to-component/scripts/confluence-publish.py +336 -0
  482. package/pipeline/skills/figma-ios/figma-to-component/scripts/figma-subcomponent-graph.py +391 -0
  483. package/pipeline/skills/figma-ios/figma-to-component/scripts/figma-update.py +292 -0
  484. package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/__init__.py +1 -0
  485. package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/issue_sync_propagate.py +93 -0
  486. package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/registry_writer.py +299 -0
  487. package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_backfill_status.py +343 -0
  488. package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_figma_update.py +206 -0
  489. package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_figma_update_http.py +149 -0
  490. package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_phase_clis.py +281 -0
  491. package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_registry_writer.py +332 -0
  492. package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_skill_figma_issue.py +176 -0
  493. package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_skill_figma_review.py +98 -0
  494. package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_update_issue.py +298 -0
  495. package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_update_issue_gh.py +195 -0
  496. package/pipeline/skills/figma-ios/figma-to-component/scripts/phase1-gather.py +1298 -0
  497. package/pipeline/skills/figma-ios/figma-to-component/scripts/phase2-finalize.py +228 -0
  498. package/pipeline/skills/figma-ios/figma-to-component/scripts/phase3-scripts.py +1089 -0
  499. package/pipeline/skills/figma-ios/figma-to-component/scripts/phase4-finalize.py +141 -0
  500. package/pipeline/skills/figma-ios/figma-to-component/scripts/phase5-finalize.py +106 -0
  501. package/pipeline/skills/figma-ios/figma-to-component/scripts/phase6-finalize.py +162 -0
  502. package/pipeline/skills/figma-ios/figma-to-component/scripts/phase7-finalize.py +105 -0
  503. package/pipeline/skills/figma-ios/figma-to-component/scripts/register-icons-codeconnect.py +179 -0
  504. package/pipeline/skills/figma-ios/figma-to-component/scripts/remote-mcp-fetch.py +260 -0
  505. package/pipeline/skills/figma-ios/figma-to-component/scripts/resolve-author-logins.py +260 -0
  506. package/pipeline/skills/figma-ios/figma-to-component/scripts/run-uicomponents-tests.sh +86 -0
  507. package/pipeline/skills/figma-ios/figma-to-component/scripts/sidebar-generator.py +321 -0
  508. package/pipeline/skills/figma-ios/figma-to-component/scripts/update-issue-from-registry.py +1470 -0
  509. package/pipeline/skills/figma-ios/figma-to-component/scripts/validate-phase4.sh +176 -0
  510. package/pipeline/skills/figma-ios/figma-to-component/scripts/validate-phase6.sh +147 -0
  511. package/pipeline/skills/figma-ios/figma-to-component/scripts/validate-phase7a.py +629 -0
  512. package/pipeline/skills/shared/README.md +212 -0
  513. package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +315 -0
  514. package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +348 -0
  515. package/pipeline/skills/shared/core/multi-agent/SKILL.md +944 -0
  516. package/pipeline/skills/shared/core/multi-agent-autopilot/SKILL.md +51 -0
  517. package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +300 -0
  518. package/pipeline/skills/shared/core/multi-agent-delete/SKILL.md +63 -0
  519. package/pipeline/skills/shared/core/multi-agent-dev/SKILL.md +64 -0
  520. package/pipeline/skills/shared/core/multi-agent-dev-autopilot/SKILL.md +56 -0
  521. package/pipeline/skills/shared/core/multi-agent-dev-local/SKILL.md +36 -0
  522. package/pipeline/skills/shared/core/multi-agent-dev-local-autopilot/SKILL.md +42 -0
  523. package/pipeline/skills/shared/core/multi-agent-diff-explain/SKILL.md +66 -0
  524. package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +292 -0
  525. package/pipeline/skills/shared/core/multi-agent-issue/SKILL.md +35 -0
  526. package/pipeline/skills/shared/core/multi-agent-jira/SKILL.md +38 -0
  527. package/pipeline/skills/shared/core/multi-agent-kill/SKILL.md +41 -0
  528. package/pipeline/skills/shared/core/multi-agent-language/SKILL.md +87 -0
  529. package/pipeline/skills/shared/core/multi-agent-local/SKILL.md +37 -0
  530. package/pipeline/skills/shared/core/multi-agent-local-autopilot/SKILL.md +53 -0
  531. package/pipeline/skills/shared/core/multi-agent-log/SKILL.md +28 -0
  532. package/pipeline/skills/shared/core/multi-agent-manual-test/SKILL.md +47 -0
  533. package/pipeline/skills/shared/core/multi-agent-purge/SKILL.md +42 -0
  534. package/pipeline/skills/shared/core/multi-agent-refactor/SKILL.md +191 -0
  535. package/pipeline/skills/shared/core/multi-agent-resume/SKILL.md +31 -0
  536. package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +61 -0
  537. package/pipeline/skills/shared/core/multi-agent-scan/SKILL.md +61 -0
  538. package/pipeline/skills/shared/core/multi-agent-search/SKILL.md +62 -0
  539. package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +309 -0
  540. package/pipeline/skills/shared/core/multi-agent-stack/SKILL.md +55 -0
  541. package/pipeline/skills/shared/core/multi-agent-status/SKILL.md +41 -0
  542. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +184 -0
  543. package/pipeline/skills/shared/core/multi-agent-test/SKILL.md +44 -0
  544. package/pipeline/skills/shared/core/multi-agent-update/SKILL.md +34 -0
  545. package/pipeline/skills/shared/external/accessibility-compliance-accessibility-audit/SKILL.md +45 -0
  546. package/pipeline/skills/shared/external/agentflow/SKILL.md +199 -0
  547. package/pipeline/skills/shared/external/alarmkit/SKILL.md +438 -0
  548. package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +584 -0
  549. package/pipeline/skills/shared/external/android-architecture/SKILL.md +407 -0
  550. package/pipeline/skills/shared/external/android-jetpack-compose-expert/SKILL.md +153 -0
  551. package/pipeline/skills/shared/external/android-performance/SKILL.md +736 -0
  552. package/pipeline/skills/shared/external/android-security/SKILL.md +577 -0
  553. package/pipeline/skills/shared/external/android_ui_verification/SKILL.md +66 -0
  554. package/pipeline/skills/shared/external/api-patterns/SKILL.md +85 -0
  555. package/pipeline/skills/shared/external/api-security-best-practices/SKILL.md +910 -0
  556. package/pipeline/skills/shared/external/app-clips/SKILL.md +436 -0
  557. package/pipeline/skills/shared/external/app-intents/SKILL.md +489 -0
  558. package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +1076 -0
  559. package/pipeline/skills/shared/external/app-store-changelog/SKILL.md +75 -0
  560. package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +409 -0
  561. package/pipeline/skills/shared/external/app-store-review/SKILL.md +411 -0
  562. package/pipeline/skills/shared/external/app-store-review/references/code-signing.md +259 -0
  563. package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +90 -0
  564. package/pipeline/skills/shared/external/app-store-review/references/rejection-patterns.md +152 -0
  565. package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +118 -0
  566. package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +500 -0
  567. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +425 -0
  568. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +344 -0
  569. package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +508 -0
  570. package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +285 -0
  571. package/pipeline/skills/shared/external/architecture/SKILL.md +60 -0
  572. package/pipeline/skills/shared/external/authentication/SKILL.md +496 -0
  573. package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +211 -0
  574. package/pipeline/skills/shared/external/background-processing/SKILL.md +499 -0
  575. package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +390 -0
  576. package/pipeline/skills/shared/external/callkit-voip/SKILL.md +461 -0
  577. package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +425 -0
  578. package/pipeline/skills/shared/external/ci-cd-pipelines/SKILL.md +462 -0
  579. package/pipeline/skills/shared/external/clean-code/SKILL.md +94 -0
  580. package/pipeline/skills/shared/external/closed-loop-delivery/SKILL.md +116 -0
  581. package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +492 -0
  582. package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +461 -0
  583. package/pipeline/skills/shared/external/compose-components/SKILL.md +441 -0
  584. package/pipeline/skills/shared/external/compose-navigation/SKILL.md +436 -0
  585. package/pipeline/skills/shared/external/compose-testing/SKILL.md +527 -0
  586. package/pipeline/skills/shared/external/contacts-framework/SKILL.md +425 -0
  587. package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +409 -0
  588. package/pipeline/skills/shared/external/context-compression/SKILL.md +266 -0
  589. package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +491 -0
  590. package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +435 -0
  591. package/pipeline/skills/shared/external/core-motion/SKILL.md +388 -0
  592. package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +405 -0
  593. package/pipeline/skills/shared/external/core-nfc/SKILL.md +495 -0
  594. package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +420 -0
  595. package/pipeline/skills/shared/external/coreml/SKILL.md +458 -0
  596. package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +765 -0
  597. package/pipeline/skills/shared/external/css-modern/SKILL.md +467 -0
  598. package/pipeline/skills/shared/external/database-patterns/SKILL.md +335 -0
  599. package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +422 -0
  600. package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +387 -0
  601. package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +298 -0
  602. package/pipeline/skills/shared/external/debugging-strategies/SKILL.md +37 -0
  603. package/pipeline/skills/shared/external/device-integrity/SKILL.md +477 -0
  604. package/pipeline/skills/shared/external/docker-expert/SKILL.md +413 -0
  605. package/pipeline/skills/shared/external/energykit/SKILL.md +460 -0
  606. package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +541 -0
  607. package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +483 -0
  608. package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +326 -0
  609. package/pipeline/skills/shared/external/fastapi-pro/SKILL.md +190 -0
  610. package/pipeline/skills/shared/external/firebase/SKILL.md +61 -0
  611. package/pipeline/skills/shared/external/github-actions-templates/SKILL.md +348 -0
  612. package/pipeline/skills/shared/external/gradle-kotlin-dsl/SKILL.md +552 -0
  613. package/pipeline/skills/shared/external/healthkit/SKILL.md +498 -0
  614. package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +602 -0
  615. package/pipeline/skills/shared/external/help-skills/SKILL.md +166 -0
  616. package/pipeline/skills/shared/external/hig-components-content/SKILL.md +81 -0
  617. package/pipeline/skills/shared/external/hig-components-layout/SKILL.md +95 -0
  618. package/pipeline/skills/shared/external/hig-components-status/SKILL.md +82 -0
  619. package/pipeline/skills/shared/external/hig-components-system/SKILL.md +101 -0
  620. package/pipeline/skills/shared/external/hig-foundations/SKILL.md +94 -0
  621. package/pipeline/skills/shared/external/hig-inputs/SKILL.md +110 -0
  622. package/pipeline/skills/shared/external/hig-patterns/SKILL.md +99 -0
  623. package/pipeline/skills/shared/external/hig-platforms/SKILL.md +81 -0
  624. package/pipeline/skills/shared/external/hig-technologies/SKILL.md +125 -0
  625. package/pipeline/skills/shared/external/homekit-matter/SKILL.md +496 -0
  626. package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +455 -0
  627. package/pipeline/skills/shared/external/html-semantic/SKILL.md +301 -0
  628. package/pipeline/skills/shared/external/humanizer/SKILL.md +118 -0
  629. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +301 -0
  630. package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +140 -0
  631. package/pipeline/skills/shared/external/ios-debugger-agent/SKILL.md +59 -0
  632. package/pipeline/skills/shared/external/ios-developer/SKILL.md +217 -0
  633. package/pipeline/skills/shared/external/ios-localization/SKILL.md +418 -0
  634. package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +627 -0
  635. package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +462 -0
  636. package/pipeline/skills/shared/external/ios-networking/SKILL.md +441 -0
  637. package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +862 -0
  638. package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -0
  639. package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +563 -0
  640. package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +1116 -0
  641. package/pipeline/skills/shared/external/ios-security/SKILL.md +496 -0
  642. package/pipeline/skills/shared/external/ios-security/references/app-review-guidelines.md +174 -0
  643. package/pipeline/skills/shared/external/ios-security/references/cryptokit-advanced.md +297 -0
  644. package/pipeline/skills/shared/external/ios-security/references/file-storage-patterns.md +354 -0
  645. package/pipeline/skills/shared/external/ios-security/references/privacy-manifest.md +117 -0
  646. package/pipeline/skills/shared/external/kotlin-coroutines-expert/SKILL.md +101 -0
  647. package/pipeline/skills/shared/external/live-activities/SKILL.md +500 -0
  648. package/pipeline/skills/shared/external/live-activities/references/live-activity-patterns.md +868 -0
  649. package/pipeline/skills/shared/external/macos-menubar-tuist-app/SKILL.md +109 -0
  650. package/pipeline/skills/shared/external/macos-spm-app-packaging/SKILL.md +110 -0
  651. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +485 -0
  652. package/pipeline/skills/shared/external/mapkit-location/references/corelocation-patterns.md +730 -0
  653. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +748 -0
  654. package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +479 -0
  655. package/pipeline/skills/shared/external/monorepo-architect/SKILL.md +64 -0
  656. package/pipeline/skills/shared/external/musickit-audio/SKILL.md +395 -0
  657. package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +363 -0
  658. package/pipeline/skills/shared/external/natural-language/SKILL.md +412 -0
  659. package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +311 -0
  660. package/pipeline/skills/shared/external/nextjs-app-router/SKILL.md +418 -0
  661. package/pipeline/skills/shared/external/nodejs-backend-patterns/SKILL.md +38 -0
  662. package/pipeline/skills/shared/external/observability-engineer/SKILL.md +235 -0
  663. package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +398 -0
  664. package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +254 -0
  665. package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +387 -0
  666. package/pipeline/skills/shared/external/pencilkit-drawing/references/paperkit-integration.md +376 -0
  667. package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +302 -0
  668. package/pipeline/skills/shared/external/permissionkit/SKILL.md +446 -0
  669. package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +435 -0
  670. package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +501 -0
  671. package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +701 -0
  672. package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +774 -0
  673. package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +869 -0
  674. package/pipeline/skills/shared/external/photos-camera-media/references/photospicker-patterns.md +597 -0
  675. package/pipeline/skills/shared/external/play-store-review/SKILL.md +350 -0
  676. package/pipeline/skills/shared/external/push-notifications/SKILL.md +501 -0
  677. package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +677 -0
  678. package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +745 -0
  679. package/pipeline/skills/shared/external/python-patterns/SKILL.md +383 -0
  680. package/pipeline/skills/shared/external/react-best-practices/SKILL.md +290 -0
  681. package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +479 -0
  682. package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +480 -0
  683. package/pipeline/skills/shared/external/rest-api-design/SKILL.md +386 -0
  684. package/pipeline/skills/shared/external/retrofit-networking/SKILL.md +506 -0
  685. package/pipeline/skills/shared/external/room-database/SKILL.md +564 -0
  686. package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +483 -0
  687. package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +544 -0
  688. package/pipeline/skills/shared/external/speech-recognition/SKILL.md +485 -0
  689. package/pipeline/skills/shared/external/storekit/SKILL.md +478 -0
  690. package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +58 -0
  691. package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +755 -0
  692. package/pipeline/skills/shared/external/swift-charts/SKILL.md +487 -0
  693. package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +895 -0
  694. package/pipeline/skills/shared/external/swift-codable/SKILL.md +467 -0
  695. package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +408 -0
  696. package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +80 -0
  697. package/pipeline/skills/shared/external/swift-concurrency/references/swift-6-2-concurrency.md +233 -0
  698. package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +187 -0
  699. package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +341 -0
  700. package/pipeline/skills/shared/external/swift-concurrency-expert/SKILL.md +113 -0
  701. package/pipeline/skills/shared/external/swift-concurrency-pro/SKILL.md +124 -0
  702. package/pipeline/skills/shared/external/swift-concurrency-pro/references/actors.md +155 -0
  703. package/pipeline/skills/shared/external/swift-concurrency-pro/references/async-streams.md +67 -0
  704. package/pipeline/skills/shared/external/swift-concurrency-pro/references/bridging.md +52 -0
  705. package/pipeline/skills/shared/external/swift-concurrency-pro/references/bug-patterns.md +100 -0
  706. package/pipeline/skills/shared/external/swift-concurrency-pro/references/cancellation.md +107 -0
  707. package/pipeline/skills/shared/external/swift-concurrency-pro/references/diagnostics.md +70 -0
  708. package/pipeline/skills/shared/external/swift-concurrency-pro/references/hotspots.md +47 -0
  709. package/pipeline/skills/shared/external/swift-concurrency-pro/references/interop.md +129 -0
  710. package/pipeline/skills/shared/external/swift-concurrency-pro/references/new-features.md +224 -0
  711. package/pipeline/skills/shared/external/swift-concurrency-pro/references/structured.md +101 -0
  712. package/pipeline/skills/shared/external/swift-concurrency-pro/references/testing.md +218 -0
  713. package/pipeline/skills/shared/external/swift-concurrency-pro/references/unstructured.md +61 -0
  714. package/pipeline/skills/shared/external/swift-language/SKILL.md +498 -0
  715. package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +505 -0
  716. package/pipeline/skills/shared/external/swift-testing/SKILL.md +462 -0
  717. package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +504 -0
  718. package/pipeline/skills/shared/external/swift-testing-pro/SKILL.md +97 -0
  719. package/pipeline/skills/shared/external/swift-testing-pro/references/async-tests.md +252 -0
  720. package/pipeline/skills/shared/external/swift-testing-pro/references/core-rules.md +52 -0
  721. package/pipeline/skills/shared/external/swift-testing-pro/references/migrating-from-xctest.md +34 -0
  722. package/pipeline/skills/shared/external/swift-testing-pro/references/new-features.md +318 -0
  723. package/pipeline/skills/shared/external/swift-testing-pro/references/writing-better-tests.md +254 -0
  724. package/pipeline/skills/shared/external/swiftdata/SKILL.md +334 -0
  725. package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +504 -0
  726. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +975 -0
  727. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +675 -0
  728. package/pipeline/skills/shared/external/swiftdata-pro/SKILL.md +102 -0
  729. package/pipeline/skills/shared/external/swiftdata-pro/references/class-inheritance.md +104 -0
  730. package/pipeline/skills/shared/external/swiftdata-pro/references/cloudkit.md +10 -0
  731. package/pipeline/skills/shared/external/swiftdata-pro/references/core-rules.md +20 -0
  732. package/pipeline/skills/shared/external/swiftdata-pro/references/indexing.md +27 -0
  733. package/pipeline/skills/shared/external/swiftdata-pro/references/predicates.md +73 -0
  734. package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +503 -0
  735. package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +821 -0
  736. package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +553 -0
  737. package/pipeline/skills/shared/external/swiftui-expert-skill/SKILL.md +102 -0
  738. package/pipeline/skills/shared/external/swiftui-expert-skill/references/accessibility-patterns.md +215 -0
  739. package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-advanced.md +403 -0
  740. package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-basics.md +284 -0
  741. package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-transitions.md +326 -0
  742. package/pipeline/skills/shared/external/swiftui-expert-skill/references/charts-accessibility.md +135 -0
  743. package/pipeline/skills/shared/external/swiftui-expert-skill/references/charts.md +602 -0
  744. package/pipeline/skills/shared/external/swiftui-expert-skill/references/image-optimization.md +203 -0
  745. package/pipeline/skills/shared/external/swiftui-expert-skill/references/latest-apis.md +464 -0
  746. package/pipeline/skills/shared/external/swiftui-expert-skill/references/layout-best-practices.md +266 -0
  747. package/pipeline/skills/shared/external/swiftui-expert-skill/references/liquid-glass.md +416 -0
  748. package/pipeline/skills/shared/external/swiftui-expert-skill/references/list-patterns.md +394 -0
  749. package/pipeline/skills/shared/external/swiftui-expert-skill/references/macos-scenes.md +318 -0
  750. package/pipeline/skills/shared/external/swiftui-expert-skill/references/macos-views.md +357 -0
  751. package/pipeline/skills/shared/external/swiftui-expert-skill/references/macos-window-styling.md +303 -0
  752. package/pipeline/skills/shared/external/swiftui-expert-skill/references/performance-patterns.md +403 -0
  753. package/pipeline/skills/shared/external/swiftui-expert-skill/references/scroll-patterns.md +293 -0
  754. package/pipeline/skills/shared/external/swiftui-expert-skill/references/sheet-navigation-patterns.md +363 -0
  755. package/pipeline/skills/shared/external/swiftui-expert-skill/references/state-management.md +417 -0
  756. package/pipeline/skills/shared/external/swiftui-expert-skill/references/view-structure.md +389 -0
  757. package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +450 -0
  758. package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +425 -0
  759. package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +336 -0
  760. package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +97 -0
  761. package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +69 -0
  762. package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +99 -0
  763. package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +147 -0
  764. package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +98 -0
  765. package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +262 -0
  766. package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +207 -0
  767. package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +177 -0
  768. package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +169 -0
  769. package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +178 -0
  770. package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +371 -0
  771. package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +486 -0
  772. package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +1097 -0
  773. package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +780 -0
  774. package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +696 -0
  775. package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +487 -0
  776. package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +46 -0
  777. package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +29 -0
  778. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +33 -0
  779. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +52 -0
  780. package/pipeline/skills/shared/external/swiftui-performance-audit/SKILL.md +114 -0
  781. package/pipeline/skills/shared/external/swiftui-pro/SKILL.md +108 -0
  782. package/pipeline/skills/shared/external/swiftui-pro/references/accessibility.md +13 -0
  783. package/pipeline/skills/shared/external/swiftui-pro/references/api.md +39 -0
  784. package/pipeline/skills/shared/external/swiftui-pro/references/data.md +43 -0
  785. package/pipeline/skills/shared/external/swiftui-pro/references/design.md +31 -0
  786. package/pipeline/skills/shared/external/swiftui-pro/references/hygiene.md +9 -0
  787. package/pipeline/skills/shared/external/swiftui-pro/references/navigation.md +14 -0
  788. package/pipeline/skills/shared/external/swiftui-pro/references/performance.md +46 -0
  789. package/pipeline/skills/shared/external/swiftui-pro/references/swift.md +56 -0
  790. package/pipeline/skills/shared/external/swiftui-pro/references/views.md +35 -0
  791. package/pipeline/skills/shared/external/swiftui-ui-patterns/SKILL.md +103 -0
  792. package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +428 -0
  793. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +534 -0
  794. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +948 -0
  795. package/pipeline/skills/shared/external/swiftui-view-refactor/SKILL.md +210 -0
  796. package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +273 -0
  797. package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +151 -0
  798. package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +95 -0
  799. package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +51 -0
  800. package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +111 -0
  801. package/pipeline/skills/shared/external/tailwind-css/SKILL.md +309 -0
  802. package/pipeline/skills/shared/external/testing-backend/SKILL.md +393 -0
  803. package/pipeline/skills/shared/external/tipkit/SKILL.md +494 -0
  804. package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +782 -0
  805. package/pipeline/skills/shared/external/typescript-patterns/SKILL.md +336 -0
  806. package/pipeline/skills/shared/external/vision-framework/SKILL.md +475 -0
  807. package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +736 -0
  808. package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +738 -0
  809. package/pipeline/skills/shared/external/vue-composition/SKILL.md +371 -0
  810. package/pipeline/skills/shared/external/weatherkit/SKILL.md +410 -0
  811. package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +567 -0
  812. package/pipeline/skills/shared/external/web-accessibility/SKILL.md +373 -0
  813. package/pipeline/skills/shared/external/web-performance/SKILL.md +345 -0
  814. package/pipeline/skills/shared/external/web-testing/SKILL.md +385 -0
  815. package/pipeline/skills/shared/external/widgetkit/SKILL.md +497 -0
  816. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +871 -0
  817. package/pipeline/skills/skills-index.md +205 -0
@@ -0,0 +1,1298 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Phase 1 Data Gathering Script
4
+ Replaces the LLM subagent with deterministic Python calls.
5
+ Expected runtime: ~5-15 seconds (vs ~20 minutes with subagent).
6
+
7
+ Usage:
8
+ python3 phase1-gather.py \
9
+ --file-key "ps4xrJzhn7WNbfkZs895Bi" \
10
+ --node-id "9047:349" \
11
+ --artifact-path "path/to/ComponentName/" \
12
+ --component-name "AvatarPassengers" \
13
+ [--figma-token TOKEN] \
14
+ [--mcp-token TOKEN] \
15
+ [--mcp-url "http://127.0.0.1:3845/mcp"]
16
+
17
+ Exit 0 = success (prints JSON summary to stdout)
18
+ Exit 1 = failure (prints JSON error to stdout)
19
+ """
20
+
21
+ import argparse
22
+ import concurrent.futures
23
+ import http.client
24
+ import json
25
+ import os
26
+ import re
27
+ import ssl
28
+ import subprocess
29
+ import sys
30
+ import time
31
+ from datetime import datetime
32
+ from pathlib import Path
33
+ from urllib.error import HTTPError, URLError
34
+ from urllib.parse import urlparse
35
+ from urllib.request import Request, urlopen
36
+
37
+ # ─── Globals ─────────────────────────────────────────────────────────────────
38
+ MCP_MODE = None # "remote" or "local"
39
+ MCP_URL = None # local MCP URL (http://127.0.0.1:3845/mcp)
40
+ MCP_TOKEN = None # OAuth token for remote MCP
41
+ SESSION_ID = None # local MCP session ID
42
+ FIGMA_TOKEN = None
43
+ FILE_KEY = None
44
+ NODE_ID = None
45
+ COMPONENT_NAME = None
46
+ ARTIFACT_PATH = None
47
+
48
+ SUBLAYER_CAP = 20 # max sublayers to fetch for get_design_context
49
+
50
+
51
+ def log(msg):
52
+ """Log to stderr so stdout stays clean for JSON output."""
53
+ print(f"[phase1] {msg}", file=sys.stderr)
54
+
55
+
56
+ def halt(gate, reason, completed_gates, artifacts, user_action=""):
57
+ """Print HALT JSON and exit 1."""
58
+ print(json.dumps({
59
+ "status": "HALT",
60
+ "phase": "phase-1",
61
+ "gate": gate,
62
+ "reason": reason,
63
+ "completed_gates": completed_gates,
64
+ "artifacts_created": artifacts,
65
+ "user_action_required": user_action,
66
+ }, indent=2))
67
+ sys.exit(1)
68
+
69
+
70
+ # ─── HTTP Helpers ────────────────────────────────────────────────────────────
71
+
72
+ def figma_get(endpoint):
73
+ """GET from Figma REST API. Returns parsed JSON."""
74
+ url = f"https://api.figma.com/v1/{endpoint}"
75
+ req = Request(url, headers={"X-Figma-Token": FIGMA_TOKEN})
76
+ with urlopen(req, timeout=30) as resp:
77
+ return json.loads(resp.read().decode())
78
+
79
+
80
+ def download_file(url, dest):
81
+ """Download a URL to a local file path. Returns True on success."""
82
+ try:
83
+ req = Request(url)
84
+ with urlopen(req, timeout=60) as resp:
85
+ Path(dest).parent.mkdir(parents=True, exist_ok=True)
86
+ with open(dest, "wb") as f:
87
+ f.write(resp.read())
88
+ return True
89
+ except (URLError, HTTPError) as exc:
90
+ log(f"Download failed ({dest}): {exc}")
91
+ return False
92
+
93
+
94
+ # ─── MCP Token Resolution ────────────────────────────────────────────────────
95
+
96
+ def get_mcp_token(cli_token=None):
97
+ """Resolve MCP OAuth token: CLI arg > Keychain FIGMA_MCP_TOKEN > Claude Code credentials."""
98
+ if cli_token:
99
+ return cli_token
100
+ # Keychain: dedicated entry
101
+ try:
102
+ token = subprocess.check_output(
103
+ ["security", "find-generic-password", "-a", os.environ["USER"],
104
+ "-s", "FIGMA_MCP_TOKEN", "-w"],
105
+ stderr=subprocess.DEVNULL,
106
+ ).decode().strip()
107
+ if token:
108
+ log("MCP token from Keychain: FIGMA_MCP_TOKEN")
109
+ return token
110
+ except (subprocess.CalledProcessError, KeyError):
111
+ pass
112
+ # Keychain: extract from Claude Code credentials
113
+ try:
114
+ raw = subprocess.check_output(
115
+ ["security", "find-generic-password", "-s", "Claude Code-credentials",
116
+ "-a", os.environ["USER"], "-w"],
117
+ stderr=subprocess.DEVNULL,
118
+ ).decode()
119
+ match = re.search(
120
+ r'"figma-remote\|[^"]+":\{[^}]*"accessToken":"([^"]+)"', raw
121
+ )
122
+ if match:
123
+ log("MCP token extracted from Claude Code credentials")
124
+ return match.group(1)
125
+ except (subprocess.CalledProcessError, KeyError):
126
+ pass
127
+ return None
128
+
129
+
130
+ def _try_refresh_mcp_token():
131
+ """Attempt to refresh MCP token using stored refresh_token and fetch-mcp-token.py."""
132
+ global MCP_TOKEN
133
+ script = Path(__file__).parent.parent.parent / "figma-setup" / "scripts" / "fetch-mcp-token.py"
134
+ if not script.exists():
135
+ # Try relative to repo root
136
+ script = Path(__file__).resolve()
137
+ for _ in range(10):
138
+ script = script.parent
139
+ candidate = script / ".instructions" / "figma" / "figma-setup" / "scripts" / "fetch-mcp-token.py"
140
+ if candidate.exists():
141
+ script = candidate
142
+ break
143
+ else:
144
+ return False
145
+ try:
146
+ result = subprocess.run(
147
+ [sys.executable, str(script), "--verify", "--save-keychain", "--quiet"],
148
+ capture_output=True, text=True, timeout=30,
149
+ )
150
+ if result.returncode == 0 and result.stdout.strip():
151
+ MCP_TOKEN = result.stdout.strip()
152
+ log("MCP token auto-refreshed via fetch-mcp-token.py")
153
+ return True
154
+ else:
155
+ log(f"MCP token refresh failed (exit {result.returncode})")
156
+ except Exception as exc:
157
+ log(f"MCP token refresh error: {exc}")
158
+ return False
159
+
160
+
161
+ # ─── MCP Helpers ─────────────────────────────────────────────────────────────
162
+
163
+ def _parse_sse_json(raw):
164
+ """Parse JSON from SSE response. Handles multiline data payloads."""
165
+ # Find the "data: " prefix
166
+ data_start = raw.find("\ndata: ")
167
+ if data_start == -1:
168
+ data_start = raw.find("data: ")
169
+ if data_start == -1:
170
+ return None
171
+
172
+ json_str = raw[raw.index("data: ", data_start) + 6:].strip()
173
+ try:
174
+ return json.loads(json_str)
175
+ except json.JSONDecodeError:
176
+ # JSON may have trailing SSE noise — find balanced braces
177
+ depth = 0
178
+ end = 0
179
+ for i, ch in enumerate(json_str):
180
+ if ch == "{":
181
+ depth += 1
182
+ elif ch == "}":
183
+ depth -= 1
184
+ if depth == 0:
185
+ end = i + 1
186
+ break
187
+ if end > 0:
188
+ try:
189
+ return json.loads(json_str[:end])
190
+ except json.JSONDecodeError:
191
+ pass
192
+ return None
193
+
194
+
195
+ def _mcp_post_local(body_dict):
196
+ """POST to local MCP (HTTP, session-based)."""
197
+ parsed = urlparse(MCP_URL)
198
+ conn = http.client.HTTPConnection(parsed.hostname, parsed.port, timeout=120)
199
+ headers = {
200
+ "Content-Type": "application/json",
201
+ "Accept": "application/json, text/event-stream",
202
+ }
203
+ if SESSION_ID:
204
+ headers["mcp-session-id"] = SESSION_ID
205
+
206
+ conn.request("POST", parsed.path, body=json.dumps(body_dict), headers=headers)
207
+ resp = conn.getresponse()
208
+ session = resp.getheader("mcp-session-id")
209
+ raw = resp.read().decode()
210
+ conn.close()
211
+
212
+ result_json = _parse_sse_json(raw)
213
+ return result_json, session
214
+
215
+
216
+ def _mcp_post_remote(body_dict):
217
+ """POST to remote MCP (HTTPS, Bearer auth, stateless)."""
218
+ conn = http.client.HTTPSConnection("mcp.figma.com", timeout=120)
219
+ headers = {
220
+ "Content-Type": "application/json",
221
+ "Accept": "application/json, text/event-stream",
222
+ "Authorization": f"Bearer {MCP_TOKEN}",
223
+ }
224
+
225
+ conn.request("POST", "/mcp", body=json.dumps(body_dict), headers=headers)
226
+ resp = conn.getresponse()
227
+ raw = resp.read().decode()
228
+ conn.close()
229
+
230
+ if resp.status != 200:
231
+ raise RuntimeError(f"Remote MCP HTTP {resp.status}: {raw[:200]}")
232
+
233
+ result_json = _parse_sse_json(raw)
234
+ return result_json, None
235
+
236
+
237
+ def _mcp_post(body_dict):
238
+ """Route to remote or local MCP based on MCP_MODE."""
239
+ if MCP_MODE == "remote":
240
+ return _mcp_post_remote(body_dict)
241
+ else:
242
+ return _mcp_post_local(body_dict)
243
+
244
+
245
+ def mcp_init():
246
+ """Initialize MCP connection. Tries remote first, falls back to local."""
247
+ global MCP_MODE, MCP_TOKEN, SESSION_ID
248
+
249
+ init_body = {
250
+ "jsonrpc": "2.0",
251
+ "method": "initialize",
252
+ "params": {
253
+ "capabilities": {},
254
+ "clientInfo": {"name": "phase1-gather", "version": "1.0.0"},
255
+ "protocolVersion": "2025-03-26",
256
+ },
257
+ "id": 1,
258
+ }
259
+
260
+ # Try remote first
261
+ if MCP_TOKEN:
262
+ try:
263
+ log("MCP: trying remote (mcp.figma.com)...")
264
+ result, _ = _mcp_post_remote(init_body)
265
+ if result:
266
+ MCP_MODE = "remote"
267
+ log("MCP: remote connected")
268
+ return
269
+ except Exception as exc:
270
+ log(f"MCP: remote failed ({exc}), attempting token refresh...")
271
+ # Try auto-refresh before falling back to local
272
+ if _try_refresh_mcp_token():
273
+ try:
274
+ result, _ = _mcp_post_remote(init_body)
275
+ if result:
276
+ MCP_MODE = "remote"
277
+ log("MCP: remote connected (after token refresh)")
278
+ return
279
+ except Exception as exc2:
280
+ log(f"MCP: remote still failed after refresh ({exc2}), trying local...")
281
+
282
+ # Fall back to local
283
+ try:
284
+ log(f"MCP: trying local ({MCP_URL})...")
285
+ result, session = _mcp_post_local(init_body)
286
+ if session:
287
+ SESSION_ID = session
288
+ MCP_MODE = "local"
289
+ log(f"MCP: local connected (session: {SESSION_ID[:12]}...)")
290
+ return
291
+ except Exception as exc:
292
+ raise RuntimeError(f"MCP init failed — remote and local both unavailable. Local error: {exc}")
293
+
294
+ raise RuntimeError("MCP init failed — no session from local, no response from remote")
295
+
296
+
297
+ _MCP_CALL_ID = 10 # auto-increment per call
298
+
299
+
300
+ def mcp_call(tool_name, arguments):
301
+ """Call an MCP tool. Returns the text content from the result."""
302
+ global _MCP_CALL_ID
303
+ _MCP_CALL_ID += 1
304
+
305
+ # Remote MCP requires fileKey in every call
306
+ if MCP_MODE == "remote" and "fileKey" not in arguments:
307
+ arguments = {**arguments, "fileKey": FILE_KEY}
308
+
309
+ body = {
310
+ "jsonrpc": "2.0",
311
+ "method": "tools/call",
312
+ "params": {"name": tool_name, "arguments": arguments},
313
+ "id": _MCP_CALL_ID,
314
+ }
315
+ result_json, _ = _mcp_post(body)
316
+ if not result_json:
317
+ raise RuntimeError(f"MCP {tool_name}: empty response")
318
+ if "error" in result_json:
319
+ raise RuntimeError(f"MCP {tool_name}: {result_json['error']}")
320
+
321
+ # Extract text from content array
322
+ content_items = (result_json.get("result") or {}).get("content", [])
323
+ texts = [item.get("text", "") for item in content_items if item.get("type") == "text"]
324
+ return "\n".join(texts)
325
+
326
+
327
+ # ─── Batch 1: Parallel Fetchers ─────────────────────────────────────────────
328
+
329
+ def fetch_screenshot_url():
330
+ """Figma REST -> screenshot image URL."""
331
+ log("REST: fetching screenshot URL...")
332
+ data = figma_get(f"images/{FILE_KEY}?ids={NODE_ID}&format=png&scale=2")
333
+ url = (data.get("images") or {}).get(NODE_ID)
334
+ if not url:
335
+ raise RuntimeError("No screenshot URL returned from images API")
336
+ log("REST: screenshot URL obtained")
337
+ return url
338
+
339
+
340
+ def fetch_node_data():
341
+ """Figma REST -> full node document (for component properties + variants)."""
342
+ log("REST: fetching node data...")
343
+ data = figma_get(f"files/{FILE_KEY}/nodes?ids={NODE_ID}")
344
+ node = (data.get("nodes") or {}).get(NODE_ID, {}).get("document")
345
+ if not node:
346
+ raise RuntimeError("No document in nodes response")
347
+ log(f"REST: node type={node.get('type')}")
348
+ return node
349
+
350
+
351
+ def fetch_metadata():
352
+ log("MCP: get_metadata...")
353
+ text = mcp_call("get_metadata", {
354
+ "nodeId": NODE_ID,
355
+ "clientLanguages": "swift",
356
+ "clientFrameworks": "swiftui",
357
+ })
358
+ log(f"MCP: metadata {len(text)} chars")
359
+ return text
360
+
361
+
362
+ def _is_too_large_response(text):
363
+ """Check if MCP returned XML metadata instead of code (design too large)."""
364
+ return "too large" in text.lower() and "<frame" in text[:300]
365
+
366
+
367
+ def _parse_sublayer_ids(text):
368
+ """Extract sublayer node IDs from the XML metadata response."""
369
+ # Match <symbol id="X:Y" ...> or <frame id="X:Y" ...> (children only)
370
+ ids = re.findall(r'<(?:symbol|frame)\s+id="([^"]+)"', text)
371
+ # Skip the root frame (first match is the container itself)
372
+ root_match = re.search(r'<frame\s+id="([^"]+)"', text)
373
+ root_id = root_match.group(1) if root_match else None
374
+ return [nid for nid in ids if nid != root_id]
375
+
376
+
377
+ def _build_design_context_args(node_id, assets_dir=None):
378
+ """Build arguments for get_design_context. Skips dirForAssetWrites on remote."""
379
+ args = {
380
+ "nodeId": node_id,
381
+ "clientLanguages": "swift",
382
+ "clientFrameworks": "swiftui",
383
+ # Disable Code Connect in design context — when Code Connect is enabled,
384
+ # the returned snippets override the original Tailwind reference code we
385
+ # need for layout/style extraction. We fetch Code Connect separately
386
+ # via get_code_connect_map.
387
+ "disableCodeConnect": True,
388
+ }
389
+ if assets_dir and MCP_MODE == "local":
390
+ args["dirForAssetWrites"] = assets_dir
391
+ return args
392
+
393
+
394
+ def _fetch_sublayer_context(node_id, assets_dir, depth=0):
395
+ """Fetch design context for a single sublayer. Recurses if also too large."""
396
+ if depth > 3:
397
+ log(f" sublayer {node_id}: max recursion depth, returning raw")
398
+ return {"nodeId": node_id, "error": "max_recursion_depth"}
399
+
400
+ text = mcp_call("get_design_context", _build_design_context_args(node_id, assets_dir))
401
+
402
+ if _is_too_large_response(text):
403
+ log(f" sublayer {node_id}: also too large, splitting further (depth={depth+1})")
404
+ child_ids = _parse_sublayer_ids(text)
405
+ if not child_ids:
406
+ return {"nodeId": node_id, "response": text}
407
+ child_responses = {}
408
+ for cid in child_ids:
409
+ child_responses[cid] = _fetch_sublayer_context(cid, assets_dir, depth + 1)
410
+ return {"nodeId": node_id, "sublayers": True, "responses": child_responses}
411
+
412
+ log(f" sublayer {node_id}: {len(text)} chars")
413
+ return {"nodeId": node_id, "response": text}
414
+
415
+
416
+ def _fetch_sublayers_parallel(sublayer_ids, assets_dir, max_workers=10):
417
+ """Fetch all sublayer design contexts in parallel using a thread pool."""
418
+ results = {}
419
+ with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as pool:
420
+ future_to_sid = {
421
+ pool.submit(_fetch_sublayer_context, sid, assets_dir): sid
422
+ for sid in sublayer_ids
423
+ }
424
+ for future in concurrent.futures.as_completed(future_to_sid):
425
+ sid = future_to_sid[future]
426
+ try:
427
+ results[sid] = future.result()
428
+ except Exception as e:
429
+ log(f" sublayer {sid}: ERROR {e}")
430
+ results[sid] = {"nodeId": sid, "error": str(e)}
431
+ return results
432
+
433
+
434
+ def fetch_design_context():
435
+ assets_dir = str((Path(ARTIFACT_PATH) / "pass1" / "assets").resolve())
436
+ Path(assets_dir).mkdir(parents=True, exist_ok=True)
437
+
438
+ log("MCP: get_design_context...")
439
+ text = mcp_call("get_design_context", _build_design_context_args(NODE_ID, assets_dir))
440
+ log(f"MCP: design_context {len(text)} chars")
441
+
442
+ if _is_too_large_response(text):
443
+ sublayer_ids = _parse_sublayer_ids(text)
444
+
445
+ # Cap check: if too many sublayers, defer to manual resolution
446
+ if len(sublayer_ids) > SUBLAYER_CAP:
447
+ log(f"MCP: {len(sublayer_ids)} sublayers exceeds cap ({SUBLAYER_CAP}) — deferring")
448
+ return {
449
+ "__deferred": True,
450
+ "__sublayer_count": len(sublayer_ids),
451
+ "__sublayer_ids": sublayer_ids,
452
+ "__metadata_xml": text,
453
+ "__cap": SUBLAYER_CAP,
454
+ }
455
+
456
+ log(f"MCP: design too large — fetching {len(sublayer_ids)} sublayers in parallel")
457
+ if sublayer_ids:
458
+ sublayer_responses = _fetch_sublayers_parallel(sublayer_ids, assets_dir)
459
+ total = sum(
460
+ len(r.get("response", "")) for r in sublayer_responses.values()
461
+ if isinstance(r, dict) and "response" in r
462
+ )
463
+ log(f"MCP: sublayer design_context total {total} chars across {len(sublayer_ids)} sublayers")
464
+ return {
465
+ "__sublayers": True,
466
+ "__sublayer_ids": sublayer_ids,
467
+ "__metadata_xml": text,
468
+ "__responses": sublayer_responses,
469
+ }
470
+
471
+ return text
472
+
473
+
474
+ def fetch_variable_defs():
475
+ log("MCP: get_variable_defs...")
476
+ text = mcp_call("get_variable_defs", {
477
+ "nodeId": NODE_ID,
478
+ "clientLanguages": "swift",
479
+ "clientFrameworks": "swiftui",
480
+ })
481
+ log(f"MCP: variable_defs {len(text)} chars")
482
+ return text
483
+
484
+
485
+ def fetch_code_connect_map():
486
+ log("MCP: get_code_connect_map...")
487
+ # Filter by SwiftUI label — without this, MCP returns all platforms
488
+ # (e.g. Compose) and we get Android snippets instead of iOS ones.
489
+ text = mcp_call("get_code_connect_map", {
490
+ "nodeId": NODE_ID,
491
+ "codeConnectLabel": "SwiftUI",
492
+ })
493
+ log(f"MCP: code_connect_map {len(text)} chars")
494
+ return text
495
+
496
+
497
+ # ─── Batch 2: Downloads & Post-Processing ───────────────────────────────────
498
+
499
+ def download_screenshot(screenshot_url):
500
+ dest = str(Path(ARTIFACT_PATH) / "pass1" / "screenshot.png")
501
+ log("Downloading screenshot PNG...")
502
+ if not download_file(screenshot_url, dest):
503
+ raise RuntimeError("Screenshot PNG download failed")
504
+ log("Screenshot saved")
505
+ return dest
506
+
507
+
508
+ def download_variant_screenshots(node_data):
509
+ """Batch-download variant screenshots. Returns list of variant info dicts."""
510
+ if node_data.get("type") != "COMPONENT_SET":
511
+ log("Not COMPONENT_SET -- skipping variants")
512
+ return []
513
+
514
+ children = [c for c in node_data.get("children", []) if c.get("type") == "COMPONENT"]
515
+ if not children:
516
+ log("No COMPONENT children found")
517
+ return []
518
+
519
+ log(f"Found {len(children)} variants -- batch fetching image URLs...")
520
+
521
+ # Single batch API call for all variant IDs
522
+ all_ids = ",".join(c["id"] for c in children)
523
+ data = figma_get(f"images/{FILE_KEY}?ids={all_ids}&format=png&scale=2")
524
+ image_urls = data.get("images") or {}
525
+
526
+ screenshots_dir = Path(ARTIFACT_PATH) / "pass1" / "screenshots"
527
+ screenshots_dir.mkdir(parents=True, exist_ok=True)
528
+
529
+ # Build download list
530
+ downloads = []
531
+ for child in children:
532
+ url = image_urls.get(child["id"])
533
+ if not url:
534
+ continue
535
+ name = child.get("name", child["id"])
536
+ # Single-property: "State=Success" -> "Success"
537
+ # Multi-property: "Size=Large, Loyalty Type=Classic" -> "Size=Large_LoyaltyType=Classic"
538
+ parts = [p.strip() for p in name.split(",")]
539
+ if len(parts) == 1 and "=" in parts[0]:
540
+ safe = parts[0].split("=", 1)[1].strip()
541
+ else:
542
+ # "Size=Large, Loyalty Type=Classic Plus" -> "Size=Large_LoyaltyType=Classic_Plus"
543
+ formatted = []
544
+ for p in parts:
545
+ if "=" in p:
546
+ k, v = p.split("=", 1)
547
+ k = k.strip().replace(" ", "") # remove spaces in key
548
+ v = v.strip().replace(" ", "_") # spaces -> underscores in value
549
+ formatted.append(f"{k}={v}")
550
+ else:
551
+ formatted.append(p.replace(" ", ""))
552
+ safe = "_".join(formatted)
553
+ safe = re.sub(r"[/\\:]", "_", safe)
554
+ downloads.append({
555
+ "id": child["id"],
556
+ "name": name,
557
+ "safe_name": safe,
558
+ "file": f"{safe}.png",
559
+ "url": url,
560
+ "dest": str(screenshots_dir / f"{safe}.png"),
561
+ })
562
+
563
+ # Parallel download
564
+ results = []
565
+ with concurrent.futures.ThreadPoolExecutor(max_workers=10) as pool:
566
+ futs = {pool.submit(download_file, d["url"], d["dest"]): d for d in downloads}
567
+ for fut in concurrent.futures.as_completed(futs):
568
+ info = futs[fut]
569
+ if fut.result():
570
+ results.append(info)
571
+ log(f" variant: {info['safe_name']}")
572
+
573
+ # Write variants.md
574
+ results.sort(key=lambda x: x["name"])
575
+ lines = [
576
+ "# Variant Screenshots\n",
577
+ "| Variant | Node ID | File |",
578
+ "|---------|---------|------|",
579
+ ]
580
+ for r in results:
581
+ lines.append(f"| {r['name']} | {r['id']} | {r['file']} |")
582
+ lines += ["", "## Usage", "- Confluence documentation", "- Snapshot testing reference", ""]
583
+ (screenshots_dir / "variants.md").write_text("\n".join(lines))
584
+
585
+ log(f"Downloaded {len(results)}/{len(downloads)} variant screenshots")
586
+ return results
587
+
588
+
589
+ def sanitize_svgs():
590
+ """Remove Xcode-incompatible attributes from downloaded SVGs."""
591
+ assets_dir = Path(ARTIFACT_PATH) / "pass1" / "assets"
592
+ if not assets_dir.exists():
593
+ return 0
594
+
595
+ count = 0
596
+ for svg in assets_dir.glob("*.svg"):
597
+ content = svg.read_text()
598
+ original = content
599
+ content = re.sub(r' width="100%"', "", content)
600
+ content = re.sub(r' height="100%"', "", content)
601
+ content = re.sub(r' preserveAspectRatio="none"', "", content)
602
+ content = re.sub(r' style="display: block;"', "", content)
603
+ content = re.sub(r' overflow="visible"', "", content)
604
+ content = re.sub(r"var\(--fill-0, ([^)]*)\)", r"\1", content)
605
+ content = re.sub(r"var\(--stroke-0, ([^)]*)\)", r"\1", content)
606
+ if content != original:
607
+ svg.write_text(content)
608
+ count += 1
609
+ if count:
610
+ log(f"Sanitized {count} SVGs")
611
+ return count
612
+
613
+
614
+ # ─── File Writers ────────────────────────────────────────────────────────────
615
+
616
+ def save_mcp_text(filename, text, try_json=True):
617
+ """Write MCP text output to a file in pass1/."""
618
+ dest = Path(ARTIFACT_PATH) / "pass1" / filename
619
+ if try_json:
620
+ try:
621
+ parsed = json.loads(text)
622
+ dest.write_text(json.dumps(parsed, indent=2, ensure_ascii=False) + "\n")
623
+ return
624
+ except (json.JSONDecodeError, TypeError):
625
+ pass
626
+ dest.write_text(text)
627
+
628
+
629
+ def write_design_context_deferred(data):
630
+ """Write deferred artifact when sublayer count exceeds cap."""
631
+ pass1 = Path(ARTIFACT_PATH) / "pass1"
632
+
633
+ # Write design_context_response.json with metadata XML only
634
+ envelope = {
635
+ "rootNodeId": NODE_ID,
636
+ "source": MCP_MODE,
637
+ "deferred": True,
638
+ "sublayers": True,
639
+ "sublayerCount": data["__sublayer_count"],
640
+ "cap": data["__cap"],
641
+ "sublayerNodeIds": data["__sublayer_ids"],
642
+ "metadataXml": data["__metadata_xml"],
643
+ "responses": {},
644
+ }
645
+ (pass1 / "design_context_response.json").write_text(
646
+ json.dumps(envelope, indent=2, ensure_ascii=False) + "\n"
647
+ )
648
+
649
+ # Write design_context_deferred.json — resolution instructions
650
+ cmd_template = (
651
+ f"python3 .instructions/figma/figma-to-swiftui/scripts/phase1-gather.py "
652
+ f"--fetch-deferred "
653
+ f"--file-key {FILE_KEY} "
654
+ f"--node-id {NODE_ID} "
655
+ f"--artifact-path {ARTIFACT_PATH} "
656
+ f"--component-name {COMPONENT_NAME} "
657
+ f"--sublayer-ids \"{{comma_separated_ids}}\""
658
+ )
659
+ deferred = {
660
+ "status": "deferred",
661
+ "reason": "sublayer_count_exceeds_cap",
662
+ "sublayerCount": data["__sublayer_count"],
663
+ "cap": data["__cap"],
664
+ "sublayerIds": data["__sublayer_ids"],
665
+ "fileKey": FILE_KEY,
666
+ "rootNodeId": NODE_ID,
667
+ "instruction": (
668
+ "Design context has too many sublayers to fetch automatically. "
669
+ "Use --fetch-deferred mode with selected sublayer IDs to resolve."
670
+ ),
671
+ "commandTemplate": cmd_template,
672
+ }
673
+ (pass1 / "design_context_deferred.json").write_text(
674
+ json.dumps(deferred, indent=2, ensure_ascii=False) + "\n"
675
+ )
676
+ log(f"design_context_deferred.json written ({data['__sublayer_count']} sublayers)")
677
+
678
+
679
+ def write_design_context_file(data):
680
+ """Save design context -- handles normal text, sublayer dict, and deferred."""
681
+ dest = Path(ARTIFACT_PATH) / "pass1" / "design_context_response.json"
682
+
683
+ # Deferred case: sublayer count exceeded cap
684
+ if isinstance(data, dict) and data.get("__deferred"):
685
+ write_design_context_deferred(data)
686
+ return
687
+
688
+ # Sublayer case: data is a dict with __sublayers=True
689
+ if isinstance(data, dict) and data.get("__sublayers"):
690
+ sublayer_ids = data["__sublayer_ids"]
691
+ responses = {}
692
+ for sid, resp in data["__responses"].items():
693
+ if isinstance(resp, dict) and "response" in resp:
694
+ responses[sid] = resp["response"]
695
+ else:
696
+ responses[sid] = resp # nested sublayer or error
697
+ envelope = {
698
+ "rootNodeId": NODE_ID,
699
+ "source": MCP_MODE,
700
+ "sublayers": True,
701
+ "sublayerNodeIds": sublayer_ids,
702
+ "responses": responses,
703
+ }
704
+ dest.write_text(json.dumps(envelope, indent=2, ensure_ascii=False) + "\n")
705
+ return
706
+
707
+ # Normal case: data is a text string
708
+ text = data
709
+ try:
710
+ parsed = json.loads(text)
711
+ dest.write_text(json.dumps(parsed, indent=2, ensure_ascii=False) + "\n")
712
+ except (json.JSONDecodeError, TypeError):
713
+ envelope = {
714
+ "rootNodeId": NODE_ID,
715
+ "source": MCP_MODE,
716
+ "sublayers": False,
717
+ "response": text,
718
+ }
719
+ dest.write_text(json.dumps(envelope, indent=2, ensure_ascii=False) + "\n")
720
+
721
+
722
+ def write_design_context_assets():
723
+ """List downloaded assets in design_context_assets.txt."""
724
+ dest = Path(ARTIFACT_PATH) / "pass1" / "design_context_assets.txt"
725
+ assets_dir = Path(ARTIFACT_PATH) / "pass1" / "assets"
726
+ files = sorted(assets_dir.glob("*")) if assets_dir.exists() else []
727
+ files = [f for f in files if f.is_file()]
728
+
729
+ if files:
730
+ lines = [
731
+ "# Asset References from Design Context",
732
+ f"# {len(files)} assets downloaded by MCP",
733
+ "",
734
+ ]
735
+ for f in files:
736
+ lines.append(f"{f.name} | local:{f}")
737
+ else:
738
+ lines = [
739
+ "# Asset References from Design Context",
740
+ "# No assets referenced in design context response.",
741
+ ]
742
+ dest.write_text("\n".join(lines) + "\n")
743
+ return len(files)
744
+
745
+
746
+ def write_screenshot_response():
747
+ dest = Path(ARTIFACT_PATH) / "pass1" / "screenshot_response.md"
748
+ today = datetime.now().strftime("%Y-%m-%d")
749
+ dest.write_text(
750
+ f"# Screenshot Response\n\n"
751
+ f"## Screenshot File\n"
752
+ f"- File: screenshot.png\n"
753
+ f"- Node ID: {NODE_ID}\n"
754
+ f"- Source: local\n"
755
+ f"- Scale: 2x\n"
756
+ f"- Timestamp: {today}\n"
757
+ )
758
+
759
+
760
+ def generate_component_properties(node_data):
761
+ """Build component_properties.json from the REST API node document."""
762
+ node_type = node_data.get("type", "UNKNOWN")
763
+ is_component = node_type in ("COMPONENT", "COMPONENT_SET")
764
+ prop_defs = node_data.get("componentPropertyDefinitions") or {}
765
+
766
+ variants_list = []
767
+ boolean_props = []
768
+ text_slots = []
769
+ nested_comps = []
770
+ cc_mappings = {}
771
+
772
+ for key, defn in prop_defs.items():
773
+ ptype = defn.get("type")
774
+ # Display name: "Text#6893:1" -> "Text", "\u21aa Left Icon#13152:11" -> "\u21aa Left Icon"
775
+ display = key.split("#")[0]
776
+ clean = display.lstrip("\u21aa ").strip()
777
+
778
+ if ptype == "VARIANT":
779
+ values = defn.get("variantOptions", [])
780
+ variants_list.append({"property": display, "values": values, "type": "VARIANT"})
781
+ if values:
782
+ pairs = ", ".join(
783
+ f'"{v}": .{v.lower().replace(" ", "")}' for v in values
784
+ )
785
+ cc_mappings[key] = f'@FigmaEnum("{display}", mapping: [{pairs}])'
786
+
787
+ elif ptype == "BOOLEAN":
788
+ boolean_props.append({
789
+ "property": clean,
790
+ "default": defn.get("defaultValue", False),
791
+ "figmaKey": key,
792
+ })
793
+ cc_mappings[key] = f'@FigmaBoolean("{clean}")'
794
+
795
+ elif ptype == "TEXT":
796
+ text_slots.append({
797
+ "name": clean,
798
+ "defaultValue": defn.get("defaultValue", ""),
799
+ "figmaKey": key,
800
+ })
801
+ cc_mappings[key] = f'@FigmaString("{clean}")'
802
+
803
+ elif ptype == "INSTANCE_SWAP":
804
+ nested_comps.append({
805
+ "name": clean,
806
+ "nodeId": defn.get("defaultValue", ""),
807
+ "figmaKey": key,
808
+ "type": "INSTANCE_SWAP",
809
+ })
810
+ cc_mappings[key] = f'@FigmaInstance("{display}")'
811
+
812
+ result = {
813
+ "componentName": COMPONENT_NAME,
814
+ "nodeId": NODE_ID,
815
+ "nodeType": node_type,
816
+ "isComponent": is_component,
817
+ "componentPropertyDefinitions": prop_defs,
818
+ "variants": variants_list,
819
+ "booleanProperties": boolean_props,
820
+ "textSlots": text_slots,
821
+ "nestedComponents": nested_comps,
822
+ "codeConnectStrategy": {
823
+ "isComponent": is_component,
824
+ "hasPropertyDefinitions": bool(prop_defs),
825
+ "mappings": cc_mappings,
826
+ },
827
+ }
828
+ dest = Path(ARTIFACT_PATH) / "pass1" / "component_properties.json"
829
+ dest.write_text(json.dumps(result, indent=2, ensure_ascii=False) + "\n")
830
+ log("component_properties.json written")
831
+ return result
832
+
833
+
834
+ def write_component_summary(comp_props, node_data):
835
+ """Condensed summary (~100 lines) for downstream phases."""
836
+ prop_defs = comp_props.get("componentPropertyDefinitions") or {}
837
+ properties = []
838
+ for key, defn in prop_defs.items():
839
+ ptype = defn.get("type")
840
+ name = key.split("#")[0].lstrip("\u21aa ").strip()
841
+ prop = {"name": name, "type": ptype, "figmaKey": key, "default": defn.get("defaultValue")}
842
+ if ptype == "VARIANT":
843
+ prop["values"] = defn.get("variantOptions", [])
844
+ properties.append(prop)
845
+
846
+ children = node_data.get("children", [])
847
+ variant_count = len([c for c in children if c.get("type") == "COMPONENT"])
848
+ bbox = node_data.get("absoluteBoundingBox") or {}
849
+
850
+ assets_dir = Path(ARTIFACT_PATH) / "pass1" / "assets"
851
+ svgs = sorted(assets_dir.glob("*.svg")) if assets_dir.exists() else []
852
+ screenshots_dir = Path(ARTIFACT_PATH) / "pass1" / "screenshots"
853
+ pngs = sorted(screenshots_dir.glob("*.png")) if screenshots_dir.exists() else []
854
+
855
+ ntype = node_data.get("type", "UNKNOWN")
856
+ layer_line = (
857
+ f"{ntype} > COMPONENT({variant_count} variants)"
858
+ if variant_count
859
+ else ntype
860
+ )
861
+
862
+ summary = {
863
+ "componentName": COMPONENT_NAME,
864
+ "nodeId": NODE_ID,
865
+ "fileKey": FILE_KEY,
866
+ "nodeType": ntype,
867
+ "variantCount": variant_count,
868
+ "dimensions": {"width": bbox.get("width", 0), "height": bbox.get("height", 0)},
869
+ "properties": properties,
870
+ "layerSummary": [layer_line],
871
+ "variableBindingCount": 0,
872
+ "variableBindingSummary": [],
873
+ "assets": {
874
+ "svgCount": len(svgs),
875
+ "screenshotCount": len(pngs),
876
+ "fileList": [s.name for s in svgs],
877
+ },
878
+ "hasExistingCodeConnect": False,
879
+ }
880
+
881
+ dest = Path(ARTIFACT_PATH) / "pass1" / "component_summary.json"
882
+ dest.write_text(json.dumps(summary, indent=2, ensure_ascii=False) + "\n")
883
+ log("component_summary.json written")
884
+ return summary
885
+
886
+
887
+ def write_index(variant_results, asset_count):
888
+ today = datetime.now().strftime("%Y-%m-%d")
889
+ screenshots_dir = Path(ARTIFACT_PATH) / "pass1" / "screenshots"
890
+ var_count = len(list(screenshots_dir.glob("*.png"))) if screenshots_dir.exists() else 0
891
+ assets_dir = Path(ARTIFACT_PATH) / "pass1" / "assets"
892
+ svg_count = len(list(assets_dir.glob("*.svg"))) if assets_dir.exists() else 0
893
+
894
+ content = f"""# Pass 1 -- Data Gathering Output
895
+
896
+ Component: {COMPONENT_NAME}
897
+ Node ID: {NODE_ID}
898
+ File Key: {FILE_KEY}
899
+ Gathered: {today}
900
+
901
+ ## Files
902
+
903
+ | File | Description |
904
+ |------|-------------|
905
+ | `pass1/screenshot.png` | Component screenshot (2x scale) |
906
+ | `pass1/screenshot_response.md` | Screenshot metadata |
907
+ | `pass1/screenshots/` | Variant screenshots ({var_count} variants) |
908
+ | `pass1/screenshots/variants.md` | Variant screenshot index |
909
+ | `pass1/metadata_response.xml` | Layer hierarchy (XML) |
910
+ | `pass1/design_context_response.json` | Generated code + structure |
911
+ | `pass1/design_context_assets.txt` | Asset download URLs ({svg_count} vectors) |
912
+ | `pass1/assets/` | Downloaded SVG assets (sanitized for Xcode) |
913
+ | `pass1/variable_defs_response.json` | Design tokens / variables |
914
+ | `pass1/component_properties.json` | Variant properties, booleans, text slots, Code Connect strategy |
915
+ | `pass1/code_connect_map_response.json` | Existing Code Connect mappings |
916
+ | `pass1/component_summary.json` | **Condensed summary -- read this FIRST** |
917
+
918
+ ## Read Instructions
919
+
920
+ **All downstream phases: read `component_summary.json` FIRST.** It contains a condensed view of the component's properties, structure, and assets in ~100 lines. Only read raw artifacts (design_context_response.json, etc.) if you need detail the summary does not provide.
921
+
922
+ - Phase 2A (Testing Identifiers): `component_summary.json`, then `design_context_response.json` if needed, `component_properties.json`
923
+ - Phase 2B (Localization): `component_summary.json`, then `design_context_response.json` if needed, `component_properties.json`
924
+ - Phase 2C (Accessibility): `component_summary.json`, then `screenshot_response.md`, `design_context_response.json` if needed
925
+ - Phase 2D (Analytics): `component_summary.json`, then `design_context_response.json` if needed
926
+ - Phase 3 sub-phases: `component_summary.json` FIRST, then phase-specific raw artifacts as needed
927
+ - Phase 4 sub-phases: `component_summary.json` for cross-reference, pass3/ analysis files as primary input
928
+ """
929
+ (Path(ARTIFACT_PATH) / "pass1" / "_index.md").write_text(content)
930
+ log("_index.md written")
931
+
932
+
933
+ def update_implementation_history(variant_count, svg_count):
934
+ hist = Path(ARTIFACT_PATH) / "IMPLEMENTATION_HISTORY.md"
935
+ if not hist.exists():
936
+ log("WARNING: IMPLEMENTATION_HISTORY.md not found -- skipping update")
937
+ return
938
+
939
+ text = hist.read_text()
940
+
941
+ phase1_block = f"""
942
+ ## Phase 1 Data
943
+ - Screenshot: pass1/screenshot_response.md
944
+ - Screenshot PNG: pass1/screenshot.png
945
+ - Metadata: pass1/metadata_response.xml
946
+ - Design Context: pass1/design_context_response.json
947
+ - Design Assets: pass1/design_context_assets.txt
948
+ - Variable Defs: pass1/variable_defs_response.json
949
+ - Component Properties: pass1/component_properties.json (for Code Connect)
950
+ - Code Connect: pass1/code_connect_map_response.json
951
+ - Assets: pass1/assets/ (SVGs sanitized)
952
+ - Variant Screenshots: pass1/screenshots/ ({variant_count} variants)
953
+ - Component Summary: pass1/component_summary.json
954
+ """
955
+
956
+ if "## Phase 1 Data" in text:
957
+ text = re.sub(r"\n## Phase 1 Data.*?(?=\n## |\Z)", "", text, flags=re.DOTALL)
958
+
959
+ text = text.rstrip() + "\n" + phase1_block
960
+ hist.write_text(text)
961
+ log("IMPLEMENTATION_HISTORY.md updated")
962
+
963
+
964
+ # ─── Deferred Resolution ─────────────────────────────────────────────────────
965
+
966
+ def fetch_deferred(sublayer_ids):
967
+ """Fetch design context for selected sublayer IDs and merge into standard format."""
968
+ t0 = time.time()
969
+ log(f"Deferred resolution: fetching {len(sublayer_ids)} sublayers...")
970
+
971
+ pass1 = Path(ARTIFACT_PATH) / "pass1"
972
+ assets_dir = str((pass1 / "assets").resolve())
973
+ Path(assets_dir).mkdir(parents=True, exist_ok=True)
974
+
975
+ # Read existing deferred JSON to get all sublayer IDs
976
+ deferred_path = pass1 / "design_context_deferred.json"
977
+ if deferred_path.exists():
978
+ deferred = json.loads(deferred_path.read_text())
979
+ all_sublayer_ids = deferred.get("sublayerIds", [])
980
+ else:
981
+ all_sublayer_ids = sublayer_ids
982
+
983
+ # Fetch each sublayer in parallel (reuses existing infrastructure)
984
+ sublayer_responses = _fetch_sublayers_parallel(sublayer_ids, assets_dir)
985
+
986
+ # Build responses dict (text only, same format as normal sublayer path)
987
+ responses = {}
988
+ for sid, resp in sublayer_responses.items():
989
+ if isinstance(resp, dict) and "response" in resp:
990
+ responses[sid] = resp["response"]
991
+ elif isinstance(resp, dict) and "error" in resp:
992
+ responses[sid] = f"<!-- Error: {resp['error']} -->"
993
+ log(f" WARNING: sublayer {sid} had error: {resp['error']}")
994
+ else:
995
+ responses[sid] = str(resp)
996
+
997
+ # Build merged design_context_response.json — same format as non-deferred
998
+ envelope = {
999
+ "rootNodeId": NODE_ID,
1000
+ "source": MCP_MODE,
1001
+ "sublayers": True,
1002
+ "representativeSelection": True,
1003
+ "sublayerNodeIds": all_sublayer_ids,
1004
+ "selectedNodeIds": sublayer_ids,
1005
+ "responses": responses,
1006
+ # Flat concatenated response for Phase 2 NORMALIZE_DESIGN_CONTEXT compatibility
1007
+ "response": "\n\n".join(
1008
+ f"// --- Sublayer: {sid} ---\n{text}"
1009
+ for sid, text in responses.items()
1010
+ if not text.startswith("<!-- Error")
1011
+ ),
1012
+ }
1013
+
1014
+ dest = pass1 / "design_context_response.json"
1015
+ dest.write_text(json.dumps(envelope, indent=2, ensure_ascii=False) + "\n")
1016
+ log(f"design_context_response.json written ({len(responses)} sublayers, {len(envelope['response'])} chars)")
1017
+
1018
+ # Update component_summary.json
1019
+ summary_path = pass1 / "component_summary.json"
1020
+ if summary_path.exists():
1021
+ summary = json.loads(summary_path.read_text())
1022
+ summary["designContextStatus"] = "representative"
1023
+ summary["selectedSublayerCount"] = len(sublayer_ids)
1024
+ summary_path.write_text(json.dumps(summary, indent=2, ensure_ascii=False) + "\n")
1025
+ log("component_summary.json updated with designContextStatus")
1026
+
1027
+ # Sanitize any new SVGs
1028
+ sanitize_svgs()
1029
+
1030
+ # Update asset list
1031
+ write_design_context_assets()
1032
+
1033
+ duration = time.time() - t0
1034
+ log(f"=== DEFERRED COMPLETE in {duration:.1f}s (MCP: {MCP_MODE}) ===")
1035
+
1036
+ result = {
1037
+ "status": "COMPLETE",
1038
+ "phase": "phase-1-deferred",
1039
+ "mcpMode": MCP_MODE,
1040
+ "sublayersFetched": len(sublayer_ids),
1041
+ "sublayersSucceeded": len([v for v in responses.values() if not v.startswith("<!-- Error")]),
1042
+ "responseChars": len(envelope["response"]),
1043
+ "duration_seconds": round(duration, 1),
1044
+ }
1045
+ print(json.dumps(result, indent=2))
1046
+ sys.exit(0)
1047
+
1048
+
1049
+ # ─── Main ────────────────────────────────────────────────────────────────────
1050
+
1051
+ def main():
1052
+ global MCP_URL, MCP_TOKEN, FIGMA_TOKEN, FILE_KEY, NODE_ID, COMPONENT_NAME, ARTIFACT_PATH
1053
+
1054
+ parser = argparse.ArgumentParser(description="Phase 1 -- Data Gathering")
1055
+ parser.add_argument("--file-key", required=True)
1056
+ parser.add_argument("--node-id", required=True, help="colon-separated, e.g. 6893:6879")
1057
+ parser.add_argument("--artifact-path", required=True)
1058
+ parser.add_argument("--component-name", required=True)
1059
+ parser.add_argument("--figma-token", help="REST API token (falls back to macOS Keychain)")
1060
+ parser.add_argument("--mcp-token", help="MCP OAuth token (falls back to Keychain/Claude Code creds)")
1061
+ parser.add_argument("--mcp-url", default="http://127.0.0.1:3845/mcp")
1062
+ parser.add_argument("--fetch-deferred", action="store_true",
1063
+ help="Deferred resolution mode: fetch design context for selected sublayer IDs")
1064
+ parser.add_argument("--sublayer-ids", help="Comma-separated sublayer node IDs (requires --fetch-deferred)")
1065
+ args = parser.parse_args()
1066
+
1067
+ FILE_KEY = args.file_key
1068
+ NODE_ID = args.node_id
1069
+ COMPONENT_NAME = args.component_name
1070
+ ARTIFACT_PATH = args.artifact_path
1071
+ MCP_URL = args.mcp_url
1072
+
1073
+ # ─── Deferred Resolution Mode ───
1074
+ if args.fetch_deferred:
1075
+ if not args.sublayer_ids:
1076
+ halt("PRE_CHECK", "--fetch-deferred requires --sublayer-ids", [], [],
1077
+ "Pass comma-separated sublayer node IDs: --sublayer-ids 'id1,id2,id3'")
1078
+ sublayer_ids = [sid.strip() for sid in args.sublayer_ids.split(",") if sid.strip()]
1079
+ if not sublayer_ids:
1080
+ halt("PRE_CHECK", "No valid sublayer IDs provided", [], [],
1081
+ "Pass comma-separated sublayer node IDs: --sublayer-ids 'id1,id2,id3'")
1082
+ # Need MCP token for fetching
1083
+ MCP_TOKEN = get_mcp_token(args.mcp_token)
1084
+ try:
1085
+ mcp_init()
1086
+ except Exception as exc:
1087
+ halt("PRE_CHECK", f"MCP init failed: {exc}", [], [],
1088
+ "Ensure Figma Desktop is running or run /figma-setup for remote MCP")
1089
+ fetch_deferred(sublayer_ids)
1090
+ return # fetch_deferred calls sys.exit(0)
1091
+
1092
+ # Resolve Figma REST API token
1093
+ FIGMA_TOKEN = args.figma_token
1094
+ if not FIGMA_TOKEN:
1095
+ try:
1096
+ FIGMA_TOKEN = subprocess.check_output(
1097
+ ["security", "find-generic-password", "-a", os.environ["USER"],
1098
+ "-s", "FIGMA_ACCESS_TOKEN", "-w"],
1099
+ stderr=subprocess.DEVNULL,
1100
+ ).decode().strip()
1101
+ except (subprocess.CalledProcessError, KeyError):
1102
+ halt("PRE_CHECK", "FIGMA_ACCESS_TOKEN not found", [], [],
1103
+ "Pass --figma-token or: security add-generic-password -a $USER -s FIGMA_ACCESS_TOKEN -w <token>")
1104
+
1105
+ # Resolve MCP OAuth token (for remote MCP)
1106
+ MCP_TOKEN = get_mcp_token(args.mcp_token)
1107
+
1108
+ t0 = time.time()
1109
+ log(f"Phase 1: {COMPONENT_NAME} node={NODE_ID} file={FILE_KEY}")
1110
+
1111
+ # Ensure directories
1112
+ pass1 = Path(ARTIFACT_PATH) / "pass1"
1113
+ pass1.mkdir(parents=True, exist_ok=True)
1114
+ (pass1 / "assets").mkdir(exist_ok=True)
1115
+ (pass1 / "screenshots").mkdir(exist_ok=True)
1116
+
1117
+ gates = ["PRE_CHECK"]
1118
+ artifacts = []
1119
+
1120
+ # -- MCP Init (remote first, local fallback) --
1121
+ try:
1122
+ mcp_init()
1123
+ except Exception as exc:
1124
+ halt("PRE_CHECK", f"MCP init failed: {exc}", [], [],
1125
+ "Ensure Figma Desktop is running or run /figma-setup for remote MCP")
1126
+
1127
+ # -- Batch 1: parallel data fetching --
1128
+ log("=== Batch 1: parallel data fetching ===")
1129
+ b1 = {}
1130
+ b1_err = {}
1131
+
1132
+ with concurrent.futures.ThreadPoolExecutor(max_workers=6) as pool:
1133
+ jobs = {
1134
+ pool.submit(fetch_screenshot_url): "screenshot_url",
1135
+ pool.submit(fetch_node_data): "node_data",
1136
+ pool.submit(fetch_metadata): "metadata",
1137
+ pool.submit(fetch_design_context): "design_context",
1138
+ pool.submit(fetch_variable_defs): "variable_defs",
1139
+ pool.submit(fetch_code_connect_map): "code_connect_map",
1140
+ }
1141
+ for fut in concurrent.futures.as_completed(jobs):
1142
+ key = jobs[fut]
1143
+ try:
1144
+ b1[key] = fut.result()
1145
+ except Exception as exc:
1146
+ b1_err[key] = str(exc)
1147
+ log(f" ERROR {key}: {exc}")
1148
+
1149
+ # Critical checks
1150
+ if "screenshot_url" in b1_err:
1151
+ halt("SCREENSHOT", b1_err["screenshot_url"], gates, artifacts,
1152
+ "Check FIGMA_ACCESS_TOKEN and node ID")
1153
+ if "node_data" in b1_err:
1154
+ halt("COMPONENT_PROPERTIES", b1_err["node_data"], gates, artifacts,
1155
+ "Check FIGMA_ACCESS_TOKEN and node ID")
1156
+
1157
+ # -- Batch 2: downloads + processing --
1158
+ log("=== Batch 2: downloads & processing ===")
1159
+
1160
+ # Screenshot
1161
+ try:
1162
+ download_screenshot(b1["screenshot_url"])
1163
+ gates.append("SCREENSHOT")
1164
+ artifacts.append("pass1/screenshot.png")
1165
+ except Exception as exc:
1166
+ halt("SCREENSHOT", str(exc), gates, artifacts)
1167
+
1168
+ write_screenshot_response()
1169
+ artifacts.append("pass1/screenshot_response.md")
1170
+
1171
+ # Variant screenshots
1172
+ node_data = b1["node_data"]
1173
+ try:
1174
+ variant_results = download_variant_screenshots(node_data)
1175
+ gates.append("VARIANT_SCREENSHOTS")
1176
+ if variant_results:
1177
+ artifacts.extend(["pass1/screenshots/", "pass1/screenshots/variants.md"])
1178
+ except Exception as exc:
1179
+ log(f"WARNING: variant screenshots failed: {exc}")
1180
+ variant_results = []
1181
+ gates.append("VARIANT_SCREENSHOTS")
1182
+
1183
+ # Metadata
1184
+ if "metadata" in b1:
1185
+ save_mcp_text("metadata_response.xml", b1["metadata"], try_json=False)
1186
+ gates.append("METADATA")
1187
+ artifacts.append("pass1/metadata_response.xml")
1188
+ elif "metadata" in b1_err:
1189
+ (pass1 / "metadata_response.xml").write_text(f"<!-- Error: {b1_err['metadata']} -->\n")
1190
+ gates.append("METADATA")
1191
+ artifacts.append("pass1/metadata_response.xml")
1192
+
1193
+ # Design context
1194
+ design_context_deferred = False
1195
+ if "design_context" in b1:
1196
+ dc_data = b1["design_context"]
1197
+ write_design_context_file(dc_data)
1198
+ gates.append("DESIGN_CONTEXT")
1199
+ artifacts.append("pass1/design_context_response.json")
1200
+ if isinstance(dc_data, dict) and dc_data.get("__deferred"):
1201
+ design_context_deferred = True
1202
+ artifacts.append("pass1/design_context_deferred.json")
1203
+ elif "design_context" in b1_err:
1204
+ halt("DESIGN_CONTEXT", b1_err["design_context"], gates, artifacts,
1205
+ "Check Figma connection and node ID")
1206
+
1207
+ # Sanitize SVGs
1208
+ sanitize_svgs()
1209
+ gates.append("SANITIZE_SVGS")
1210
+
1211
+ # Asset list
1212
+ asset_count = write_design_context_assets()
1213
+ artifacts.append("pass1/design_context_assets.txt")
1214
+
1215
+ # Variable defs
1216
+ if "variable_defs" in b1:
1217
+ save_mcp_text("variable_defs_response.json", b1["variable_defs"])
1218
+ else:
1219
+ # REST API fallback (requires Enterprise plan — may 403)
1220
+ log("MCP variable_defs failed — trying REST API fallback...")
1221
+ try:
1222
+ rest_vars = figma_get(f"files/{FILE_KEY}/variables/local")
1223
+ (pass1 / "variable_defs_response.json").write_text(
1224
+ json.dumps(rest_vars, indent=2, ensure_ascii=False) + "\n"
1225
+ )
1226
+ log("REST API variable_defs fallback succeeded")
1227
+ except HTTPError as exc:
1228
+ if exc.code == 403:
1229
+ log("REST API variable_defs: 403 (requires Enterprise plan) — skipping")
1230
+ else:
1231
+ log(f"REST API variable_defs fallback failed: {exc}")
1232
+ (pass1 / "variable_defs_response.json").write_text(json.dumps({
1233
+ "error": "variable_defs_unavailable",
1234
+ "source": "none",
1235
+ "note": "Variable definitions could not be retrieved. MCP unavailable, REST API returned 403 (Enterprise plan required).",
1236
+ }, indent=2) + "\n")
1237
+ except Exception as exc:
1238
+ log(f"REST API variable_defs fallback failed: {exc}")
1239
+ (pass1 / "variable_defs_response.json").write_text(json.dumps({
1240
+ "error": "variable_defs_unavailable",
1241
+ "source": "none",
1242
+ "note": f"Variable definitions could not be retrieved via MCP or REST API: {exc}",
1243
+ }, indent=2) + "\n")
1244
+ gates.append("VARIABLE_DEFS")
1245
+ artifacts.append("pass1/variable_defs_response.json")
1246
+
1247
+ # Component properties
1248
+ comp_props = generate_component_properties(node_data)
1249
+ gates.append("COMPONENT_PROPERTIES")
1250
+ artifacts.append("pass1/component_properties.json")
1251
+
1252
+ # Code connect map
1253
+ if "code_connect_map" in b1:
1254
+ save_mcp_text("code_connect_map_response.json", b1["code_connect_map"])
1255
+ else:
1256
+ (pass1 / "code_connect_map_response.json").write_text("{}\n")
1257
+ gates.append("CODE_CONNECT_MAP")
1258
+ artifacts.append("pass1/code_connect_map_response.json")
1259
+
1260
+ # -- Finalize --
1261
+ log("=== Finalize ===")
1262
+
1263
+ write_component_summary(comp_props, node_data)
1264
+ artifacts.append("pass1/component_summary.json")
1265
+
1266
+ write_index(variant_results, asset_count)
1267
+ artifacts.append("pass1/_index.md")
1268
+
1269
+ update_implementation_history(
1270
+ len(variant_results),
1271
+ len(list((pass1 / "assets").glob("*.svg"))) if (pass1 / "assets").exists() else 0,
1272
+ )
1273
+
1274
+ gates.append("FINALIZE")
1275
+ duration = time.time() - t0
1276
+
1277
+ # -- Success --
1278
+ status = "COMPLETE_WITH_DEFERRED" if design_context_deferred else "COMPLETE"
1279
+ log(f"=== {status} in {duration:.1f}s (MCP: {MCP_MODE}) ===")
1280
+ summary = {
1281
+ "status": status,
1282
+ "phase": "phase-1",
1283
+ "mcpMode": MCP_MODE,
1284
+ "gates_passed": gates,
1285
+ "artifacts_created": artifacts,
1286
+ "duration_seconds": round(duration, 1),
1287
+ }
1288
+ if design_context_deferred:
1289
+ summary["deferred"] = {
1290
+ "artifact": "pass1/design_context_deferred.json",
1291
+ "reason": "sublayer_count_exceeds_cap",
1292
+ }
1293
+ print(json.dumps(summary, indent=2))
1294
+ sys.exit(0)
1295
+
1296
+
1297
+ if __name__ == "__main__":
1298
+ main()