@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.
- package/CHANGELOG.md +68 -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 +39 -17
- package/install/copilot.mjs +22 -16
- 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/lib/credential-store.sh +8 -4
- 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/build-skills-index.mjs +3 -6
- package/pipeline/scripts/eval-mine-corpus.mjs +187 -0
- package/pipeline/scripts/fixtures/install-layout.tsv +6 -5
- package/pipeline/scripts/gen-mode-dispatch.mjs +3 -3
- package/pipeline/scripts/lint-skills.mjs +26 -1
- 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 +17 -6
- package/pipeline/skills/.skill-manifest.json +40 -160
- package/pipeline/skills/.skills-index.json +3 -373
- 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 +3 -44
- 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,1089 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Phase 3 scriptable sub-phases: 3B (tokens), 3E (assets), 3F (utilities), 3G (coverage).
|
|
3
|
-
|
|
4
|
-
Usage:
|
|
5
|
-
python3 phase3-scripts.py 3b --artifact-path /path/to/artifacts
|
|
6
|
-
python3 phase3-scripts.py 3e --artifact-path /path/to/artifacts
|
|
7
|
-
python3 phase3-scripts.py 3f --artifact-path /path/to/artifacts
|
|
8
|
-
python3 phase3-scripts.py 3g --artifact-path /path/to/artifacts
|
|
9
|
-
python3 phase3-scripts.py wave1 --artifact-path /path/to/artifacts # runs 3b+3e+3f
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
from __future__ import annotations
|
|
13
|
-
|
|
14
|
-
import argparse
|
|
15
|
-
import json
|
|
16
|
-
import os
|
|
17
|
-
import re
|
|
18
|
-
import subprocess
|
|
19
|
-
import sys
|
|
20
|
-
from datetime import datetime, timezone
|
|
21
|
-
from pathlib import Path
|
|
22
|
-
from typing import Optional
|
|
23
|
-
|
|
24
|
-
# --- Constants ---
|
|
25
|
-
|
|
26
|
-
SCRIPT_DIR = Path(__file__).parent
|
|
27
|
-
REPO_ROOT = SCRIPT_DIR.parent.parent.parent.parent # .instructions/figma/figma-to-swiftui/scripts -> repo root
|
|
28
|
-
CONFIG_PKG = REPO_ROOT / "{repos.packagesContainer}" / "Packages" / "{project.slug}-common"
|
|
29
|
-
CLI_BINARY = CONFIG_PKG / ".build" / "debug" / "FigmaRegistryUtility"
|
|
30
|
-
ASSET_CATALOG_ROOT = CONFIG_PKG / "iOS" / "Sources" / "UIAssets" / "Generated" / "Resources" / "Images"
|
|
31
|
-
|
|
32
|
-
ISO_DATE = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
33
|
-
DATE_SHORT = datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
# --- CLI helpers ---
|
|
37
|
-
|
|
38
|
-
def run_cli(command: str, args: list[str]) -> dict | None:
|
|
39
|
-
"""Run FigmaRegistryUtility command from config package dir."""
|
|
40
|
-
cmd = [str(CLI_BINARY), command] + args
|
|
41
|
-
try:
|
|
42
|
-
result = subprocess.run(
|
|
43
|
-
cmd, capture_output=True, text=True, timeout=30,
|
|
44
|
-
cwd=str(CONFIG_PKG)
|
|
45
|
-
)
|
|
46
|
-
if result.returncode == 0 and result.stdout.strip():
|
|
47
|
-
return json.loads(result.stdout)
|
|
48
|
-
except (subprocess.TimeoutExpired, json.JSONDecodeError, FileNotFoundError) as e:
|
|
49
|
-
print(f" [warn] CLI {command} failed: {e}", file=sys.stderr)
|
|
50
|
-
return None
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def ensure_cli():
|
|
54
|
-
"""Build CLI if binary doesn't exist."""
|
|
55
|
-
if not CLI_BINARY.exists():
|
|
56
|
-
print(" Building FigmaRegistryUtility...", file=sys.stderr)
|
|
57
|
-
subprocess.run(
|
|
58
|
-
["swift", "build", "--product", "FigmaRegistryUtility"],
|
|
59
|
-
cwd=str(CONFIG_PKG), capture_output=True, timeout=120
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
# --- 3B: Token Mapping ---
|
|
64
|
-
|
|
65
|
-
def classify_variable(name: str, value: str) -> str | None:
|
|
66
|
-
"""Classify a variable_defs entry by type."""
|
|
67
|
-
if value.startswith("#") and len(value) in (4, 7, 9):
|
|
68
|
-
return "color"
|
|
69
|
-
if value.startswith("Font("):
|
|
70
|
-
return "typography_composite"
|
|
71
|
-
if name.startswith("Font/") or name.startswith("[TBD]Font/"):
|
|
72
|
-
return "font_primitive"
|
|
73
|
-
if name.startswith("Spacing/"):
|
|
74
|
-
return "spacing"
|
|
75
|
-
if name.startswith("Radius/"):
|
|
76
|
-
return "radius"
|
|
77
|
-
# Try to parse as number
|
|
78
|
-
try:
|
|
79
|
-
float(value)
|
|
80
|
-
return "number"
|
|
81
|
-
except ValueError:
|
|
82
|
-
pass
|
|
83
|
-
# String values (font family names etc)
|
|
84
|
-
if not value.startswith("{") and not value.startswith("["):
|
|
85
|
-
return "font_family"
|
|
86
|
-
return None
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def run_3b(artifact_path: str) -> bool:
|
|
90
|
-
"""Phase 3B: Variable Token Mapping."""
|
|
91
|
-
print("Phase 3B: Token Mapping")
|
|
92
|
-
ap = Path(artifact_path)
|
|
93
|
-
|
|
94
|
-
# Read inputs
|
|
95
|
-
var_defs_path = ap / "pass1" / "variable_defs_response.json"
|
|
96
|
-
if not var_defs_path.exists():
|
|
97
|
-
print(" [error] variable_defs_response.json not found", file=sys.stderr)
|
|
98
|
-
return False
|
|
99
|
-
|
|
100
|
-
with open(var_defs_path) as f:
|
|
101
|
-
var_defs = json.load(f)
|
|
102
|
-
|
|
103
|
-
# Get component name from component_properties.json
|
|
104
|
-
comp_name = "(from artifacts)"
|
|
105
|
-
comp_props_path = ap / "pass1" / "component_properties.json"
|
|
106
|
-
if comp_props_path.exists():
|
|
107
|
-
with open(comp_props_path) as f:
|
|
108
|
-
comp_name = json.load(f).get("componentName", comp_name)
|
|
109
|
-
|
|
110
|
-
# Classify variables
|
|
111
|
-
colors, spacings, radii, font_prims, typo_composites, font_families = [], [], [], [], [], []
|
|
112
|
-
lookup_vars = []
|
|
113
|
-
|
|
114
|
-
for name, value in var_defs.items():
|
|
115
|
-
vtype = classify_variable(name, value)
|
|
116
|
-
if vtype == "color":
|
|
117
|
-
colors.append(name)
|
|
118
|
-
lookup_vars.append(name)
|
|
119
|
-
elif vtype == "spacing":
|
|
120
|
-
spacings.append(name)
|
|
121
|
-
lookup_vars.append(name)
|
|
122
|
-
elif vtype == "radius":
|
|
123
|
-
radii.append(name)
|
|
124
|
-
lookup_vars.append(name)
|
|
125
|
-
elif vtype == "number":
|
|
126
|
-
lookup_vars.append(name)
|
|
127
|
-
elif vtype == "typography_composite":
|
|
128
|
-
typo_composites.append(name)
|
|
129
|
-
elif vtype == "font_primitive":
|
|
130
|
-
font_prims.append(name)
|
|
131
|
-
lookup_vars.append(name)
|
|
132
|
-
elif vtype == "font_family":
|
|
133
|
-
font_families.append(name)
|
|
134
|
-
|
|
135
|
-
# Batch CLI lookup for all non-composite variables
|
|
136
|
-
token_map = {}
|
|
137
|
-
if lookup_vars:
|
|
138
|
-
result = run_cli("ui-token-lookup", ["--variables", ",".join(lookup_vars), "--json"])
|
|
139
|
-
if result:
|
|
140
|
-
token_map = result
|
|
141
|
-
|
|
142
|
-
# Typography lookup for composite styles - pass the raw Figma variable names
|
|
143
|
-
# through untransformed. Resolution is the CLI's job, not ours.
|
|
144
|
-
typo_map = {}
|
|
145
|
-
if typo_composites:
|
|
146
|
-
result = run_cli("typography-lookup", ["--styles", ",".join(typo_composites), "--json"])
|
|
147
|
-
if result:
|
|
148
|
-
typo_map = result
|
|
149
|
-
|
|
150
|
-
# Build output sections
|
|
151
|
-
lines = [
|
|
152
|
-
f"# Token Mapping\n",
|
|
153
|
-
f"Component: {comp_name}",
|
|
154
|
-
f"Generated: {ISO_DATE}",
|
|
155
|
-
f"Source: pass1/variable_defs_response.json, pass1/design_context_response.json\n",
|
|
156
|
-
]
|
|
157
|
-
|
|
158
|
-
# Typography section
|
|
159
|
-
lines.append("## Typography\n")
|
|
160
|
-
typo_count = 0
|
|
161
|
-
lines.append("| Figma Style | Token Type | Swift Modifier | Swift Font |")
|
|
162
|
-
lines.append("|-------------|------------|----------------|------------|")
|
|
163
|
-
for tc_name in typo_composites:
|
|
164
|
-
entry = typo_map.get(tc_name)
|
|
165
|
-
if entry and entry.get("status") == "found":
|
|
166
|
-
swift_token = entry["swiftToken"] # e.g. ".smallBold"
|
|
167
|
-
lines.append(f"| {tc_name} | typography | `.typographyStyle({swift_token})` | `Font{swift_token}` |")
|
|
168
|
-
typo_count += 1
|
|
169
|
-
else:
|
|
170
|
-
lines.append(f"| {tc_name} | typography | ⚠️ not found | ⚠️ not found |")
|
|
171
|
-
if not typo_composites:
|
|
172
|
-
lines.append("| (none) | - | - | - |")
|
|
173
|
-
lines.append("")
|
|
174
|
-
|
|
175
|
-
# Colors section
|
|
176
|
-
lines.append("## Colors\n")
|
|
177
|
-
lines.append("| Figma Variable | Token Type | Swift Usage |")
|
|
178
|
-
lines.append("|----------------|------------|-------------|")
|
|
179
|
-
color_count = 0
|
|
180
|
-
for var_name in colors:
|
|
181
|
-
token = token_map.get(var_name)
|
|
182
|
-
if token:
|
|
183
|
-
# token format: "color:.Text.textSuccess"
|
|
184
|
-
type_prefix, swift_path = token.split(":", 1)
|
|
185
|
-
lines.append(f"| {var_name} | {type_prefix} | `Color{swift_path}` |")
|
|
186
|
-
color_count += 1
|
|
187
|
-
else:
|
|
188
|
-
lines.append(f"| {var_name} | color | ⚠️ not found |")
|
|
189
|
-
if not colors:
|
|
190
|
-
lines.append("| (none) | - | - |")
|
|
191
|
-
lines.append("")
|
|
192
|
-
|
|
193
|
-
# Spacing section
|
|
194
|
-
lines.append("## Spacing\n")
|
|
195
|
-
lines.append("| Figma Variable | Token Type | Swift Token |")
|
|
196
|
-
lines.append("|----------------|------------|-------------|")
|
|
197
|
-
spacing_count = 0
|
|
198
|
-
for var_name in spacings:
|
|
199
|
-
token = token_map.get(var_name)
|
|
200
|
-
if token:
|
|
201
|
-
type_prefix, swift_path = token.split(":", 1)
|
|
202
|
-
lines.append(f"| {var_name} | {type_prefix} | `{swift_path}` |")
|
|
203
|
-
spacing_count += 1
|
|
204
|
-
else:
|
|
205
|
-
lines.append(f"| {var_name} | number | ⚠️ not found |")
|
|
206
|
-
if not spacings:
|
|
207
|
-
lines.append("| (none) | - | - |")
|
|
208
|
-
lines.append("")
|
|
209
|
-
|
|
210
|
-
# Radius section
|
|
211
|
-
lines.append("## Radius\n")
|
|
212
|
-
lines.append("| Figma Variable | Token Type | Swift Token |")
|
|
213
|
-
lines.append("|----------------|------------|-------------|")
|
|
214
|
-
radius_count = 0
|
|
215
|
-
for var_name in radii:
|
|
216
|
-
token = token_map.get(var_name)
|
|
217
|
-
if token:
|
|
218
|
-
type_prefix, swift_path = token.split(":", 1)
|
|
219
|
-
lines.append(f"| {var_name} | {type_prefix} | `{swift_path}` |")
|
|
220
|
-
radius_count += 1
|
|
221
|
-
else:
|
|
222
|
-
lines.append(f"| {var_name} | number | ⚠️ not found |")
|
|
223
|
-
if not radii:
|
|
224
|
-
lines.append("| (none) | - | - |")
|
|
225
|
-
lines.append("")
|
|
226
|
-
|
|
227
|
-
# Images section
|
|
228
|
-
lines.append("## Images / Icons\n")
|
|
229
|
-
lines.append("None detected in variable definitions.\n")
|
|
230
|
-
|
|
231
|
-
# Font primitives section
|
|
232
|
-
if font_prims:
|
|
233
|
-
lines.append("## Font Primitives (Sub-tokens)\n")
|
|
234
|
-
lines.append("These are typography sub-properties. Not used directly - encapsulated by composite typography tokens above.\n")
|
|
235
|
-
lines.append("| Figma Variable | Token Type | Swift Token | Note |")
|
|
236
|
-
lines.append("|----------------|------------|-------------|------|")
|
|
237
|
-
for var_name in font_prims:
|
|
238
|
-
token = token_map.get(var_name)
|
|
239
|
-
if token:
|
|
240
|
-
type_prefix, swift_path = token.split(":", 1)
|
|
241
|
-
lines.append(f"| {var_name} | {type_prefix} | `{swift_path}` | Font primitive (use composite token instead) |")
|
|
242
|
-
else:
|
|
243
|
-
lines.append(f"| {var_name} | - | N/A | Font primitive - not a standalone token |")
|
|
244
|
-
lines.append("")
|
|
245
|
-
|
|
246
|
-
# Font families
|
|
247
|
-
if font_families:
|
|
248
|
-
lines.append("## Font Families\n")
|
|
249
|
-
for name in font_families:
|
|
250
|
-
lines.append(f"- `{name}`: \"{var_defs[name]}\" - font family reference, not a standalone token\n")
|
|
251
|
-
|
|
252
|
-
# Missing tokens - exclude font primitives and font families (encapsulated by composite)
|
|
253
|
-
missing = []
|
|
254
|
-
for var_name in lookup_vars:
|
|
255
|
-
token = token_map.get(var_name)
|
|
256
|
-
if token is None:
|
|
257
|
-
vtype = classify_variable(var_name, var_defs[var_name])
|
|
258
|
-
# Font family refs and font primitives are encapsulated by composite typography
|
|
259
|
-
if vtype in ("font_family", "font_primitive"):
|
|
260
|
-
continue
|
|
261
|
-
missing.append((var_name, vtype or "unknown"))
|
|
262
|
-
for tc_name in typo_composites:
|
|
263
|
-
entry = typo_map.get(tc_name)
|
|
264
|
-
if not entry or entry.get("status") != "found":
|
|
265
|
-
missing.append((tc_name, "typography"))
|
|
266
|
-
|
|
267
|
-
# Font primitives/families that returned null are NOT missing - they're encapsulated
|
|
268
|
-
font_null_note = []
|
|
269
|
-
for var_name in lookup_vars:
|
|
270
|
-
token = token_map.get(var_name)
|
|
271
|
-
if token is None:
|
|
272
|
-
vtype = classify_variable(var_name, var_defs[var_name])
|
|
273
|
-
if vtype in ("font_family", "font_primitive"):
|
|
274
|
-
font_null_note.append((var_name, vtype))
|
|
275
|
-
|
|
276
|
-
lines.append("## Missing Tokens\n")
|
|
277
|
-
if missing:
|
|
278
|
-
lines.append("| Token | Type | Resolution |")
|
|
279
|
-
lines.append("|-------|------|------------|")
|
|
280
|
-
for name, ttype in missing:
|
|
281
|
-
lines.append(f"| {name} | {ttype} | See reference/missing-tokens.md |")
|
|
282
|
-
else:
|
|
283
|
-
if font_null_note:
|
|
284
|
-
lines.append(f"All tokens resolved. ({len(font_null_note)} font primitive/family references excluded - encapsulated by composite typography token.)\n")
|
|
285
|
-
else:
|
|
286
|
-
lines.append("All tokens resolved.\n")
|
|
287
|
-
|
|
288
|
-
# Summary
|
|
289
|
-
mapped_prims = sum(1 for v in font_prims if token_map.get(v) is not None)
|
|
290
|
-
actionable_missing = [m for m in missing if m[1] != "font_family"]
|
|
291
|
-
|
|
292
|
-
lines.append("## Summary\n")
|
|
293
|
-
lines.append(f"- Typography: {typo_count} mapped")
|
|
294
|
-
lines.append(f"- Colors: {color_count} mapped")
|
|
295
|
-
lines.append(f"- Spacing: {spacing_count} mapped")
|
|
296
|
-
lines.append(f"- Radius: {radius_count} mapped")
|
|
297
|
-
lines.append(f"- Images: 0 mapped")
|
|
298
|
-
if font_prims:
|
|
299
|
-
lines.append(f"- Font Primitives: {mapped_prims} mapped (sub-tokens of typography)")
|
|
300
|
-
lines.append(f"- Missing: {len(actionable_missing)} actionable")
|
|
301
|
-
|
|
302
|
-
# Write
|
|
303
|
-
os.makedirs(ap / "pass3", exist_ok=True)
|
|
304
|
-
out_path = ap / "pass3" / "02_token_mapping.md"
|
|
305
|
-
with open(out_path, "w") as f:
|
|
306
|
-
f.write("\n".join(lines) + "\n")
|
|
307
|
-
|
|
308
|
-
print(f" ✅ {out_path} ({typo_count + color_count + spacing_count + radius_count} tokens mapped, {len(actionable_missing)} missing)")
|
|
309
|
-
return True
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
# --- 3E: Asset Inventory ---
|
|
313
|
-
|
|
314
|
-
def parse_asset_filename(filename: str) -> str:
|
|
315
|
-
"""Try to identify an asset from its filename (hash or named)."""
|
|
316
|
-
# Strip extension
|
|
317
|
-
stem = Path(filename).stem
|
|
318
|
-
# If it's a hex hash, we need to inspect SVG content or check code_connect_map
|
|
319
|
-
if re.match(r'^[a-f0-9]{30,}$', stem):
|
|
320
|
-
return stem # hash-named, needs further resolution
|
|
321
|
-
return stem
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
def run_3e(artifact_path: str) -> bool:
|
|
325
|
-
"""Phase 3E: Asset Inventory."""
|
|
326
|
-
print("Phase 3E: Asset Inventory")
|
|
327
|
-
ap = Path(artifact_path)
|
|
328
|
-
|
|
329
|
-
assets_txt = ap / "pass1" / "design_context_assets.txt"
|
|
330
|
-
if not assets_txt.exists() or assets_txt.stat().st_size == 0:
|
|
331
|
-
# Write placeholder
|
|
332
|
-
os.makedirs(ap / "pass3", exist_ok=True)
|
|
333
|
-
out_path = ap / "pass3" / "06_assets.md"
|
|
334
|
-
with open(out_path, "w") as f:
|
|
335
|
-
f.write(f"# Asset Inventory\n\nNo downloadable assets found for this component.\nGenerated: {ISO_DATE}\n")
|
|
336
|
-
print(f" ⏭️ No assets - placeholder written")
|
|
337
|
-
return True
|
|
338
|
-
|
|
339
|
-
# Parse asset entries
|
|
340
|
-
assets = []
|
|
341
|
-
with open(assets_txt) as f:
|
|
342
|
-
for line in f:
|
|
343
|
-
line = line.strip()
|
|
344
|
-
if not line or line.startswith("#"):
|
|
345
|
-
continue
|
|
346
|
-
# Format: "filename | url" or "filename | local:path"
|
|
347
|
-
parts = line.split("|", 1)
|
|
348
|
-
filename = parts[0].strip()
|
|
349
|
-
url = parts[1].strip() if len(parts) > 1 else ""
|
|
350
|
-
assets.append({"filename": filename, "url": url})
|
|
351
|
-
|
|
352
|
-
if not assets:
|
|
353
|
-
os.makedirs(ap / "pass3", exist_ok=True)
|
|
354
|
-
out_path = ap / "pass3" / "06_assets.md"
|
|
355
|
-
with open(out_path, "w") as f:
|
|
356
|
-
f.write(f"# Asset Inventory\n\nNo downloadable assets found for this component.\nGenerated: {ISO_DATE}\n")
|
|
357
|
-
print(f" ⏭️ No assets - placeholder written")
|
|
358
|
-
return True
|
|
359
|
-
|
|
360
|
-
# Try to identify assets via code_connect_map (for icon names)
|
|
361
|
-
# Icon entries have snippet like "Image.plusCircle" with empty source
|
|
362
|
-
icon_names = set()
|
|
363
|
-
ccm_path = ap / "pass1" / "code_connect_map_response.json"
|
|
364
|
-
if ccm_path.exists():
|
|
365
|
-
with open(ccm_path) as f:
|
|
366
|
-
ccm = json.load(f)
|
|
367
|
-
for entry in ccm.values():
|
|
368
|
-
# Check componentName field
|
|
369
|
-
name = entry.get("componentName", "")
|
|
370
|
-
if name.startswith("Image."):
|
|
371
|
-
icon_names.add(name.replace("Image.", ""))
|
|
372
|
-
# Also check snippet field for "Image.xxx" patterns (icon entries use this)
|
|
373
|
-
snippet = entry.get("snippet", "")
|
|
374
|
-
m = re.match(r'^Image\.(\w+)$', snippet.strip())
|
|
375
|
-
if m:
|
|
376
|
-
icon_names.add(m.group(1))
|
|
377
|
-
|
|
378
|
-
# Check each asset against registry and catalog
|
|
379
|
-
results = []
|
|
380
|
-
for asset in assets:
|
|
381
|
-
filename = asset["filename"]
|
|
382
|
-
stem = parse_asset_filename(filename)
|
|
383
|
-
ext = Path(filename).suffix.lstrip(".")
|
|
384
|
-
|
|
385
|
-
# Check SVG content for icon identification (if local file exists)
|
|
386
|
-
local_path = asset["url"].replace("local:", "") if asset["url"].startswith("local:") else None
|
|
387
|
-
svg_identified_name = None
|
|
388
|
-
|
|
389
|
-
# Try registry lookup for known icon names from code_connect_map
|
|
390
|
-
status = "Unknown"
|
|
391
|
-
target = "N/A"
|
|
392
|
-
identified_name = stem
|
|
393
|
-
swift_accessor = None
|
|
394
|
-
|
|
395
|
-
for icon_name in icon_names:
|
|
396
|
-
lookup = run_cli("ui-token-lookup", ["--variables", icon_name, "--json"])
|
|
397
|
-
if lookup:
|
|
398
|
-
val = lookup.get(icon_name)
|
|
399
|
-
if val and val.startswith("image:"):
|
|
400
|
-
swift_path = val.split(":", 1)[1]
|
|
401
|
-
identified_name = icon_name
|
|
402
|
-
swift_accessor = f"Image{swift_path}"
|
|
403
|
-
status = "Existing"
|
|
404
|
-
break
|
|
405
|
-
|
|
406
|
-
# Also search asset catalog directly if hash-named
|
|
407
|
-
if status == "Unknown" and re.match(r'^[a-f0-9]{30,}$', stem):
|
|
408
|
-
# Check if any icon from code_connect_map exists in catalog
|
|
409
|
-
for icon_name in icon_names:
|
|
410
|
-
# Convert camelCase to kebab-case for catalog search
|
|
411
|
-
kebab = re.sub(r'([A-Z])', r'-\1', icon_name).lower().lstrip("-")
|
|
412
|
-
matches = list(ASSET_CATALOG_ROOT.rglob(f"*{kebab}*")) if ASSET_CATALOG_ROOT.exists() else []
|
|
413
|
-
if matches:
|
|
414
|
-
identified_name = kebab
|
|
415
|
-
status = "Existing"
|
|
416
|
-
target = str(matches[0].relative_to(CONFIG_PKG))
|
|
417
|
-
break
|
|
418
|
-
|
|
419
|
-
results.append({
|
|
420
|
-
"filename": filename,
|
|
421
|
-
"ext": ext,
|
|
422
|
-
"identified_name": identified_name,
|
|
423
|
-
"status": status,
|
|
424
|
-
"target": target,
|
|
425
|
-
"swift_accessor": swift_accessor,
|
|
426
|
-
})
|
|
427
|
-
|
|
428
|
-
# Write output
|
|
429
|
-
new_count = sum(1 for r in results if r["status"] == "New")
|
|
430
|
-
existing_count = sum(1 for r in results if r["status"] == "Existing")
|
|
431
|
-
unknown_count = sum(1 for r in results if r["status"] == "Unknown")
|
|
432
|
-
|
|
433
|
-
lines = [
|
|
434
|
-
f"# Asset Inventory\n",
|
|
435
|
-
f"Generated: {ISO_DATE}",
|
|
436
|
-
f"Source: pass1/design_context_assets.txt\n",
|
|
437
|
-
"## Assets\n",
|
|
438
|
-
"| File | Type | Identified As | Status | Swift Accessor |",
|
|
439
|
-
"|------|------|---------------|--------|----------------|",
|
|
440
|
-
]
|
|
441
|
-
|
|
442
|
-
for r in results:
|
|
443
|
-
accessor = r["swift_accessor"] or " - "
|
|
444
|
-
lines.append(f"| {r['filename']} | {r['ext']} | {r['identified_name']} | {r['status']} | `{accessor}` |" if r["swift_accessor"] else
|
|
445
|
-
f"| {r['filename']} | {r['ext']} | {r['identified_name']} | {r['status']} | - |")
|
|
446
|
-
|
|
447
|
-
lines.extend([
|
|
448
|
-
"",
|
|
449
|
-
"## Summary\n",
|
|
450
|
-
f"- Total assets: {len(results)}",
|
|
451
|
-
f"- New (to add): {new_count}",
|
|
452
|
-
f"- Existing (no action): {existing_count}",
|
|
453
|
-
f"- Unknown (check manually): {unknown_count}",
|
|
454
|
-
])
|
|
455
|
-
|
|
456
|
-
if new_count > 0:
|
|
457
|
-
lines.extend(["", "## Action Items\n"])
|
|
458
|
-
for r in results:
|
|
459
|
-
if r["status"] == "New":
|
|
460
|
-
lines.append(f"- [ ] Add `{r['identified_name']}` to asset catalog")
|
|
461
|
-
|
|
462
|
-
if new_count == 0:
|
|
463
|
-
lines.extend(["", "## Action Items\n", "No new assets need to be added."])
|
|
464
|
-
|
|
465
|
-
os.makedirs(ap / "pass3", exist_ok=True)
|
|
466
|
-
out_path = ap / "pass3" / "06_assets.md"
|
|
467
|
-
with open(out_path, "w") as f:
|
|
468
|
-
f.write("\n".join(lines) + "\n")
|
|
469
|
-
|
|
470
|
-
print(f" ✅ {out_path} ({new_count} new, {existing_count} existing, {unknown_count} unknown)")
|
|
471
|
-
return True
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
# --- 3F: Utility Integration ---
|
|
475
|
-
|
|
476
|
-
def run_3f(artifact_path: str) -> bool:
|
|
477
|
-
"""Phase 3F: Utility Integration Summary."""
|
|
478
|
-
print("Phase 3F: Utility Integration")
|
|
479
|
-
ap = Path(artifact_path)
|
|
480
|
-
|
|
481
|
-
# Primary source: component-utilities.json (structured, from Phase 2)
|
|
482
|
-
utils_json = ap / "pass2" / "component-utilities.json"
|
|
483
|
-
if not utils_json.exists():
|
|
484
|
-
print(f" [error] pass2/component-utilities.json not found", file=sys.stderr)
|
|
485
|
-
return False
|
|
486
|
-
|
|
487
|
-
with open(utils_json) as f:
|
|
488
|
-
utils = json.load(f)
|
|
489
|
-
|
|
490
|
-
component_name = utils.get("componentName", "Unknown")
|
|
491
|
-
if isinstance(component_name, dict):
|
|
492
|
-
component_name = component_name.get("ios", "Unknown")
|
|
493
|
-
|
|
494
|
-
testing_ids = utils.get("testingIdentifiers", [])
|
|
495
|
-
loc_keys = utils.get("localizationKeys", [])
|
|
496
|
-
a11y_keys = utils.get("accessibilityKeys", [])
|
|
497
|
-
analytics = utils.get("analyticsEvents", [])
|
|
498
|
-
|
|
499
|
-
# Build element-to-utility mapping table
|
|
500
|
-
# Collect all unique elements from all utility categories
|
|
501
|
-
elements = {} # element_name -> {testing, localization, accessibility, analytics}
|
|
502
|
-
|
|
503
|
-
for tid in testing_ids:
|
|
504
|
-
key = tid.get("key", "")
|
|
505
|
-
elem = tid.get("usage", key.split(".")[-1] if "." in key else key)
|
|
506
|
-
if elem not in elements:
|
|
507
|
-
elements[elem] = {"testing": None, "localization": None, "accessibility": None, "analytics": None}
|
|
508
|
-
elements[elem]["testing"] = f"`.{key}`"
|
|
509
|
-
|
|
510
|
-
for lk in loc_keys:
|
|
511
|
-
key = lk.get("key", "")
|
|
512
|
-
elem = lk.get("usage", key.split(".")[-1] if "." in key else key)
|
|
513
|
-
if elem not in elements:
|
|
514
|
-
elements[elem] = {"testing": None, "localization": None, "accessibility": None, "analytics": None}
|
|
515
|
-
elements[elem]["localization"] = f"`.{key}`"
|
|
516
|
-
|
|
517
|
-
for ak in a11y_keys:
|
|
518
|
-
key = ak.get("key", "")
|
|
519
|
-
elem = ak.get("usage", ak.get("purpose", key.split(".")[-1] if "." in key else key))
|
|
520
|
-
if elem not in elements:
|
|
521
|
-
elements[elem] = {"testing": None, "localization": None, "accessibility": None, "analytics": None}
|
|
522
|
-
elements[elem]["accessibility"] = f"`.{key}`"
|
|
523
|
-
|
|
524
|
-
for ae in analytics:
|
|
525
|
-
elem = ae.get("element", "unknown")
|
|
526
|
-
if elem not in elements:
|
|
527
|
-
elements[elem] = {"testing": None, "localization": None, "accessibility": None, "analytics": None}
|
|
528
|
-
elements[elem]["analytics"] = ae.get("event", " - ")
|
|
529
|
-
|
|
530
|
-
lines = [
|
|
531
|
-
f"# Utility Integration\n",
|
|
532
|
-
f"Component: {component_name}",
|
|
533
|
-
f"Generated: {ISO_DATE}",
|
|
534
|
-
f"Source: pass2/component-utilities.json\n",
|
|
535
|
-
]
|
|
536
|
-
|
|
537
|
-
# Element mapping table
|
|
538
|
-
if elements:
|
|
539
|
-
lines.append("## Element-to-Utility Mapping\n")
|
|
540
|
-
lines.append("| Element | Testing ID | Localization Key | Accessibility | Analytics |")
|
|
541
|
-
lines.append("|---------|------------|------------------|---------------|-----------|")
|
|
542
|
-
for elem, utils_map in elements.items():
|
|
543
|
-
t = utils_map["testing"] or " - "
|
|
544
|
-
l = utils_map["localization"] or " - "
|
|
545
|
-
a = utils_map["accessibility"] or " - "
|
|
546
|
-
an = utils_map["analytics"] or " - "
|
|
547
|
-
lines.append(f"| {elem} | {t} | {l} | {a} | {an} |")
|
|
548
|
-
lines.append("")
|
|
549
|
-
|
|
550
|
-
# Testing Identifiers
|
|
551
|
-
lines.append("## Testing Identifiers\n")
|
|
552
|
-
if testing_ids:
|
|
553
|
-
lines.append("| Key | Value | Usage |")
|
|
554
|
-
lines.append("|-----|-------|-------|")
|
|
555
|
-
for tid in testing_ids:
|
|
556
|
-
lines.append(f"| {tid.get('key', '')} | {tid.get('value', '')} | `.accessibilityIdentifier(.{tid.get('key', '')})` |")
|
|
557
|
-
else:
|
|
558
|
-
lines.append("No testing identifiers.")
|
|
559
|
-
lines.append("")
|
|
560
|
-
|
|
561
|
-
# Localization Keys
|
|
562
|
-
lines.append("## Localization Keys\n")
|
|
563
|
-
if loc_keys:
|
|
564
|
-
lines.append("| Key | Pattern |")
|
|
565
|
-
lines.append("|-----|---------|")
|
|
566
|
-
for lk in loc_keys:
|
|
567
|
-
lines.append(f"| {lk.get('key', '')} | {lk.get('pattern', '')} |")
|
|
568
|
-
else:
|
|
569
|
-
lines.append("No localization keys (dynamic text only).")
|
|
570
|
-
lines.append("")
|
|
571
|
-
|
|
572
|
-
# Accessibility
|
|
573
|
-
lines.append("## Accessibility\n")
|
|
574
|
-
if a11y_keys:
|
|
575
|
-
lines.append("| Key | Purpose |")
|
|
576
|
-
lines.append("|-----|---------|")
|
|
577
|
-
for ak in a11y_keys:
|
|
578
|
-
lines.append(f"| {ak.get('key', '')} | {ak.get('purpose', '')} |")
|
|
579
|
-
else:
|
|
580
|
-
lines.append("No dedicated keys - using runtime accessibility patterns.")
|
|
581
|
-
lines.append("")
|
|
582
|
-
|
|
583
|
-
# Analytics
|
|
584
|
-
lines.append("## Analytics Events\n")
|
|
585
|
-
if analytics:
|
|
586
|
-
lines.append("| Element | Event | Identifier Key |")
|
|
587
|
-
lines.append("|---------|-------|----------------|")
|
|
588
|
-
for ae in analytics:
|
|
589
|
-
lines.append(f"| {ae.get('element', '')} | {ae.get('event', '')} | {ae.get('identifierKey', '')} |")
|
|
590
|
-
else:
|
|
591
|
-
lines.append("No analytics events (no interactive elements).")
|
|
592
|
-
lines.append("")
|
|
593
|
-
|
|
594
|
-
# Summary
|
|
595
|
-
lines.append("## Summary\n")
|
|
596
|
-
lines.append("| Category | Count |")
|
|
597
|
-
lines.append("|----------|-------|")
|
|
598
|
-
lines.append(f"| Testing Identifiers | {len(testing_ids)} |")
|
|
599
|
-
lines.append(f"| Localization Keys | {len(loc_keys)} |")
|
|
600
|
-
lines.append(f"| Accessibility Keys | {len(a11y_keys)} |")
|
|
601
|
-
lines.append(f"| Analytics Events | {len(analytics)} |")
|
|
602
|
-
|
|
603
|
-
os.makedirs(ap / "pass3", exist_ok=True)
|
|
604
|
-
out_path = ap / "pass3" / "08_utility_integration.md"
|
|
605
|
-
with open(out_path, "w") as f:
|
|
606
|
-
f.write("\n".join(lines) + "\n")
|
|
607
|
-
|
|
608
|
-
print(f" ✅ {out_path} ({len(testing_ids)} testing, {len(loc_keys)} loc, {len(a11y_keys)} a11y, {len(analytics)} analytics)")
|
|
609
|
-
return True
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
# --- 3G: Property Coverage ---
|
|
613
|
-
|
|
614
|
-
def run_3g(artifact_path: str) -> bool:
|
|
615
|
-
"""Phase 3G: Property Coverage Verification."""
|
|
616
|
-
print("Phase 3G: Property Coverage")
|
|
617
|
-
ap = Path(artifact_path)
|
|
618
|
-
|
|
619
|
-
# Read component_properties.json (Phase 1 output)
|
|
620
|
-
comp_props_path = ap / "pass1" / "component_properties.json"
|
|
621
|
-
if not comp_props_path.exists():
|
|
622
|
-
print(f" [error] component_properties.json not found", file=sys.stderr)
|
|
623
|
-
return False
|
|
624
|
-
|
|
625
|
-
with open(comp_props_path) as f:
|
|
626
|
-
comp_props = json.load(f)
|
|
627
|
-
|
|
628
|
-
# Read 3D's structured JSON output
|
|
629
|
-
arch_json_path = ap / "pass3" / "04b_component_architecture.json"
|
|
630
|
-
if not arch_json_path.exists():
|
|
631
|
-
print(f" [error] 04b_component_architecture.json not found (3D must emit this)", file=sys.stderr)
|
|
632
|
-
return False
|
|
633
|
-
|
|
634
|
-
with open(arch_json_path) as f:
|
|
635
|
-
arch = json.load(f)
|
|
636
|
-
|
|
637
|
-
# Extract all Figma properties
|
|
638
|
-
prop_defs = comp_props.get("componentPropertyDefinitions", {})
|
|
639
|
-
arch_props = {p["figmaKey"]: p for p in arch.get("properties", [])}
|
|
640
|
-
|
|
641
|
-
component_name = comp_props.get("componentName", "Unknown")
|
|
642
|
-
|
|
643
|
-
# Match each Figma property against architecture
|
|
644
|
-
properties = []
|
|
645
|
-
unmapped = []
|
|
646
|
-
|
|
647
|
-
for figma_key, figma_def in prop_defs.items():
|
|
648
|
-
prop_type = figma_def.get("type", "UNKNOWN")
|
|
649
|
-
# Clean display name from figma_key (remove #nodeId suffix)
|
|
650
|
-
display_name = re.sub(r'#\d+:\d+$', '', figma_key).strip()
|
|
651
|
-
# Remove leading arrow for instance swaps
|
|
652
|
-
display_name_clean = display_name.lstrip("↪ ").strip()
|
|
653
|
-
|
|
654
|
-
if figma_key in arch_props:
|
|
655
|
-
ap_entry = arch_props[figma_key]
|
|
656
|
-
properties.append({
|
|
657
|
-
"name": display_name,
|
|
658
|
-
"type": prop_type,
|
|
659
|
-
"figmaKey": figma_key,
|
|
660
|
-
"status": ap_entry.get("status", "IMPLEMENT"),
|
|
661
|
-
"swiftType": ap_entry.get("swiftType"),
|
|
662
|
-
"reason": ap_entry.get("reason"),
|
|
663
|
-
})
|
|
664
|
-
else:
|
|
665
|
-
# Not found in architecture - unmapped
|
|
666
|
-
unmapped.append({
|
|
667
|
-
"name": display_name,
|
|
668
|
-
"type": prop_type,
|
|
669
|
-
"figmaKey": figma_key,
|
|
670
|
-
})
|
|
671
|
-
|
|
672
|
-
# Include LAYER_TEXT entries from architecture (dynamic layer texts not in Figma properties)
|
|
673
|
-
for arch_prop in arch.get("properties", []):
|
|
674
|
-
if arch_prop.get("type") == "LAYER_TEXT" and arch_prop.get("status") == "IMPLEMENT":
|
|
675
|
-
properties.append({
|
|
676
|
-
"name": arch_prop["name"],
|
|
677
|
-
"type": "LAYER_TEXT",
|
|
678
|
-
"figmaKey": None,
|
|
679
|
-
"status": "IMPLEMENT",
|
|
680
|
-
"swiftType": arch_prop.get("swiftType"),
|
|
681
|
-
"reason": None,
|
|
682
|
-
})
|
|
683
|
-
|
|
684
|
-
# Counts
|
|
685
|
-
implemented = sum(1 for p in properties if p["status"] == "IMPLEMENT")
|
|
686
|
-
skipped = sum(1 for p in properties if p["status"] == "SKIP")
|
|
687
|
-
deferred = sum(1 for p in properties if p["status"] == "DEFER")
|
|
688
|
-
total = len(properties)
|
|
689
|
-
|
|
690
|
-
# Validate
|
|
691
|
-
if unmapped:
|
|
692
|
-
print(f" [error] {len(unmapped)} unmapped properties:", file=sys.stderr)
|
|
693
|
-
for u in unmapped:
|
|
694
|
-
print(f" - {u['name']} ({u['type']}): {u['figmaKey']}", file=sys.stderr)
|
|
695
|
-
# Still write the file but include unmapped
|
|
696
|
-
|
|
697
|
-
# Validate SKIP/DEFER have reasons
|
|
698
|
-
missing_reasons = [p for p in properties if p["status"] in ("SKIP", "DEFER") and not p.get("reason")]
|
|
699
|
-
if missing_reasons:
|
|
700
|
-
print(f" [warn] {len(missing_reasons)} SKIP/DEFER properties missing reasons", file=sys.stderr)
|
|
701
|
-
|
|
702
|
-
# Write output
|
|
703
|
-
output = {
|
|
704
|
-
"componentName": component_name,
|
|
705
|
-
"generated": DATE_SHORT,
|
|
706
|
-
"totalProperties": total,
|
|
707
|
-
"implemented": implemented,
|
|
708
|
-
"skipped": skipped,
|
|
709
|
-
"deferred": deferred,
|
|
710
|
-
"properties": properties,
|
|
711
|
-
"unmapped": unmapped,
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
os.makedirs(ap / "pass3", exist_ok=True)
|
|
715
|
-
out_path = ap / "pass3" / "00_property_coverage.json"
|
|
716
|
-
with open(out_path, "w") as f:
|
|
717
|
-
json.dump(output, f, indent=2, ensure_ascii=False)
|
|
718
|
-
|
|
719
|
-
status = "✅" if not unmapped else "⚠️"
|
|
720
|
-
print(f" {status} {out_path} ({implemented} implement, {skipped} skip, {deferred} defer, {len(unmapped)} unmapped)")
|
|
721
|
-
return len(unmapped) == 0
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
# --- spec: Implementation Spec Compilation ---
|
|
725
|
-
|
|
726
|
-
def extract_md_value(filepath: Path, field: str) -> str:
|
|
727
|
-
"""Extract a value from a markdown table row like '| Field | Value |'."""
|
|
728
|
-
if not filepath.exists():
|
|
729
|
-
return ""
|
|
730
|
-
with open(filepath) as f:
|
|
731
|
-
for line in f:
|
|
732
|
-
if f"| {field} |" in line or f"| {field.lower()} |" in line.lower():
|
|
733
|
-
parts = [p.strip() for p in line.split("|")]
|
|
734
|
-
if len(parts) >= 3:
|
|
735
|
-
return parts[2]
|
|
736
|
-
return ""
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
def extract_md_section_lines(filepath: Path, heading: str) -> list[str]:
|
|
740
|
-
"""Extract lines under a markdown ## heading until next heading."""
|
|
741
|
-
if not filepath.exists():
|
|
742
|
-
return []
|
|
743
|
-
lines = []
|
|
744
|
-
in_section = False
|
|
745
|
-
with open(filepath) as f:
|
|
746
|
-
for line in f:
|
|
747
|
-
if line.startswith("## ") and heading.lower() in line.lower():
|
|
748
|
-
in_section = True
|
|
749
|
-
continue
|
|
750
|
-
elif line.startswith("## ") and in_section:
|
|
751
|
-
break
|
|
752
|
-
elif in_section:
|
|
753
|
-
lines.append(line.rstrip())
|
|
754
|
-
return lines
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
def generate_api_shape_code(arch: dict) -> str:
|
|
758
|
-
"""Generate Swift API shape code from architecture JSON."""
|
|
759
|
-
lines = []
|
|
760
|
-
lines.append("```swift")
|
|
761
|
-
|
|
762
|
-
# Enums
|
|
763
|
-
for enum in arch.get("enums", []):
|
|
764
|
-
cases = ", ".join(f".{c}" for c in enum["cases"])
|
|
765
|
-
lines.append(f"public enum {enum['name']}: String, CaseIterable, Sendable {{")
|
|
766
|
-
for case in enum["cases"]:
|
|
767
|
-
lines.append(f" case {case}")
|
|
768
|
-
lines.append("}")
|
|
769
|
-
lines.append("")
|
|
770
|
-
|
|
771
|
-
# Configuration struct (if COMPLEX)
|
|
772
|
-
api = arch.get("apiDesign", {})
|
|
773
|
-
config = api.get("configurationStruct")
|
|
774
|
-
if config:
|
|
775
|
-
lines.append(f"public struct {config['name']}: Sendable {{")
|
|
776
|
-
for prop in config.get("properties", []):
|
|
777
|
-
lines.append(f" public let {prop['name']}: {prop['swiftType']}")
|
|
778
|
-
lines.append("")
|
|
779
|
-
# Init
|
|
780
|
-
init_params = []
|
|
781
|
-
for prop in config.get("properties", []):
|
|
782
|
-
default = prop.get("default")
|
|
783
|
-
if default:
|
|
784
|
-
init_params.append(f" {prop['name']}: {prop['swiftType']} = {default}")
|
|
785
|
-
else:
|
|
786
|
-
init_params.append(f" {prop['name']}: {prop['swiftType']}")
|
|
787
|
-
lines.append(" public init(")
|
|
788
|
-
lines.append(",\n".join(init_params))
|
|
789
|
-
lines.append(" ) { ... }")
|
|
790
|
-
lines.append("}")
|
|
791
|
-
lines.append("")
|
|
792
|
-
|
|
793
|
-
# Component struct
|
|
794
|
-
comp = api.get("componentStruct", {})
|
|
795
|
-
comp_name = comp.get("name", arch.get("componentName", "Component"))
|
|
796
|
-
lines.append(f"public struct {comp_name}: View {{")
|
|
797
|
-
if config:
|
|
798
|
-
lines.append(f" private let configuration: {config['name']}")
|
|
799
|
-
lines.append("")
|
|
800
|
-
lines.append(f" public init(configuration: {config['name']}) {{")
|
|
801
|
-
lines.append(" self.configuration = configuration")
|
|
802
|
-
lines.append(" }")
|
|
803
|
-
else:
|
|
804
|
-
# SIMPLE - direct init params
|
|
805
|
-
for param in comp.get("initParameters", []):
|
|
806
|
-
lines.append(f" private let {param['name']}: {param['swiftType']}")
|
|
807
|
-
lines.append("}")
|
|
808
|
-
lines.append("```")
|
|
809
|
-
|
|
810
|
-
return "\n".join(lines)
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
def run_spec(artifact_path: str) -> bool:
|
|
814
|
-
"""Compile 07_implementation_spec.md from pass3 structured files."""
|
|
815
|
-
print("Phase 3 Spec: Compiling implementation spec")
|
|
816
|
-
ap = Path(artifact_path)
|
|
817
|
-
|
|
818
|
-
# Read architecture JSON (primary data source)
|
|
819
|
-
arch_path = ap / "pass3" / "04b_component_architecture.json"
|
|
820
|
-
if not arch_path.exists():
|
|
821
|
-
print(" [error] 04b_component_architecture.json not found", file=sys.stderr)
|
|
822
|
-
return False
|
|
823
|
-
|
|
824
|
-
with open(arch_path) as f:
|
|
825
|
-
arch = json.load(f)
|
|
826
|
-
|
|
827
|
-
comp_name = arch.get("componentName", "Unknown")
|
|
828
|
-
complexity = arch.get("complexity", "SIMPLE")
|
|
829
|
-
prop_count = arch.get("propertyCount", 0)
|
|
830
|
-
|
|
831
|
-
# Read component_summary.json for dimensions
|
|
832
|
-
summary_path = ap / "pass1" / "component_summary.json"
|
|
833
|
-
dimensions = "unknown"
|
|
834
|
-
node_id = ""
|
|
835
|
-
if summary_path.exists():
|
|
836
|
-
with open(summary_path) as f:
|
|
837
|
-
summary = json.load(f)
|
|
838
|
-
w = summary.get("dimensions", {}).get("width", "?")
|
|
839
|
-
h = summary.get("dimensions", {}).get("height", "?")
|
|
840
|
-
dimensions = f"{w} x {h} (component set)"
|
|
841
|
-
node_id = summary.get("nodeId", "")
|
|
842
|
-
|
|
843
|
-
# Read component location
|
|
844
|
-
loc_path = ap / "pass3" / "01_component_location.md"
|
|
845
|
-
comp_location = extract_md_value(loc_path, "Location")
|
|
846
|
-
if not comp_location:
|
|
847
|
-
comp_location = "(see 01_component_location.md)"
|
|
848
|
-
|
|
849
|
-
# Read property coverage
|
|
850
|
-
cov_path = ap / "pass3" / "00_property_coverage.json"
|
|
851
|
-
if cov_path.exists():
|
|
852
|
-
with open(cov_path) as f:
|
|
853
|
-
cov = json.load(f)
|
|
854
|
-
else:
|
|
855
|
-
cov = {}
|
|
856
|
-
|
|
857
|
-
# Generate API shape code
|
|
858
|
-
api_code = generate_api_shape_code(arch)
|
|
859
|
-
|
|
860
|
-
# Build variant mapping table
|
|
861
|
-
variant_lines = []
|
|
862
|
-
variant_lines.append("| Figma Property | Swift Property | Type |")
|
|
863
|
-
variant_lines.append("|----------------|---------------|------|")
|
|
864
|
-
|
|
865
|
-
all_props = arch.get("properties", []) + arch.get("implicitProperties", [])
|
|
866
|
-
for prop in all_props:
|
|
867
|
-
status = prop.get("status", "IMPLEMENT")
|
|
868
|
-
if status == "SKIP":
|
|
869
|
-
# Show merged info
|
|
870
|
-
reason = prop.get("reason", "")
|
|
871
|
-
swift_type = " - "
|
|
872
|
-
swift_name = " - "
|
|
873
|
-
if "merged" in reason.lower():
|
|
874
|
-
# Find the implicit property this was merged into
|
|
875
|
-
for ip in arch.get("implicitProperties", []):
|
|
876
|
-
if ip.get("figmaKey") == prop.get("figmaKey"):
|
|
877
|
-
swift_name = ip["name"]
|
|
878
|
-
swift_type = f"{ip['swiftType']} (merged - nil = hidden)"
|
|
879
|
-
break
|
|
880
|
-
variant_lines.append(f"| {prop['name']} ({prop['type']}) | {swift_name} | {swift_type} |")
|
|
881
|
-
else:
|
|
882
|
-
swift_type = prop.get("swiftType", "?")
|
|
883
|
-
figma_key = prop.get("figmaKey", prop.get("name", ""))
|
|
884
|
-
# Swift property name
|
|
885
|
-
swift_name = prop.get("name", "")
|
|
886
|
-
# camelCase the name
|
|
887
|
-
if swift_name and " " in swift_name:
|
|
888
|
-
parts = swift_name.split()
|
|
889
|
-
swift_name = parts[0].lower() + "".join(p.capitalize() for p in parts[1:])
|
|
890
|
-
elif swift_name:
|
|
891
|
-
swift_name = swift_name[0].lower() + swift_name[1:]
|
|
892
|
-
|
|
893
|
-
if prop.get("type") == "BOOLEAN_MERGED":
|
|
894
|
-
continue # Already shown via the SKIP entry
|
|
895
|
-
type_desc = f"{swift_type} enum" if prop.get("values") else swift_type
|
|
896
|
-
if prop.get("values"):
|
|
897
|
-
vals = "/".join(f".{v}" for v in (arch.get("enums", [{}])[0].get("cases", []) if False else []))
|
|
898
|
-
# Find the matching enum
|
|
899
|
-
for e in arch.get("enums", []):
|
|
900
|
-
if e.get("name") == swift_type:
|
|
901
|
-
vals = "/".join(f".{c}" for c in e["cases"])
|
|
902
|
-
type_desc = f"{swift_type} enum ({vals})"
|
|
903
|
-
break
|
|
904
|
-
if swift_type == "Bool" and prop.get("values"):
|
|
905
|
-
type_desc = "Bool (Yes→true, No→false)"
|
|
906
|
-
|
|
907
|
-
variant_lines.append(f"| {prop['name']} ({prop['type']}) | {swift_name} | {type_desc} |")
|
|
908
|
-
|
|
909
|
-
# Nested dependencies
|
|
910
|
-
nested_path = ap / "pass3" / "03_nested_components.md"
|
|
911
|
-
nested_lines = extract_md_section_lines(nested_path, "Connected")
|
|
912
|
-
if not nested_lines or all(not l.strip() or l.startswith("|--") for l in nested_lines):
|
|
913
|
-
nested_section = "Self-contained component. No nested dependencies."
|
|
914
|
-
else:
|
|
915
|
-
nested_section = "\n".join(nested_lines)
|
|
916
|
-
|
|
917
|
-
# Check for inline elements mention
|
|
918
|
-
nested_full = ""
|
|
919
|
-
if nested_path.exists():
|
|
920
|
-
with open(nested_path) as f:
|
|
921
|
-
nested_full = f.read()
|
|
922
|
-
inline_mention = ""
|
|
923
|
-
if "inline" in nested_full.lower():
|
|
924
|
-
for line in nested_full.split("\n"):
|
|
925
|
-
if "inline" in line.lower() and not line.startswith("#"):
|
|
926
|
-
inline_mention = line.strip().lstrip("- ")
|
|
927
|
-
break
|
|
928
|
-
|
|
929
|
-
# Assets
|
|
930
|
-
assets_path = ap / "pass3" / "06_assets.md"
|
|
931
|
-
asset_summary_lines = extract_md_section_lines(assets_path, "Summary")
|
|
932
|
-
asset_summary = "\n".join(l for l in asset_summary_lines if l.strip())
|
|
933
|
-
if not asset_summary:
|
|
934
|
-
asset_summary = "No assets."
|
|
935
|
-
|
|
936
|
-
# Token summary
|
|
937
|
-
token_path = ap / "pass3" / "02_token_mapping.md"
|
|
938
|
-
token_summary_lines = extract_md_section_lines(token_path, "Summary")
|
|
939
|
-
token_summary = "\n".join(l for l in token_summary_lines if l.strip())
|
|
940
|
-
|
|
941
|
-
# Missing tokens
|
|
942
|
-
missing_lines = extract_md_section_lines(token_path, "Missing Tokens")
|
|
943
|
-
missing_count = sum(1 for l in missing_lines if l.strip().startswith("|") and "⚠️" not in l and "Token" not in l and "---" not in l)
|
|
944
|
-
# Parse from summary
|
|
945
|
-
token_mapped = 0
|
|
946
|
-
token_missing = 0
|
|
947
|
-
for l in token_summary_lines:
|
|
948
|
-
if "mapped" in l.lower():
|
|
949
|
-
m = re.search(r'(\d+)\s+mapped', l)
|
|
950
|
-
if m:
|
|
951
|
-
token_mapped += int(m.group(1))
|
|
952
|
-
if "missing" in l.lower():
|
|
953
|
-
m = re.search(r'(\d+)\s+(?:actionable|missing)', l)
|
|
954
|
-
if m:
|
|
955
|
-
token_missing += int(m.group(1))
|
|
956
|
-
|
|
957
|
-
# Data models
|
|
958
|
-
api_design = arch.get("apiDesign", {})
|
|
959
|
-
sub_configs = api_design.get("subConfigurations", [])
|
|
960
|
-
list_types = api_design.get("listTypes", [])
|
|
961
|
-
if not sub_configs and not list_types:
|
|
962
|
-
data_models = "No list inference. No sub-configurations. All properties are flat within the configuration struct."
|
|
963
|
-
else:
|
|
964
|
-
parts = []
|
|
965
|
-
if sub_configs:
|
|
966
|
-
parts.append(f"Sub-configurations: {', '.join(sc.get('name', '?') for sc in sub_configs)}")
|
|
967
|
-
if list_types:
|
|
968
|
-
parts.append(f"List types: {', '.join(lt.get('name', '?') for lt in list_types)}")
|
|
969
|
-
data_models = " ".join(parts)
|
|
970
|
-
|
|
971
|
-
# Files to create
|
|
972
|
-
files_to_create = [f"- `{comp_name}.swift` (main view)"]
|
|
973
|
-
if complexity == "COMPLEX":
|
|
974
|
-
config_name = arch.get("configurationStruct", f"{comp_name}Configuration")
|
|
975
|
-
files_to_create.append(f"- `{config_name}.swift` (configuration struct + enums)")
|
|
976
|
-
files_to_create.append("- `FIGMA.md` (documentation)")
|
|
977
|
-
|
|
978
|
-
# Workflow
|
|
979
|
-
if complexity == "COMPLEX":
|
|
980
|
-
config_name = arch.get("configurationStruct", f"{comp_name}Configuration")
|
|
981
|
-
workflow = f"COMPLEX → Configuration struct pattern. {config_name} wraps all {prop_count} parameters."
|
|
982
|
-
else:
|
|
983
|
-
workflow = f"SIMPLE → Direct init parameters. {prop_count} parameters passed to init."
|
|
984
|
-
|
|
985
|
-
# --- COMPILE SPEC ---
|
|
986
|
-
spec = f"""# Implementation Spec
|
|
987
|
-
|
|
988
|
-
Component: {comp_name}
|
|
989
|
-
Generated: {DATE_SHORT}
|
|
990
|
-
|
|
991
|
-
## Component
|
|
992
|
-
|
|
993
|
-
| Field | Value |
|
|
994
|
-
|-------|-------|
|
|
995
|
-
| Name | {comp_name} |
|
|
996
|
-
| Path | {comp_location} |
|
|
997
|
-
| Node ID | {node_id} |
|
|
998
|
-
| Dimensions | {dimensions} |
|
|
999
|
-
| Complexity | {complexity} |
|
|
1000
|
-
| Properties | {prop_count} (after pattern reduction) |
|
|
1001
|
-
|
|
1002
|
-
## Files to Create
|
|
1003
|
-
|
|
1004
|
-
{chr(10).join(files_to_create)}
|
|
1005
|
-
|
|
1006
|
-
## API Shape
|
|
1007
|
-
|
|
1008
|
-
{api_code}
|
|
1009
|
-
|
|
1010
|
-
## Variant Mapping
|
|
1011
|
-
|
|
1012
|
-
{chr(10).join(variant_lines)}
|
|
1013
|
-
|
|
1014
|
-
## Data Models
|
|
1015
|
-
|
|
1016
|
-
{data_models}
|
|
1017
|
-
|
|
1018
|
-
## Nested Dependencies
|
|
1019
|
-
|
|
1020
|
-
{nested_section}
|
|
1021
|
-
{f'{chr(10)}{inline_mention}' if inline_mention else ''}
|
|
1022
|
-
|
|
1023
|
-
## New Assets
|
|
1024
|
-
|
|
1025
|
-
{asset_summary}
|
|
1026
|
-
|
|
1027
|
-
## Token Mapping
|
|
1028
|
-
|
|
1029
|
-
- Mapped: {token_mapped}
|
|
1030
|
-
- Missing: {token_missing}
|
|
1031
|
-
|
|
1032
|
-
## Workflow
|
|
1033
|
-
|
|
1034
|
-
{workflow}
|
|
1035
|
-
"""
|
|
1036
|
-
|
|
1037
|
-
out_path = ap / "pass3" / "07_implementation_spec.md"
|
|
1038
|
-
with open(out_path, "w") as f:
|
|
1039
|
-
f.write(spec)
|
|
1040
|
-
|
|
1041
|
-
print(f" ✅ {out_path}")
|
|
1042
|
-
return True
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
# --- Main ---
|
|
1046
|
-
|
|
1047
|
-
def main():
|
|
1048
|
-
parser = argparse.ArgumentParser(description="Phase 3 scriptable sub-phases")
|
|
1049
|
-
parser.add_argument("subphase", choices=["3b", "3e", "3f", "3g", "spec", "wave1", "all"],
|
|
1050
|
-
help="Which sub-phase to run")
|
|
1051
|
-
parser.add_argument("--artifact-path", required=True, help="Path to artifact directory")
|
|
1052
|
-
|
|
1053
|
-
args = parser.parse_args()
|
|
1054
|
-
|
|
1055
|
-
ensure_cli()
|
|
1056
|
-
|
|
1057
|
-
results = {}
|
|
1058
|
-
|
|
1059
|
-
if args.subphase in ("3b", "wave1", "all"):
|
|
1060
|
-
results["3b"] = run_3b(args.artifact_path)
|
|
1061
|
-
|
|
1062
|
-
if args.subphase in ("3e", "wave1", "all"):
|
|
1063
|
-
results["3e"] = run_3e(args.artifact_path)
|
|
1064
|
-
|
|
1065
|
-
if args.subphase in ("3f", "wave1", "all"):
|
|
1066
|
-
results["3f"] = run_3f(args.artifact_path)
|
|
1067
|
-
|
|
1068
|
-
if args.subphase in ("3g", "all"):
|
|
1069
|
-
results["3g"] = run_3g(args.artifact_path)
|
|
1070
|
-
|
|
1071
|
-
if args.subphase in ("spec", "all"):
|
|
1072
|
-
results["spec"] = run_spec(args.artifact_path)
|
|
1073
|
-
|
|
1074
|
-
# Summary
|
|
1075
|
-
print(f"\n{'='*50}")
|
|
1076
|
-
for phase, ok in results.items():
|
|
1077
|
-
status = "✅ PASS" if ok else "❌ FAIL"
|
|
1078
|
-
print(f" {phase}: {status}")
|
|
1079
|
-
|
|
1080
|
-
if all(results.values()):
|
|
1081
|
-
print(f"\nAll sub-phases complete.")
|
|
1082
|
-
else:
|
|
1083
|
-
failed = [k for k, v in results.items() if not v]
|
|
1084
|
-
print(f"\nFailed: {', '.join(failed)}", file=sys.stderr)
|
|
1085
|
-
sys.exit(1)
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
if __name__ == "__main__":
|
|
1089
|
-
main()
|