@mmerterden/multi-agent-pipeline 11.2.0 → 11.3.1

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 (335) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +2 -2
  3. package/docs/FIGMA_PIPELINE.md +12 -25
  4. package/docs/architecture.md +3 -5
  5. package/docs/features.md +8 -8
  6. package/install/_dev-only-files.mjs +0 -6
  7. package/install/claude.mjs +39 -17
  8. package/install/copilot.mjs +22 -16
  9. package/install/templates/copilot-instructions.md +3 -3
  10. package/package.json +1 -1
  11. package/pipeline/commands/{multi-agent.md → multi-agent/SKILL.md} +39 -39
  12. package/pipeline/commands/multi-agent/{analysis.md → analysis/SKILL.md} +18 -18
  13. package/pipeline/commands/multi-agent/{analysis-resolve.md → analysis-resolve/SKILL.md} +4 -4
  14. package/pipeline/commands/multi-agent/{autopilot.md → autopilot/SKILL.md} +4 -4
  15. package/pipeline/commands/multi-agent/{build-optimize.md → build-optimize/SKILL.md} +1 -1
  16. package/pipeline/commands/multi-agent/{channels.md → channels/SKILL.md} +12 -12
  17. package/pipeline/commands/multi-agent/{dev.md → dev/SKILL.md} +4 -4
  18. package/pipeline/commands/multi-agent/{dev-autopilot.md → dev-autopilot/SKILL.md} +4 -4
  19. package/pipeline/commands/multi-agent/{dev-local.md → dev-local/SKILL.md} +5 -5
  20. package/pipeline/commands/multi-agent/{dev-local-autopilot.md → dev-local-autopilot/SKILL.md} +4 -4
  21. package/pipeline/commands/multi-agent/{finish.md → finish/SKILL.md} +6 -6
  22. package/pipeline/commands/multi-agent/{generate.md → generate/SKILL.md} +3 -3
  23. package/pipeline/commands/multi-agent/{help.md → help/SKILL.md} +4 -4
  24. package/pipeline/commands/multi-agent/{local.md → local/SKILL.md} +5 -5
  25. package/pipeline/commands/multi-agent/{local-autopilot.md → local-autopilot/SKILL.md} +5 -5
  26. package/pipeline/commands/multi-agent/{manual-test.md → manual-test/SKILL.md} +1 -1
  27. package/pipeline/commands/multi-agent/refactor/SKILL.md +257 -0
  28. package/pipeline/commands/multi-agent/{resume.md → resume/SKILL.md} +1 -1
  29. package/pipeline/commands/multi-agent/{review.md → review/SKILL.md} +5 -5
  30. package/pipeline/commands/multi-agent/{setup.md → setup/SKILL.md} +1 -1
  31. package/pipeline/commands/multi-agent/{sync.md → sync/SKILL.md} +20 -58
  32. package/pipeline/lib/credential-store.sh +8 -4
  33. package/pipeline/{commands/multi-agent → multi-agent-refs}/_account-picker.md +2 -2
  34. package/pipeline/{commands/multi-agent → multi-agent-refs}/_dev-context.md +2 -2
  35. package/pipeline/{commands/multi-agent → multi-agent-refs}/_repo-picker.md +2 -2
  36. package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/android-guide.md +1 -1
  37. package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/channels/jira.md +1 -1
  38. package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/channels/wiki.md +7 -7
  39. package/pipeline/multi-agent-refs/component-dispatch.md +92 -0
  40. package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/cross-cli-contract.md +10 -77
  41. package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/features/verify-by-test.md +1 -1
  42. package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/generate-issue.md +2 -2
  43. package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/issue-jira-triad.md +1 -1
  44. package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/keychain.md +1 -1
  45. package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases/log-format.md +1 -1
  46. package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases/modes.md +2 -2
  47. package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases/operations.md +1 -1
  48. package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases/phase-0-init.md +10 -10
  49. package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases/phase-1-analysis.md +6 -6
  50. package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases/phase-2-planning.md +3 -3
  51. package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases/phase-3-dev.md +10 -10
  52. package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases/phase-4-review.md +8 -8
  53. package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases/phase-5-test.md +3 -3
  54. package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases/phase-6-commit.md +4 -4
  55. package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases/phase-7-report.md +6 -6
  56. package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases.md +12 -12
  57. package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/progress-contract.md +4 -4
  58. package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/swiftui-guide.md +1 -1
  59. package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/tracker-contract.md +3 -3
  60. package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/wiki-capture.md +4 -4
  61. package/pipeline/preferences-template.json +17 -1
  62. package/pipeline/rules/figma-pipeline.md +2 -2
  63. package/pipeline/schemas/prefs.schema.json +0 -34
  64. package/pipeline/scripts/README.md +1 -3
  65. package/pipeline/scripts/build-skills-index.mjs +3 -6
  66. package/pipeline/scripts/eval-mine-corpus.mjs +187 -0
  67. package/pipeline/scripts/fixtures/install-layout.tsv +6 -5
  68. package/pipeline/scripts/gen-mode-dispatch.mjs +3 -3
  69. package/pipeline/scripts/lint-skills.mjs +26 -1
  70. package/pipeline/scripts/phase-banner.sh +2 -2
  71. package/pipeline/scripts/review-scope.mjs +94 -0
  72. package/pipeline/scripts/scan-agent-config.sh +2 -2
  73. package/pipeline/scripts/smoke-agent-log-cost.sh +1 -1
  74. package/pipeline/scripts/smoke-agent-model-routing.sh +4 -4
  75. package/pipeline/scripts/smoke-autopilot-circuit-breaker.sh +2 -2
  76. package/pipeline/scripts/smoke-channels-approval-gate.sh +2 -2
  77. package/pipeline/scripts/smoke-channels-flow.sh +5 -5
  78. package/pipeline/scripts/smoke-clarify.sh +1 -1
  79. package/pipeline/scripts/smoke-commands-skills-parity.sh +3 -3
  80. package/pipeline/scripts/smoke-community-gates.sh +1 -1
  81. package/pipeline/scripts/smoke-compliance-skills.sh +2 -2
  82. package/pipeline/scripts/smoke-cost-summary.sh +1 -1
  83. package/pipeline/scripts/smoke-cross-cli-behavior.sh +17 -43
  84. package/pipeline/scripts/smoke-cross-phase-cohesion.sh +1 -1
  85. package/pipeline/scripts/smoke-dev-critic.sh +1 -1
  86. package/pipeline/scripts/smoke-diff-explain.sh +1 -1
  87. package/pipeline/scripts/smoke-diff-risk.sh +1 -1
  88. package/pipeline/scripts/smoke-evidence-gate.sh +1 -1
  89. package/pipeline/scripts/smoke-figma-dispatch.sh +46 -57
  90. package/pipeline/scripts/smoke-gate-hooks.sh +1 -1
  91. package/pipeline/scripts/smoke-generate-issue.sh +8 -8
  92. package/pipeline/scripts/smoke-handoff-contract.sh +3 -3
  93. package/pipeline/scripts/smoke-identity-isolation.sh +2 -2
  94. package/pipeline/scripts/smoke-intent-guard.sh +1 -1
  95. package/pipeline/scripts/smoke-issue-comment-template.sh +2 -2
  96. package/pipeline/scripts/smoke-issue-jira-triad.sh +4 -4
  97. package/pipeline/scripts/smoke-language-axis.sh +6 -6
  98. package/pipeline/scripts/smoke-learnings-ledger.sh +3 -3
  99. package/pipeline/scripts/smoke-mode-dispatch-drift.sh +16 -16
  100. package/pipeline/scripts/smoke-model-fallback.sh +3 -3
  101. package/pipeline/scripts/smoke-multi-repo-integration.sh +2 -2
  102. package/pipeline/scripts/smoke-multi-repo-worktree.sh +1 -1
  103. package/pipeline/scripts/smoke-no-token-prompt.sh +5 -5
  104. package/pipeline/scripts/smoke-phase-0-multi-repo.sh +1 -1
  105. package/pipeline/scripts/smoke-phase-6-multi.sh +1 -1
  106. package/pipeline/scripts/smoke-phase4-gates.sh +45 -0
  107. package/pipeline/scripts/smoke-plan-approval-gate.sh +5 -5
  108. package/pipeline/scripts/smoke-plan-todos.sh +3 -3
  109. package/pipeline/scripts/smoke-pr-review-actions.sh +3 -3
  110. package/pipeline/scripts/smoke-prefs-language.sh +1 -1
  111. package/pipeline/scripts/smoke-progress-contract.sh +2 -2
  112. package/pipeline/scripts/smoke-push-retry.sh +1 -1
  113. package/pipeline/scripts/smoke-repo-map.sh +2 -2
  114. package/pipeline/scripts/smoke-shadow-git.sh +1 -1
  115. package/pipeline/scripts/smoke-skill-authoring.sh +4 -9
  116. package/pipeline/scripts/smoke-tasklist-ordering.sh +10 -10
  117. package/pipeline/scripts/smoke-test-gap.sh +1 -1
  118. package/pipeline/scripts/smoke-token-budget.sh +1 -1
  119. package/pipeline/scripts/smoke-token-preflight.sh +2 -2
  120. package/pipeline/scripts/smoke-tracker-contract.sh +13 -13
  121. package/pipeline/scripts/smoke-tracker-tokens-invocation.sh +5 -5
  122. package/pipeline/scripts/smoke-triage-memory.sh +2 -2
  123. package/pipeline/scripts/smoke-update-check.sh +1 -1
  124. package/pipeline/scripts/smoke-url-enrichment.sh +2 -2
  125. package/pipeline/scripts/smoke-validator-gates.sh +1 -1
  126. package/pipeline/scripts/smoke-verify-by-test.sh +3 -3
  127. package/pipeline/scripts/smoke-wiki-integration.sh +9 -33
  128. package/pipeline/scripts/smoke-work-summary.sh +1 -1
  129. package/pipeline/scripts/smoke-worktree-path-convention.sh +4 -4
  130. package/pipeline/scripts/test-integrity-gate.mjs +83 -0
  131. package/pipeline/scripts/uninstall.mjs +17 -6
  132. package/pipeline/skills/.skill-manifest.json +40 -160
  133. package/pipeline/skills/.skills-index.json +3 -373
  134. package/pipeline/skills/shared/README.md +1 -1
  135. package/pipeline/skills/shared/core/multi-agent/SKILL.md +1 -1
  136. package/pipeline/skills/shared/core/multi-agent-analysis/SKILL.md +2 -2
  137. package/pipeline/skills/shared/core/multi-agent-analysis-resolve/SKILL.md +3 -3
  138. package/pipeline/skills/shared/core/multi-agent-build-optimize/SKILL.md +1 -1
  139. package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +1 -1
  140. package/pipeline/skills/shared/core/multi-agent-finish/SKILL.md +1 -1
  141. package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +1 -1
  142. package/pipeline/skills/shared/core/multi-agent-refactor/SKILL.md +116 -51
  143. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +8 -30
  144. package/pipeline/skills/shared/external/compose-components/SKILL.md +14 -14
  145. package/pipeline/skills/skills-index.md +3 -44
  146. package/pipeline/commands/multi-agent/refactor.md +0 -188
  147. package/pipeline/commands/multi-agent/refs/component-dispatch.md +0 -92
  148. package/pipeline/scripts/smoke-existing-discovery-gate.sh +0 -71
  149. package/pipeline/scripts/smoke-figma-android-parity.sh +0 -148
  150. package/pipeline/scripts/smoke-figma-credential-store.sh +0 -105
  151. package/pipeline/scripts/smoke-figma-cross-cli-inventory.sh +0 -177
  152. package/pipeline/scripts/smoke-figma-sync.sh +0 -149
  153. package/pipeline/scripts/smoke-sync-delegation.sh +0 -74
  154. package/pipeline/skills/figma-android/README.md +0 -38
  155. package/pipeline/skills/figma-android/figma-component-code-connect/SKILL.md +0 -62
  156. package/pipeline/skills/figma-android/figma-component-implement/SKILL.md +0 -158
  157. package/pipeline/skills/figma-android/figma-component-test/SKILL.md +0 -120
  158. package/pipeline/skills/figma-android/figma-component-wiki/SKILL.md +0 -35
  159. package/pipeline/skills/figma-android/figma-to-component/SKILL.md +0 -124
  160. package/pipeline/skills/figma-common/README.md +0 -64
  161. package/pipeline/skills/figma-common/figma-bottom-sheets/SKILL.md +0 -152
  162. package/pipeline/skills/figma-common/figma-cli-iterate/SKILL.md +0 -282
  163. package/pipeline/skills/figma-common/figma-cli-iterate-mend/SKILL.md +0 -503
  164. package/pipeline/skills/figma-common/figma-cli-lean-iterate/SKILL.md +0 -288
  165. package/pipeline/skills/figma-common/figma-cli-skip/SKILL.md +0 -362
  166. package/pipeline/skills/figma-common/figma-commit/COMMON_REBASE.md +0 -206
  167. package/pipeline/skills/figma-common/figma-commit/REVIEW.md +0 -337
  168. package/pipeline/skills/figma-common/figma-commit/SKILL.md +0 -216
  169. package/pipeline/skills/figma-common/figma-component-confluence-sync/SKILL.md +0 -223
  170. package/pipeline/skills/figma-common/figma-component-start/SKILL.md +0 -251
  171. package/pipeline/skills/figma-common/figma-component-status-update/SKILL.md +0 -78
  172. package/pipeline/skills/figma-common/figma-evolve-component/SKILL.md +0 -61
  173. package/pipeline/skills/figma-common/figma-fix/SKILL.md +0 -321
  174. package/pipeline/skills/figma-common/figma-form-integration/SKILL.md +0 -542
  175. package/pipeline/skills/figma-common/figma-issue/SKILL.md +0 -750
  176. package/pipeline/skills/figma-common/figma-iterate/SKILL.md +0 -208
  177. package/pipeline/skills/figma-common/figma-iteration-commit/SKILL.md +0 -1020
  178. package/pipeline/skills/figma-common/figma-mend/SKILL.md +0 -331
  179. package/pipeline/skills/figma-common/figma-navigation/SKILL.md +0 -156
  180. package/pipeline/skills/figma-common/figma-overlays/SKILL.md +0 -142
  181. package/pipeline/skills/figma-common/figma-price-integration/SKILL.md +0 -398
  182. package/pipeline/skills/figma-common/figma-remote-mcp-auth/SKILL.md +0 -104
  183. package/pipeline/skills/figma-common/figma-review/SKILL.md +0 -400
  184. package/pipeline/skills/figma-common/figma-setup/SKILL.md +0 -514
  185. package/pipeline/skills/figma-common/figma-setup/scripts/fetch-mcp-token.py +0 -592
  186. package/pipeline/skills/figma-common/figma-skip/SKILL.md +0 -129
  187. package/pipeline/skills/figma-common/figma-ui-patterns/SKILL.md +0 -105
  188. package/pipeline/skills/figma-common/figma-ui-patterns/patterns/animated-gradient-border.md +0 -116
  189. package/pipeline/skills/figma-common/figma-utility/SKILL.md +0 -274
  190. package/pipeline/skills/figma-common/figma-utility/scripts/figma-utility.py +0 -808
  191. package/pipeline/skills/figma-common/figma-validate/SKILL.md +0 -633
  192. package/pipeline/skills/figma-common/performance-iteration-commit-all/SKILL.md +0 -716
  193. package/pipeline/skills/figma-common/performance-review-next/SKILL.md +0 -238
  194. package/pipeline/skills/figma-common/performance-start/SKILL.md +0 -430
  195. package/pipeline/skills/figma-common/performance-swiftui/SKILL.md +0 -706
  196. package/pipeline/skills/figma-common/performance-tour/SKILL.md +0 -423
  197. package/pipeline/skills/figma-ios/REVIEW_CHECKLIST.md +0 -67
  198. package/pipeline/skills/figma-ios/figma-component-code-connect/SKILL.md +0 -178
  199. package/pipeline/skills/figma-ios/figma-component-implement/SKILL.md +0 -184
  200. package/pipeline/skills/figma-ios/figma-component-test/SKILL.md +0 -219
  201. package/pipeline/skills/figma-ios/figma-component-wiki/SKILL.md +0 -274
  202. package/pipeline/skills/figma-ios/figma-to-component/SKILL.md +0 -416
  203. package/pipeline/skills/figma-ios/figma-to-component/halt-return-protocol.md +0 -57
  204. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-0-init.md +0 -307
  205. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-1-gathering.md +0 -119
  206. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-1.5-existing-discovery.md +0 -174
  207. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-2-orchestrator.md +0 -333
  208. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-2a-testing-identifiers.md +0 -368
  209. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-2b-localization.md +0 -393
  210. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-2c-accessibility.md +0 -617
  211. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-2d-analytics.md +0 -352
  212. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3-orchestrator.md +0 -337
  213. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3a-location.md +0 -206
  214. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3b-tokens.md +0 -235
  215. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3c-nested.md +0 -214
  216. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3d-patterns.md +0 -902
  217. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3e-assets.md +0 -156
  218. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3f-utilities.md +0 -175
  219. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3g-property-coverage.md +0 -176
  220. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3h-variant-config.md +0 -333
  221. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-4-orchestrator.md +0 -412
  222. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-4a-configuration.md +0 -336
  223. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-4b-view.md +0 -705
  224. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-4c-documentation.md +0 -332
  225. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-4d-preview.md +0 -380
  226. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-4e-modifiers.md +0 -262
  227. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-5-orchestrator.md +0 -482
  228. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-5a-viewinspector.md +0 -274
  229. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-5b-snapshot.md +0 -636
  230. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-5c-unit.md +0 -142
  231. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-6-code-connect.md +0 -547
  232. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-7-wiki.md +0 -39
  233. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-7a-confluence-generate.md +0 -659
  234. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-7a-wiki-generate.md +0 -580
  235. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-8-cleanup.md +0 -51
  236. package/pipeline/skills/figma-ios/figma-to-component/reference/accessibility.md +0 -183
  237. package/pipeline/skills/figma-ios/figma-to-component/reference/analytics-events.md +0 -64
  238. package/pipeline/skills/figma-ios/figma-to-component/reference/code-connect.md +0 -531
  239. package/pipeline/skills/figma-ios/figma-to-component/reference/confluence-api.md +0 -89
  240. package/pipeline/skills/figma-ios/figma-to-component/reference/confluence-xhtml.md +0 -155
  241. package/pipeline/skills/figma-ios/figma-to-component/reference/figma-to-swiftui-effects.md +0 -196
  242. package/pipeline/skills/figma-ios/figma-to-component/reference/halt-return-protocol.md +0 -57
  243. package/pipeline/skills/figma-ios/figma-to-component/reference/localization-naming.md +0 -89
  244. package/pipeline/skills/figma-ios/figma-to-component/reference/macros.md +0 -227
  245. package/pipeline/skills/figma-ios/figma-to-component/reference/missing-tokens.md +0 -157
  246. package/pipeline/skills/figma-ios/figma-to-component/reference/orchestrator-discipline.md +0 -90
  247. package/pipeline/skills/figma-ios/figma-to-component/reference/registry.md +0 -116
  248. package/pipeline/skills/figma-ios/figma-to-component/reference/remote-mcp-script.md +0 -153
  249. package/pipeline/skills/figma-ios/figma-to-component/reference/rest-api-script.md +0 -130
  250. package/pipeline/skills/figma-ios/figma-to-component/reference/scripts-inventory.md +0 -218
  251. package/pipeline/skills/figma-ios/figma-to-component/reference/snapshot-testing.md +0 -188
  252. package/pipeline/skills/figma-ios/figma-to-component/reference/subcomponent-graph.md +0 -93
  253. package/pipeline/skills/figma-ios/figma-to-component/reference/testing-identifiers-naming.md +0 -98
  254. package/pipeline/skills/figma-ios/figma-to-component/reference/tools.md +0 -261
  255. package/pipeline/skills/figma-ios/figma-to-component/reference/viewinspector.md +0 -147
  256. package/pipeline/skills/figma-ios/figma-to-component/reference/wiki-to-confluence-mapping.md +0 -182
  257. package/pipeline/skills/figma-ios/figma-to-component/scripts/apply-author-login-map.py +0 -185
  258. package/pipeline/skills/figma-ios/figma-to-component/scripts/backfill-status.py +0 -609
  259. package/pipeline/skills/figma-ios/figma-to-component/scripts/build-author-registry.py +0 -332
  260. package/pipeline/skills/figma-ios/figma-to-component/scripts/bulk-sync-issues.py +0 -261
  261. package/pipeline/skills/figma-ios/figma-to-component/scripts/code-connect-data-gather.py +0 -184
  262. package/pipeline/skills/figma-ios/figma-to-component/scripts/code-connect-publish.sh +0 -188
  263. package/pipeline/skills/figma-ios/figma-to-component/scripts/confluence-component-status-upload.py +0 -768
  264. package/pipeline/skills/figma-ios/figma-to-component/scripts/confluence-component-status.py +0 -191
  265. package/pipeline/skills/figma-ios/figma-to-component/scripts/confluence-data-gather.py +0 -420
  266. package/pipeline/skills/figma-ios/figma-to-component/scripts/confluence-page-ids.example.json +0 -9
  267. package/pipeline/skills/figma-ios/figma-to-component/scripts/confluence-publish.py +0 -336
  268. package/pipeline/skills/figma-ios/figma-to-component/scripts/figma-subcomponent-graph.py +0 -391
  269. package/pipeline/skills/figma-ios/figma-to-component/scripts/figma-update.py +0 -292
  270. package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/__init__.py +0 -1
  271. package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/issue_sync_propagate.py +0 -93
  272. package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/registry_writer.py +0 -299
  273. package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_backfill_status.py +0 -343
  274. package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_figma_update.py +0 -206
  275. package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_figma_update_http.py +0 -149
  276. package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_phase_clis.py +0 -281
  277. package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_registry_writer.py +0 -332
  278. package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_skill_figma_issue.py +0 -176
  279. package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_skill_figma_review.py +0 -98
  280. package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_update_issue.py +0 -298
  281. package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_update_issue_gh.py +0 -195
  282. package/pipeline/skills/figma-ios/figma-to-component/scripts/phase1-gather.py +0 -1298
  283. package/pipeline/skills/figma-ios/figma-to-component/scripts/phase2-finalize.py +0 -228
  284. package/pipeline/skills/figma-ios/figma-to-component/scripts/phase3-scripts.py +0 -1089
  285. package/pipeline/skills/figma-ios/figma-to-component/scripts/phase4-finalize.py +0 -141
  286. package/pipeline/skills/figma-ios/figma-to-component/scripts/phase5-finalize.py +0 -106
  287. package/pipeline/skills/figma-ios/figma-to-component/scripts/phase6-finalize.py +0 -162
  288. package/pipeline/skills/figma-ios/figma-to-component/scripts/phase7-finalize.py +0 -105
  289. package/pipeline/skills/figma-ios/figma-to-component/scripts/register-icons-codeconnect.py +0 -179
  290. package/pipeline/skills/figma-ios/figma-to-component/scripts/remote-mcp-fetch.py +0 -260
  291. package/pipeline/skills/figma-ios/figma-to-component/scripts/resolve-author-logins.py +0 -260
  292. package/pipeline/skills/figma-ios/figma-to-component/scripts/run-uicomponents-tests.sh +0 -86
  293. package/pipeline/skills/figma-ios/figma-to-component/scripts/sidebar-generator.py +0 -321
  294. package/pipeline/skills/figma-ios/figma-to-component/scripts/update-issue-from-registry.py +0 -1470
  295. package/pipeline/skills/figma-ios/figma-to-component/scripts/validate-phase4.sh +0 -176
  296. package/pipeline/skills/figma-ios/figma-to-component/scripts/validate-phase6.sh +0 -147
  297. package/pipeline/skills/figma-ios/figma-to-component/scripts/validate-phase7a.py +0 -629
  298. /package/pipeline/commands/multi-agent/{delete.md → delete/SKILL.md} +0 -0
  299. /package/pipeline/commands/multi-agent/{diff-explain.md → diff-explain/SKILL.md} +0 -0
  300. /package/pipeline/commands/multi-agent/{garbage-collect.md → garbage-collect/SKILL.md} +0 -0
  301. /package/pipeline/commands/multi-agent/{issue.md → issue/SKILL.md} +0 -0
  302. /package/pipeline/commands/multi-agent/{jira.md → jira/SKILL.md} +0 -0
  303. /package/pipeline/commands/multi-agent/{kill.md → kill/SKILL.md} +0 -0
  304. /package/pipeline/commands/multi-agent/{language.md → language/SKILL.md} +0 -0
  305. /package/pipeline/commands/multi-agent/{log.md → log/SKILL.md} +0 -0
  306. /package/pipeline/commands/multi-agent/{prune-logs.md → prune-logs/SKILL.md} +0 -0
  307. /package/pipeline/commands/multi-agent/{purge.md → purge/SKILL.md} +0 -0
  308. /package/pipeline/commands/multi-agent/{scan.md → scan/SKILL.md} +0 -0
  309. /package/pipeline/commands/multi-agent/{search.md → search/SKILL.md} +0 -0
  310. /package/pipeline/commands/multi-agent/{stack.md → stack/SKILL.md} +0 -0
  311. /package/pipeline/commands/multi-agent/{status.md → status/SKILL.md} +0 -0
  312. /package/pipeline/commands/multi-agent/{test.md → test/SKILL.md} +0 -0
  313. /package/pipeline/commands/multi-agent/{update.md → update/SKILL.md} +0 -0
  314. /package/pipeline/{commands/multi-agent → multi-agent-refs}/_input-parser.md +0 -0
  315. /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/analysis-template.md +0 -0
  316. /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/audit-guide.md +0 -0
  317. /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/backend-guide.md +0 -0
  318. /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/channels/confluence.md +0 -0
  319. /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/channels/issue-comment.md +0 -0
  320. /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/channels/pr-review-actions.md +0 -0
  321. /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/channels/pr.md +0 -0
  322. /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/conventions-defaults.md +0 -0
  323. /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/features/autopilot-circuit-breaker.md +0 -0
  324. /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/features/dev-critic.md +0 -0
  325. /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/features/external-context-injection.md +0 -0
  326. /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/features/model-fallback.md +0 -0
  327. /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/features/plan-todos.md +0 -0
  328. /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/features/prior-fix-detection.md +0 -0
  329. /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/features/repo-map.md +0 -0
  330. /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/features/shadow-git.md +0 -0
  331. /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/frontend-guide.md +0 -0
  332. /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/knowledge.md +0 -0
  333. /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/multi-repo-integration-build.md +0 -0
  334. /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/picker-contract.md +0 -0
  335. /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/rules.md +0 -0
