@design-ai/cli 4.55.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/.claude-plugin/plugin.json +164 -0
- package/AGENTS.ko.md +126 -0
- package/AGENTS.md +182 -0
- package/CHANGELOG.md +6883 -0
- package/CLAUDE.md +39 -0
- package/LICENSE +21 -0
- package/README.ko.md +245 -0
- package/README.md +277 -0
- package/agents/README.md +14 -0
- package/agents/a11y-reviewer.md +121 -0
- package/agents/component-architect.md +57 -0
- package/agents/design-critic.md +46 -0
- package/agents/token-extractor.md +63 -0
- package/cli/bin/design-ai-mcp.mjs +6 -0
- package/cli/bin/design-ai.mjs +40 -0
- package/cli/commands/audit.mjs +111 -0
- package/cli/commands/check.mjs +226 -0
- package/cli/commands/doctor.mjs +141 -0
- package/cli/commands/examples.mjs +58 -0
- package/cli/commands/help.mjs +271 -0
- package/cli/commands/install.mjs +161 -0
- package/cli/commands/learn.mjs +1871 -0
- package/cli/commands/list.mjs +143 -0
- package/cli/commands/mcp.mjs +27 -0
- package/cli/commands/pack.mjs +211 -0
- package/cli/commands/prompt.mjs +199 -0
- package/cli/commands/route.mjs +247 -0
- package/cli/commands/search.mjs +60 -0
- package/cli/commands/show.mjs +52 -0
- package/cli/commands/site.mjs +558 -0
- package/cli/commands/status.mjs +161 -0
- package/cli/commands/uninstall.mjs +119 -0
- package/cli/commands/update.mjs +184 -0
- package/cli/commands/version.mjs +94 -0
- package/cli/commands/workspace.mjs +176 -0
- package/cli/lib/brief.mjs +52 -0
- package/cli/lib/check.mjs +642 -0
- package/cli/lib/dispatch.mjs +109 -0
- package/cli/lib/doctor.mjs +505 -0
- package/cli/lib/examples.mjs +258 -0
- package/cli/lib/exec.mjs +63 -0
- package/cli/lib/help-flags.mjs +5 -0
- package/cli/lib/learn.mjs +3694 -0
- package/cli/lib/log.mjs +47 -0
- package/cli/lib/mcp-server.mjs +426 -0
- package/cli/lib/output.mjs +101 -0
- package/cli/lib/pack.mjs +599 -0
- package/cli/lib/paths.mjs +63 -0
- package/cli/lib/prompt.mjs +603 -0
- package/cli/lib/route.mjs +740 -0
- package/cli/lib/search.mjs +158 -0
- package/cli/lib/show.mjs +161 -0
- package/cli/lib/signals.mjs +2337 -0
- package/cli/lib/site.mjs +6617 -0
- package/cli/lib/skill-proposals.mjs +2046 -0
- package/cli/lib/suggest.mjs +66 -0
- package/cli/lib/workspace.mjs +1056 -0
- package/commands/README.md +25 -0
- package/commands/component-spec.md +34 -0
- package/commands/conversational.md +44 -0
- package/commands/design-from-brief.md +141 -0
- package/commands/design-review.md +62 -0
- package/commands/document-from-brief.md +47 -0
- package/commands/extract-tokens.md +38 -0
- package/commands/game-ui.md +40 -0
- package/commands/illustration.md +37 -0
- package/commands/iterate.md +167 -0
- package/commands/motion-design.md +54 -0
- package/commands/palette-from-brand.md +50 -0
- package/commands/print.md +49 -0
- package/commands/slide-deck.md +46 -0
- package/commands/spatial.md +47 -0
- package/commands/stability-review.md +74 -0
- package/commands/video.md +42 -0
- package/commands/website-improvement.md +77 -0
- package/docs/AGENT-DEVELOPMENT.md +658 -0
- package/docs/AI-LEARNING.md +429 -0
- package/docs/AIDER-INTEGRATION.md +224 -0
- package/docs/ARCHITECTURE.ko.md +183 -0
- package/docs/ARCHITECTURE.md +105 -0
- package/docs/CODEX-INTEGRATION.md +311 -0
- package/docs/COMPANY-WEBSITE-DOGFOOD.ko.md +144 -0
- package/docs/COMPANY-WEBSITE-DOGFOOD.md +150 -0
- package/docs/COMPANY-WEBSITE-INTAKE-TEMPLATE.ko.md +153 -0
- package/docs/COMPANY-WEBSITE-INTAKE-TEMPLATE.md +154 -0
- package/docs/CONTRIBUTING.ko.md +203 -0
- package/docs/CONTRIBUTING.md +242 -0
- package/docs/CURSOR-INTEGRATION.md +220 -0
- package/docs/DISTRIBUTION.ko.md +248 -0
- package/docs/DISTRIBUTION.md +253 -0
- package/docs/DOGFOOD-FINDINGS.md +180 -0
- package/docs/DOGFOOD-V4-FINDINGS.md +139 -0
- package/docs/DOGFOOD-V4-MKDOCS-FINDINGS.md +233 -0
- package/docs/DOGFOOD-V4-NPM-FINDINGS.md +157 -0
- package/docs/DOGFOOD-V4-VSCODE-FINDINGS.md +135 -0
- package/docs/FIGMA-INTEGRATION.md +222 -0
- package/docs/MCP-INTEGRATION.md +243 -0
- package/docs/MIGRATION-v4.md +121 -0
- package/docs/PLUGIN-PACKAGING.md +240 -0
- package/docs/PRODUCT-READINESS.md +66 -0
- package/docs/QUICKSTART.ko.md +178 -0
- package/docs/QUICKSTART.md +179 -0
- package/docs/RELEASE-CHECKLIST.md +292 -0
- package/docs/ROADMAP.md +19495 -0
- package/docs/SESSION-LOG.md +940 -0
- package/docs/TOKEN-SYNC.md +237 -0
- package/docs/USING.ko.md +206 -0
- package/docs/USING.md +218 -0
- package/docs/WEBSITE-IMPROVEMENT.ko.md +75 -0
- package/docs/WEBSITE-IMPROVEMENT.md +89 -0
- package/docs/announcements/README.md +56 -0
- package/docs/announcements/dev-to-korea.md +206 -0
- package/docs/announcements/hashnode-post.ko.md +232 -0
- package/docs/announcements/okky-post.ko.md +158 -0
- package/docs/announcements/press-kit.md +101 -0
- package/docs/announcements/reddit-r-korea.md +196 -0
- package/docs/announcements/show-hn.md +114 -0
- package/docs/announcements/twitter-thread.md +255 -0
- package/docs/external-status.md +34 -0
- package/docs/integrations/aider-walkthrough.ko.md +275 -0
- package/docs/integrations/aider-walkthrough.md +274 -0
- package/docs/integrations/codex-walkthrough.ko.md +297 -0
- package/docs/integrations/codex-walkthrough.md +297 -0
- package/docs/integrations/cursor-walkthrough.ko.md +281 -0
- package/docs/integrations/cursor-walkthrough.md +284 -0
- package/docs/integrations/design-ai-mcp-server.md +169 -0
- package/docs/integrations/figma-mcp.md +149 -0
- package/docs/integrations/github-mcp.md +178 -0
- package/docs/integrations/linear-mcp.md +215 -0
- package/docs/integrations/notion-mcp.md +174 -0
- package/docs/integrations/sdk-walkthrough.ko.md +375 -0
- package/docs/integrations/sdk-walkthrough.md +375 -0
- package/docs/integrations/slack-mcp.md +208 -0
- package/docs/integrations/vscode-walkthrough.ko.md +202 -0
- package/docs/integrations/vscode-walkthrough.md +205 -0
- package/docs/requirements.txt +16 -0
- package/docs/site-overrides/extra.css +83 -0
- package/docs/site-overrides/favicon.svg +4 -0
- package/docs/site-overrides/logo.svg +10 -0
- package/docs/site-overrides/main.html +18 -0
- package/docs/website-console/app.js +2540 -0
- package/docs/website-console/index.html +24 -0
- package/docs/website-console/styles.css +975 -0
- package/examples/README.md +252 -0
- package/examples/cases/dogfood-v4-korean-hr-onboarding.md +356 -0
- package/examples/component-accordion-actions.md +153 -0
- package/examples/component-accordion-details.md +147 -0
- package/examples/component-accordion-summary.md +180 -0
- package/examples/component-accordion.md +229 -0
- package/examples/component-account-card.md +284 -0
- package/examples/component-address-input.md +260 -0
- package/examples/component-affix.md +190 -0
- package/examples/component-alert-dialog.md +210 -0
- package/examples/component-alert-title.md +77 -0
- package/examples/component-alert.md +192 -0
- package/examples/component-amount-input.md +278 -0
- package/examples/component-anchor.md +198 -0
- package/examples/component-app-bar.md +249 -0
- package/examples/component-aspect-ratio.md +156 -0
- package/examples/component-auto-complete.md +199 -0
- package/examples/component-avatar-group.md +170 -0
- package/examples/component-avatar.md +191 -0
- package/examples/component-back-top.md +123 -0
- package/examples/component-backdrop.md +148 -0
- package/examples/component-badge.md +181 -0
- package/examples/component-banner.md +237 -0
- package/examples/component-biometric-gate.md +281 -0
- package/examples/component-blockquote.md +153 -0
- package/examples/component-border-beam.md +207 -0
- package/examples/component-bottom-navigation.md +226 -0
- package/examples/component-box.md +112 -0
- package/examples/component-breadcrumb.md +222 -0
- package/examples/component-button-base.md +190 -0
- package/examples/component-button-group.md +117 -0
- package/examples/component-button.md +213 -0
- package/examples/component-calendar.md +280 -0
- package/examples/component-callout.md +131 -0
- package/examples/component-card-actions.md +101 -0
- package/examples/component-card-content.md +90 -0
- package/examples/component-card-header.md +122 -0
- package/examples/component-card-media.md +79 -0
- package/examples/component-card.md +233 -0
- package/examples/component-carousel.md +266 -0
- package/examples/component-cascader.md +135 -0
- package/examples/component-category-picker.md +271 -0
- package/examples/component-chart.md +237 -0
- package/examples/component-chat-interface.md +399 -0
- package/examples/component-checkbox.md +149 -0
- package/examples/component-click-away-listener.md +87 -0
- package/examples/component-code.md +213 -0
- package/examples/component-collapsible.md +219 -0
- package/examples/component-color-picker.md +110 -0
- package/examples/component-combobox.md +267 -0
- package/examples/component-command.md +299 -0
- package/examples/component-config-provider.md +190 -0
- package/examples/component-context-menu.md +179 -0
- package/examples/component-css-baseline.md +148 -0
- package/examples/component-date-picker.md +249 -0
- package/examples/component-descriptions.md +242 -0
- package/examples/component-dialog-actions.md +111 -0
- package/examples/component-dialog-content-text.md +76 -0
- package/examples/component-dialog-content.md +105 -0
- package/examples/component-dialog-title.md +104 -0
- package/examples/component-dialog.md +176 -0
- package/examples/component-divider.md +182 -0
- package/examples/component-doc-page.md +217 -0
- package/examples/component-drawer.md +257 -0
- package/examples/component-dropdown.md +220 -0
- package/examples/component-email-layout.md +253 -0
- package/examples/component-empty-state.md +269 -0
- package/examples/component-empty.md +172 -0
- package/examples/component-fade.md +87 -0
- package/examples/component-feature-grid.md +201 -0
- package/examples/component-field.md +266 -0
- package/examples/component-filled-input.md +114 -0
- package/examples/component-flex.md +117 -0
- package/examples/component-float-button.md +214 -0
- package/examples/component-form-control-label.md +134 -0
- package/examples/component-form-control.md +113 -0
- package/examples/component-form-controls.md +251 -0
- package/examples/component-form-group.md +86 -0
- package/examples/component-form-helper-text.md +95 -0
- package/examples/component-form-label.md +98 -0
- package/examples/component-form.md +291 -0
- package/examples/component-game-hud.md +362 -0
- package/examples/component-game-menu.md +327 -0
- package/examples/component-grid.md +140 -0
- package/examples/component-grow.md +79 -0
- package/examples/component-hero-block.md +252 -0
- package/examples/component-hover-card.md +211 -0
- package/examples/component-icon-button.md +142 -0
- package/examples/component-icon.md +102 -0
- package/examples/component-illustration.md +280 -0
- package/examples/component-image-list.md +107 -0
- package/examples/component-image.md +282 -0
- package/examples/component-input-adornment.md +101 -0
- package/examples/component-input-base.md +134 -0
- package/examples/component-input-number.md +141 -0
- package/examples/component-input-otp.md +271 -0
- package/examples/component-input.md +213 -0
- package/examples/component-item.md +243 -0
- package/examples/component-kbd.md +197 -0
- package/examples/component-krw-amount.md +219 -0
- package/examples/component-label.md +118 -0
- package/examples/component-layout.md +217 -0
- package/examples/component-link.md +223 -0
- package/examples/component-list-item-avatar.md +72 -0
- package/examples/component-list-item-button.md +139 -0
- package/examples/component-list-item-icon.md +82 -0
- package/examples/component-list-item-text.md +98 -0
- package/examples/component-list-item.md +116 -0
- package/examples/component-list-subheader.md +70 -0
- package/examples/component-list.md +198 -0
- package/examples/component-loading-button.md +125 -0
- package/examples/component-loading-sequence.md +254 -0
- package/examples/component-lottie-player.md +279 -0
- package/examples/component-masonry.md +191 -0
- package/examples/component-mentions.md +189 -0
- package/examples/component-menu-item.md +102 -0
- package/examples/component-menu-list.md +98 -0
- package/examples/component-menu.md +139 -0
- package/examples/component-menubar.md +259 -0
- package/examples/component-message.md +162 -0
- package/examples/component-mobile-stepper.md +155 -0
- package/examples/component-modal.md +220 -0
- package/examples/component-navigation-menu.md +295 -0
- package/examples/component-notification.md +166 -0
- package/examples/component-otp-countdown.md +245 -0
- package/examples/component-outlined-input.md +145 -0
- package/examples/component-page-transition.md +281 -0
- package/examples/component-pagination.md +226 -0
- package/examples/component-paper.md +199 -0
- package/examples/component-pass-auth.md +265 -0
- package/examples/component-payment-brand-button.md +253 -0
- package/examples/component-payment-method-selector.md +272 -0
- package/examples/component-payment-receipt.md +277 -0
- package/examples/component-popconfirm.md +144 -0
- package/examples/component-popover.md +239 -0
- package/examples/component-popper.md +129 -0
- package/examples/component-pricing-cards.md +247 -0
- package/examples/component-progress.md +195 -0
- package/examples/component-qr-code.md +201 -0
- package/examples/component-radio.md +167 -0
- package/examples/component-rate.md +202 -0
- package/examples/component-resizable.md +163 -0
- package/examples/component-result.md +270 -0
- package/examples/component-scroll-area.md +199 -0
- package/examples/component-scroll-reveal.md +320 -0
- package/examples/component-segmented.md +178 -0
- package/examples/component-select.md +258 -0
- package/examples/component-separator.md +177 -0
- package/examples/component-sheet.md +277 -0
- package/examples/component-sidebar.md +257 -0
- package/examples/component-skeleton.md +178 -0
- package/examples/component-slide.md +89 -0
- package/examples/component-slider.md +208 -0
- package/examples/component-snackbar-content.md +92 -0
- package/examples/component-snackbar.md +121 -0
- package/examples/component-sonner.md +165 -0
- package/examples/component-space.md +125 -0
- package/examples/component-spatial-locomotion.md +373 -0
- package/examples/component-spatial-panel.md +330 -0
- package/examples/component-speed-dial-action.md +78 -0
- package/examples/component-speed-dial.md +177 -0
- package/examples/component-spin.md +173 -0
- package/examples/component-spinner.md +204 -0
- package/examples/component-splitter.md +210 -0
- package/examples/component-stack.md +130 -0
- package/examples/component-statistic.md +282 -0
- package/examples/component-step-button.md +157 -0
- package/examples/component-step-connector.md +157 -0
- package/examples/component-step-content.md +121 -0
- package/examples/component-step-icon.md +115 -0
- package/examples/component-step-label.md +101 -0
- package/examples/component-step.md +84 -0
- package/examples/component-steps.md +264 -0
- package/examples/component-stock-chart.md +251 -0
- package/examples/component-swipeable-drawer.md +149 -0
- package/examples/component-switch.md +99 -0
- package/examples/component-tab-scroll-button.md +180 -0
- package/examples/component-tab.md +120 -0
- package/examples/component-table-body.md +82 -0
- package/examples/component-table-cell.md +118 -0
- package/examples/component-table-container.md +75 -0
- package/examples/component-table-footer.md +72 -0
- package/examples/component-table-head.md +78 -0
- package/examples/component-table-pagination.md +137 -0
- package/examples/component-table-row.md +113 -0
- package/examples/component-table-sort-label.md +109 -0
- package/examples/component-table.md +202 -0
- package/examples/component-tabs.md +184 -0
- package/examples/component-tag-badge.md +309 -0
- package/examples/component-tag.md +102 -0
- package/examples/component-testimonial-carousel.md +218 -0
- package/examples/component-textarea-autosize.md +114 -0
- package/examples/component-textarea.md +173 -0
- package/examples/component-time-picker.md +205 -0
- package/examples/component-timeline.md +170 -0
- package/examples/component-toast.md +224 -0
- package/examples/component-toggle-button.md +129 -0
- package/examples/component-toggle.md +220 -0
- package/examples/component-toolbar.md +104 -0
- package/examples/component-tooltip.md +178 -0
- package/examples/component-tour.md +176 -0
- package/examples/component-transaction-list-item.md +243 -0
- package/examples/component-transfer.md +156 -0
- package/examples/component-tree-select.md +202 -0
- package/examples/component-tree.md +297 -0
- package/examples/component-typography.md +215 -0
- package/examples/component-upload.md +295 -0
- package/examples/component-video-hero.md +409 -0
- package/examples/component-video-player.md +340 -0
- package/examples/component-voice-input.md +408 -0
- package/examples/component-watermark.md +217 -0
- package/examples/component-zoom.md +92 -0
- package/examples/doc-explanation-example.md +196 -0
- package/examples/doc-how-to-example.md +205 -0
- package/examples/doc-tutorial-example.md +162 -0
- package/examples/dogfood-korean-fintech-system.md +433 -0
- package/examples/email-transactional-example.md +259 -0
- package/examples/palette-saas-violet.md +235 -0
- package/examples/print-business-card-spec.md +205 -0
- package/examples/print-packaging-spec.md +285 -0
- package/examples/report-example.md +228 -0
- package/examples/slide-deck-example.md +237 -0
- package/examples/website-improvement-report.md +128 -0
- package/examples/website-improvement-workspace.json +126 -0
- package/install.sh +189 -0
- package/knowledge/COVERAGE.md +740 -0
- package/knowledge/PRINCIPLES.md +108 -0
- package/knowledge/a11y/contrast.md +70 -0
- package/knowledge/a11y/keyboard-and-focus.md +108 -0
- package/knowledge/colors/color-theory.md +90 -0
- package/knowledge/colors/mui-palette-structure.md +105 -0
- package/knowledge/colors/palettes-by-product-type.md +876 -0
- package/knowledge/components/INDEX.md +231 -0
- package/knowledge/components/index.json +1470 -0
- package/knowledge/components/shadcn-registry.md +159 -0
- package/knowledge/conversational/ai-chat-interfaces.md +323 -0
- package/knowledge/conversational/chatbot-design.md +312 -0
- package/knowledge/conversational/conversational-ui-fundamentals.md +299 -0
- package/knowledge/conversational/korean-voice-conventions.md +281 -0
- package/knowledge/conversational/voice-ui-patterns.md +297 -0
- package/knowledge/design-tokens/ant-design.md +124 -0
- package/knowledge/design-tokens/material-3.md +235 -0
- package/knowledge/design-tokens/polaris-and-carbon.md +231 -0
- package/knowledge/design-tokens/tailwind-v4.md +284 -0
- package/knowledge/game-ui/game-accessibility.md +317 -0
- package/knowledge/game-ui/game-ui-fundamentals.md +281 -0
- package/knowledge/game-ui/hud-design.md +324 -0
- package/knowledge/game-ui/korean-gaming-conventions.md +312 -0
- package/knowledge/game-ui/menu-systems.md +388 -0
- package/knowledge/i18n/korean-app-store-visual.md +223 -0
- package/knowledge/i18n/korean-document-style.md +301 -0
- package/knowledge/i18n/korean-payments.md +211 -0
- package/knowledge/i18n/korean-product-conventions.md +96 -0
- package/knowledge/i18n/korean-publishing.md +139 -0
- package/knowledge/i18n/korean-typography.md +123 -0
- package/knowledge/icons/curated-sets.md +225 -0
- package/knowledge/illustration/hero-illustrations.md +234 -0
- package/knowledge/illustration/illustration-systems.md +221 -0
- package/knowledge/illustration/mascot-design.md +208 -0
- package/knowledge/illustration/spot-illustrations.md +246 -0
- package/knowledge/illustration/svg-optimization.md +308 -0
- package/knowledge/layout/spacing-and-grid.md +119 -0
- package/knowledge/motion/app-loading-sequences.md +277 -0
- package/knowledge/motion/choreography-depth.md +297 -0
- package/knowledge/motion/marketing-motion.md +223 -0
- package/knowledge/motion/micro-interactions.md +266 -0
- package/knowledge/motion/motion-tools.md +281 -0
- package/knowledge/motion/principles.md +126 -0
- package/knowledge/patterns/auth-flow-design.md +316 -0
- package/knowledge/patterns/b2b-onboarding-flows.md +182 -0
- package/knowledge/patterns/brand-identity.md +238 -0
- package/knowledge/patterns/brand-references.md +97 -0
- package/knowledge/patterns/chart-color-encoding.md +269 -0
- package/knowledge/patterns/chart-types.md +344 -0
- package/knowledge/patterns/dashboard-composition.md +299 -0
- package/knowledge/patterns/design-system-qa.md +356 -0
- package/knowledge/patterns/document-typography.md +248 -0
- package/knowledge/patterns/email-design.md +338 -0
- package/knowledge/patterns/empty-states.md +263 -0
- package/knowledge/patterns/error-states.md +318 -0
- package/knowledge/patterns/form-design.md +237 -0
- package/knowledge/patterns/information-architecture.md +341 -0
- package/knowledge/patterns/landing-hero-design.md +283 -0
- package/knowledge/patterns/landing-page-patterns.md +681 -0
- package/knowledge/patterns/list-and-feed.md +371 -0
- package/knowledge/patterns/mobile-navigation.md +278 -0
- package/knowledge/patterns/money-and-amount.md +301 -0
- package/knowledge/patterns/onboarding.md +300 -0
- package/knowledge/patterns/pricing-page-design.md +261 -0
- package/knowledge/patterns/realtime-data.md +280 -0
- package/knowledge/patterns/report-design.md +330 -0
- package/knowledge/patterns/search-ux.md +373 -0
- package/knowledge/patterns/settings-page.md +325 -0
- package/knowledge/patterns/slide-deck-design.md +316 -0
- package/knowledge/patterns/styles-catalog.md +2599 -0
- package/knowledge/patterns/technical-writing.md +306 -0
- package/knowledge/patterns/ui-reasoning.md +1853 -0
- package/knowledge/patterns/ux-guidelines.md +1454 -0
- package/knowledge/platforms/react-native.md +395 -0
- package/knowledge/print/brochures-and-flyers.md +276 -0
- package/knowledge/print/korean-print-conventions.md +284 -0
- package/knowledge/print/packaging.md +321 -0
- package/knowledge/print/print-fundamentals.md +238 -0
- package/knowledge/print/signage-and-posters.md +235 -0
- package/knowledge/print/stationery.md +262 -0
- package/knowledge/spatial/ar-patterns.md +253 -0
- package/knowledge/spatial/comfort-and-accessibility.md +245 -0
- package/knowledge/spatial/spatial-design-fundamentals.md +346 -0
- package/knowledge/spatial/spatial-ui-elements.md +343 -0
- package/knowledge/spatial/vr-patterns.md +302 -0
- package/knowledge/typography/font-pairings.md +332 -0
- package/knowledge/typography/mui-type-scale.md +57 -0
- package/knowledge/typography/type-scale-fundamentals.md +105 -0
- package/knowledge/video/in-product-video.md +277 -0
- package/knowledge/video/korean-video-conventions.md +247 -0
- package/knowledge/video/marketing-video.md +258 -0
- package/knowledge/video/social-and-short-form.md +257 -0
- package/knowledge/video/video-fundamentals.md +296 -0
- package/package.json +106 -0
- package/skills/README.md +39 -0
- package/skills/color-palette/PLAYBOOK.md +175 -0
- package/skills/color-palette/SKILL.md +6 -0
- package/skills/color-palette/TEMPLATE.md +103 -0
- package/skills/component-spec-writer/PLAYBOOK.md +169 -0
- package/skills/component-spec-writer/SKILL.md +6 -0
- package/skills/component-spec-writer/TEMPLATE.md +115 -0
- package/skills/conversational-ui-designer/PLAYBOOK.md +231 -0
- package/skills/conversational-ui-designer/SKILL.md +6 -0
- package/skills/design-broadcast/PLAYBOOK.md +146 -0
- package/skills/design-broadcast/SKILL.md +6 -0
- package/skills/design-critique/PLAYBOOK.md +137 -0
- package/skills/design-critique/SKILL.md +6 -0
- package/skills/design-pr-review/PLAYBOOK.md +171 -0
- package/skills/design-pr-review/SKILL.md +6 -0
- package/skills/design-system-builder/PLAYBOOK.md +218 -0
- package/skills/design-system-builder/SKILL.md +6 -0
- package/skills/design-system-qa/PLAYBOOK.md +163 -0
- package/skills/design-system-qa/SKILL.md +6 -0
- package/skills/document-author/PLAYBOOK.md +167 -0
- package/skills/document-author/SKILL.md +6 -0
- package/skills/figma-token-sync/PLAYBOOK.md +149 -0
- package/skills/figma-token-sync/SKILL.md +6 -0
- package/skills/game-ui-designer/PLAYBOOK.md +209 -0
- package/skills/game-ui-designer/SKILL.md +6 -0
- package/skills/handoff-spec/PLAYBOOK.md +170 -0
- package/skills/handoff-spec/SKILL.md +6 -0
- package/skills/illustration-designer/PLAYBOOK.md +166 -0
- package/skills/illustration-designer/SKILL.md +6 -0
- package/skills/motion-designer/PLAYBOOK.md +223 -0
- package/skills/motion-designer/SKILL.md +6 -0
- package/skills/print-designer/PLAYBOOK.md +209 -0
- package/skills/print-designer/SKILL.md +6 -0
- package/skills/slide-deck-author/PLAYBOOK.md +198 -0
- package/skills/slide-deck-author/SKILL.md +6 -0
- package/skills/spatial-designer/PLAYBOOK.md +238 -0
- package/skills/spatial-designer/SKILL.md +6 -0
- package/skills/ux-audit/PLAYBOOK.md +142 -0
- package/skills/ux-audit/SKILL.md +6 -0
- package/skills/video-designer/PLAYBOOK.md +240 -0
- package/skills/video-designer/SKILL.md +6 -0
- package/skills/website-improvement/PLAYBOOK.md +118 -0
- package/skills/website-improvement/SKILL.md +6 -0
- package/skills/website-improvement/TEMPLATE.md +103 -0
- package/tools/audit/changelog-generate.py +176 -0
- package/tools/audit/check-coverage.py +483 -0
- package/tools/audit/doctor_assertions.py +256 -0
- package/tools/audit/example-qa.py +108 -0
- package/tools/audit/frontmatter-check.py +199 -0
- package/tools/audit/integration-check.py +95 -0
- package/tools/audit/korean-copy-check.py +161 -0
- package/tools/audit/link-check.py +146 -0
- package/tools/audit/local-ci.py +558 -0
- package/tools/audit/package-contents.py +723 -0
- package/tools/audit/package-smoke.py +22443 -0
- package/tools/audit/raw-hex-check.py +252 -0
- package/tools/audit/registry-smoke.py +8188 -0
- package/tools/audit/release-metadata.py +9832 -0
- package/tools/audit/run-all.py +351 -0
- package/tools/audit/smoke_assertions.py +12742 -0
- package/tools/audit/stability-review.py +332 -0
- package/tools/audit/stale-check.py +156 -0
- package/tools/migrations/add-version-frontmatter.py +132 -0
- package/tools/migrations/bump-last-updated.py +131 -0
- package/tools/migrations/promote-stability.py +172 -0
- package/tools/preview/README.md +58 -0
- package/tools/preview/render-tokens.py +454 -0
|
@@ -0,0 +1,723 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Verify the npm package dry-run contents before publishing.
|
|
4
|
+
|
|
5
|
+
This turns the human "scan npm pack output" release step into a deterministic
|
|
6
|
+
gate. It checks that required runtime files, every plugin manifest path, and
|
|
7
|
+
skill sidecar docs are included, while test/build/cache-only files stay out of
|
|
8
|
+
the tarball.
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
python3 tools/audit/package-contents.py
|
|
12
|
+
python3 tools/audit/package-contents.py --json
|
|
13
|
+
python3 tools/audit/package-contents.py --self-test
|
|
14
|
+
npm pack --dry-run --json | python3 tools/audit/package-contents.py --pack-json -
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
import json
|
|
20
|
+
import re
|
|
21
|
+
import subprocess
|
|
22
|
+
import sys
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
ROOT = Path(__file__).resolve().parents[2]
|
|
26
|
+
PACKAGE_JSON = ROOT / "package.json"
|
|
27
|
+
PLUGIN_JSON = ROOT / ".claude-plugin" / "plugin.json"
|
|
28
|
+
|
|
29
|
+
MAX_PACKAGE_SIZE_MB = 15
|
|
30
|
+
MAX_PACKAGE_SIZE_BYTES = MAX_PACKAGE_SIZE_MB * 1024 * 1024
|
|
31
|
+
CATALOG_COUNT_LABELS = {
|
|
32
|
+
"skills": "skill",
|
|
33
|
+
"commands": "command",
|
|
34
|
+
"agents": "agent",
|
|
35
|
+
}
|
|
36
|
+
CATALOG_COUNT_SECTIONS = {
|
|
37
|
+
"skill": "skills",
|
|
38
|
+
"skills": "skills",
|
|
39
|
+
"command": "commands",
|
|
40
|
+
"commands": "commands",
|
|
41
|
+
"agent": "agents",
|
|
42
|
+
"agents": "agents",
|
|
43
|
+
}
|
|
44
|
+
CATALOG_COUNT_RE = re.compile(
|
|
45
|
+
r"\b(?P<count>\d+)\s+(?:(?:review|slash)\s+)?"
|
|
46
|
+
r"(?P<section>skills?|commands?|agents?)\b"
|
|
47
|
+
)
|
|
48
|
+
MARKDOWN_LINK_RE = re.compile(r"\[([^\]]*)\]\(([^)\s]+?)(?:#[^)]+)?\)")
|
|
49
|
+
INLINE_CODE_RE = re.compile(r"`[^`]*`")
|
|
50
|
+
EXTERNAL_LINK_PREFIXES = ("http://", "https://", "mailto:", "ftp://", "tel:")
|
|
51
|
+
|
|
52
|
+
REQUIRED_PATHS = {
|
|
53
|
+
"package.json",
|
|
54
|
+
"README.md",
|
|
55
|
+
"README.ko.md",
|
|
56
|
+
"LICENSE",
|
|
57
|
+
"AGENTS.md",
|
|
58
|
+
"AGENTS.ko.md",
|
|
59
|
+
"CLAUDE.md",
|
|
60
|
+
"CHANGELOG.md",
|
|
61
|
+
"install.sh",
|
|
62
|
+
".claude-plugin/plugin.json",
|
|
63
|
+
"cli/bin/design-ai.mjs",
|
|
64
|
+
"cli/bin/design-ai-mcp.mjs",
|
|
65
|
+
"cli/lib/dispatch.mjs",
|
|
66
|
+
"cli/lib/mcp-server.mjs",
|
|
67
|
+
"knowledge/PRINCIPLES.md",
|
|
68
|
+
"examples/README.md",
|
|
69
|
+
"skills/component-spec-writer/PLAYBOOK.md",
|
|
70
|
+
"commands/design-from-brief.md",
|
|
71
|
+
"docs/DISTRIBUTION.md",
|
|
72
|
+
"docs/DISTRIBUTION.ko.md",
|
|
73
|
+
"docs/QUICKSTART.md",
|
|
74
|
+
"docs/QUICKSTART.ko.md",
|
|
75
|
+
"docs/USING.md",
|
|
76
|
+
"docs/USING.ko.md",
|
|
77
|
+
"tools/audit/run-all.py",
|
|
78
|
+
"tools/audit/frontmatter-check.py",
|
|
79
|
+
"tools/audit/link-check.py",
|
|
80
|
+
"tools/audit/korean-copy-check.py",
|
|
81
|
+
"tools/audit/raw-hex-check.py",
|
|
82
|
+
"tools/audit/integration-check.py",
|
|
83
|
+
"tools/audit/stale-check.py",
|
|
84
|
+
"tools/audit/check-coverage.py",
|
|
85
|
+
"tools/audit/doctor_assertions.py",
|
|
86
|
+
"tools/audit/smoke_assertions.py",
|
|
87
|
+
"tools/audit/example-qa.py",
|
|
88
|
+
"tools/audit/local-ci.py",
|
|
89
|
+
"tools/audit/package-contents.py",
|
|
90
|
+
"tools/audit/package-smoke.py",
|
|
91
|
+
"tools/audit/registry-smoke.py",
|
|
92
|
+
"tools/audit/release-metadata.py",
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
FORBIDDEN_PREFIXES = (
|
|
96
|
+
"refs/",
|
|
97
|
+
".git/",
|
|
98
|
+
".github/",
|
|
99
|
+
"node_modules/",
|
|
100
|
+
"tools/extractors/",
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
FORBIDDEN_SUFFIXES = (
|
|
104
|
+
".pyc",
|
|
105
|
+
".tgz",
|
|
106
|
+
".test.mjs",
|
|
107
|
+
".test.js",
|
|
108
|
+
".spec.mjs",
|
|
109
|
+
".spec.js",
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
FORBIDDEN_SEGMENTS = (
|
|
113
|
+
"/__pycache__/",
|
|
114
|
+
"/node_modules/",
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def parse_pack_payload(raw: str, *, source: str) -> dict:
|
|
119
|
+
try:
|
|
120
|
+
payload = json.loads(raw)
|
|
121
|
+
except json.JSONDecodeError as error:
|
|
122
|
+
print(raw, file=sys.stderr)
|
|
123
|
+
raise SystemExit(f"failed to parse npm pack JSON from {source}: {error}") from error
|
|
124
|
+
|
|
125
|
+
if isinstance(payload, list):
|
|
126
|
+
if len(payload) != 1:
|
|
127
|
+
raise SystemExit(f"expected exactly one npm pack result from {source}, got {len(payload)}")
|
|
128
|
+
return payload[0]
|
|
129
|
+
|
|
130
|
+
if isinstance(payload, dict):
|
|
131
|
+
return payload
|
|
132
|
+
|
|
133
|
+
raise SystemExit(f"expected npm pack JSON object or single-item array from {source}")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def run_npm_pack_dry_run() -> dict:
|
|
137
|
+
result = subprocess.run(
|
|
138
|
+
["npm", "pack", "--dry-run", "--json"],
|
|
139
|
+
cwd=ROOT,
|
|
140
|
+
text=True,
|
|
141
|
+
capture_output=True,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
if result.returncode != 0:
|
|
145
|
+
if result.stdout:
|
|
146
|
+
print(result.stdout, end="")
|
|
147
|
+
if result.stderr:
|
|
148
|
+
print(result.stderr, end="", file=sys.stderr)
|
|
149
|
+
raise SystemExit(f"npm pack --dry-run --json failed with exit code {result.returncode}")
|
|
150
|
+
|
|
151
|
+
return parse_pack_payload(result.stdout, source="npm pack --dry-run --json")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def load_pack_json(pack_json: str) -> dict:
|
|
155
|
+
if pack_json == "-":
|
|
156
|
+
return parse_pack_payload(sys.stdin.read(), source="stdin")
|
|
157
|
+
|
|
158
|
+
pack_json_path = Path(pack_json).resolve()
|
|
159
|
+
try:
|
|
160
|
+
raw = pack_json_path.read_text(encoding="utf-8")
|
|
161
|
+
except OSError as error:
|
|
162
|
+
raise SystemExit(f"failed to read npm pack JSON fixture: {pack_json_path}") from error
|
|
163
|
+
|
|
164
|
+
return parse_pack_payload(raw, source=str(pack_json_path))
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def load_manifest_paths() -> set[str]:
|
|
168
|
+
manifest = load_plugin_json()
|
|
169
|
+
required: set[str] = set()
|
|
170
|
+
|
|
171
|
+
for section in ("skills", "commands", "agents"):
|
|
172
|
+
entries = manifest.get(section, [])
|
|
173
|
+
if not isinstance(entries, list):
|
|
174
|
+
raise SystemExit(f"plugin manifest section is not a list: {section}")
|
|
175
|
+
for entry in entries:
|
|
176
|
+
path = entry.get("path") if isinstance(entry, dict) else None
|
|
177
|
+
if not path:
|
|
178
|
+
raise SystemExit(f"plugin manifest entry in {section} is missing path")
|
|
179
|
+
required.add(path)
|
|
180
|
+
|
|
181
|
+
return required
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def load_skill_sidecar_paths() -> set[str]:
|
|
185
|
+
required: set[str] = set()
|
|
186
|
+
skills_root = ROOT / "skills"
|
|
187
|
+
for file_path in skills_root.glob("*/*.md"):
|
|
188
|
+
if file_path.name in {"SKILL.md", "PLAYBOOK.md", "TEMPLATE.md"}:
|
|
189
|
+
required.add(file_path.relative_to(ROOT).as_posix())
|
|
190
|
+
return required
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def load_required_paths() -> set[str]:
|
|
194
|
+
return REQUIRED_PATHS | load_manifest_paths() | load_skill_sidecar_paths()
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def load_run_all_audit_scripts() -> list[str]:
|
|
198
|
+
text = (ROOT / "tools/audit/run-all.py").read_text(encoding="utf-8")
|
|
199
|
+
match = re.search(
|
|
200
|
+
r"AUDITS: tuple\[AuditSpec, \.\.\.\] = \(\n(?P<body>.*?)\n\)\n\n\n@dataclass",
|
|
201
|
+
text,
|
|
202
|
+
re.DOTALL,
|
|
203
|
+
)
|
|
204
|
+
if not match:
|
|
205
|
+
raise SystemExit("failed to locate AUDITS tuple in tools/audit/run-all.py")
|
|
206
|
+
return re.findall(r'script="([^"]+\.py)"', match.group("body"))
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def load_package_json() -> dict:
|
|
210
|
+
return json.loads(PACKAGE_JSON.read_text(encoding="utf-8"))
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def load_plugin_json() -> dict:
|
|
214
|
+
return json.loads(PLUGIN_JSON.read_text(encoding="utf-8"))
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def matches_package_files_exclusion(path: str, pattern: str) -> bool:
|
|
218
|
+
normalized_pattern = pattern.rstrip("/")
|
|
219
|
+
if path == normalized_pattern or path.startswith(f"{normalized_pattern}/"):
|
|
220
|
+
return True
|
|
221
|
+
if pattern == "**/__pycache__/**":
|
|
222
|
+
return "/__pycache__/" in f"/{path}"
|
|
223
|
+
if pattern == "**/*.pyc":
|
|
224
|
+
return path.endswith(".pyc")
|
|
225
|
+
if pattern.startswith("cli/**/*."):
|
|
226
|
+
return path.startswith("cli/") and path.endswith(pattern.removeprefix("cli/**/*"))
|
|
227
|
+
return False
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def load_package_file_fixture_paths(package_json: dict) -> set[str]:
|
|
231
|
+
package_files = package_json.get("files")
|
|
232
|
+
if not isinstance(package_files, list):
|
|
233
|
+
raise SystemExit("package.json files field is not a list")
|
|
234
|
+
|
|
235
|
+
included: set[str] = set()
|
|
236
|
+
exclusions: list[str] = []
|
|
237
|
+
for entry in package_files:
|
|
238
|
+
if not isinstance(entry, str) or not entry:
|
|
239
|
+
raise SystemExit("package.json files field contains a non-string entry")
|
|
240
|
+
if entry.startswith("!"):
|
|
241
|
+
exclusions.append(entry[1:])
|
|
242
|
+
continue
|
|
243
|
+
|
|
244
|
+
path = entry.rstrip("/")
|
|
245
|
+
absolute_path = ROOT / path
|
|
246
|
+
if absolute_path.is_file():
|
|
247
|
+
included.add(path)
|
|
248
|
+
elif absolute_path.is_dir():
|
|
249
|
+
for file_path in absolute_path.rglob("*"):
|
|
250
|
+
if file_path.is_file():
|
|
251
|
+
included.add(file_path.relative_to(ROOT).as_posix())
|
|
252
|
+
|
|
253
|
+
for exclusion in exclusions:
|
|
254
|
+
included = {
|
|
255
|
+
path
|
|
256
|
+
for path in included
|
|
257
|
+
if not matches_package_files_exclusion(path, exclusion)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
included.add("package.json")
|
|
261
|
+
return included
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def format_catalog_count(count: int, singular: str) -> str:
|
|
265
|
+
return f"{count} {singular}{'' if count == 1 else 's'}"
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def catalog_counts_from_manifest(plugin_json: dict) -> tuple[dict[str, int], list[str]]:
|
|
269
|
+
counts: dict[str, int] = {}
|
|
270
|
+
errors: list[str] = []
|
|
271
|
+
for section in CATALOG_COUNT_LABELS:
|
|
272
|
+
entries = plugin_json.get(section)
|
|
273
|
+
if not isinstance(entries, list):
|
|
274
|
+
errors.append(f"plugin manifest section is not a list: {section}")
|
|
275
|
+
continue
|
|
276
|
+
counts[section] = len(entries)
|
|
277
|
+
return counts, errors
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def catalog_count_mentions(description: str) -> dict[str, list[tuple[int, str]]]:
|
|
281
|
+
mentions: dict[str, list[tuple[int, str]]] = {}
|
|
282
|
+
for match in CATALOG_COUNT_RE.finditer(description):
|
|
283
|
+
section = CATALOG_COUNT_SECTIONS[match.group("section")]
|
|
284
|
+
mentions.setdefault(section, []).append((int(match.group("count")), match.group(0)))
|
|
285
|
+
return mentions
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def replace_first_catalog_count(description: str, section: str, count: int) -> str:
|
|
289
|
+
replaced = False
|
|
290
|
+
|
|
291
|
+
def replace(match: re.Match[str]) -> str:
|
|
292
|
+
nonlocal replaced
|
|
293
|
+
if replaced or CATALOG_COUNT_SECTIONS[match.group("section")] != section:
|
|
294
|
+
return match.group(0)
|
|
295
|
+
|
|
296
|
+
replaced = True
|
|
297
|
+
return match.group(0).replace(match.group("count"), str(count), 1)
|
|
298
|
+
|
|
299
|
+
updated = CATALOG_COUNT_RE.sub(replace, description)
|
|
300
|
+
if not replaced:
|
|
301
|
+
raise SystemExit(f"self-test failed: fixture description is missing {section} count")
|
|
302
|
+
return updated
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def metadata_inventory_errors(package_json: dict, plugin_json: dict) -> list[str]:
|
|
306
|
+
expected_counts, errors = catalog_counts_from_manifest(plugin_json)
|
|
307
|
+
if errors:
|
|
308
|
+
return errors
|
|
309
|
+
|
|
310
|
+
descriptions = {
|
|
311
|
+
"package.json description": package_json.get("description"),
|
|
312
|
+
".claude-plugin/plugin.json description": plugin_json.get("description"),
|
|
313
|
+
}
|
|
314
|
+
for label, description in descriptions.items():
|
|
315
|
+
if not isinstance(description, str) or not description.strip():
|
|
316
|
+
errors.append(f"{label} is missing")
|
|
317
|
+
continue
|
|
318
|
+
|
|
319
|
+
mentions = catalog_count_mentions(description)
|
|
320
|
+
for section, expected_count in expected_counts.items():
|
|
321
|
+
expected_label = format_catalog_count(expected_count, CATALOG_COUNT_LABELS[section])
|
|
322
|
+
section_mentions = mentions.get(section, [])
|
|
323
|
+
if not section_mentions:
|
|
324
|
+
errors.append(f"{label} missing catalog count: {expected_label}")
|
|
325
|
+
continue
|
|
326
|
+
|
|
327
|
+
for count, phrase in section_mentions:
|
|
328
|
+
if count != expected_count:
|
|
329
|
+
errors.append(
|
|
330
|
+
f"{label} catalog count mismatch: {phrase} != {expected_label}"
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
return errors
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def is_forbidden(path: str) -> bool:
|
|
337
|
+
if path.startswith(FORBIDDEN_PREFIXES):
|
|
338
|
+
return True
|
|
339
|
+
if path.endswith(FORBIDDEN_SUFFIXES):
|
|
340
|
+
return True
|
|
341
|
+
normalized = f"/{path}"
|
|
342
|
+
return any(segment in normalized for segment in FORBIDDEN_SEGMENTS)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def is_external_link(url: str) -> bool:
|
|
346
|
+
return url.startswith(EXTERNAL_LINK_PREFIXES)
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def is_anchor_only_link(url: str) -> bool:
|
|
350
|
+
return url.startswith("#")
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def is_refs_link(url: str) -> bool:
|
|
354
|
+
"""refs/ links are intentionally excluded from package/runtime closure checks."""
|
|
355
|
+
return "refs/" in url
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def normalize_package_link_target(source_path: Path, link: str) -> tuple[str | None, str | None]:
|
|
359
|
+
"""Resolve a markdown link target to a repo-relative package path.
|
|
360
|
+
|
|
361
|
+
Returns (target_path, error). Links that are intentionally skipped return
|
|
362
|
+
(None, None). Only concrete files are checked against the package file list;
|
|
363
|
+
directory links remain valid because npm pack reports files, not directories.
|
|
364
|
+
"""
|
|
365
|
+
if is_external_link(link) or is_anchor_only_link(link) or is_refs_link(link):
|
|
366
|
+
return None, None
|
|
367
|
+
|
|
368
|
+
normalized_link = link.split("?", 1)[0].split("#", 1)[0]
|
|
369
|
+
if not normalized_link:
|
|
370
|
+
return None, None
|
|
371
|
+
|
|
372
|
+
resolved = (source_path.parent / normalized_link).resolve()
|
|
373
|
+
try:
|
|
374
|
+
relative = resolved.relative_to(ROOT)
|
|
375
|
+
except ValueError:
|
|
376
|
+
return None, (
|
|
377
|
+
f"packaged markdown link points outside repository: "
|
|
378
|
+
f"{source_path.relative_to(ROOT).as_posix()} -> {link}"
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
if not resolved.exists() or not resolved.is_file():
|
|
382
|
+
return None, None
|
|
383
|
+
|
|
384
|
+
return relative.as_posix(), None
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def packaged_markdown_link_errors(file_paths: set[str]) -> list[str]:
|
|
388
|
+
errors: list[str] = []
|
|
389
|
+
markdown_paths = sorted(path for path in file_paths if path.endswith(".md"))
|
|
390
|
+
|
|
391
|
+
for markdown_path in markdown_paths:
|
|
392
|
+
source_path = ROOT / markdown_path
|
|
393
|
+
if not source_path.is_file():
|
|
394
|
+
continue
|
|
395
|
+
|
|
396
|
+
in_code_block = False
|
|
397
|
+
text = source_path.read_text(encoding="utf-8")
|
|
398
|
+
for line in text.splitlines():
|
|
399
|
+
if line.strip().startswith("```"):
|
|
400
|
+
in_code_block = not in_code_block
|
|
401
|
+
continue
|
|
402
|
+
if in_code_block:
|
|
403
|
+
continue
|
|
404
|
+
|
|
405
|
+
line_without_code = INLINE_CODE_RE.sub("", line)
|
|
406
|
+
for match in MARKDOWN_LINK_RE.finditer(line_without_code):
|
|
407
|
+
_, link = match.groups()
|
|
408
|
+
target_path, target_error = normalize_package_link_target(source_path, link)
|
|
409
|
+
if target_error:
|
|
410
|
+
errors.append(target_error)
|
|
411
|
+
continue
|
|
412
|
+
if target_path is None:
|
|
413
|
+
continue
|
|
414
|
+
if target_path not in file_paths:
|
|
415
|
+
errors.append(
|
|
416
|
+
"packaged markdown link target is not included: "
|
|
417
|
+
f"{markdown_path} -> {link} ({target_path})"
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
return errors
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def format_bytes(size: int) -> str:
|
|
424
|
+
if size >= 1024 * 1024:
|
|
425
|
+
return f"{size / 1024 / 1024:.1f} MB"
|
|
426
|
+
if size >= 1024:
|
|
427
|
+
return f"{size / 1024:.1f} KB"
|
|
428
|
+
return f"{size} B"
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def verify_package_contents(
|
|
432
|
+
pack: dict,
|
|
433
|
+
*,
|
|
434
|
+
package_json: dict | None = None,
|
|
435
|
+
plugin_json: dict | None = None,
|
|
436
|
+
required_paths: set[str] | None = None,
|
|
437
|
+
) -> dict:
|
|
438
|
+
files = pack.get("files", [])
|
|
439
|
+
if not isinstance(files, list):
|
|
440
|
+
raise SystemExit("npm pack JSON did not include a files array")
|
|
441
|
+
|
|
442
|
+
file_paths = {entry.get("path") for entry in files if isinstance(entry, dict)}
|
|
443
|
+
file_paths.discard(None)
|
|
444
|
+
|
|
445
|
+
package_json = package_json or load_package_json()
|
|
446
|
+
plugin_json = plugin_json or load_plugin_json()
|
|
447
|
+
required_paths = required_paths or load_required_paths()
|
|
448
|
+
|
|
449
|
+
errors: list[str] = []
|
|
450
|
+
missing = sorted(path for path in required_paths if path not in file_paths)
|
|
451
|
+
forbidden = sorted(path for path in file_paths if is_forbidden(path))
|
|
452
|
+
markdown_link_errors = packaged_markdown_link_errors(file_paths)
|
|
453
|
+
|
|
454
|
+
package_size = int(pack.get("size") or 0)
|
|
455
|
+
if package_size > MAX_PACKAGE_SIZE_BYTES:
|
|
456
|
+
errors.append(
|
|
457
|
+
f"package size {format_bytes(package_size)} exceeds {MAX_PACKAGE_SIZE_MB} MB limit"
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
if pack.get("name") != package_json.get("name"):
|
|
461
|
+
errors.append(f"package name mismatch: {pack.get('name')} != {package_json.get('name')}")
|
|
462
|
+
if pack.get("version") != package_json.get("version"):
|
|
463
|
+
errors.append(
|
|
464
|
+
f"package version mismatch: {pack.get('version')} != {package_json.get('version')}"
|
|
465
|
+
)
|
|
466
|
+
if plugin_json.get("version") != package_json.get("version"):
|
|
467
|
+
errors.append(
|
|
468
|
+
"plugin manifest version mismatch: "
|
|
469
|
+
f"{plugin_json.get('version')} != {package_json.get('version')}"
|
|
470
|
+
)
|
|
471
|
+
errors.extend(metadata_inventory_errors(package_json, plugin_json))
|
|
472
|
+
|
|
473
|
+
if missing:
|
|
474
|
+
errors.append("missing required package path(s): " + ", ".join(missing))
|
|
475
|
+
if forbidden:
|
|
476
|
+
errors.append("forbidden package path(s): " + ", ".join(forbidden))
|
|
477
|
+
errors.extend(markdown_link_errors)
|
|
478
|
+
|
|
479
|
+
return {
|
|
480
|
+
"name": pack.get("name"),
|
|
481
|
+
"version": pack.get("version"),
|
|
482
|
+
"plugin_version": plugin_json.get("version"),
|
|
483
|
+
"filename": pack.get("filename"),
|
|
484
|
+
"file_count": len(file_paths),
|
|
485
|
+
"package_size": package_size,
|
|
486
|
+
"unpacked_size": int(pack.get("unpackedSize") or 0),
|
|
487
|
+
"missing": missing,
|
|
488
|
+
"forbidden": forbidden,
|
|
489
|
+
"markdown_link_errors": markdown_link_errors,
|
|
490
|
+
"errors": errors,
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def assert_condition(condition: bool, message: str) -> None:
|
|
495
|
+
if not condition:
|
|
496
|
+
raise SystemExit(f"self-test failed: {message}")
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
def run_self_test() -> int:
|
|
500
|
+
package_json = load_package_json()
|
|
501
|
+
plugin_json = load_plugin_json()
|
|
502
|
+
required_paths = sorted(load_required_paths())
|
|
503
|
+
package_file_paths = sorted(load_package_file_fixture_paths(package_json))
|
|
504
|
+
assert_condition(
|
|
505
|
+
any(path.endswith("/PLAYBOOK.md") for path in required_paths),
|
|
506
|
+
"skill playbook paths should be part of required package contents",
|
|
507
|
+
)
|
|
508
|
+
assert_condition(
|
|
509
|
+
any(path.endswith("/TEMPLATE.md") for path in required_paths),
|
|
510
|
+
"skill template paths should be part of required package contents when present",
|
|
511
|
+
)
|
|
512
|
+
assert_condition(
|
|
513
|
+
"tools/audit/smoke_assertions.py" in required_paths,
|
|
514
|
+
"shared smoke assertion helper should be required package contents",
|
|
515
|
+
)
|
|
516
|
+
assert_condition(
|
|
517
|
+
"tools/audit/check-coverage.py" in required_paths,
|
|
518
|
+
"coverage audit script should be required package contents",
|
|
519
|
+
)
|
|
520
|
+
for localized_entrypoint in (
|
|
521
|
+
"README.ko.md",
|
|
522
|
+
"AGENTS.ko.md",
|
|
523
|
+
"docs/QUICKSTART.ko.md",
|
|
524
|
+
"docs/USING.ko.md",
|
|
525
|
+
"docs/DISTRIBUTION.ko.md",
|
|
526
|
+
):
|
|
527
|
+
assert_condition(
|
|
528
|
+
localized_entrypoint in required_paths,
|
|
529
|
+
f"{localized_entrypoint} should be required package contents",
|
|
530
|
+
)
|
|
531
|
+
run_all_audit_paths = {
|
|
532
|
+
f"tools/audit/{script}"
|
|
533
|
+
for script in load_run_all_audit_scripts()
|
|
534
|
+
}
|
|
535
|
+
missing_run_all_audit_paths = sorted(
|
|
536
|
+
path for path in run_all_audit_paths if path not in required_paths
|
|
537
|
+
)
|
|
538
|
+
assert_condition(
|
|
539
|
+
len(run_all_audit_paths) == 8,
|
|
540
|
+
"run-all.py should still enumerate eight repository audits",
|
|
541
|
+
)
|
|
542
|
+
assert_condition(
|
|
543
|
+
not missing_run_all_audit_paths,
|
|
544
|
+
"run-all.py audit scripts should be required package contents: "
|
|
545
|
+
+ ", ".join(missing_run_all_audit_paths),
|
|
546
|
+
)
|
|
547
|
+
missing_fixture_paths = sorted(
|
|
548
|
+
path for path in required_paths if path not in package_file_paths
|
|
549
|
+
)
|
|
550
|
+
assert_condition(
|
|
551
|
+
not missing_fixture_paths,
|
|
552
|
+
"package.json files fixture should include all required package contents: "
|
|
553
|
+
+ ", ".join(missing_fixture_paths),
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
passing_pack = {
|
|
557
|
+
"name": package_json["name"],
|
|
558
|
+
"version": package_json["version"],
|
|
559
|
+
"filename": "fixture.tgz",
|
|
560
|
+
"size": 1024,
|
|
561
|
+
"unpackedSize": 4096,
|
|
562
|
+
"files": [{"path": path} for path in package_file_paths],
|
|
563
|
+
}
|
|
564
|
+
passing_summary = verify_package_contents(
|
|
565
|
+
passing_pack,
|
|
566
|
+
package_json=package_json,
|
|
567
|
+
plugin_json=plugin_json,
|
|
568
|
+
required_paths=set(required_paths),
|
|
569
|
+
)
|
|
570
|
+
assert_condition(
|
|
571
|
+
passing_summary["errors"] == [],
|
|
572
|
+
"complete fixture should pass without errors",
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
missing_markdown_target_paths = set(package_file_paths)
|
|
576
|
+
missing_markdown_target_paths.remove("AGENTS.ko.md")
|
|
577
|
+
missing_markdown_required_paths = set(required_paths)
|
|
578
|
+
missing_markdown_required_paths.remove("AGENTS.ko.md")
|
|
579
|
+
missing_markdown_target_summary = verify_package_contents(
|
|
580
|
+
{
|
|
581
|
+
**passing_pack,
|
|
582
|
+
"files": [{"path": path} for path in sorted(missing_markdown_target_paths)],
|
|
583
|
+
},
|
|
584
|
+
package_json=package_json,
|
|
585
|
+
plugin_json=plugin_json,
|
|
586
|
+
required_paths=missing_markdown_required_paths,
|
|
587
|
+
)
|
|
588
|
+
assert_condition(
|
|
589
|
+
"docs/USING.ko.md -> ../AGENTS.ko.md"
|
|
590
|
+
in "\n".join(missing_markdown_target_summary["markdown_link_errors"]),
|
|
591
|
+
"packaged markdown links should fail when their existing target is not packaged",
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
failing_pack = {
|
|
595
|
+
"name": package_json["name"],
|
|
596
|
+
"version": "0.0.0",
|
|
597
|
+
"filename": "bad-fixture.tgz",
|
|
598
|
+
"size": MAX_PACKAGE_SIZE_BYTES + 1,
|
|
599
|
+
"unpackedSize": 0,
|
|
600
|
+
"files": [
|
|
601
|
+
{"path": "package.json"},
|
|
602
|
+
{"path": "refs/source.md"},
|
|
603
|
+
{"path": "cli/lib/check.test.mjs"},
|
|
604
|
+
],
|
|
605
|
+
}
|
|
606
|
+
mismatched_plugin_json = {**plugin_json, "version": "0.0.0"}
|
|
607
|
+
failing_summary = verify_package_contents(
|
|
608
|
+
failing_pack,
|
|
609
|
+
package_json=package_json,
|
|
610
|
+
plugin_json=mismatched_plugin_json,
|
|
611
|
+
required_paths=set(required_paths),
|
|
612
|
+
)
|
|
613
|
+
joined_errors = "\n".join(failing_summary["errors"])
|
|
614
|
+
|
|
615
|
+
assert_condition(
|
|
616
|
+
any(path.endswith("plugin.json") for path in failing_summary["missing"]),
|
|
617
|
+
"incomplete fixture should report missing manifest/runtime paths",
|
|
618
|
+
)
|
|
619
|
+
assert_condition(
|
|
620
|
+
"refs/source.md" in failing_summary["forbidden"],
|
|
621
|
+
"forbidden refs/ path should be detected",
|
|
622
|
+
)
|
|
623
|
+
assert_condition(
|
|
624
|
+
"cli/lib/check.test.mjs" in failing_summary["forbidden"],
|
|
625
|
+
"forbidden CLI test file should be detected",
|
|
626
|
+
)
|
|
627
|
+
assert_condition(
|
|
628
|
+
"package size" in joined_errors,
|
|
629
|
+
"oversized package should be reported",
|
|
630
|
+
)
|
|
631
|
+
assert_condition(
|
|
632
|
+
"package version mismatch" in joined_errors,
|
|
633
|
+
"version mismatch should be reported",
|
|
634
|
+
)
|
|
635
|
+
assert_condition(
|
|
636
|
+
"plugin manifest version mismatch" in joined_errors,
|
|
637
|
+
"plugin manifest version mismatch should be reported",
|
|
638
|
+
)
|
|
639
|
+
|
|
640
|
+
stale_package_json = {
|
|
641
|
+
**package_json,
|
|
642
|
+
"description": replace_first_catalog_count(
|
|
643
|
+
package_json["description"],
|
|
644
|
+
"skills",
|
|
645
|
+
len(plugin_json["skills"]) - 1,
|
|
646
|
+
),
|
|
647
|
+
}
|
|
648
|
+
stale_package_summary = verify_package_contents(
|
|
649
|
+
passing_pack,
|
|
650
|
+
package_json=stale_package_json,
|
|
651
|
+
plugin_json=plugin_json,
|
|
652
|
+
required_paths=set(required_paths),
|
|
653
|
+
)
|
|
654
|
+
assert_condition(
|
|
655
|
+
"package.json description catalog count mismatch"
|
|
656
|
+
in "\n".join(stale_package_summary["errors"]),
|
|
657
|
+
"stale package.json inventory count should be reported",
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
stale_plugin_json = {
|
|
661
|
+
**plugin_json,
|
|
662
|
+
"description": replace_first_catalog_count(
|
|
663
|
+
plugin_json["description"],
|
|
664
|
+
"agents",
|
|
665
|
+
len(plugin_json["agents"]) - 1,
|
|
666
|
+
),
|
|
667
|
+
}
|
|
668
|
+
stale_plugin_summary = verify_package_contents(
|
|
669
|
+
passing_pack,
|
|
670
|
+
package_json=package_json,
|
|
671
|
+
plugin_json=stale_plugin_json,
|
|
672
|
+
required_paths=set(required_paths),
|
|
673
|
+
)
|
|
674
|
+
assert_condition(
|
|
675
|
+
".claude-plugin/plugin.json description catalog count mismatch"
|
|
676
|
+
in "\n".join(stale_plugin_summary["errors"]),
|
|
677
|
+
"stale plugin manifest inventory count should be reported",
|
|
678
|
+
)
|
|
679
|
+
|
|
680
|
+
print("Package contents self-test passed")
|
|
681
|
+
return 0
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
def main() -> int:
|
|
685
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
686
|
+
parser.add_argument("--json", action="store_true", help="Print machine-readable summary")
|
|
687
|
+
parser.add_argument(
|
|
688
|
+
"--self-test",
|
|
689
|
+
action="store_true",
|
|
690
|
+
help="Run built-in success and failure fixture checks without invoking npm pack",
|
|
691
|
+
)
|
|
692
|
+
parser.add_argument(
|
|
693
|
+
"--pack-json",
|
|
694
|
+
metavar="FILE",
|
|
695
|
+
help="Read npm pack --json output from a fixture file instead of running npm pack; use - for stdin",
|
|
696
|
+
)
|
|
697
|
+
args = parser.parse_args()
|
|
698
|
+
|
|
699
|
+
if args.self_test:
|
|
700
|
+
return run_self_test()
|
|
701
|
+
|
|
702
|
+
pack = load_pack_json(args.pack_json) if args.pack_json else run_npm_pack_dry_run()
|
|
703
|
+
summary = verify_package_contents(pack)
|
|
704
|
+
if args.json:
|
|
705
|
+
print(json.dumps(summary, ensure_ascii=False, indent=2))
|
|
706
|
+
elif summary["errors"]:
|
|
707
|
+
print("Package contents check failed:")
|
|
708
|
+
for error in summary["errors"]:
|
|
709
|
+
print(f"- {error}")
|
|
710
|
+
else:
|
|
711
|
+
print(
|
|
712
|
+
"Package contents check passed: "
|
|
713
|
+
f"{summary['name']}@{summary['version']}, "
|
|
714
|
+
f"{summary['file_count']} files, "
|
|
715
|
+
f"packed {format_bytes(summary['package_size'])}, "
|
|
716
|
+
f"unpacked {format_bytes(summary['unpacked_size'])}"
|
|
717
|
+
)
|
|
718
|
+
|
|
719
|
+
return 1 if summary["errors"] else 0
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
if __name__ == "__main__":
|
|
723
|
+
raise SystemExit(main())
|