@mmerterden/multi-agent-pipeline 11.2.0 → 11.3.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.
- package/CHANGELOG.md +37 -0
- package/README.md +2 -2
- package/docs/FIGMA_PIPELINE.md +12 -25
- package/docs/architecture.md +3 -5
- package/docs/features.md +8 -8
- package/install/_dev-only-files.mjs +0 -6
- package/install/claude.mjs +33 -15
- package/install/copilot.mjs +16 -14
- package/install/templates/copilot-instructions.md +3 -3
- package/package.json +1 -1
- package/pipeline/commands/{multi-agent.md → multi-agent/SKILL.md} +39 -39
- package/pipeline/commands/multi-agent/{analysis.md → analysis/SKILL.md} +18 -18
- package/pipeline/commands/multi-agent/{analysis-resolve.md → analysis-resolve/SKILL.md} +4 -4
- package/pipeline/commands/multi-agent/{autopilot.md → autopilot/SKILL.md} +4 -4
- package/pipeline/commands/multi-agent/{build-optimize.md → build-optimize/SKILL.md} +1 -1
- package/pipeline/commands/multi-agent/{channels.md → channels/SKILL.md} +12 -12
- package/pipeline/commands/multi-agent/{dev.md → dev/SKILL.md} +4 -4
- package/pipeline/commands/multi-agent/{dev-autopilot.md → dev-autopilot/SKILL.md} +4 -4
- package/pipeline/commands/multi-agent/{dev-local.md → dev-local/SKILL.md} +5 -5
- package/pipeline/commands/multi-agent/{dev-local-autopilot.md → dev-local-autopilot/SKILL.md} +4 -4
- package/pipeline/commands/multi-agent/{finish.md → finish/SKILL.md} +6 -6
- package/pipeline/commands/multi-agent/{generate.md → generate/SKILL.md} +3 -3
- package/pipeline/commands/multi-agent/{help.md → help/SKILL.md} +4 -4
- package/pipeline/commands/multi-agent/{local.md → local/SKILL.md} +5 -5
- package/pipeline/commands/multi-agent/{local-autopilot.md → local-autopilot/SKILL.md} +5 -5
- package/pipeline/commands/multi-agent/{manual-test.md → manual-test/SKILL.md} +1 -1
- package/pipeline/commands/multi-agent/refactor/SKILL.md +257 -0
- package/pipeline/commands/multi-agent/{resume.md → resume/SKILL.md} +1 -1
- package/pipeline/commands/multi-agent/{review.md → review/SKILL.md} +5 -5
- package/pipeline/commands/multi-agent/{setup.md → setup/SKILL.md} +1 -1
- package/pipeline/commands/multi-agent/{sync.md → sync/SKILL.md} +20 -58
- package/pipeline/{commands/multi-agent → multi-agent-refs}/_account-picker.md +2 -2
- package/pipeline/{commands/multi-agent → multi-agent-refs}/_dev-context.md +2 -2
- package/pipeline/{commands/multi-agent → multi-agent-refs}/_repo-picker.md +2 -2
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/android-guide.md +1 -1
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/channels/jira.md +1 -1
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/channels/wiki.md +7 -7
- package/pipeline/multi-agent-refs/component-dispatch.md +92 -0
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/cross-cli-contract.md +10 -77
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/features/verify-by-test.md +1 -1
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/generate-issue.md +2 -2
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/issue-jira-triad.md +1 -1
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/keychain.md +1 -1
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases/log-format.md +1 -1
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases/modes.md +2 -2
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases/operations.md +1 -1
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases/phase-0-init.md +10 -10
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases/phase-1-analysis.md +6 -6
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases/phase-2-planning.md +3 -3
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases/phase-3-dev.md +10 -10
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases/phase-4-review.md +8 -8
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases/phase-5-test.md +3 -3
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases/phase-6-commit.md +4 -4
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases/phase-7-report.md +6 -6
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases.md +12 -12
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/progress-contract.md +4 -4
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/swiftui-guide.md +1 -1
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/tracker-contract.md +3 -3
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/wiki-capture.md +4 -4
- package/pipeline/preferences-template.json +17 -1
- package/pipeline/rules/figma-pipeline.md +2 -2
- package/pipeline/schemas/prefs.schema.json +0 -34
- package/pipeline/scripts/README.md +1 -3
- package/pipeline/scripts/fixtures/install-layout.tsv +6 -5
- package/pipeline/scripts/gen-mode-dispatch.mjs +3 -3
- package/pipeline/scripts/phase-banner.sh +2 -2
- package/pipeline/scripts/review-scope.mjs +94 -0
- package/pipeline/scripts/scan-agent-config.sh +2 -2
- package/pipeline/scripts/smoke-agent-log-cost.sh +1 -1
- package/pipeline/scripts/smoke-agent-model-routing.sh +4 -4
- package/pipeline/scripts/smoke-autopilot-circuit-breaker.sh +2 -2
- package/pipeline/scripts/smoke-channels-approval-gate.sh +2 -2
- package/pipeline/scripts/smoke-channels-flow.sh +5 -5
- package/pipeline/scripts/smoke-clarify.sh +1 -1
- package/pipeline/scripts/smoke-commands-skills-parity.sh +3 -3
- package/pipeline/scripts/smoke-community-gates.sh +1 -1
- package/pipeline/scripts/smoke-compliance-skills.sh +2 -2
- package/pipeline/scripts/smoke-cost-summary.sh +1 -1
- package/pipeline/scripts/smoke-cross-cli-behavior.sh +17 -43
- package/pipeline/scripts/smoke-cross-phase-cohesion.sh +1 -1
- package/pipeline/scripts/smoke-dev-critic.sh +1 -1
- package/pipeline/scripts/smoke-diff-explain.sh +1 -1
- package/pipeline/scripts/smoke-diff-risk.sh +1 -1
- package/pipeline/scripts/smoke-evidence-gate.sh +1 -1
- package/pipeline/scripts/smoke-figma-dispatch.sh +46 -57
- package/pipeline/scripts/smoke-gate-hooks.sh +1 -1
- package/pipeline/scripts/smoke-generate-issue.sh +8 -8
- package/pipeline/scripts/smoke-handoff-contract.sh +3 -3
- package/pipeline/scripts/smoke-identity-isolation.sh +2 -2
- package/pipeline/scripts/smoke-intent-guard.sh +1 -1
- package/pipeline/scripts/smoke-issue-comment-template.sh +2 -2
- package/pipeline/scripts/smoke-issue-jira-triad.sh +4 -4
- package/pipeline/scripts/smoke-language-axis.sh +6 -6
- package/pipeline/scripts/smoke-learnings-ledger.sh +3 -3
- package/pipeline/scripts/smoke-mode-dispatch-drift.sh +16 -16
- package/pipeline/scripts/smoke-model-fallback.sh +3 -3
- package/pipeline/scripts/smoke-multi-repo-integration.sh +2 -2
- package/pipeline/scripts/smoke-multi-repo-worktree.sh +1 -1
- package/pipeline/scripts/smoke-no-token-prompt.sh +5 -5
- package/pipeline/scripts/smoke-phase-0-multi-repo.sh +1 -1
- package/pipeline/scripts/smoke-phase-6-multi.sh +1 -1
- package/pipeline/scripts/smoke-phase4-gates.sh +45 -0
- package/pipeline/scripts/smoke-plan-approval-gate.sh +5 -5
- package/pipeline/scripts/smoke-plan-todos.sh +3 -3
- package/pipeline/scripts/smoke-pr-review-actions.sh +3 -3
- package/pipeline/scripts/smoke-prefs-language.sh +1 -1
- package/pipeline/scripts/smoke-progress-contract.sh +2 -2
- package/pipeline/scripts/smoke-push-retry.sh +1 -1
- package/pipeline/scripts/smoke-repo-map.sh +2 -2
- package/pipeline/scripts/smoke-shadow-git.sh +1 -1
- package/pipeline/scripts/smoke-skill-authoring.sh +4 -9
- package/pipeline/scripts/smoke-tasklist-ordering.sh +10 -10
- package/pipeline/scripts/smoke-test-gap.sh +1 -1
- package/pipeline/scripts/smoke-token-budget.sh +1 -1
- package/pipeline/scripts/smoke-token-preflight.sh +2 -2
- package/pipeline/scripts/smoke-tracker-contract.sh +13 -13
- package/pipeline/scripts/smoke-tracker-tokens-invocation.sh +5 -5
- package/pipeline/scripts/smoke-triage-memory.sh +2 -2
- package/pipeline/scripts/smoke-update-check.sh +1 -1
- package/pipeline/scripts/smoke-url-enrichment.sh +2 -2
- package/pipeline/scripts/smoke-validator-gates.sh +1 -1
- package/pipeline/scripts/smoke-verify-by-test.sh +3 -3
- package/pipeline/scripts/smoke-wiki-integration.sh +9 -33
- package/pipeline/scripts/smoke-work-summary.sh +1 -1
- package/pipeline/scripts/smoke-worktree-path-convention.sh +4 -4
- package/pipeline/scripts/test-integrity-gate.mjs +83 -0
- package/pipeline/scripts/uninstall.mjs +2 -2
- package/pipeline/skills/.skill-manifest.json +40 -160
- package/pipeline/skills/.skills-index.json +3 -372
- package/pipeline/skills/shared/README.md +1 -1
- package/pipeline/skills/shared/core/multi-agent/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-analysis/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent-analysis-resolve/SKILL.md +3 -3
- package/pipeline/skills/shared/core/multi-agent-build-optimize/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-finish/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-refactor/SKILL.md +116 -51
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +8 -30
- package/pipeline/skills/shared/external/compose-components/SKILL.md +14 -14
- package/pipeline/skills/skills-index.md +2 -43
- package/pipeline/commands/multi-agent/refactor.md +0 -188
- package/pipeline/commands/multi-agent/refs/component-dispatch.md +0 -92
- package/pipeline/scripts/smoke-existing-discovery-gate.sh +0 -71
- package/pipeline/scripts/smoke-figma-android-parity.sh +0 -148
- package/pipeline/scripts/smoke-figma-credential-store.sh +0 -105
- package/pipeline/scripts/smoke-figma-cross-cli-inventory.sh +0 -177
- package/pipeline/scripts/smoke-figma-sync.sh +0 -149
- package/pipeline/scripts/smoke-sync-delegation.sh +0 -74
- package/pipeline/skills/figma-android/README.md +0 -38
- package/pipeline/skills/figma-android/figma-component-code-connect/SKILL.md +0 -62
- package/pipeline/skills/figma-android/figma-component-implement/SKILL.md +0 -158
- package/pipeline/skills/figma-android/figma-component-test/SKILL.md +0 -120
- package/pipeline/skills/figma-android/figma-component-wiki/SKILL.md +0 -35
- package/pipeline/skills/figma-android/figma-to-component/SKILL.md +0 -124
- package/pipeline/skills/figma-common/README.md +0 -64
- package/pipeline/skills/figma-common/figma-bottom-sheets/SKILL.md +0 -152
- package/pipeline/skills/figma-common/figma-cli-iterate/SKILL.md +0 -282
- package/pipeline/skills/figma-common/figma-cli-iterate-mend/SKILL.md +0 -503
- package/pipeline/skills/figma-common/figma-cli-lean-iterate/SKILL.md +0 -288
- package/pipeline/skills/figma-common/figma-cli-skip/SKILL.md +0 -362
- package/pipeline/skills/figma-common/figma-commit/COMMON_REBASE.md +0 -206
- package/pipeline/skills/figma-common/figma-commit/REVIEW.md +0 -337
- package/pipeline/skills/figma-common/figma-commit/SKILL.md +0 -216
- package/pipeline/skills/figma-common/figma-component-confluence-sync/SKILL.md +0 -223
- package/pipeline/skills/figma-common/figma-component-start/SKILL.md +0 -251
- package/pipeline/skills/figma-common/figma-component-status-update/SKILL.md +0 -78
- package/pipeline/skills/figma-common/figma-evolve-component/SKILL.md +0 -61
- package/pipeline/skills/figma-common/figma-fix/SKILL.md +0 -321
- package/pipeline/skills/figma-common/figma-form-integration/SKILL.md +0 -542
- package/pipeline/skills/figma-common/figma-issue/SKILL.md +0 -750
- package/pipeline/skills/figma-common/figma-iterate/SKILL.md +0 -208
- package/pipeline/skills/figma-common/figma-iteration-commit/SKILL.md +0 -1020
- package/pipeline/skills/figma-common/figma-mend/SKILL.md +0 -331
- package/pipeline/skills/figma-common/figma-navigation/SKILL.md +0 -156
- package/pipeline/skills/figma-common/figma-overlays/SKILL.md +0 -142
- package/pipeline/skills/figma-common/figma-price-integration/SKILL.md +0 -398
- package/pipeline/skills/figma-common/figma-remote-mcp-auth/SKILL.md +0 -104
- package/pipeline/skills/figma-common/figma-review/SKILL.md +0 -400
- package/pipeline/skills/figma-common/figma-setup/SKILL.md +0 -514
- package/pipeline/skills/figma-common/figma-setup/scripts/fetch-mcp-token.py +0 -592
- package/pipeline/skills/figma-common/figma-skip/SKILL.md +0 -129
- package/pipeline/skills/figma-common/figma-ui-patterns/SKILL.md +0 -105
- package/pipeline/skills/figma-common/figma-ui-patterns/patterns/animated-gradient-border.md +0 -116
- package/pipeline/skills/figma-common/figma-utility/SKILL.md +0 -274
- package/pipeline/skills/figma-common/figma-utility/scripts/figma-utility.py +0 -808
- package/pipeline/skills/figma-common/figma-validate/SKILL.md +0 -633
- package/pipeline/skills/figma-common/performance-iteration-commit-all/SKILL.md +0 -716
- package/pipeline/skills/figma-common/performance-review-next/SKILL.md +0 -238
- package/pipeline/skills/figma-common/performance-start/SKILL.md +0 -430
- package/pipeline/skills/figma-common/performance-swiftui/SKILL.md +0 -706
- package/pipeline/skills/figma-common/performance-tour/SKILL.md +0 -423
- package/pipeline/skills/figma-ios/REVIEW_CHECKLIST.md +0 -67
- package/pipeline/skills/figma-ios/figma-component-code-connect/SKILL.md +0 -178
- package/pipeline/skills/figma-ios/figma-component-implement/SKILL.md +0 -184
- package/pipeline/skills/figma-ios/figma-component-test/SKILL.md +0 -219
- package/pipeline/skills/figma-ios/figma-component-wiki/SKILL.md +0 -274
- package/pipeline/skills/figma-ios/figma-to-component/SKILL.md +0 -416
- package/pipeline/skills/figma-ios/figma-to-component/halt-return-protocol.md +0 -57
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-0-init.md +0 -307
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-1-gathering.md +0 -119
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-1.5-existing-discovery.md +0 -174
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-2-orchestrator.md +0 -333
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-2a-testing-identifiers.md +0 -368
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-2b-localization.md +0 -393
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-2c-accessibility.md +0 -617
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-2d-analytics.md +0 -352
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3-orchestrator.md +0 -337
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3a-location.md +0 -206
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3b-tokens.md +0 -235
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3c-nested.md +0 -214
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3d-patterns.md +0 -902
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3e-assets.md +0 -156
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3f-utilities.md +0 -175
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3g-property-coverage.md +0 -176
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3h-variant-config.md +0 -333
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-4-orchestrator.md +0 -412
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-4a-configuration.md +0 -336
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-4b-view.md +0 -705
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-4c-documentation.md +0 -332
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-4d-preview.md +0 -380
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-4e-modifiers.md +0 -262
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-5-orchestrator.md +0 -482
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-5a-viewinspector.md +0 -274
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-5b-snapshot.md +0 -636
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-5c-unit.md +0 -142
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-6-code-connect.md +0 -547
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-7-wiki.md +0 -39
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-7a-confluence-generate.md +0 -659
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-7a-wiki-generate.md +0 -580
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-8-cleanup.md +0 -51
- package/pipeline/skills/figma-ios/figma-to-component/reference/accessibility.md +0 -183
- package/pipeline/skills/figma-ios/figma-to-component/reference/analytics-events.md +0 -64
- package/pipeline/skills/figma-ios/figma-to-component/reference/code-connect.md +0 -531
- package/pipeline/skills/figma-ios/figma-to-component/reference/confluence-api.md +0 -89
- package/pipeline/skills/figma-ios/figma-to-component/reference/confluence-xhtml.md +0 -155
- package/pipeline/skills/figma-ios/figma-to-component/reference/figma-to-swiftui-effects.md +0 -196
- package/pipeline/skills/figma-ios/figma-to-component/reference/halt-return-protocol.md +0 -57
- package/pipeline/skills/figma-ios/figma-to-component/reference/localization-naming.md +0 -89
- package/pipeline/skills/figma-ios/figma-to-component/reference/macros.md +0 -227
- package/pipeline/skills/figma-ios/figma-to-component/reference/missing-tokens.md +0 -157
- package/pipeline/skills/figma-ios/figma-to-component/reference/orchestrator-discipline.md +0 -90
- package/pipeline/skills/figma-ios/figma-to-component/reference/registry.md +0 -116
- package/pipeline/skills/figma-ios/figma-to-component/reference/remote-mcp-script.md +0 -153
- package/pipeline/skills/figma-ios/figma-to-component/reference/rest-api-script.md +0 -130
- package/pipeline/skills/figma-ios/figma-to-component/reference/scripts-inventory.md +0 -218
- package/pipeline/skills/figma-ios/figma-to-component/reference/snapshot-testing.md +0 -188
- package/pipeline/skills/figma-ios/figma-to-component/reference/subcomponent-graph.md +0 -93
- package/pipeline/skills/figma-ios/figma-to-component/reference/testing-identifiers-naming.md +0 -98
- package/pipeline/skills/figma-ios/figma-to-component/reference/tools.md +0 -261
- package/pipeline/skills/figma-ios/figma-to-component/reference/viewinspector.md +0 -147
- package/pipeline/skills/figma-ios/figma-to-component/reference/wiki-to-confluence-mapping.md +0 -182
- package/pipeline/skills/figma-ios/figma-to-component/scripts/apply-author-login-map.py +0 -185
- package/pipeline/skills/figma-ios/figma-to-component/scripts/backfill-status.py +0 -609
- package/pipeline/skills/figma-ios/figma-to-component/scripts/build-author-registry.py +0 -332
- package/pipeline/skills/figma-ios/figma-to-component/scripts/bulk-sync-issues.py +0 -261
- package/pipeline/skills/figma-ios/figma-to-component/scripts/code-connect-data-gather.py +0 -184
- package/pipeline/skills/figma-ios/figma-to-component/scripts/code-connect-publish.sh +0 -188
- package/pipeline/skills/figma-ios/figma-to-component/scripts/confluence-component-status-upload.py +0 -768
- package/pipeline/skills/figma-ios/figma-to-component/scripts/confluence-component-status.py +0 -191
- package/pipeline/skills/figma-ios/figma-to-component/scripts/confluence-data-gather.py +0 -420
- package/pipeline/skills/figma-ios/figma-to-component/scripts/confluence-page-ids.example.json +0 -9
- package/pipeline/skills/figma-ios/figma-to-component/scripts/confluence-publish.py +0 -336
- package/pipeline/skills/figma-ios/figma-to-component/scripts/figma-subcomponent-graph.py +0 -391
- package/pipeline/skills/figma-ios/figma-to-component/scripts/figma-update.py +0 -292
- package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/__init__.py +0 -1
- package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/issue_sync_propagate.py +0 -93
- package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/registry_writer.py +0 -299
- package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_backfill_status.py +0 -343
- package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_figma_update.py +0 -206
- package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_figma_update_http.py +0 -149
- package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_phase_clis.py +0 -281
- package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_registry_writer.py +0 -332
- package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_skill_figma_issue.py +0 -176
- package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_skill_figma_review.py +0 -98
- package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_update_issue.py +0 -298
- package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_update_issue_gh.py +0 -195
- package/pipeline/skills/figma-ios/figma-to-component/scripts/phase1-gather.py +0 -1298
- package/pipeline/skills/figma-ios/figma-to-component/scripts/phase2-finalize.py +0 -228
- package/pipeline/skills/figma-ios/figma-to-component/scripts/phase3-scripts.py +0 -1089
- package/pipeline/skills/figma-ios/figma-to-component/scripts/phase4-finalize.py +0 -141
- package/pipeline/skills/figma-ios/figma-to-component/scripts/phase5-finalize.py +0 -106
- package/pipeline/skills/figma-ios/figma-to-component/scripts/phase6-finalize.py +0 -162
- package/pipeline/skills/figma-ios/figma-to-component/scripts/phase7-finalize.py +0 -105
- package/pipeline/skills/figma-ios/figma-to-component/scripts/register-icons-codeconnect.py +0 -179
- package/pipeline/skills/figma-ios/figma-to-component/scripts/remote-mcp-fetch.py +0 -260
- package/pipeline/skills/figma-ios/figma-to-component/scripts/resolve-author-logins.py +0 -260
- package/pipeline/skills/figma-ios/figma-to-component/scripts/run-uicomponents-tests.sh +0 -86
- package/pipeline/skills/figma-ios/figma-to-component/scripts/sidebar-generator.py +0 -321
- package/pipeline/skills/figma-ios/figma-to-component/scripts/update-issue-from-registry.py +0 -1470
- package/pipeline/skills/figma-ios/figma-to-component/scripts/validate-phase4.sh +0 -176
- package/pipeline/skills/figma-ios/figma-to-component/scripts/validate-phase6.sh +0 -147
- package/pipeline/skills/figma-ios/figma-to-component/scripts/validate-phase7a.py +0 -629
- /package/pipeline/commands/multi-agent/{delete.md → delete/SKILL.md} +0 -0
- /package/pipeline/commands/multi-agent/{diff-explain.md → diff-explain/SKILL.md} +0 -0
- /package/pipeline/commands/multi-agent/{garbage-collect.md → garbage-collect/SKILL.md} +0 -0
- /package/pipeline/commands/multi-agent/{issue.md → issue/SKILL.md} +0 -0
- /package/pipeline/commands/multi-agent/{jira.md → jira/SKILL.md} +0 -0
- /package/pipeline/commands/multi-agent/{kill.md → kill/SKILL.md} +0 -0
- /package/pipeline/commands/multi-agent/{language.md → language/SKILL.md} +0 -0
- /package/pipeline/commands/multi-agent/{log.md → log/SKILL.md} +0 -0
- /package/pipeline/commands/multi-agent/{prune-logs.md → prune-logs/SKILL.md} +0 -0
- /package/pipeline/commands/multi-agent/{purge.md → purge/SKILL.md} +0 -0
- /package/pipeline/commands/multi-agent/{scan.md → scan/SKILL.md} +0 -0
- /package/pipeline/commands/multi-agent/{search.md → search/SKILL.md} +0 -0
- /package/pipeline/commands/multi-agent/{stack.md → stack/SKILL.md} +0 -0
- /package/pipeline/commands/multi-agent/{status.md → status/SKILL.md} +0 -0
- /package/pipeline/commands/multi-agent/{test.md → test/SKILL.md} +0 -0
- /package/pipeline/commands/multi-agent/{update.md → update/SKILL.md} +0 -0
- /package/pipeline/{commands/multi-agent → multi-agent-refs}/_input-parser.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/analysis-template.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/audit-guide.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/backend-guide.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/channels/confluence.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/channels/issue-comment.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/channels/pr-review-actions.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/channels/pr.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/conventions-defaults.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/features/autopilot-circuit-breaker.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/features/dev-critic.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/features/external-context-injection.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/features/model-fallback.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/features/plan-todos.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/features/prior-fix-detection.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/features/repo-map.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/features/shadow-git.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/frontend-guide.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/knowledge.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/multi-repo-integration-build.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/picker-contract.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/rules.md +0 -0
|
@@ -1,391 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Figma Sub-Component Graph Generator
|
|
4
|
-
|
|
5
|
-
Queries the Figma REST API to discover which components use other components
|
|
6
|
-
as instances (sub-components). Produces sub-component-graph.json.
|
|
7
|
-
|
|
8
|
-
Data flow:
|
|
9
|
-
1. Read component-catalog.json (source of truth for component list)
|
|
10
|
-
2. For each Figma file, batch-fetch node trees via REST API
|
|
11
|
-
3. Walk each node tree for INSTANCE children
|
|
12
|
-
4. Resolve INSTANCE.componentId → catalog component via response metadata
|
|
13
|
-
5. Write sub-component-graph.json
|
|
14
|
-
|
|
15
|
-
Usage:
|
|
16
|
-
python3 figma-subcomponent-graph.py [--dry-run] [--output path] [--skip-assets]
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
import argparse
|
|
20
|
-
import json
|
|
21
|
-
import os
|
|
22
|
-
import re
|
|
23
|
-
import subprocess
|
|
24
|
-
import sys
|
|
25
|
-
import time
|
|
26
|
-
import urllib.parse
|
|
27
|
-
import urllib.request
|
|
28
|
-
|
|
29
|
-
FIGMA_API = "https://api.figma.com/v1"
|
|
30
|
-
BATCH_SIZE = 25
|
|
31
|
-
RATE_DELAY = 1.0 # seconds between API calls (respect rate limits)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def get_token():
|
|
35
|
-
"""Retrieve Figma API token from macOS Keychain."""
|
|
36
|
-
r = subprocess.run(
|
|
37
|
-
["security", "find-generic-password", "-a", os.environ["USER"],
|
|
38
|
-
"-s", "FIGMA_ACCESS_TOKEN", "-w"],
|
|
39
|
-
capture_output=True, text=True, check=True
|
|
40
|
-
)
|
|
41
|
-
return r.stdout.strip()
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def api_get(token, path):
|
|
45
|
-
"""GET request to Figma REST API."""
|
|
46
|
-
url = f"{FIGMA_API}{path}"
|
|
47
|
-
req = urllib.request.Request(url)
|
|
48
|
-
req.add_header("X-Figma-Token", token)
|
|
49
|
-
try:
|
|
50
|
-
with urllib.request.urlopen(req, timeout=30) as resp:
|
|
51
|
-
return json.loads(resp.read())
|
|
52
|
-
except urllib.error.HTTPError as e:
|
|
53
|
-
body = e.read().decode("utf-8", errors="replace")[:300]
|
|
54
|
-
raise RuntimeError(f"Figma API {e.code}: {body}")
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def extract_file_key(figma_url):
|
|
58
|
-
"""Extract fileKey from a Figma design URL."""
|
|
59
|
-
m = re.search(r'/design/([^/]+)/', figma_url)
|
|
60
|
-
return m.group(1) if m else None
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def sanitize_type_name(name):
|
|
64
|
-
"""
|
|
65
|
-
Replicate FigmaRegistryUtility.sanitizeTypeName() in Python.
|
|
66
|
-
Splits on non-alphanumeric, PascalCases each part, joins.
|
|
67
|
-
"""
|
|
68
|
-
parts = re.split(r'[^a-zA-Z0-9]+', name)
|
|
69
|
-
result = []
|
|
70
|
-
for part in parts:
|
|
71
|
-
if not part:
|
|
72
|
-
continue
|
|
73
|
-
has_upper = any(c.isupper() for c in part)
|
|
74
|
-
has_lower = any(c.islower() for c in part)
|
|
75
|
-
if has_upper and has_lower:
|
|
76
|
-
result.append(part[0].upper() + part[1:])
|
|
77
|
-
else:
|
|
78
|
-
result.append(part[0].upper() + part[1:].lower())
|
|
79
|
-
return "".join(result)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def find_instances(node):
|
|
83
|
-
"""Recursively find all INSTANCE componentIds in a node subtree."""
|
|
84
|
-
found = set()
|
|
85
|
-
if not isinstance(node, dict):
|
|
86
|
-
return found
|
|
87
|
-
if node.get("type") == "INSTANCE":
|
|
88
|
-
cid = node.get("componentId")
|
|
89
|
-
if cid:
|
|
90
|
-
found.add(cid)
|
|
91
|
-
for child in node.get("children", []):
|
|
92
|
-
found |= find_instances(child)
|
|
93
|
-
return found
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def find_catalog(script_dir):
|
|
97
|
-
"""Locate component-catalog.json from various possible working directories."""
|
|
98
|
-
candidates = [
|
|
99
|
-
os.path.join(script_dir, "..", "..", "..", "..",
|
|
100
|
-
"{repos.packagesContainer}/Packages/"
|
|
101
|
-
"{project.slug}-common/"
|
|
102
|
-
"Shared/Figma/Generated/component-catalog.json"),
|
|
103
|
-
"{repos.packagesContainer}/Packages/"
|
|
104
|
-
"{project.slug}-common/"
|
|
105
|
-
"Shared/Figma/Generated/component-catalog.json",
|
|
106
|
-
"Shared/Figma/Generated/component-catalog.json",
|
|
107
|
-
]
|
|
108
|
-
for c in candidates:
|
|
109
|
-
p = os.path.normpath(c)
|
|
110
|
-
if os.path.isfile(p):
|
|
111
|
-
return p
|
|
112
|
-
return None
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def find_output_dir(script_dir):
|
|
116
|
-
"""Locate Shared/Figma/ directory for output."""
|
|
117
|
-
candidates = [
|
|
118
|
-
os.path.join(script_dir, "..", "..", "..", "..",
|
|
119
|
-
"{repos.packagesContainer}/Packages/"
|
|
120
|
-
"{project.slug}-common/Shared/Figma"),
|
|
121
|
-
"{repos.packagesContainer}/Packages/"
|
|
122
|
-
"{project.slug}-common/Shared/Figma",
|
|
123
|
-
"Shared/Figma",
|
|
124
|
-
]
|
|
125
|
-
for c in candidates:
|
|
126
|
-
p = os.path.normpath(c)
|
|
127
|
-
if os.path.isdir(p):
|
|
128
|
-
return p
|
|
129
|
-
return None
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
def main():
|
|
133
|
-
parser = argparse.ArgumentParser(
|
|
134
|
-
description="Generate sub-component dependency graph from Figma REST API")
|
|
135
|
-
parser.add_argument("--dry-run", action="store_true",
|
|
136
|
-
help="Preview API calls without executing")
|
|
137
|
-
parser.add_argument("--output", help="Override output file path")
|
|
138
|
-
parser.add_argument("--skip-assets", action="store_true",
|
|
139
|
-
help="Skip Visual Assets file (icons/flags rarely have sub-components)")
|
|
140
|
-
parser.add_argument("--verbose", action="store_true",
|
|
141
|
-
help="Print detailed progress")
|
|
142
|
-
args = parser.parse_args()
|
|
143
|
-
|
|
144
|
-
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
145
|
-
|
|
146
|
-
# --- 1. Load catalog ---
|
|
147
|
-
cat_path = find_catalog(script_dir)
|
|
148
|
-
if not cat_path:
|
|
149
|
-
print("ERROR: component-catalog.json not found")
|
|
150
|
-
print("Run: FigmaRegistryUtility catalog")
|
|
151
|
-
sys.exit(1)
|
|
152
|
-
|
|
153
|
-
with open(cat_path) as f:
|
|
154
|
-
catalog = json.load(f)
|
|
155
|
-
|
|
156
|
-
print(f"Catalog: {cat_path}")
|
|
157
|
-
print(f"Total: {catalog['summary']['totalComponents']} components")
|
|
158
|
-
|
|
159
|
-
# --- 2. Build lookup tables ---
|
|
160
|
-
# catalog_by_node: nodeId → {name, figmaName, fileKey}
|
|
161
|
-
# catalog_by_name: sanitizedName → nodeId
|
|
162
|
-
# file_groups: fileKey → [(nodeId, name, fileLabel), ...]
|
|
163
|
-
catalog_by_node = {}
|
|
164
|
-
catalog_by_name = {}
|
|
165
|
-
file_groups = {}
|
|
166
|
-
|
|
167
|
-
for file_entry in catalog["files"]:
|
|
168
|
-
file_label = file_entry["fileLabel"]
|
|
169
|
-
for page in file_entry["pages"]:
|
|
170
|
-
for comp in page["components"]:
|
|
171
|
-
name = comp["name"]
|
|
172
|
-
node_id = comp["nodeId"]
|
|
173
|
-
figma_url = comp.get("figmaUrl", "")
|
|
174
|
-
file_key = extract_file_key(figma_url)
|
|
175
|
-
|
|
176
|
-
catalog_by_node[node_id] = {
|
|
177
|
-
"name": name,
|
|
178
|
-
"figmaName": comp.get("figmaName", ""),
|
|
179
|
-
"fileKey": file_key,
|
|
180
|
-
"fileLabel": file_label,
|
|
181
|
-
}
|
|
182
|
-
catalog_by_name[name] = node_id
|
|
183
|
-
|
|
184
|
-
if file_key:
|
|
185
|
-
if args.skip_assets and file_label == "Visual Assets":
|
|
186
|
-
continue
|
|
187
|
-
file_groups.setdefault(file_key, {
|
|
188
|
-
"label": file_label,
|
|
189
|
-
"components": []
|
|
190
|
-
})["components"].append((node_id, name))
|
|
191
|
-
|
|
192
|
-
print(f"Files: {len(file_groups)}")
|
|
193
|
-
for fk, fg in file_groups.items():
|
|
194
|
-
print(f" {fg['label']}: {len(fg['components'])} components ({fk[:8]}...)")
|
|
195
|
-
|
|
196
|
-
if args.dry_run:
|
|
197
|
-
total_batches = sum(
|
|
198
|
-
(len(fg["components"]) + BATCH_SIZE - 1) // BATCH_SIZE
|
|
199
|
-
for fg in file_groups.values()
|
|
200
|
-
)
|
|
201
|
-
print(f"\n[DRY RUN] Would make ~{total_batches} API requests")
|
|
202
|
-
print(f"[DRY RUN] Rate: {RATE_DELAY}s delay → ~{total_batches * RATE_DELAY:.0f}s total")
|
|
203
|
-
return
|
|
204
|
-
|
|
205
|
-
# --- 3. Fetch from Figma REST API ---
|
|
206
|
-
token = get_token()
|
|
207
|
-
graph = {} # nodeId → {name, subComponents: [{nodeId, name}], instanceCount}
|
|
208
|
-
errors = []
|
|
209
|
-
api_calls = 0
|
|
210
|
-
|
|
211
|
-
for file_key, fg in file_groups.items():
|
|
212
|
-
file_label = fg["label"]
|
|
213
|
-
components = fg["components"]
|
|
214
|
-
print(f"\n{'='*60}")
|
|
215
|
-
print(f"File: {file_label} ({len(components)} components)")
|
|
216
|
-
print(f"{'='*60}")
|
|
217
|
-
|
|
218
|
-
node_ids = [nid for nid, _ in components]
|
|
219
|
-
|
|
220
|
-
for i in range(0, len(node_ids), BATCH_SIZE):
|
|
221
|
-
batch = node_ids[i:i + BATCH_SIZE]
|
|
222
|
-
batch_num = i // BATCH_SIZE + 1
|
|
223
|
-
total_batches = (len(node_ids) + BATCH_SIZE - 1) // BATCH_SIZE
|
|
224
|
-
|
|
225
|
-
ids_param = urllib.parse.quote(",".join(batch))
|
|
226
|
-
path = f"/files/{file_key}/nodes?ids={ids_param}"
|
|
227
|
-
|
|
228
|
-
print(f" Batch {batch_num}/{total_batches} ({len(batch)} nodes)...", end=" ", flush=True)
|
|
229
|
-
|
|
230
|
-
try:
|
|
231
|
-
data = api_get(token, path)
|
|
232
|
-
api_calls += 1
|
|
233
|
-
except Exception as e:
|
|
234
|
-
print(f"ERROR: {e}")
|
|
235
|
-
errors.append({"batch": batch, "error": str(e)})
|
|
236
|
-
time.sleep(RATE_DELAY)
|
|
237
|
-
continue
|
|
238
|
-
|
|
239
|
-
nodes_data = data.get("nodes", {})
|
|
240
|
-
batch_subs = 0
|
|
241
|
-
|
|
242
|
-
for nid in batch:
|
|
243
|
-
node_result = nodes_data.get(nid)
|
|
244
|
-
if not node_result:
|
|
245
|
-
graph[nid] = {
|
|
246
|
-
"name": catalog_by_node[nid]["name"],
|
|
247
|
-
"subComponents": [],
|
|
248
|
-
"instanceCount": 0,
|
|
249
|
-
"error": "node not returned"
|
|
250
|
-
}
|
|
251
|
-
continue
|
|
252
|
-
|
|
253
|
-
doc = node_result.get("document", {})
|
|
254
|
-
components_map = node_result.get("components", {})
|
|
255
|
-
component_sets_map = node_result.get("componentSets", {})
|
|
256
|
-
|
|
257
|
-
# Find all INSTANCE componentIds in this subtree
|
|
258
|
-
instance_ids = find_instances(doc)
|
|
259
|
-
|
|
260
|
-
# Resolve each instance to a catalog component
|
|
261
|
-
sub_components = []
|
|
262
|
-
seen = set()
|
|
263
|
-
|
|
264
|
-
for cid in instance_ids:
|
|
265
|
-
# Look up in response components map
|
|
266
|
-
comp_meta = components_map.get(cid, {})
|
|
267
|
-
comp_set_id = comp_meta.get("componentSetId")
|
|
268
|
-
|
|
269
|
-
# The "real" component is the COMPONENT_SET if it exists,
|
|
270
|
-
# otherwise the COMPONENT itself
|
|
271
|
-
resolved_id = comp_set_id if comp_set_id else cid
|
|
272
|
-
|
|
273
|
-
# Check if resolved ID is in our catalog
|
|
274
|
-
if resolved_id in catalog_by_node and resolved_id != nid:
|
|
275
|
-
if resolved_id not in seen:
|
|
276
|
-
seen.add(resolved_id)
|
|
277
|
-
sub_components.append({
|
|
278
|
-
"nodeId": resolved_id,
|
|
279
|
-
"name": catalog_by_node[resolved_id]["name"]
|
|
280
|
-
})
|
|
281
|
-
continue
|
|
282
|
-
|
|
283
|
-
# Fallback: try matching by sanitized name
|
|
284
|
-
raw_name = comp_meta.get("name", "")
|
|
285
|
-
if not raw_name and comp_set_id and comp_set_id in component_sets_map:
|
|
286
|
-
raw_name = component_sets_map[comp_set_id].get("name", "")
|
|
287
|
-
|
|
288
|
-
if raw_name:
|
|
289
|
-
sanitized = sanitize_type_name(raw_name)
|
|
290
|
-
matched_nid = catalog_by_name.get(sanitized)
|
|
291
|
-
if matched_nid and matched_nid != nid and matched_nid not in seen:
|
|
292
|
-
seen.add(matched_nid)
|
|
293
|
-
sub_components.append({
|
|
294
|
-
"nodeId": matched_nid,
|
|
295
|
-
"name": sanitized
|
|
296
|
-
})
|
|
297
|
-
|
|
298
|
-
sub_components.sort(key=lambda x: x["name"])
|
|
299
|
-
comp_name = catalog_by_node[nid]["name"]
|
|
300
|
-
|
|
301
|
-
graph[nid] = {
|
|
302
|
-
"name": comp_name,
|
|
303
|
-
"subComponents": sub_components,
|
|
304
|
-
"instanceCount": len(instance_ids),
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
if sub_components:
|
|
308
|
-
batch_subs += 1
|
|
309
|
-
if args.verbose:
|
|
310
|
-
names = [s["name"] for s in sub_components]
|
|
311
|
-
print(f"\n {comp_name} → {names}")
|
|
312
|
-
|
|
313
|
-
print(f"OK ({batch_subs} with subs)")
|
|
314
|
-
time.sleep(RATE_DELAY)
|
|
315
|
-
|
|
316
|
-
# --- 4. Compute reverse map (usedBy) ---
|
|
317
|
-
used_by = {} # nodeId → [nodeId, ...]
|
|
318
|
-
for nid, entry in graph.items():
|
|
319
|
-
for sub in entry["subComponents"]:
|
|
320
|
-
used_by.setdefault(sub["nodeId"], []).append(nid)
|
|
321
|
-
|
|
322
|
-
for nid in used_by:
|
|
323
|
-
used_by[nid] = sorted(
|
|
324
|
-
used_by[nid],
|
|
325
|
-
key=lambda x: graph.get(x, {}).get("name", "")
|
|
326
|
-
)
|
|
327
|
-
|
|
328
|
-
# --- 5. Build output ---
|
|
329
|
-
with_subs = sum(1 for v in graph.values() if v["subComponents"])
|
|
330
|
-
used_as_sub = len(used_by)
|
|
331
|
-
|
|
332
|
-
output = {
|
|
333
|
-
"version": "1.0",
|
|
334
|
-
"generatedAt": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
|
335
|
-
"source": "Figma REST API /files/:key/nodes - INSTANCE child inspection",
|
|
336
|
-
"stats": {
|
|
337
|
-
"totalComponents": len(graph),
|
|
338
|
-
"componentsWithSubComponents": with_subs,
|
|
339
|
-
"componentsUsedAsSub": used_as_sub,
|
|
340
|
-
"apiCalls": api_calls,
|
|
341
|
-
"errors": len(errors),
|
|
342
|
-
},
|
|
343
|
-
"graph": {},
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
for nid, entry in sorted(graph.items(), key=lambda x: x[1]["name"]):
|
|
347
|
-
node_entry = {
|
|
348
|
-
"name": entry["name"],
|
|
349
|
-
"subComponents": [s["name"] for s in entry["subComponents"]],
|
|
350
|
-
}
|
|
351
|
-
# Add usedBy if this component is used by others
|
|
352
|
-
if nid in used_by:
|
|
353
|
-
node_entry["usedBy"] = [
|
|
354
|
-
graph[uid]["name"] for uid in used_by[nid] if uid in graph
|
|
355
|
-
]
|
|
356
|
-
# Only include instanceCount if there are unresolved instances
|
|
357
|
-
unresolved = entry["instanceCount"] - len(entry["subComponents"])
|
|
358
|
-
if unresolved > 0:
|
|
359
|
-
node_entry["unresolvedInstances"] = unresolved
|
|
360
|
-
if entry.get("error"):
|
|
361
|
-
node_entry["error"] = entry["error"]
|
|
362
|
-
|
|
363
|
-
output["graph"][nid] = node_entry
|
|
364
|
-
|
|
365
|
-
# --- 6. Write output ---
|
|
366
|
-
out_path = args.output
|
|
367
|
-
if not out_path:
|
|
368
|
-
out_dir = find_output_dir(script_dir)
|
|
369
|
-
if out_dir:
|
|
370
|
-
out_path = os.path.join(out_dir, "sub-component-graph.json")
|
|
371
|
-
else:
|
|
372
|
-
out_path = "sub-component-graph.json"
|
|
373
|
-
|
|
374
|
-
with open(out_path, "w") as f:
|
|
375
|
-
json.dump(output, f, indent=2, sort_keys=False, ensure_ascii=False)
|
|
376
|
-
f.write("\n")
|
|
377
|
-
|
|
378
|
-
print(f"\n{'='*60}")
|
|
379
|
-
print(f"Written: {out_path}")
|
|
380
|
-
print(f"Components: {len(graph)}")
|
|
381
|
-
print(f"With sub-comps: {with_subs}")
|
|
382
|
-
print(f"Used as sub-comp: {used_as_sub}")
|
|
383
|
-
print(f"API calls: {api_calls}")
|
|
384
|
-
if errors:
|
|
385
|
-
print(f"Errors: {len(errors)}")
|
|
386
|
-
for err in errors[:5]:
|
|
387
|
-
print(f" {err['error'][:100]}")
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
if __name__ == "__main__":
|
|
391
|
-
main()
|
|
@@ -1,292 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""figma-update.py - detect Figma-side changes and mark components stale.
|
|
3
|
-
|
|
4
|
-
Fetches the file-level `lastModified` timestamp from the Figma REST API
|
|
5
|
-
(`GET /v1/files/:key`) and compares it to the `figmaUpdatedAt` stored in
|
|
6
|
-
each component registry entry. If Figma has newer data, the script calls
|
|
7
|
-
`lib.registry_writer.mark_stale()` for every registry entry that belongs
|
|
8
|
-
to that file - setting `status.<platform>.stale = true` on any platform
|
|
9
|
-
that has been implemented.
|
|
10
|
-
|
|
11
|
-
This is a FILE-LEVEL check. Any change anywhere in the Figma file will
|
|
12
|
-
mark all components in that file as stale. Conservative but correct:
|
|
13
|
-
never misses a real change, only produces false positives that a
|
|
14
|
-
developer can acknowledge by re-running Phase 4 (which clears stale via
|
|
15
|
-
`set_implemented`'s side effect).
|
|
16
|
-
|
|
17
|
-
Usage:
|
|
18
|
-
# Check one file explicitly
|
|
19
|
-
python3 figma-update.py --file-key ps4xrJzhn7WNbfkZs895Bi
|
|
20
|
-
|
|
21
|
-
# Scan every unique fileKey across the registry
|
|
22
|
-
python3 figma-update.py --all
|
|
23
|
-
|
|
24
|
-
# Dry-run (no writes, prints what would change)
|
|
25
|
-
python3 figma-update.py --all --dry-run
|
|
26
|
-
|
|
27
|
-
# Limit the number of registry entries touched (for smoke tests)
|
|
28
|
-
python3 figma-update.py --all --limit 10
|
|
29
|
-
|
|
30
|
-
Token: FIGMA_ACCESS_TOKEN env var, or macOS Keychain FIGMA_ACCESS_TOKEN.
|
|
31
|
-
|
|
32
|
-
Exit codes:
|
|
33
|
-
0 - Success
|
|
34
|
-
1 - Error (network, auth, bad registry files, etc.)
|
|
35
|
-
"""
|
|
36
|
-
|
|
37
|
-
import argparse
|
|
38
|
-
import http.client
|
|
39
|
-
import json
|
|
40
|
-
import os
|
|
41
|
-
import subprocess
|
|
42
|
-
import sys
|
|
43
|
-
from pathlib import Path
|
|
44
|
-
from typing import Optional
|
|
45
|
-
|
|
46
|
-
sys.path.insert(0, str(Path(__file__).parent))
|
|
47
|
-
from lib.registry_writer import mark_stale # noqa: E402
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
FIGMA_API_HOST = "api.figma.com"
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
# --------------------------------------------------------------------
|
|
54
|
-
# Repo layout helpers
|
|
55
|
-
# --------------------------------------------------------------------
|
|
56
|
-
|
|
57
|
-
def find_repo_root() -> Path:
|
|
58
|
-
p = Path.cwd()
|
|
59
|
-
while p != p.parent:
|
|
60
|
-
if (p / ".git").exists():
|
|
61
|
-
return p
|
|
62
|
-
p = p.parent
|
|
63
|
-
return Path.cwd()
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def default_registry_dir() -> Path:
|
|
67
|
-
return (
|
|
68
|
-
find_repo_root()
|
|
69
|
-
/ "{repos.packagesContainer}"
|
|
70
|
-
/ "Packages"
|
|
71
|
-
/ "{project.slug}-common"
|
|
72
|
-
/ "Shared"
|
|
73
|
-
/ "Figma"
|
|
74
|
-
/ "Components"
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
# --------------------------------------------------------------------
|
|
79
|
-
# Figma API (injectable for tests)
|
|
80
|
-
# --------------------------------------------------------------------
|
|
81
|
-
|
|
82
|
-
def load_figma_token() -> Optional[str]:
|
|
83
|
-
"""FIGMA_ACCESS_TOKEN env var first, then macOS Keychain."""
|
|
84
|
-
env_token = os.environ.get("FIGMA_ACCESS_TOKEN")
|
|
85
|
-
if env_token:
|
|
86
|
-
return env_token
|
|
87
|
-
|
|
88
|
-
try:
|
|
89
|
-
result = subprocess.run(
|
|
90
|
-
[
|
|
91
|
-
"security", "find-generic-password",
|
|
92
|
-
"-a", os.environ.get("USER", ""),
|
|
93
|
-
"-s", "FIGMA_ACCESS_TOKEN",
|
|
94
|
-
"-w",
|
|
95
|
-
],
|
|
96
|
-
capture_output=True,
|
|
97
|
-
text=True,
|
|
98
|
-
timeout=5,
|
|
99
|
-
)
|
|
100
|
-
if result.returncode == 0 and result.stdout.strip():
|
|
101
|
-
return result.stdout.strip()
|
|
102
|
-
except (subprocess.SubprocessError, FileNotFoundError):
|
|
103
|
-
pass
|
|
104
|
-
return None
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def fetch_file_last_modified(file_key: str, token: str) -> str:
|
|
108
|
-
"""Call Figma REST API and return the file's lastModified timestamp.
|
|
109
|
-
|
|
110
|
-
Uses the `depth=1` parameter to minimize payload - we only need the
|
|
111
|
-
top-level metadata, not the full node tree. Figma returns a JSON
|
|
112
|
-
object with a top-level `lastModified` ISO 8601 string.
|
|
113
|
-
|
|
114
|
-
Raises:
|
|
115
|
-
RuntimeError on HTTP error, missing field, or auth failure.
|
|
116
|
-
"""
|
|
117
|
-
conn = http.client.HTTPSConnection(FIGMA_API_HOST, timeout=30)
|
|
118
|
-
try:
|
|
119
|
-
conn.request(
|
|
120
|
-
"GET",
|
|
121
|
-
f"/v1/files/{file_key}?depth=1",
|
|
122
|
-
headers={"X-Figma-Token": token},
|
|
123
|
-
)
|
|
124
|
-
response = conn.getresponse()
|
|
125
|
-
body = response.read().decode("utf-8")
|
|
126
|
-
if response.status != 200:
|
|
127
|
-
raise RuntimeError(
|
|
128
|
-
f"Figma API returned {response.status}: {body[:200]}"
|
|
129
|
-
)
|
|
130
|
-
data = json.loads(body)
|
|
131
|
-
last_modified = data.get("lastModified")
|
|
132
|
-
if not last_modified:
|
|
133
|
-
raise RuntimeError("Figma response missing lastModified field")
|
|
134
|
-
return last_modified
|
|
135
|
-
finally:
|
|
136
|
-
conn.close()
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
# --------------------------------------------------------------------
|
|
140
|
-
# Core logic (pure, no network)
|
|
141
|
-
# --------------------------------------------------------------------
|
|
142
|
-
|
|
143
|
-
def scan_registry_for_file_keys(registry_dir: Path) -> dict[str, list[str]]:
|
|
144
|
-
"""Walk the registry directory and return { fileKey: [node_id, ...] }.
|
|
145
|
-
|
|
146
|
-
Skips files that are not valid JSON or lack nodeId/fileKey.
|
|
147
|
-
"""
|
|
148
|
-
by_file: dict[str, list[str]] = {}
|
|
149
|
-
if not registry_dir.exists():
|
|
150
|
-
return by_file
|
|
151
|
-
|
|
152
|
-
for path in sorted(registry_dir.glob("*.json")):
|
|
153
|
-
try:
|
|
154
|
-
data = json.loads(path.read_text(encoding="utf-8"))
|
|
155
|
-
except (json.JSONDecodeError, OSError):
|
|
156
|
-
continue
|
|
157
|
-
node_id = data.get("nodeId")
|
|
158
|
-
file_key = data.get("fileKey")
|
|
159
|
-
if not node_id or not file_key:
|
|
160
|
-
continue
|
|
161
|
-
by_file.setdefault(file_key, []).append(node_id)
|
|
162
|
-
return by_file
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
def update_file_components(
|
|
166
|
-
registry_dir: Path,
|
|
167
|
-
file_key: str,
|
|
168
|
-
node_ids: list[str],
|
|
169
|
-
new_last_modified: str,
|
|
170
|
-
dry_run: bool,
|
|
171
|
-
limit: Optional[int] = None,
|
|
172
|
-
) -> dict:
|
|
173
|
-
"""For every node in `node_ids`, call mark_stale if not a dry run.
|
|
174
|
-
|
|
175
|
-
Returns a summary dict: { updated: [node_id...], skipped: [node_id...] }.
|
|
176
|
-
"""
|
|
177
|
-
updated: list[str] = []
|
|
178
|
-
skipped: list[str] = []
|
|
179
|
-
|
|
180
|
-
to_process = node_ids if limit is None else node_ids[:limit]
|
|
181
|
-
|
|
182
|
-
for node_id in to_process:
|
|
183
|
-
node_id_dash = node_id.replace(":", "-")
|
|
184
|
-
path = registry_dir / f"{node_id_dash}.json"
|
|
185
|
-
if not path.exists():
|
|
186
|
-
skipped.append(node_id)
|
|
187
|
-
continue
|
|
188
|
-
|
|
189
|
-
if dry_run:
|
|
190
|
-
# Read and compare without writing
|
|
191
|
-
try:
|
|
192
|
-
data = json.loads(path.read_text(encoding="utf-8"))
|
|
193
|
-
except (json.JSONDecodeError, OSError):
|
|
194
|
-
skipped.append(node_id)
|
|
195
|
-
continue
|
|
196
|
-
prev = data.get("figmaUpdatedAt")
|
|
197
|
-
if prev and prev >= new_last_modified:
|
|
198
|
-
skipped.append(node_id)
|
|
199
|
-
else:
|
|
200
|
-
updated.append(node_id)
|
|
201
|
-
else:
|
|
202
|
-
changed = mark_stale(registry_dir, node_id, new_last_modified)
|
|
203
|
-
if changed:
|
|
204
|
-
updated.append(node_id)
|
|
205
|
-
else:
|
|
206
|
-
skipped.append(node_id)
|
|
207
|
-
|
|
208
|
-
return {
|
|
209
|
-
"fileKey": file_key,
|
|
210
|
-
"lastModified": new_last_modified,
|
|
211
|
-
"updated": updated,
|
|
212
|
-
"skipped": skipped,
|
|
213
|
-
"limit_applied": limit,
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
# --------------------------------------------------------------------
|
|
218
|
-
# CLI
|
|
219
|
-
# --------------------------------------------------------------------
|
|
220
|
-
|
|
221
|
-
def main() -> int:
|
|
222
|
-
parser = argparse.ArgumentParser(
|
|
223
|
-
description="Scan registry and mark components stale when Figma has newer data",
|
|
224
|
-
)
|
|
225
|
-
scope = parser.add_mutually_exclusive_group(required=True)
|
|
226
|
-
scope.add_argument("--file-key", help="Check one specific Figma file")
|
|
227
|
-
scope.add_argument("--all", action="store_true", help="Check every unique fileKey in the registry")
|
|
228
|
-
|
|
229
|
-
parser.add_argument("--registry-dir", help="Override registry directory path")
|
|
230
|
-
parser.add_argument("--dry-run", action="store_true", help="Print changes without writing")
|
|
231
|
-
parser.add_argument("--limit", type=int, help="Max nodes to touch per file (smoke-test safety)")
|
|
232
|
-
parser.add_argument("--token", help="Figma API token (overrides env var and keychain)")
|
|
233
|
-
args = parser.parse_args()
|
|
234
|
-
|
|
235
|
-
registry_dir = Path(args.registry_dir) if args.registry_dir else default_registry_dir()
|
|
236
|
-
if not registry_dir.exists():
|
|
237
|
-
print(f"ERROR: registry directory does not exist: {registry_dir}", file=sys.stderr)
|
|
238
|
-
return 1
|
|
239
|
-
|
|
240
|
-
token = args.token or load_figma_token()
|
|
241
|
-
if not token:
|
|
242
|
-
print("ERROR: no Figma token found (set FIGMA_ACCESS_TOKEN or use --token)", file=sys.stderr)
|
|
243
|
-
return 1
|
|
244
|
-
|
|
245
|
-
if args.file_key:
|
|
246
|
-
by_file: dict[str, list[str]] = scan_registry_for_file_keys(registry_dir)
|
|
247
|
-
nodes_in_file = by_file.get(args.file_key, [])
|
|
248
|
-
if not nodes_in_file:
|
|
249
|
-
print(
|
|
250
|
-
json.dumps({"status": "ok", "message": f"no registry entries for {args.file_key}"}),
|
|
251
|
-
)
|
|
252
|
-
return 0
|
|
253
|
-
file_keys = {args.file_key: nodes_in_file}
|
|
254
|
-
else:
|
|
255
|
-
file_keys = scan_registry_for_file_keys(registry_dir)
|
|
256
|
-
|
|
257
|
-
summary: list[dict] = []
|
|
258
|
-
total_updated = 0
|
|
259
|
-
total_skipped = 0
|
|
260
|
-
|
|
261
|
-
for fk, nodes in file_keys.items():
|
|
262
|
-
try:
|
|
263
|
-
last_modified = fetch_file_last_modified(fk, token)
|
|
264
|
-
except RuntimeError as e:
|
|
265
|
-
print(f"ERROR fetching {fk}: {e}", file=sys.stderr)
|
|
266
|
-
return 1
|
|
267
|
-
|
|
268
|
-
file_summary = update_file_components(
|
|
269
|
-
registry_dir=registry_dir,
|
|
270
|
-
file_key=fk,
|
|
271
|
-
node_ids=nodes,
|
|
272
|
-
new_last_modified=last_modified,
|
|
273
|
-
dry_run=args.dry_run,
|
|
274
|
-
limit=args.limit,
|
|
275
|
-
)
|
|
276
|
-
summary.append(file_summary)
|
|
277
|
-
total_updated += len(file_summary["updated"])
|
|
278
|
-
total_skipped += len(file_summary["skipped"])
|
|
279
|
-
|
|
280
|
-
print(json.dumps({
|
|
281
|
-
"status": "ok",
|
|
282
|
-
"dry_run": args.dry_run,
|
|
283
|
-
"total_files": len(summary),
|
|
284
|
-
"total_updated": total_updated,
|
|
285
|
-
"total_skipped": total_skipped,
|
|
286
|
-
"files": summary,
|
|
287
|
-
}, indent=2, ensure_ascii=False))
|
|
288
|
-
return 0
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
if __name__ == "__main__":
|
|
292
|
-
sys.exit(main())
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"""Shared helpers for figma-to-swiftui pipeline scripts."""
|