@@ -1,768 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Generates and uploads a Component Implementation Status page to Confluence.
4
-
5
- Step 1: Build a unified component-index.json from:
6
- - component-catalog.json (catalog: names, variants, figma URLs, file/page grouping)
7
- - Components/{nodeId}.json (registry: codeConnect, confluence links, skip, componentName)
8
- - sub-component-graph.json (graph: sub-component dependencies)
9
- Step 2: Generate XHTML from the index.
10
- Step 3: Upload to Confluence (delete + recreate, no version bloat).
11
-
12
- Usage:
13
- python3 .instructions/figma/figma-to-swiftui/scripts/confluence-component-status.py [--dry-run] [--output status.html]
14
- """
15
-
16
- import argparse
17
- import json
18
- import os
19
- import re
20
- import subprocess
21
- import sys
22
- import urllib.parse
23
- import urllib.request
24
-
25
- BASE_URL = "{confluence.baseUrl}"
26
- SPACE = "DK"
27
- PAGE_TITLE = "Component Implementation Status"
28
-
29
-
30
- def get_token():
31
- r = subprocess.run(
32
- ["security", "find-generic-password", "-a", os.environ["USER"],
33
- "-s", "CONFLUENCE_TOKEN", "-w"],
34
- capture_output=True, text=True, check=True
35
- )
36
- return r.stdout.strip()
37
-
38
-
39
- def xml_escape(t):
40
- return t.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
41
-
42
-
43
- def node_anchor(node_id):
44
- """Convert nodeId like '6893:6879' to anchor-safe 'comp-6893-6879'."""
45
- return "comp-" + node_id.replace(":", "-")
46
-
47
-
48
- # -------------------------------------------------------------------
49
- # STEP 1: Build unified component index
50
- # -------------------------------------------------------------------
51
-
52
- def build_component_index(figma_dir):
53
- """
54
- Merge catalog + registry + graph into one index.
55
- Returns: {
56
- "components": { nodeId: { ...merged fields... } },
57
- "files": [ { fileLabel, pages: [ { pageName, nodeIds: [...] } ] } ],
58
- "summary": { ... }
59
- }
60
- Also builds name → nodeId lookup for sub-component resolution.
61
- """
62
- catalog_path = os.path.join(figma_dir, "Generated", "component-catalog.json")
63
- graph_path = os.path.join(figma_dir, "sub-component-graph.json")
64
- components_dir = os.path.join(figma_dir, "Components")
65
-
66
- # --- Load catalog ---
67
- with open(catalog_path) as f:
68
- catalog = json.load(f)
69
-
70
- # Flat component dict keyed by nodeId
71
- components = {}
72
- files = []
73
- for file_entry in catalog["files"]:
74
- file_pages = []
75
- for page in file_entry["pages"]:
76
- page_node_ids = []
77
- for comp in page["components"]:
78
- nid = comp["nodeId"]
79
- components[nid] = {
80
- "nodeId": nid,
81
- "name": comp["name"],
82
- "figmaName": comp.get("figmaName", ""),
83
- "figmaUrl": comp.get("figmaUrl", ""),
84
- "variantCount": comp.get("variantCount", 0),
85
- "codeConnect": comp.get("codeConnect", {}),
86
- "subComponents": [],
87
- # Registry fields - filled below
88
- "componentName": {},
89
- "skip": False,
90
- "confluence": {},
91
- }
92
- page_node_ids.append(nid)
93
- file_pages.append({
94
- "pageName": page["pageName"],
95
- "nodeIds": page_node_ids,
96
- })
97
- files.append({
98
- "fileLabel": file_entry["fileLabel"],
99
- "totalComponents": file_entry["totalComponents"],
100
- "pages": file_pages,
101
- })
102
-
103
- # --- Load ALL registry files (source of truth for codeConnect, skip, confluence, componentName) ---
104
- registry_count = 0
105
- if os.path.isdir(components_dir):
106
- for fname in os.listdir(components_dir):
107
- if not fname.endswith(".json"):
108
- continue
109
- fpath = os.path.join(components_dir, fname)
110
- try:
111
- with open(fpath) as f:
112
- reg = json.load(f)
113
- except (json.JSONDecodeError, OSError):
114
- continue
115
- nid = reg.get("nodeId", fname[:-5].replace("-", ":"))
116
- registry_count += 1
117
-
118
- if nid in components:
119
- # Merge registry into existing catalog entry
120
- c = components[nid]
121
- # Registry is source of truth for these fields
122
- c["codeConnect"] = reg.get("codeConnect", c["codeConnect"])
123
- c["componentName"] = reg.get("componentName", {})
124
- c["skip"] = reg.get("skip", False)
125
- c["confluence"] = reg.get("confluence", {})
126
- else:
127
- # Registry-only component (e.g. icons not in catalog)
128
- ios_name = reg.get("componentName", {}).get("ios", "")
129
- components[nid] = {
130
- "nodeId": nid,
131
- "name": ios_name or nid,
132
- "figmaName": "",
133
- "figmaUrl": "",
134
- "variantCount": 0,
135
- "codeConnect": reg.get("codeConnect", {}),
136
- "subComponents": [],
137
- "componentName": reg.get("componentName", {}),
138
- "skip": reg.get("skip", False),
139
- "confluence": reg.get("confluence", {}),
140
- "registryOnly": True, # not in catalog
141
- }
142
-
143
- # --- Build name → nodeId lookup (multiple aliases per component) ---
144
- name_to_nid = {}
145
- # Secondary fuzzy lookup keyed by figmaName with separators (`/ _ -`) and
146
- # whitespace stripped, lowercased. The Figma sub-component graph emits
147
- # plain instance-names like "CardsFareAvailabilityFareTypes" - those won't
148
- # match a catalog `figmaName` of "Cards/Fare/Availability/FareTypes" via
149
- # the exact-match map below, but they collapse to the same key here.
150
- # Used as a fallback when an exact lookup misses.
151
- def _strip_key(s):
152
- if not s:
153
- return ""
154
- return re.sub(r"[\s/_\-]+", "", s).lower()
155
- fuzzy_to_nid = {}
156
- for nid, c in components.items():
157
- # Catalog display name
158
- if c["name"]:
159
- name_to_nid[c["name"]] = nid
160
- # Figma name (e.g. "User", "CaretRight")
161
- if c["figmaName"] and c["figmaName"] not in name_to_nid:
162
- name_to_nid[c["figmaName"]] = nid
163
- # iOS component name suffix: "Image.caretRight" → "caretRight"
164
- cn = c.get("componentName", {})
165
- ios_name = cn.get("ios", "") if isinstance(cn, dict) else cn or ""
166
- if ios_name:
167
- name_to_nid[ios_name] = nid
168
- # Also strip "Image." prefix for matching graph names
169
- if "." in ios_name:
170
- suffix = ios_name.split(".", 1)[1]
171
- if suffix not in name_to_nid:
172
- name_to_nid[suffix] = nid
173
- # Case-insensitive: "caretRight" should match "CaretRight"
174
- if suffix[0:1].upper() + suffix[1:] not in name_to_nid:
175
- name_to_nid[suffix[0:1].upper() + suffix[1:]] = nid
176
- # Populate fuzzy lookup from every alias so collapse-keyed matches work.
177
- for alias in (c["name"], c.get("figmaName"), ios_name):
178
- k = _strip_key(alias)
179
- if k and k not in fuzzy_to_nid:
180
- fuzzy_to_nid[k] = nid
181
-
182
- # --- Load sub-component graph and resolve names → nodeIds ---
183
- graph_merged = 0
184
- fuzzy_resolved = 0
185
- if os.path.isfile(graph_path):
186
- with open(graph_path) as f:
187
- graph_data = json.load(f)
188
- graph = graph_data.get("graph", {})
189
- for nid, gdata in graph.items():
190
- sub_names = gdata.get("subComponents", [])
191
- if not sub_names:
192
- continue
193
- # Resolve sub-component names to nodeIds - exact match first,
194
- # then fall back to the stripped/lowercased key. The fuzzy pass
195
- # turns the previous {nodeId: null} entries (which silently
196
- # bypass the readiness algorithm downstream) into real refs.
197
- resolved_subs = []
198
- for sname in sub_names:
199
- sub_nid = name_to_nid.get(sname)
200
- if not sub_nid:
201
- sub_nid = fuzzy_to_nid.get(_strip_key(sname))
202
- if sub_nid:
203
- fuzzy_resolved += 1
204
- if sub_nid:
205
- resolved_subs.append({"nodeId": sub_nid, "name": sname})
206
- else:
207
- resolved_subs.append({"nodeId": None, "name": sname})
208
- if nid in components:
209
- components[nid]["subComponents"] = resolved_subs
210
- graph_merged += 1
211
- if fuzzy_resolved:
212
- print(f" fuzzy-resolved {fuzzy_resolved} sub-component refs that exact-match would have left as null")
213
-
214
- # --- Summary ---
215
- total = sum(1 for c in components.values() if not c.get("registryOnly"))
216
- total_variants = catalog["summary"].get("totalVariants", 0)
217
-
218
- index = {
219
- "components": components,
220
- "nameLookup": name_to_nid,
221
- "files": files,
222
- "summary": {
223
- "totalComponents": total,
224
- "totalVariants": total_variants,
225
- "registryFiles": registry_count,
226
- "graphMerged": graph_merged,
227
- "resolvedNames": len(name_to_nid),
228
- },
229
- }
230
- return index
231
-
232
-
233
- # -------------------------------------------------------------------
234
- # STEP 2: Readiness + XHTML generation
235
- # -------------------------------------------------------------------
236
-
237
- READINESS_ICON = {"implemented": "✅", "ready": "🟢", "blocked": "🔴", "skipped": "⏭️"}
238
- READINESS_COLOR = {
239
- "implemented": "#2196F3", # blue
240
- "ready": "#4CAF50", # green
241
- "blocked": "#F44336", # red
242
- "skipped": "#9E9E9E", # gray
243
- }
244
- READINESS_ORDER = {"implemented": 0, "ready": 1, "blocked": 2, "skipped": 3}
245
-
246
-
247
- def compute_readiness(comp, components, platform="ios"):
248
- """Readiness from the unified index."""
249
- if comp.get("skip"):
250
- return "skipped"
251
- if comp.get("codeConnect", {}).get(platform, False):
252
- return "implemented"
253
- subs = comp.get("subComponents", [])
254
- if not subs:
255
- return "ready"
256
- for sub in subs:
257
- sub_nid = sub.get("nodeId")
258
- if not sub_nid:
259
- continue # Unresolved name (not in catalog or registry) - doesn't block
260
- sub_comp = components.get(sub_nid)
261
- if not sub_comp:
262
- continue
263
- if sub_comp.get("skip"):
264
- continue
265
- if not sub_comp.get("codeConnect", {}).get(platform, False):
266
- return "blocked"
267
- return "ready"
268
-
269
-
270
- def make_anchor(anchor_id):
271
- return (f'<ac:structured-macro ac:name="anchor">'
272
- f'<ac:parameter ac:name="">{anchor_id}</ac:parameter>'
273
- f'</ac:structured-macro>')
274
-
275
-
276
- def make_anchor_link(anchor_id, text, color=None):
277
- """Confluence link-to-anchor on same page."""
278
- if color:
279
- return (f'<ac:link ac:anchor="{anchor_id}">'
280
- f'<ac:link-body>'
281
- f'<span style="color:{color}">{xml_escape(text)}</span>'
282
- f'</ac:link-body>'
283
- f'</ac:link>')
284
- return (f'<ac:link ac:anchor="{anchor_id}">'
285
- f'<ac:plain-text-link-body>'
286
- f'<![CDATA[{text}]]>'
287
- f'</ac:plain-text-link-body>'
288
- f'</ac:link>')
289
-
290
-
291
- def platform_cell(comp, components, platform):
292
- """Build iOS/Android cell: readiness icon + component name if connected."""
293
- r = compute_readiness(comp, components, platform)
294
- icon = READINESS_ICON.get(r, "⬜")
295
- if r == "implemented":
296
- display = comp.get("componentName", {}).get(platform) or comp["name"]
297
- return f'{icon}&nbsp;&nbsp;<code>{xml_escape(display)}</code>'
298
- if r == "skipped":
299
- return f'{icon} <span style="color:#9E9E9E"><s>{xml_escape(comp["name"])}</s></span>'
300
- return icon
301
-
302
-
303
- def generate_xhtml(index):
304
- components = index["components"]
305
- files = index["files"]
306
- summary = index["summary"]
307
- total = summary["totalComponents"]
308
- total_variants = summary["totalVariants"]
309
-
310
- # Collect catalog components (not registry-only) for counting
311
- catalog_comps = [c for c in components.values() if not c.get("registryOnly")]
312
-
313
- ios_impl = sum(1 for c in catalog_comps if compute_readiness(c, components, "ios") == "implemented")
314
- ios_ready = sum(1 for c in catalog_comps if compute_readiness(c, components, "ios") == "ready")
315
- ios_blocked = sum(1 for c in catalog_comps if compute_readiness(c, components, "ios") == "blocked")
316
- ios_skipped = sum(1 for c in catalog_comps if compute_readiness(c, components, "ios") == "skipped")
317
- android_impl = sum(1 for c in catalog_comps if compute_readiness(c, components, "android") == "implemented")
318
- android_ready = sum(1 for c in catalog_comps if compute_readiness(c, components, "android") == "ready")
319
- android_blocked = sum(1 for c in catalog_comps if compute_readiness(c, components, "android") == "blocked")
320
- android_skipped = sum(1 for c in catalog_comps if compute_readiness(c, components, "android") == "skipped")
321
-
322
- p = []
323
-
324
- p.append(f'<p><em>Component implementation status - generated from unified component index '
325
- f'({summary["resolvedNames"]} name aliases, {summary["registryFiles"]} registry files).</em></p>')
326
- p.append(f'<p><strong>Legend:</strong> '
327
- f'✅ Implemented (Code Connected) - shows component name | '
328
- f'🟢 Ready for Development | '
329
- f'🔴 Blocked (has unconnected sub-components) | '
330
- f'⏭️ Skipped (will not be implemented)<br/>'
331
- f'<strong>Sub-component colors:</strong> '
332
- f'<span style="color:#2196F3">Blue = implemented</span> | '
333
- f'<span style="color:#4CAF50">Green = ready</span> | '
334
- f'<span style="color:#F44336">Red = blocked</span> | '
335
- f'<span style="color:#9E9E9E">Gray = skipped or unresolved</span></p>')
336
-
337
- # Script: find anchor (outside expand), open the nearest expand-container sibling, scroll
338
- p.append('<ac:structured-macro ac:name="html">')
339
- p.append('<ac:plain-text-body><![CDATA[<script>')
340
- p.append("""(function(){
341
- function openExpand(container){
342
- if(!container||!container.classList.contains('expand-container'))return;
343
- var body=container.querySelector('.expand-content');
344
- if(body){body.style.display='block';body.classList.remove('expand-hidden');}
345
- var ctrl=container.querySelector('.expand-control');
346
- if(ctrl)ctrl.classList.add('expanded');
347
- }
348
- function expandAndScroll(id){
349
- var el=document.getElementById(id);
350
- if(!el)el=document.querySelector('.confluence-anchor-link[id="'+id+'"]');
351
- if(!el)return false;
352
- var sib=el;
353
- while(sib){
354
- if(sib.classList&&sib.classList.contains('expand-container')){openExpand(sib);break;}
355
- sib=sib.nextElementSibling;
356
- }
357
- if(!sib){
358
- var parent=el.parentElement;
359
- while(parent){
360
- var expands=parent.querySelectorAll('.expand-container');
361
- if(expands.length>0){for(var i=0;i<expands.length;i++)openExpand(expands[i]);break;}
362
- parent=parent.parentElement;
363
- }
364
- }
365
- setTimeout(function(){el.scrollIntoView({behavior:'smooth',block:'start'});},150);
366
- return true;
367
- }
368
- function onHash(){
369
- var h=window.location.hash;
370
- if(h&&h.length>1)expandAndScroll(decodeURIComponent(h.substring(1)));
371
- }
372
- window.addEventListener('hashchange',onHash);
373
- if(document.readyState==='complete')setTimeout(onHash,500);
374
- else window.addEventListener('load',function(){setTimeout(onHash,500);});
375
- })();""")
376
- p.append('</script>]]></ac:plain-text-body>')
377
- p.append('</ac:structured-macro>')
378
-
379
- p.append('<hr/>')
380
-
381
- p.append('<ac:structured-macro ac:name="toc">')
382
- p.append('<ac:parameter ac:name="printable">true</ac:parameter>')
383
- p.append('<ac:parameter ac:name="style">disc</ac:parameter>')
384
- p.append('<ac:parameter ac:name="maxLevel">3</ac:parameter>')
385
- p.append('<ac:parameter ac:name="minLevel">2</ac:parameter>')
386
- p.append('</ac:structured-macro>')
387
- p.append('<hr/>')
388
-
389
- # --- Summary table ---
390
- p.append('<h2>Summary</h2>')
391
- p.append('<table><tbody>')
392
- p.append('<tr><th>Metric</th><th>iOS</th><th>Android</th></tr>')
393
- p.append(f'<tr><td>Total Components</td><td colspan="2">{total}</td></tr>')
394
- p.append(f'<tr><td>✅ Implemented</td><td>{ios_impl}</td><td>{android_impl}</td></tr>')
395
- p.append(f'<tr><td>🟢 Ready</td><td>{ios_ready}</td><td>{android_ready}</td></tr>')
396
- p.append(f'<tr><td>🔴 Blocked</td><td>{ios_blocked}</td><td>{android_blocked}</td></tr>')
397
- p.append(f'<tr><td>⏭️ Skipped</td><td>{ios_skipped}</td><td>{android_skipped}</td></tr>')
398
- p.append(f'<tr><td>Total Variants</td><td colspan="2">{total_variants}</td></tr>')
399
- effective_total = total - ios_skipped
400
- effective_total_android = total - android_skipped
401
- ios_pct = (100 * ios_impl // max(effective_total, 1))
402
- android_pct = (100 * android_impl // max(effective_total_android, 1))
403
- p.append(f'<tr><td>Progress</td><td>{ios_impl}/{effective_total} ({ios_pct}%)</td>'
404
- f'<td>{android_impl}/{effective_total_android} ({android_pct}%)</td></tr>')
405
- p.append('</tbody></table>')
406
- p.append('<hr/>')
407
-
408
- # --- Per file > page > component tables ---
409
- for file_entry in files:
410
- file_label = file_entry["fileLabel"]
411
- file_total = file_entry["totalComponents"]
412
-
413
- file_comps = [components[nid] for pg in file_entry["pages"] for nid in pg["nodeIds"]]
414
- file_ios_impl = sum(1 for c in file_comps if compute_readiness(c, components, "ios") == "implemented")
415
- file_ios_skipped = sum(1 for c in file_comps if compute_readiness(c, components, "ios") == "skipped")
416
- file_ios_eff = file_total - file_ios_skipped
417
- file_android_impl = sum(1 for c in file_comps if compute_readiness(c, components, "android") == "implemented")
418
- file_android_skipped = sum(1 for c in file_comps if compute_readiness(c, components, "android") == "skipped")
419
- file_android_eff = file_total - file_android_skipped
420
-
421
- file_label_esc = xml_escape(file_label)
422
- file_stats = f'iOS {file_ios_impl}/{file_ios_eff} / Android {file_android_impl}/{file_android_eff}'
423
- if file_ios_eff == 0 and file_android_eff == 0:
424
- # All skipped - no TOC entry, just a styled paragraph
425
- p.append(f'<p><strong style="font-size:20px"><s>{file_label_esc} ({file_stats})</s></strong></p>')
426
- else:
427
- p.append(f'<h2>{file_label_esc} ({file_stats})</h2>')
428
-
429
- for page_entry in file_entry["pages"]:
430
- page_name = page_entry["pageName"]
431
- page_comps = [components[nid] for nid in page_entry["nodeIds"]]
432
- pg_ios_impl = sum(1 for c in page_comps if compute_readiness(c, components, "ios") == "implemented")
433
- pg_ios_skipped = sum(1 for c in page_comps if compute_readiness(c, components, "ios") == "skipped")
434
- pg_ios_eff = len(page_comps) - pg_ios_skipped
435
- pg_android_impl = sum(1 for c in page_comps if compute_readiness(c, components, "android") == "implemented")
436
- pg_android_skipped = sum(1 for c in page_comps if compute_readiness(c, components, "android") == "skipped")
437
- pg_android_eff = len(page_comps) - pg_android_skipped
438
-
439
- page_name_esc = xml_escape(page_name)
440
- pg_stats = f'iOS {pg_ios_impl}/{pg_ios_eff} / Android {pg_android_impl}/{pg_android_eff}'
441
- if pg_ios_eff == 0 and pg_android_eff == 0:
442
- # All skipped - no TOC entry, just a styled paragraph
443
- p.append(f'<p><strong style="font-size:16px"><s>{page_name_esc} ({pg_stats})</s></strong></p>')
444
- else:
445
- p.append(f'<h3>{page_name_esc} ({pg_stats})</h3>')
446
-
447
- sorted_comps = sorted(page_comps, key=lambda c: (
448
- READINESS_ORDER.get(compute_readiness(c, components, "ios"), 9), c["name"]))
449
-
450
- # Anchors OUTSIDE expand - Confluence can't resolve anchors inside collapsed expand
451
- for c in sorted_comps:
452
- p.append(make_anchor(node_anchor(c["nodeId"])))
453
-
454
- p.append('<ac:structured-macro ac:name="expand">')
455
- p.append(f'<ac:parameter ac:name="title">{page_name_esc} - '
456
- f'iOS {pg_ios_impl}/{pg_ios_eff} / Android {pg_android_impl}/{pg_android_eff} '
457
- f'({len(page_comps)} components)</ac:parameter>')
458
- p.append('<ac:rich-text-body>')
459
- p.append('<table><tbody>')
460
- p.append('<tr>'
461
- '<th>iOS</th>'
462
- '<th>Android</th>'
463
- '<th>Component</th>'
464
- '<th>Figma Component</th>'
465
- '<th>Figma Name</th>'
466
- '<th>Variants</th>'
467
- '<th>Sub-Components</th>'
468
- '</tr>')
469
-
470
- for c in sorted_comps:
471
- name_esc = xml_escape(c["name"])
472
- figma_esc = xml_escape(c.get("figmaName", ""))
473
- is_skipped = c.get("skip", False)
474
-
475
- # Component column
476
- conf_url = c.get("confluence", {}).get("page")
477
- if is_skipped:
478
- comp_cell = f'<span style="color:#9E9E9E"><s>{name_esc}</s></span>'
479
- elif conf_url:
480
- comp_cell = f'<a href="{conf_url}">{name_esc}</a>'
481
- else:
482
- comp_cell = name_esc
483
-
484
- # Platform columns
485
- ios_cell = platform_cell(c, components, "ios")
486
- android_cell = platform_cell(c, components, "android")
487
-
488
- # Figma Component column
489
- if c.get("figmaUrl"):
490
- figma_cell = f'<a href="{c["figmaUrl"]}">{name_esc}</a>'
491
- else:
492
- figma_cell = name_esc
493
-
494
- # Variant count
495
- vc = c.get("variantCount", 0)
496
- variant_cell = str(vc) if vc > 0 else " - "
497
-
498
- # Sub-components - colored links (blue/green/red/gray), NO emoji
499
- subs = c.get("subComponents", [])
500
- if subs:
501
- sub_links = []
502
- for sub in subs:
503
- sub_nid = sub.get("nodeId")
504
- sub_name = sub["name"]
505
- if sub_nid:
506
- sub_comp = components.get(sub_nid)
507
- if sub_comp:
508
- s_anchor = node_anchor(sub_nid)
509
- s_readiness = compute_readiness(sub_comp, components, "ios")
510
- s_color = READINESS_COLOR.get(s_readiness, "#999")
511
- sub_links.append(make_anchor_link(s_anchor, sub_name, s_color))
512
- else:
513
- sub_links.append(f'<span style="color:#999">{xml_escape(sub_name)}</span>')
514
- else:
515
- sub_links.append(f'<span style="color:#999">{xml_escape(sub_name)}</span>')
516
- subs_cell = ", ".join(sub_links)
517
- else:
518
- subs_cell = " - "
519
-
520
- row_style = ' style="background:#f5f5f5; color:#9E9E9E"' if is_skipped else ''
521
- p.append(
522
- f'<tr{row_style}>'
523
- f'<td>{ios_cell}</td>'
524
- f'<td>{android_cell}</td>'
525
- f'<td>{comp_cell}</td>'
526
- f'<td>{figma_cell}</td>'
527
- f'<td><code>{figma_esc}</code></td>'
528
- f'<td>{variant_cell}</td>'
529
- f'<td>{subs_cell}</td>'
530
- f'</tr>'
531
- )
532
-
533
- p.append('</tbody></table>')
534
- p.append('</ac:rich-text-body>')
535
- p.append('</ac:structured-macro>')
536
-
537
- p.append('<hr/>')
538
-
539
- # --- Connected Components - iOS ---
540
- ios_connected = [c for c in catalog_comps if c.get("codeConnect", {}).get("ios")]
541
- p.append('<h2>Connected Components - iOS</h2>')
542
- if ios_connected:
543
- p.append('<table><tbody>')
544
- p.append('<tr><th>Component</th><th>Figma Name</th></tr>')
545
- for c in sorted(ios_connected, key=lambda c: c["name"]):
546
- anchor = node_anchor(c["nodeId"])
547
- figma_esc = xml_escape(c.get("figmaName", ""))
548
- p.append(
549
- f'<tr>'
550
- f'<td>{make_anchor_link(anchor, c["name"])}</td>'
551
- f'<td><code>{figma_esc}</code></td>'
552
- f'</tr>'
553
- )
554
- p.append('</tbody></table>')
555
- else:
556
- p.append('<p><em>No iOS-connected components yet.</em></p>')
557
-
558
- # --- Connected Components - Android ---
559
- android_connected = [c for c in catalog_comps if c.get("codeConnect", {}).get("android")]
560
- p.append('<h2>Connected Components - Android</h2>')
561
- if android_connected:
562
- p.append('<table><tbody>')
563
- p.append('<tr><th>Component</th><th>Figma Name</th></tr>')
564
- for c in sorted(android_connected, key=lambda c: c["name"]):
565
- anchor = node_anchor(c["nodeId"])
566
- figma_esc = xml_escape(c.get("figmaName", ""))
567
- p.append(
568
- f'<tr>'
569
- f'<td>{make_anchor_link(anchor, c["name"])}</td>'
570
- f'<td><code>{figma_esc}</code></td>'
571
- f'</tr>'
572
- )
573
- p.append('</tbody></table>')
574
- else:
575
- p.append('<p><em>No Android-connected components yet.</em></p>')
576
-
577
- p.append('<hr/>')
578
- p.append(f'<p><em>Index: {len(components)} components, '
579
- f'{summary["resolvedNames"]} name aliases, '
580
- f'{summary["registryFiles"]} registry files</em></p>')
581
-
582
- return "\n".join(p)
583
-
584
-
585
- # -------------------------------------------------------------------
586
- # STEP 3: Confluence upload
587
- # -------------------------------------------------------------------
588
-
589
- def lookup_page(token, title, parent_id=None):
590
- encoded = urllib.parse.quote(title)
591
- url = f"{BASE_URL}/rest/api/content?spaceKey={SPACE}&title={encoded}&expand=version,ancestors"
592
- req = urllib.request.Request(url)
593
- req.add_header("Authorization", f"Bearer {token}")
594
- with urllib.request.urlopen(req) as resp:
595
- d = json.loads(resp.read())
596
- for r in d.get("results", []):
597
- if parent_id:
598
- anc = r.get("ancestors", [])
599
- pid = anc[-1]["id"] if anc else None
600
- if str(pid) != str(parent_id):
601
- continue
602
- return {"id": r["id"], "version": r["version"]["number"]}
603
- return None
604
-
605
-
606
- def delete_page(token, page_id):
607
- """Delete page and purge from trash to avoid version/storage bloat."""
608
- req = urllib.request.Request(
609
- f"{BASE_URL}/rest/api/content/{page_id}", method="DELETE"
610
- )
611
- req.add_header("Authorization", f"Bearer {token}")
612
- urllib.request.urlopen(req)
613
- print(f"Deleted: id={page_id}")
614
- try:
615
- req = urllib.request.Request(
616
- f"{BASE_URL}/rest/api/content/{page_id}?status=trashed", method="DELETE"
617
- )
618
- req.add_header("Authorization", f"Bearer {token}")
619
- urllib.request.urlopen(req)
620
- print(f"Purged from trash: id={page_id}")
621
- except urllib.error.HTTPError:
622
- print(f"Note: Could not purge from trash (may need admin rights)")
623
-
624
-
625
- def create_page(token, title, content, parent_id):
626
- """Create a fresh page (always v1)."""
627
- payload = {
628
- "type": "page", "title": title,
629
- "ancestors": [{"id": str(parent_id)}],
630
- "space": {"key": SPACE},
631
- "body": {"storage": {"value": content, "representation": "storage"}}
632
- }
633
- body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
634
- req = urllib.request.Request(f"{BASE_URL}/rest/api/content", data=body, method="POST")
635
- req.add_header("Authorization", f"Bearer {token}")
636
- req.add_header("Content-Type", "application/json; charset=utf-8")
637
- try:
638
- with urllib.request.urlopen(req) as resp:
639
- d = json.loads(resp.read())
640
- except urllib.error.HTTPError as e:
641
- raise RuntimeError(f"Create failed: {e.read().decode()[:500]}")
642
- print(f"Created: \"{title}\" -> {d['id']} (v1, fresh)")
643
- return d["id"]
644
-
645
-
646
- def update_page(token, page_id, title, content, version):
647
- """Update an existing page in-place (fallback when delete is forbidden)."""
648
- payload = {
649
- "type": "page", "title": title,
650
- "version": {"number": version + 1},
651
- "body": {"storage": {"value": content, "representation": "storage"}}
652
- }
653
- body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
654
- req = urllib.request.Request(
655
- f"{BASE_URL}/rest/api/content/{page_id}", data=body, method="PUT"
656
- )
657
- req.add_header("Authorization", f"Bearer {token}")
658
- req.add_header("Content-Type", "application/json; charset=utf-8")
659
- try:
660
- with urllib.request.urlopen(req) as resp:
661
- d = json.loads(resp.read())
662
- except urllib.error.HTTPError as e:
663
- raise RuntimeError(f"Update failed: {e.read().decode()[:500]}")
664
- print(f"Updated: \"{title}\" -> {d['id']} (v{version + 1})")
665
- return d["id"]
666
-
667
-
668
- def upload(token, title, content, parent_id, dry_run=False):
669
- """Delete existing page + recreate fresh, or update in-place as fallback."""
670
- existing = lookup_page(token, title, parent_id)
671
- if existing:
672
- if dry_run:
673
- print(f"[DRY RUN] Would delete+recreate: \"{title}\" (id={existing['id']}, was v{existing['version']})")
674
- return existing["id"]
675
- try:
676
- delete_page(token, existing["id"])
677
- except urllib.error.HTTPError:
678
- print(f"Delete forbidden, updating in-place instead...")
679
- return update_page(token, existing["id"], title, content, existing["version"])
680
- elif dry_run:
681
- print(f"[DRY RUN] Would create: \"{title}\"")
682
- return "DRY"
683
- return create_page(token, title, content, parent_id)
684
-
685
-
686
- # -------------------------------------------------------------------
687
- # Main
688
- # -------------------------------------------------------------------
689
-
690
- def main():
691
- parser = argparse.ArgumentParser()
692
- parser.add_argument("--dry-run", action="store_true")
693
- parser.add_argument("--output", help="Write XHTML to file instead of uploading")
694
- args = parser.parse_args()
695
-
696
- # Find Shared/Figma/ directory
697
- for base in [".", "{repos.common.path}"]:
698
- cat = os.path.join(base, "Shared/Figma/Generated/component-catalog.json")
699
- if os.path.isfile(cat):
700
- figma_dir = os.path.dirname(os.path.dirname(cat))
701
- break
702
- else:
703
- print("ERROR: component-catalog.json not found")
704
- print("Run: FigmaRegistryUtility catalog")
705
- sys.exit(1)
706
-
707
- print(f"Figma dir: {figma_dir}")
708
-
709
- # STEP 1: Build unified index
710
- index = build_component_index(figma_dir)
711
- s = index["summary"]
712
- print(f"Components: {s['totalComponents']} (catalog) + "
713
- f"{sum(1 for c in index['components'].values() if c.get('registryOnly'))} (registry-only)")
714
- print(f"Registry: {s['registryFiles']} files")
715
- print(f"Names: {s['resolvedNames']} aliases indexed")
716
- print(f"Graph: {s['graphMerged']} components with sub-component data")
717
-
718
- # Write index to disk for inspection
719
- index_path = os.path.join(figma_dir, "Generated", "component-index.json")
720
- # Write a slim version (no nameLookup - it's huge)
721
- slim = {
722
- "components": index["components"],
723
- "files": index["files"],
724
- "summary": index["summary"],
725
- }
726
- with open(index_path, "w") as f:
727
- json.dump(slim, f, indent=2, ensure_ascii=False)
728
- f.write("\n")
729
- print(f"Index: {index_path}")
730
-
731
- # Count resolved vs unresolved sub-component names
732
- resolved = 0
733
- unresolved = 0
734
- for c in index["components"].values():
735
- for sub in c.get("subComponents", []):
736
- if sub.get("nodeId"):
737
- resolved += 1
738
- else:
739
- unresolved += 1
740
- print(f"Sub-comps: {resolved} resolved, {unresolved} unresolved")
741
-
742
- # STEP 2: Generate XHTML
743
- xhtml = generate_xhtml(index)
744
-
745
- if args.output:
746
- with open(args.output, "w") as f:
747
- f.write(xhtml)
748
- print(f"Written: {args.output} ({len(xhtml)} bytes)")
749
- return
750
-
751
- # STEP 3: Upload
752
- script_dir = os.path.dirname(os.path.abspath(__file__))
753
- ids_file = os.path.join(script_dir, "confluence-page-ids.json")
754
- with open(ids_file) as f:
755
- page_ids = json.load(f)
756
-
757
- parent_id = page_ids.get("Figma Components")
758
- if not parent_id:
759
- print("ERROR: Figma Components page ID not found")
760
- sys.exit(1)
761
-
762
- token = get_token()
763
- upload(token, PAGE_TITLE, xhtml, parent_id, args.dry_run)
764
- print("Done!")
765
-
766
-
767
- if __name__ == "__main__":
768
- main()