@cometchat/skills 4.4.2 → 5.0.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/.claude-plugin/marketplace.json +55 -0
- package/.claude-plugin/plugin.json +14 -0
- package/CHANGELOG.md +44 -0
- package/LICENSE +21 -0
- package/MIGRATING.md +187 -0
- package/README.md +52 -156
- package/RULES.md +125 -0
- package/bin/cometchat-skills.mjs +1280 -0
- package/bin/legacy-skills.json +95 -0
- package/catalogs/android-v6.json +1646 -0
- package/catalogs/flutter-v6.json +651 -0
- package/catalogs/ios-v5.json +1383 -0
- package/catalogs/rn-calls-v5.json +134 -0
- package/catalogs/rn-calls-v5.symbols.txt +58 -0
- package/catalogs/rn-sdk-v4.json +672 -0
- package/catalogs/rn-v5.json +449 -0
- package/catalogs/rn-v5.symbols.txt +216 -0
- package/catalogs/sdk-android-v5.json +514 -0
- package/contracts.android-calls-v5.json +122 -0
- package/contracts.android-v6.json +135 -0
- package/contracts.angular-v5.json +694 -0
- package/contracts.flutter-calls-v5.json +77 -0
- package/contracts.flutter-v6.json +126 -0
- package/contracts.ios-calls-v5.json +67 -0
- package/contracts.ios-v5.json +436 -0
- package/contracts.json +82 -0
- package/contracts.rn-calls-v5.json +77 -0
- package/contracts.rn-v5.json +727 -0
- package/contracts.sdk-android-v5.json +15 -0
- package/contracts.web-calls-v5.json +45 -0
- package/features.android-calls-v5.json +246 -0
- package/features.android-v6.json +65 -0
- package/features.angular-v5.json +521 -0
- package/features.flutter-calls-v5.json +286 -0
- package/features.flutter-v6.json +484 -0
- package/features.ios-calls-v5.json +236 -0
- package/features.ios-v5.json +573 -0
- package/features.json +61 -0
- package/features.rn-calls-v5.json +187 -0
- package/features.rn-v5.json +614 -0
- package/features.sdk-android-v5.json +40 -0
- package/features.sdk-jsv4.json +24 -0
- package/features.web-calls-v5.json +108 -0
- package/package.json +156 -74
- package/peers.yaml +355 -0
- package/registry/README.md +31 -0
- package/registry/android-calls-v5.json +94 -0
- package/registry/android-v6.json +177 -0
- package/registry/angular-v5.json +154 -0
- package/registry/flutter-calls-v5.json +94 -0
- package/registry/flutter-v6.json +159 -0
- package/registry/ios-calls-v5.json +99 -0
- package/registry/ios-v5.json +152 -0
- package/registry/rn-calls-v5.json +99 -0
- package/registry/rn-v5.json +169 -0
- package/registry/sdk-android-v5.json +93 -0
- package/registry/sdk-jsv4.json +93 -0
- package/registry/web-calls-v5.json +94 -0
- package/registry/web-v7.json +154 -0
- package/skills/cometchat/SKILL.md +47 -2096
- package/skills/cometchat-a11y/SKILL.md +44 -472
- package/skills/cometchat-analytics/SKILL.md +61 -0
- package/skills/cometchat-android-v5-calls-sdk/SKILL.md +206 -0
- package/skills/cometchat-android-v5-calls-sdk/references/build-truth.md +63 -0
- package/skills/cometchat-android-v5-calls-sdk/references/docs-map.md +119 -0
- package/skills/cometchat-android-v5-calls-sdk/references/method-map.md +53 -0
- package/skills/cometchat-android-v5-calls-sdk/references/pitfalls.md +72 -0
- package/skills/cometchat-android-v5-calls-sdk/references/ringing.md +92 -0
- package/skills/cometchat-android-v5-calls-sdk/references/user-switch.md +90 -0
- package/skills/cometchat-android-v5-sdk/SKILL.md +127 -0
- package/skills/cometchat-android-v5-sdk/references/app-surface.md +88 -0
- package/skills/cometchat-android-v5-sdk/references/docs-map.md +91 -0
- package/skills/cometchat-android-v6-builder-settings/SKILL.md +51 -137
- package/skills/cometchat-android-v6-calls/SKILL.md +128 -413
- package/skills/cometchat-android-v6-compose-components/SKILL.md +84 -194
- package/skills/cometchat-android-v6-compose-customization/SKILL.md +66 -411
- package/skills/cometchat-android-v6-compose-placement/SKILL.md +154 -237
- package/skills/cometchat-android-v6-core/SKILL.md +236 -672
- package/skills/cometchat-android-v6-core/references/anti-patterns.md +31 -0
- package/skills/cometchat-android-v6-core/references/component-props.md +168 -0
- package/skills/cometchat-android-v6-core/references/docs-map.md +153 -0
- package/skills/cometchat-android-v6-core/references/layout.md +112 -0
- package/skills/cometchat-android-v6-core/references/lifecycle.md +75 -0
- package/skills/cometchat-android-v6-core/references/setup-credentials.md +105 -0
- package/skills/cometchat-android-v6-core/references/troubleshooting.md +36 -0
- package/skills/cometchat-android-v6-events/SKILL.md +46 -184
- package/skills/cometchat-android-v6-extensions/SKILL.md +39 -179
- package/skills/cometchat-android-v6-features/SKILL.md +74 -143
- package/skills/cometchat-android-v6-kotlin-components/SKILL.md +75 -193
- package/skills/cometchat-android-v6-kotlin-customization/SKILL.md +67 -419
- package/skills/cometchat-android-v6-kotlin-placement/SKILL.md +77 -223
- package/skills/cometchat-android-v6-migration/SKILL.md +67 -378
- package/skills/cometchat-android-v6-production/SKILL.md +41 -179
- package/skills/cometchat-android-v6-push/SKILL.md +39 -289
- package/skills/cometchat-android-v6-testing/SKILL.md +50 -265
- package/skills/cometchat-android-v6-troubleshooting/SKILL.md +70 -254
- package/skills/cometchat-angular-v5-calls/SKILL.md +193 -0
- package/skills/cometchat-angular-v5-components/SKILL.md +131 -0
- package/skills/cometchat-angular-v5-components/references/catalog.md +111 -0
- package/skills/cometchat-angular-v5-components/references/host-composed.md +46 -0
- package/skills/cometchat-angular-v5-core/SKILL.md +300 -0
- package/skills/cometchat-angular-v5-core/references/anti-patterns.md +49 -0
- package/skills/cometchat-angular-v5-core/references/component-props.md +55 -0
- package/skills/cometchat-angular-v5-core/references/dependencies.md +66 -0
- package/skills/cometchat-angular-v5-core/references/docs-map.md +132 -0
- package/skills/cometchat-angular-v5-core/references/layout.md +64 -0
- package/skills/cometchat-angular-v5-core/references/lifecycle.md +202 -0
- package/skills/cometchat-angular-v5-core/references/setup-credentials.md +137 -0
- package/skills/cometchat-angular-v5-core/references/theming.md +70 -0
- package/skills/cometchat-angular-v5-core/references/troubleshooting.md +27 -0
- package/skills/cometchat-angular-v5-customization/SKILL.md +94 -0
- package/skills/cometchat-angular-v5-features/SKILL.md +107 -0
- package/skills/cometchat-angular-v5-features/references/custom-messages.md +133 -0
- package/skills/cometchat-angular-v5-features/references/formatters.md +28 -0
- package/skills/cometchat-angular-v5-features/references/host-composed-extensions.md +85 -0
- package/skills/cometchat-angular-v5-migration/SKILL.md +40 -0
- package/skills/cometchat-angular-v5-migration/references/migration-guide.md +165 -0
- package/skills/cometchat-angular-v5-patterns/SKILL.md +107 -0
- package/skills/cometchat-angular-v5-placement/SKILL.md +133 -0
- package/skills/cometchat-angular-v5-placement/references/combined-app.md +174 -0
- package/skills/cometchat-angular-v5-placement/references/core-surface.md +199 -0
- package/skills/cometchat-angular-v5-production/SKILL.md +102 -0
- package/skills/cometchat-angular-v5-push/SKILL.md +61 -0
- package/skills/cometchat-angular-v5-testing/SKILL.md +112 -0
- package/skills/cometchat-angular-v5-troubleshooting/SKILL.md +96 -0
- package/skills/cometchat-audit/SKILL.md +61 -0
- package/skills/cometchat-compliance/SKILL.md +63 -0
- package/skills/cometchat-flutter-v5-sdk/SKILL.md +188 -0
- package/skills/cometchat-flutter-v5-sdk/references/doc-corrections.md +55 -0
- package/skills/cometchat-flutter-v5-sdk/references/docs-map.md +211 -0
- package/skills/cometchat-flutter-v5-sdk/references/platform-notes.md +48 -0
- package/skills/cometchat-flutter-v5-sdk/references/transcription.md +87 -0
- package/skills/cometchat-flutter-v6-calls/SKILL.md +75 -389
- package/skills/cometchat-flutter-v6-components/SKILL.md +88 -361
- package/skills/cometchat-flutter-v6-core/SKILL.md +154 -593
- package/skills/cometchat-flutter-v6-core/references/anti-patterns.md +55 -0
- package/skills/cometchat-flutter-v6-core/references/docs-map.md +143 -0
- package/skills/cometchat-flutter-v6-core/references/layout.md +45 -0
- package/skills/cometchat-flutter-v6-core/references/lifecycle.md +87 -0
- package/skills/cometchat-flutter-v6-core/references/setup-credentials.md +84 -0
- package/skills/cometchat-flutter-v6-core/references/troubleshooting.md +26 -0
- package/skills/cometchat-flutter-v6-customization/SKILL.md +70 -712
- package/skills/cometchat-flutter-v6-events/SKILL.md +79 -174
- package/skills/cometchat-flutter-v6-features/SKILL.md +55 -394
- package/skills/cometchat-flutter-v6-migration/SKILL.md +68 -367
- package/skills/cometchat-flutter-v6-migration/references/migration-guide.md +132 -0
- package/skills/cometchat-flutter-v6-patterns/SKILL.md +61 -0
- package/skills/cometchat-flutter-v6-placement/SKILL.md +94 -381
- package/skills/cometchat-flutter-v6-production/SKILL.md +59 -615
- package/skills/cometchat-flutter-v6-push/SKILL.md +46 -455
- package/skills/cometchat-flutter-v6-testing/SKILL.md +68 -299
- package/skills/cometchat-flutter-v6-troubleshooting/SKILL.md +54 -878
- package/skills/cometchat-i18n/SKILL.md +35 -404
- package/skills/cometchat-ios-calls/SKILL.md +112 -563
- package/skills/cometchat-ios-components/SKILL.md +44 -834
- package/skills/cometchat-ios-core/SKILL.md +182 -1068
- package/skills/cometchat-ios-core/references/anti-patterns.md +113 -0
- package/skills/cometchat-ios-core/references/docs-map.md +124 -0
- package/skills/cometchat-ios-core/references/install.md +23 -0
- package/skills/cometchat-ios-core/references/layout.md +219 -0
- package/skills/cometchat-ios-core/references/setup-credentials.md +197 -0
- package/skills/cometchat-ios-core/references/swiftui.md +91 -0
- package/skills/cometchat-ios-core/references/troubleshooting.md +100 -0
- package/skills/cometchat-ios-customization/SKILL.md +33 -968
- package/skills/cometchat-ios-features/SKILL.md +29 -738
- package/skills/cometchat-ios-migration/SKILL.md +39 -0
- package/skills/cometchat-ios-placement/SKILL.md +51 -729
- package/skills/cometchat-ios-production/SKILL.md +67 -723
- package/skills/cometchat-ios-push/SKILL.md +24 -602
- package/skills/cometchat-ios-testing/SKILL.md +36 -355
- package/skills/cometchat-ios-troubleshooting/SKILL.md +31 -989
- package/skills/cometchat-ios-v5-sdk/SKILL.md +161 -0
- package/skills/cometchat-ios-v5-sdk/references/docs-map.md +326 -0
- package/skills/cometchat-js-v5-sdk/SKILL.md +95 -0
- package/skills/cometchat-js-v5-sdk/references/docs-map.md +114 -0
- package/skills/cometchat-migrate-from-getstream/SKILL.md +65 -0
- package/skills/cometchat-migrate-from-getstream/references/concept-map.md +98 -0
- package/skills/cometchat-migrate-from-getstream/references/data-migration.md +74 -0
- package/skills/cometchat-migrate-from-getstream/references/feature-map.md +108 -0
- package/skills/cometchat-migrate-from-getstream/references/inventory.md +82 -0
- package/skills/cometchat-migrate-from-getstream/references/native-build.md +9 -0
- package/skills/cometchat-migrate-from-getstream/references/report-template.md +59 -0
- package/skills/cometchat-migrate-from-sendbird/SKILL.md +64 -0
- package/skills/cometchat-migrate-from-sendbird/references/concept-map.md +99 -0
- package/skills/cometchat-migrate-from-sendbird/references/data-migration.md +74 -0
- package/skills/cometchat-migrate-from-sendbird/references/feature-map.md +104 -0
- package/skills/cometchat-migrate-from-sendbird/references/inventory.md +86 -0
- package/skills/cometchat-migrate-from-sendbird/references/native-build.md +10 -0
- package/skills/cometchat-migrate-from-sendbird/references/report-template.md +59 -0
- package/skills/cometchat-migrate-from-twilio/SKILL.md +52 -0
- package/skills/cometchat-migrate-from-twilio/references/concept-map.md +49 -0
- package/skills/cometchat-migrate-from-twilio/references/data-migration.md +50 -0
- package/skills/cometchat-migrate-from-twilio/references/feature-map.md +27 -0
- package/skills/cometchat-migrate-from-twilio/references/inventory.md +53 -0
- package/skills/cometchat-migrate-from-twilio/references/report-template.md +52 -0
- package/skills/cometchat-moderation/SKILL.md +58 -0
- package/skills/cometchat-onboarding/SKILL.md +92 -0
- package/skills/cometchat-onboarding/references/archetypes.md +51 -0
- package/skills/cometchat-onboarding/references/calling-first.md +77 -0
- package/skills/cometchat-onboarding/references/not-in-this-pack.md +8 -0
- package/skills/cometchat-onboarding/references/plan-artifact.md +133 -0
- package/skills/cometchat-onboarding/references/platforms.md +105 -0
- package/skills/cometchat-react-native-bare-patterns/SKILL.md +120 -0
- package/skills/cometchat-react-native-calls/SKILL.md +200 -0
- package/skills/cometchat-react-native-components/SKILL.md +142 -0
- package/skills/cometchat-react-native-core/SKILL.md +335 -0
- package/skills/cometchat-react-native-core/references/anti-patterns.md +79 -0
- package/skills/cometchat-react-native-core/references/component-props.md +105 -0
- package/skills/cometchat-react-native-core/references/dependencies.md +62 -0
- package/skills/cometchat-react-native-core/references/docs-map.md +127 -0
- package/skills/cometchat-react-native-core/references/layout.md +61 -0
- package/skills/cometchat-react-native-core/references/lifecycle.md +64 -0
- package/skills/cometchat-react-native-core/references/setup-credentials.md +39 -0
- package/skills/cometchat-react-native-customization/SKILL.md +198 -0
- package/skills/cometchat-react-native-expo-patterns/SKILL.md +116 -0
- package/skills/cometchat-react-native-features/SKILL.md +122 -0
- package/skills/cometchat-react-native-migration/SKILL.md +103 -0
- package/skills/cometchat-react-native-placement/SKILL.md +158 -0
- package/skills/cometchat-react-native-production/SKILL.md +79 -0
- package/skills/cometchat-react-native-push/SKILL.md +88 -0
- package/skills/cometchat-react-native-sdk/SKILL.md +133 -0
- package/skills/cometchat-react-native-sdk/references/method-map.md +239 -0
- package/skills/cometchat-react-native-sdk/references/page-map.md +96 -0
- package/skills/cometchat-react-native-testing/SKILL.md +94 -0
- package/skills/cometchat-react-native-troubleshooting/SKILL.md +174 -0
- package/skills/cometchat-react-native-v5-sdk/SKILL.md +150 -0
- package/skills/cometchat-react-native-v5-sdk/references/docs-map.md +127 -0
- package/skills/cometchat-react-native-v5-sdk/references/ringing-voip.md +369 -0
- package/skills/cometchat-react-v7-calls/SKILL.md +75 -0
- package/skills/cometchat-react-v7-components/SKILL.md +72 -0
- package/skills/cometchat-react-v7-core/SKILL.md +101 -0
- package/skills/cometchat-react-v7-core/references/anti-patterns.md +19 -0
- package/skills/cometchat-react-v7-core/references/component-props.md +82 -0
- package/skills/cometchat-react-v7-core/references/dependencies.md +23 -0
- package/skills/cometchat-react-v7-core/references/docs-map.md +117 -0
- package/skills/cometchat-react-v7-core/references/layout.md +150 -0
- package/skills/cometchat-react-v7-core/references/lifecycle.md +74 -0
- package/skills/cometchat-react-v7-core/references/setup-credentials.md +58 -0
- package/skills/cometchat-react-v7-core/references/ssr.md +11 -0
- package/skills/cometchat-react-v7-core/references/theming.md +52 -0
- package/skills/cometchat-react-v7-customization/SKILL.md +84 -0
- package/skills/cometchat-react-v7-features/SKILL.md +92 -0
- package/skills/cometchat-react-v7-migration/SKILL.md +39 -0
- package/skills/cometchat-react-v7-migration/references/migration-guide.md +119 -0
- package/skills/cometchat-react-v7-patterns/SKILL.md +43 -0
- package/skills/cometchat-react-v7-placement/SKILL.md +106 -0
- package/skills/cometchat-react-v7-placement/references/recipes.md +354 -0
- package/skills/cometchat-react-v7-production/SKILL.md +84 -0
- package/skills/cometchat-react-v7-push/SKILL.md +53 -0
- package/skills/cometchat-react-v7-testing/SKILL.md +60 -0
- package/skills/cometchat-react-v7-troubleshooting/SKILL.md +47 -0
- package/skills/cometchat-security/SKILL.md +80 -0
- package/skills/cometchat-self-host/SKILL.md +56 -0
- package/test-suite/README.md +70 -0
- package/test-suite/catalogs/android-calls-v5.json +830 -0
- package/test-suite/catalogs/android-calls-v5.symbols.txt +499 -0
- package/test-suite/catalogs/android-v6.json +1646 -0
- package/test-suite/catalogs/android-v6.symbols.txt +298 -0
- package/test-suite/catalogs/angular-v5.json +7587 -0
- package/test-suite/catalogs/flutter-calls-v5.json +196 -0
- package/test-suite/catalogs/flutter-calls-v5.symbols.txt +59 -0
- package/test-suite/catalogs/flutter-v6.json +651 -0
- package/test-suite/catalogs/ios-calls-v5.json +158 -0
- package/test-suite/catalogs/ios-calls-v5.symbols.txt +70 -0
- package/test-suite/catalogs/ios-v5.json +1383 -0
- package/test-suite/catalogs/ios-v5.symbols.txt +699 -0
- package/test-suite/catalogs/rn-calls-v5.json +134 -0
- package/test-suite/catalogs/rn-sdk-v4.json +672 -0
- package/test-suite/catalogs/rn-v5.json +449 -0
- package/test-suite/catalogs/sdk-android-v5.json +514 -0
- package/test-suite/catalogs/sdk-android-v5.symbols.txt +254 -0
- package/test-suite/catalogs/sdk-jsv4.json +800 -0
- package/test-suite/catalogs/web-calls-v5.json +195 -0
- package/test-suite/catalogs/web-v7.json +1243 -0
- package/test-suite/scripts/build-registry.mjs +113 -0
- package/test-suite/scripts/catalog-refresh-dart.mjs +151 -0
- package/test-suite/scripts/catalog-refresh.mjs +100 -0
- package/test-suite/scripts/lint-skills.mjs +932 -0
- package/test-suite/scripts/measure-tokens.mjs +169 -0
- package/test-suite/scripts/typecheck-fences-dart.mjs +437 -0
- package/test-suite/scripts/typecheck-fences-kotlin.mjs +269 -0
- package/test-suite/scripts/typecheck-fences.mjs +206 -0
- package/test-suite/scripts/verify-catalog.mjs +242 -0
- package/test-suite/scripts/verify-contract-fences.mjs +94 -0
- package/test-suite/scripts/verify-detection.mjs +178 -0
- package/test-suite/scripts/verify-docs-base.mjs +91 -0
- package/test-suite/scripts/verify-family-agnostic.mjs +134 -0
- package/test-suite/scripts/verify-golden-path.mjs +138 -0
- package/test-suite/scripts/verify-oracle-components.mjs +133 -0
- package/test-suite/scripts/verify-template-bindings.mjs +148 -0
- package/test-suite/typecheck/angular-v5/package.json +21 -0
- package/test-suite/typecheck/angular-v5/tsconfig.json +21 -0
- package/test-suite/typecheck/flutter-v6/analysis_options.yaml +25 -0
- package/test-suite/typecheck/flutter-v6/pubspec.yaml +21 -0
- package/test-suite/typecheck/web-v7/package.json +18 -0
- package/test-suite/typecheck/web-v7/tsconfig.json +19 -0
- package/bin/install.js +0 -1003
- package/skills/.claude-plugin/marketplace.json +0 -86
- package/skills/cometchat/references/asking-questions.md +0 -48
- package/skills/cometchat-android-v5/SKILL.md +0 -121
- package/skills/cometchat-android-v5-calls/SKILL.md +0 -429
- package/skills/cometchat-android-v5-calls/references/README.md +0 -53
- package/skills/cometchat-android-v5-calls/references/add-calls-to-existing-chat.md +0 -197
- package/skills/cometchat-android-v5-calls/references/audio-controls.md +0 -91
- package/skills/cometchat-android-v5-calls/references/background-handling.md +0 -168
- package/skills/cometchat-android-v5-calls/references/call-layouts.md +0 -131
- package/skills/cometchat-android-v5-calls/references/call-logs.md +0 -113
- package/skills/cometchat-android-v5-calls/references/call-session.md +0 -274
- package/skills/cometchat-android-v5-calls/references/custom-ui.md +0 -150
- package/skills/cometchat-android-v5-calls/references/event-listeners.md +0 -123
- package/skills/cometchat-android-v5-calls/references/group-calls.md +0 -205
- package/skills/cometchat-android-v5-calls/references/idle-timeout.md +0 -117
- package/skills/cometchat-android-v5-calls/references/in-call-chat.md +0 -134
- package/skills/cometchat-android-v5-calls/references/join-session.md +0 -120
- package/skills/cometchat-android-v5-calls/references/migration-v4-to-v5.md +0 -222
- package/skills/cometchat-android-v5-calls/references/participant-management.md +0 -110
- package/skills/cometchat-android-v5-calls/references/picture-in-picture.md +0 -106
- package/skills/cometchat-android-v5-calls/references/raise-hand.md +0 -248
- package/skills/cometchat-android-v5-calls/references/recording.md +0 -101
- package/skills/cometchat-android-v5-calls/references/ringing-integration.md +0 -152
- package/skills/cometchat-android-v5-calls/references/screen-sharing.md +0 -78
- package/skills/cometchat-android-v5-calls/references/server-fcm-voip.md +0 -215
- package/skills/cometchat-android-v5-calls/references/session-settings.md +0 -121
- package/skills/cometchat-android-v5-calls/references/setup.md +0 -137
- package/skills/cometchat-android-v5-calls/references/share-invite.md +0 -156
- package/skills/cometchat-android-v5-calls/references/video-controls.md +0 -87
- package/skills/cometchat-android-v5-calls/references/voip-calling.md +0 -526
- package/skills/cometchat-android-v5-components/SKILL.md +0 -431
- package/skills/cometchat-android-v5-core/SKILL.md +0 -486
- package/skills/cometchat-android-v5-customization/SKILL.md +0 -469
- package/skills/cometchat-android-v5-extensions/SKILL.md +0 -127
- package/skills/cometchat-android-v5-features/SKILL.md +0 -131
- package/skills/cometchat-android-v5-placement/SKILL.md +0 -277
- package/skills/cometchat-android-v5-production/SKILL.md +0 -95
- package/skills/cometchat-android-v5-push/SKILL.md +0 -619
- package/skills/cometchat-android-v5-testing/SKILL.md +0 -188
- package/skills/cometchat-android-v5-theming/SKILL.md +0 -167
- package/skills/cometchat-android-v5-troubleshooting/SKILL.md +0 -74
- package/skills/cometchat-android-v6/SKILL.md +0 -108
- package/skills/cometchat-android-v6-calls/references/add-calls-to-existing-chat.md +0 -97
- package/skills/cometchat-android-v6-calls/references/advanced-features.md +0 -87
- package/skills/cometchat-android-v6-calls/references/call-layouts.md +0 -100
- package/skills/cometchat-android-v6-calls/references/call-session.md +0 -163
- package/skills/cometchat-android-v6-calls/references/device-management.md +0 -172
- package/skills/cometchat-android-v6-calls/references/group-calls.md +0 -191
- package/skills/cometchat-android-v6-calls/references/idle-timeout.md +0 -110
- package/skills/cometchat-android-v6-calls/references/in-call-chat.md +0 -141
- package/skills/cometchat-android-v6-calls/references/raise-hand.md +0 -188
- package/skills/cometchat-android-v6-calls/references/ringing-integration.md +0 -176
- package/skills/cometchat-android-v6-calls/references/server-fcm-voip.md +0 -127
- package/skills/cometchat-android-v6-calls/references/share-invite.md +0 -105
- package/skills/cometchat-android-v6-compose-theming/SKILL.md +0 -250
- package/skills/cometchat-android-v6-kotlin-theming/SKILL.md +0 -233
- package/skills/cometchat-angular-calls/SKILL.md +0 -380
- package/skills/cometchat-angular-calls/references/add-calls-to-existing-chat.md +0 -96
- package/skills/cometchat-angular-calls/references/call-layouts.md +0 -123
- package/skills/cometchat-angular-calls/references/call-session.md +0 -167
- package/skills/cometchat-angular-calls/references/custom-ui.md +0 -239
- package/skills/cometchat-angular-calls/references/device-management.md +0 -154
- package/skills/cometchat-angular-calls/references/group-calls.md +0 -433
- package/skills/cometchat-angular-calls/references/idle-timeout.md +0 -161
- package/skills/cometchat-angular-calls/references/in-call-chat.md +0 -148
- package/skills/cometchat-angular-calls/references/lazy-loading-pitfalls.md +0 -205
- package/skills/cometchat-angular-calls/references/migration-v4-to-v5.md +0 -53
- package/skills/cometchat-angular-calls/references/ngzone-and-async-callbacks.md +0 -184
- package/skills/cometchat-angular-calls/references/picture-in-picture.md +0 -285
- package/skills/cometchat-angular-calls/references/raise-hand.md +0 -172
- package/skills/cometchat-angular-calls/references/recording-screen-share.md +0 -260
- package/skills/cometchat-angular-calls/references/ringing-integration.md +0 -133
- package/skills/cometchat-angular-calls/references/server-web-push-vapid.md +0 -100
- package/skills/cometchat-angular-calls/references/share-invite.md +0 -122
- package/skills/cometchat-angular-calls/references/virtual-background.md +0 -29
- package/skills/cometchat-angular-components/SKILL.md +0 -235
- package/skills/cometchat-angular-core/SKILL.md +0 -386
- package/skills/cometchat-angular-customization/SKILL.md +0 -616
- package/skills/cometchat-angular-features/SKILL.md +0 -329
- package/skills/cometchat-angular-patterns/SKILL.md +0 -256
- package/skills/cometchat-angular-placement/SKILL.md +0 -314
- package/skills/cometchat-angular-production/SKILL.md +0 -564
- package/skills/cometchat-angular-push/SKILL.md +0 -389
- package/skills/cometchat-angular-testing/SKILL.md +0 -605
- package/skills/cometchat-angular-theming/SKILL.md +0 -290
- package/skills/cometchat-angular-troubleshooting/SKILL.md +0 -364
- package/skills/cometchat-astro-patterns/SKILL.md +0 -708
- package/skills/cometchat-calls/SKILL.md +0 -364
- package/skills/cometchat-calls/references/use-case-broadcast.md +0 -199
- package/skills/cometchat-calls/references/use-case-marketplace.md +0 -157
- package/skills/cometchat-calls/references/use-case-support.md +0 -189
- package/skills/cometchat-calls/references/use-case-team.md +0 -180
- package/skills/cometchat-calls/references/use-case-telehealth.md +0 -153
- package/skills/cometchat-components/SKILL.md +0 -1142
- package/skills/cometchat-core/SKILL.md +0 -1048
- package/skills/cometchat-customization/SKILL.md +0 -580
- package/skills/cometchat-customization/references/component-catalog.md +0 -236
- package/skills/cometchat-features/SKILL.md +0 -861
- package/skills/cometchat-flutter-v5/SKILL.md +0 -173
- package/skills/cometchat-flutter-v5-calls/SKILL.md +0 -455
- package/skills/cometchat-flutter-v5-calls/references/add-calls-to-existing-chat.md +0 -193
- package/skills/cometchat-flutter-v5-calls/references/call-layouts.md +0 -103
- package/skills/cometchat-flutter-v5-calls/references/call-session.md +0 -285
- package/skills/cometchat-flutter-v5-calls/references/device-management.md +0 -124
- package/skills/cometchat-flutter-v5-calls/references/group-calls.md +0 -181
- package/skills/cometchat-flutter-v5-calls/references/idle-timeout.md +0 -99
- package/skills/cometchat-flutter-v5-calls/references/in-call-chat.md +0 -135
- package/skills/cometchat-flutter-v5-calls/references/migration-v4-to-v5.md +0 -210
- package/skills/cometchat-flutter-v5-calls/references/raise-hand.md +0 -255
- package/skills/cometchat-flutter-v5-calls/references/ringing-integration.md +0 -196
- package/skills/cometchat-flutter-v5-calls/references/server-push-bridge.md +0 -101
- package/skills/cometchat-flutter-v5-calls/references/share-invite.md +0 -148
- package/skills/cometchat-flutter-v5-conversations/SKILL.md +0 -118
- package/skills/cometchat-flutter-v5-core/SKILL.md +0 -239
- package/skills/cometchat-flutter-v5-customization/SKILL.md +0 -358
- package/skills/cometchat-flutter-v5-events/SKILL.md +0 -156
- package/skills/cometchat-flutter-v5-messages/SKILL.md +0 -259
- package/skills/cometchat-flutter-v5-production/SKILL.md +0 -216
- package/skills/cometchat-flutter-v5-push/SKILL.md +0 -285
- package/skills/cometchat-flutter-v5-testing/SKILL.md +0 -401
- package/skills/cometchat-flutter-v5-theming/SKILL.md +0 -161
- package/skills/cometchat-flutter-v5-troubleshooting/SKILL.md +0 -130
- package/skills/cometchat-flutter-v5-users-groups/SKILL.md +0 -152
- package/skills/cometchat-flutter-v6/SKILL.md +0 -446
- package/skills/cometchat-flutter-v6-calls/references/add-calls-to-existing-chat.md +0 -136
- package/skills/cometchat-flutter-v6-calls/references/advanced-features.md +0 -89
- package/skills/cometchat-flutter-v6-calls/references/call-layouts.md +0 -105
- package/skills/cometchat-flutter-v6-calls/references/call-session.md +0 -229
- package/skills/cometchat-flutter-v6-calls/references/device-management.md +0 -105
- package/skills/cometchat-flutter-v6-calls/references/group-calls.md +0 -190
- package/skills/cometchat-flutter-v6-calls/references/idle-timeout.md +0 -88
- package/skills/cometchat-flutter-v6-calls/references/in-call-chat.md +0 -125
- package/skills/cometchat-flutter-v6-calls/references/raise-hand.md +0 -275
- package/skills/cometchat-flutter-v6-calls/references/ringing-integration.md +0 -171
- package/skills/cometchat-flutter-v6-calls/references/server-push-bridge.md +0 -93
- package/skills/cometchat-flutter-v6-calls/references/share-invite.md +0 -102
- package/skills/cometchat-flutter-v6-conversations/SKILL.md +0 -238
- package/skills/cometchat-flutter-v6-messages/SKILL.md +0 -384
- package/skills/cometchat-flutter-v6-theming/SKILL.md +0 -293
- package/skills/cometchat-flutter-v6-users-groups/SKILL.md +0 -224
- package/skills/cometchat-ios/SKILL.md +0 -325
- package/skills/cometchat-ios-calls/references/add-calls-to-existing-chat.md +0 -181
- package/skills/cometchat-ios-calls/references/audio-modes-and-controls.md +0 -45
- package/skills/cometchat-ios-calls/references/avaudiosession-routing.md +0 -188
- package/skills/cometchat-ios-calls/references/call-layouts.md +0 -76
- package/skills/cometchat-ios-calls/references/call-session.md +0 -218
- package/skills/cometchat-ios-calls/references/callkit-and-pushkit.md +0 -357
- package/skills/cometchat-ios-calls/references/custom-ui.md +0 -322
- package/skills/cometchat-ios-calls/references/device-management.md +0 -171
- package/skills/cometchat-ios-calls/references/group-calls.md +0 -359
- package/skills/cometchat-ios-calls/references/idle-timeout.md +0 -184
- package/skills/cometchat-ios-calls/references/in-call-chat.md +0 -179
- package/skills/cometchat-ios-calls/references/migration-v4-to-v5.md +0 -196
- package/skills/cometchat-ios-calls/references/picture-in-picture.md +0 -246
- package/skills/cometchat-ios-calls/references/raise-hand.md +0 -26
- package/skills/cometchat-ios-calls/references/recording-screen-share.md +0 -252
- package/skills/cometchat-ios-calls/references/ringing-integration.md +0 -143
- package/skills/cometchat-ios-calls/references/server-apns-pushkit.md +0 -267
- package/skills/cometchat-ios-calls/references/share-invite.md +0 -148
- package/skills/cometchat-ios-calls/references/swiftui-uikit-hosting.md +0 -246
- package/skills/cometchat-ios-calls/references/voip-cert-setup.md +0 -166
- package/skills/cometchat-ios-theming/SKILL.md +0 -667
- package/skills/cometchat-native-bare-patterns/SKILL.md +0 -550
- package/skills/cometchat-native-calls/SKILL.md +0 -671
- package/skills/cometchat-native-calls/references/add-calls-to-existing-chat.md +0 -203
- package/skills/cometchat-native-calls/references/call-layouts.md +0 -125
- package/skills/cometchat-native-calls/references/call-session.md +0 -182
- package/skills/cometchat-native-calls/references/custom-ui.md +0 -254
- package/skills/cometchat-native-calls/references/device-management.md +0 -158
- package/skills/cometchat-native-calls/references/expo-vs-bare.md +0 -179
- package/skills/cometchat-native-calls/references/group-calls.md +0 -296
- package/skills/cometchat-native-calls/references/idle-timeout.md +0 -128
- package/skills/cometchat-native-calls/references/in-call-chat.md +0 -145
- package/skills/cometchat-native-calls/references/migration-v4-to-v5.md +0 -72
- package/skills/cometchat-native-calls/references/picture-in-picture.md +0 -276
- package/skills/cometchat-native-calls/references/raise-hand.md +0 -205
- package/skills/cometchat-native-calls/references/recording-screen-share.md +0 -169
- package/skills/cometchat-native-calls/references/ringing-integration.md +0 -118
- package/skills/cometchat-native-calls/references/server-push-bridge.md +0 -135
- package/skills/cometchat-native-calls/references/server-push-payloads.md +0 -193
- package/skills/cometchat-native-calls/references/share-invite.md +0 -146
- package/skills/cometchat-native-calls/references/voip-push-end-to-end.md +0 -319
- package/skills/cometchat-native-components/SKILL.md +0 -628
- package/skills/cometchat-native-core/SKILL.md +0 -679
- package/skills/cometchat-native-customization/SKILL.md +0 -716
- package/skills/cometchat-native-expo-patterns/SKILL.md +0 -552
- package/skills/cometchat-native-features/SKILL.md +0 -638
- package/skills/cometchat-native-placement/SKILL.md +0 -509
- package/skills/cometchat-native-production/SKILL.md +0 -700
- package/skills/cometchat-native-push/SKILL.md +0 -594
- package/skills/cometchat-native-testing/SKILL.md +0 -582
- package/skills/cometchat-native-theming/SKILL.md +0 -681
- package/skills/cometchat-native-troubleshooting/SKILL.md +0 -501
- package/skills/cometchat-nextjs-patterns/SKILL.md +0 -899
- package/skills/cometchat-placement/SKILL.md +0 -1360
- package/skills/cometchat-production/SKILL.md +0 -1027
- package/skills/cometchat-react-calls/SKILL.md +0 -752
- package/skills/cometchat-react-calls/references/add-calls-to-existing-chat.md +0 -146
- package/skills/cometchat-react-calls/references/call-layouts.md +0 -160
- package/skills/cometchat-react-calls/references/call-session.md +0 -235
- package/skills/cometchat-react-calls/references/custom-ui.md +0 -247
- package/skills/cometchat-react-calls/references/device-management.md +0 -210
- package/skills/cometchat-react-calls/references/group-calls.md +0 -257
- package/skills/cometchat-react-calls/references/idle-timeout.md +0 -188
- package/skills/cometchat-react-calls/references/in-call-chat.md +0 -212
- package/skills/cometchat-react-calls/references/migration-v4-to-v5.md +0 -170
- package/skills/cometchat-react-calls/references/picture-in-picture.md +0 -245
- package/skills/cometchat-react-calls/references/raise-hand.md +0 -238
- package/skills/cometchat-react-calls/references/recording-screen-share.md +0 -144
- package/skills/cometchat-react-calls/references/ringing-integration.md +0 -255
- package/skills/cometchat-react-calls/references/server-web-push-vapid.md +0 -241
- package/skills/cometchat-react-calls/references/share-invite.md +0 -175
- package/skills/cometchat-react-calls/references/testing-calls-on-web.md +0 -216
- package/skills/cometchat-react-calls/references/virtual-background.md +0 -32
- package/skills/cometchat-react-calls/references/voip-and-web-push.md +0 -165
- package/skills/cometchat-react-patterns/SKILL.md +0 -628
- package/skills/cometchat-react-push/SKILL.md +0 -634
- package/skills/cometchat-react-router-patterns/SKILL.md +0 -758
- package/skills/cometchat-react-testing/SKILL.md +0 -413
- package/skills/cometchat-theming/SKILL.md +0 -498
- package/skills/cometchat-troubleshooting/SKILL.md +0 -294
|
@@ -0,0 +1,932 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* lint-skills.mjs — Tier-0 static integrity gate (dependency-free, no network).
|
|
4
|
+
*
|
|
5
|
+
* The cheapest CI gate: it proves each shipped skill is structurally shippable
|
|
6
|
+
* BEFORE the more expensive catalog/compile tiers run. It fails the build on the
|
|
7
|
+
* machine-checkable invariants a hand-authored or drifted skill breaks:
|
|
8
|
+
*
|
|
9
|
+
* 1. Frontmatter present with name / description / license / compatibility
|
|
10
|
+
* and metadata.version (we publish versioned skills).
|
|
11
|
+
* 2. Frontmatter `name` === directory name (dir/name parity).
|
|
12
|
+
* 3. Every non-core skill carries the "Companion skills" block pointing at core
|
|
13
|
+
* (BUILD-ORDER.md DRY mechanism — non-core must not restate core).
|
|
14
|
+
* 4. Every skill dir maps to a routing entry: its capability slug is listed in
|
|
15
|
+
* peers.yaml under a peer's `skills:` (routing-as-data can't silently drift).
|
|
16
|
+
* 5. The thin router `cometchat/SKILL.md` exists (the entry the pack advertises).
|
|
17
|
+
*
|
|
18
|
+
* Plus three PACK-LEVEL durability gates (retroactive hardening of past AUDIT fixes
|
|
19
|
+
* that lived only as prose — the AUDIT-020→024 "correct-but-ungated silently regresses"
|
|
20
|
+
* lesson applied to the CLASS, so a regression fails CI instead of shipping):
|
|
21
|
+
* A. AUDIT-025 — no broken per-theme CSS selector in any ```css fence: the descendant
|
|
22
|
+
* `[data-theme=…] .cometchat {` targets a `.cometchat` NESTED in a `[data-theme]`
|
|
23
|
+
* ancestor, but the provider puts both on ONE div, so it matches nothing (silent
|
|
24
|
+
* no-op). Scans FENCES only (prose/anti-patterns may quote the bad form to warn).
|
|
25
|
+
* B. AUDIT-018 — shipped RULES.md carries NO skill-authoring/CI artifacts (a customer's
|
|
26
|
+
* agent must never be nudged to test/CI the app it built). Blocklist of authoring-only
|
|
27
|
+
* artifact tokens (predicate.mjs / verify.yml / verify:ci / "run the gates" / …).
|
|
28
|
+
* C. AUDIT-013 — no NEW `[v7-verify]` provenance marker ships: the known-intentional
|
|
29
|
+
* carry-over markers are allowlisted; any ADDITIONAL one fails (a regressed/unverified
|
|
30
|
+
* claim can't ship silently).
|
|
31
|
+
* D. AUDIT-029 — the follow-OS-theme RECIPE stays taught: core/references/theming.md must
|
|
32
|
+
* keep the matchMedia('(prefers-color-scheme: dark)') + change-listener + theme/data-theme
|
|
33
|
+
* sync recipe (the only way to follow the OS since the kit has no theme="system", AUDIT-004).
|
|
34
|
+
* Gates that the how-to is TAUGHT — NOT that OS-follow is the product default (open decision).
|
|
35
|
+
*
|
|
36
|
+
* Scope: the react-v7 pack (skills/cometchat-react-v7-*). Family-agnostic — reads
|
|
37
|
+
* peers.yaml, so a new family the skill-creator adds is linted automatically.
|
|
38
|
+
*
|
|
39
|
+
* Usage: node test-suite/scripts/lint-skills.mjs [--json]
|
|
40
|
+
* Exit: 0 all green · 1 one or more skills failed · 2 bad invocation.
|
|
41
|
+
*/
|
|
42
|
+
import { readFileSync, readdirSync, existsSync, statSync } from "node:fs";
|
|
43
|
+
import { join, dirname } from "node:path";
|
|
44
|
+
import { fileURLToPath } from "node:url";
|
|
45
|
+
|
|
46
|
+
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
47
|
+
const SKILLS_DIR = join(ROOT, "skills");
|
|
48
|
+
const PEERS = join(ROOT, "peers.yaml");
|
|
49
|
+
const JSON_OUT = process.argv.includes("--json");
|
|
50
|
+
|
|
51
|
+
// ── minimal YAML-ish readers (no dep) ────────────────────────────────────────
|
|
52
|
+
function frontmatter(txt) {
|
|
53
|
+
const m = txt.match(/^---\n([\s\S]*?)\n---/);
|
|
54
|
+
return m ? m[1] : null;
|
|
55
|
+
}
|
|
56
|
+
const topKey = (b, k) => (b.match(new RegExp("^" + k + ":\\s*(.+)$", "m")) || [])[1];
|
|
57
|
+
const nestedKey = (b, k) => (b.match(new RegExp("^\\s+" + k + ":\\s*(.+)$", "m")) || [])[1];
|
|
58
|
+
|
|
59
|
+
// Bare, platform-NEUTRAL skills that carry NO <platform>-<major> segment
|
|
60
|
+
// (NAMING.md): the cross-family skills (a11y, i18n) authored once for every family,
|
|
61
|
+
// and the onboarding JOURNEY gate (the DISCOVER→UNDERSTAND→PLAN front door). These are
|
|
62
|
+
// named `cometchat-<slug>` and are routed via peers.yaml's `gate:` / top-level pointers
|
|
63
|
+
// (the entry router ALWAYS routes to the gate first), NOT via a peer's `skills:` list —
|
|
64
|
+
// so they must be recognized here instead of failing the per-platform grammar.
|
|
65
|
+
const CROSS_FAMILY_SLUGS = new Set(["a11y", "i18n", "onboarding", "security", "compliance", "self-host", "moderation", "audit", "analytics"]);
|
|
66
|
+
// Competitor-migration skills are cross-family too (one per vendor, installed for every family):
|
|
67
|
+
// `cometchat-migrate-from-<vendor>`, routed via peers.yaml `migrations:` (NAMING.md).
|
|
68
|
+
const COMPETITOR_MIGRATION = /^cometchat-migrate-from-([a-z0-9]+)$/;
|
|
69
|
+
|
|
70
|
+
// The gates grafted below were authored for the ANGULAR family + the shared skills. Other
|
|
71
|
+
// families are maintained elsewhere; applied pack-wide they turned this repo's 15
|
|
72
|
+
// pre-existing problems into 123 by firing on 40+ android/ios/flutter/rn skills. A gate we
|
|
73
|
+
// wrote must not fail a family we do not own — same scoping the token and fence gates need.
|
|
74
|
+
const OWNED_SKILL = (d) =>
|
|
75
|
+
/^cometchat-angular-v5-/.test(d) || d === "cometchat" || d === "cometchat-onboarding" || COMPETITOR_MIGRATION.test(d);
|
|
76
|
+
|
|
77
|
+
// Parse the capability slugs each peer routes to (peers.yaml `skills: [...]`), plus the
|
|
78
|
+
// platform-neutral routing pointers (`gate:` and any bare cross-family targets).
|
|
79
|
+
function routedSlugs() {
|
|
80
|
+
if (!existsSync(PEERS)) return { platforms: [], slugs: new Set(), neutral: new Set() };
|
|
81
|
+
const y = readFileSync(PEERS, "utf8");
|
|
82
|
+
const slugs = new Set();
|
|
83
|
+
const neutral = new Set();
|
|
84
|
+
const platforms = [];
|
|
85
|
+
for (const m of y.matchAll(/^\s*-\s*platform:\s*(\S+)/gm)) platforms.push(m[1].trim());
|
|
86
|
+
for (const m of y.matchAll(/^\s*skills:\s*\[([^\]]*)\]/gm)) {
|
|
87
|
+
for (const s of m[1].split(",")) { const t = s.trim(); if (t) slugs.add(t); }
|
|
88
|
+
}
|
|
89
|
+
// `gate: cometchat-onboarding` (and any future bare cross-family pointer) → routed.
|
|
90
|
+
const gate = (y.match(/^\s*gate:\s*(\S+)/m) || [])[1];
|
|
91
|
+
if (gate) neutral.add(gate.trim());
|
|
92
|
+
// `migrations:` entries (`skill: cometchat-migrate-from-<vendor>`) → routed, platform-neutral.
|
|
93
|
+
const mig = (y.match(/^migrations:\s*\n([\s\S]*?)(?=^\S)/m) || [])[1] || "";
|
|
94
|
+
for (const m of mig.matchAll(/^\s*skill:\s*(\S+)/gm)) neutral.add(m[1].trim());
|
|
95
|
+
// `enterprise:` cross-family skills (bare `- cometchat-<slug>`) → routed, platform-neutral.
|
|
96
|
+
const ent = (y.match(/^enterprise:\s*\n([\s\S]*?)(?=^\S)/m) || [])[1] || "";
|
|
97
|
+
for (const m of ent.matchAll(/^\s*-\s*(\S+)/gm)) neutral.add(m[1].trim());
|
|
98
|
+
const cf = (y.match(/^crossFamily:\s*\n([\s\S]*?)(?=^\S)/m) || [])[1] || "";
|
|
99
|
+
for (const m of cf.matchAll(/^\s*-\s*(\S+)/gm)) neutral.add(m[1].trim());
|
|
100
|
+
return { platforms, slugs, neutral };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// react-v7-core → {platform:"react", major:"v7", slug:"core"}
|
|
104
|
+
// android-v6-builder-settings → {platform:"android", major:"v6", slug:"builder-settings"}
|
|
105
|
+
// (SKILL-INVENTORY sanctions HYPHENATED type slugs — the android extras `builder-settings`,
|
|
106
|
+
// `events`, `extensions` — so the slug group allows internal hyphens, not just [a-z]+.)
|
|
107
|
+
// cometchat-onboarding / cometchat-a11y → {crossFamily:true, slug:"onboarding"}
|
|
108
|
+
// The sanctioned platforms, from NAMING.md. Longest FIRST so `react-native` is matched before
|
|
109
|
+
// `react` would swallow it. Anchoring on this list (rather than a bare [a-z]+ segment) is what
|
|
110
|
+
// lets a HYPHENATED platform exist at all.
|
|
111
|
+
// AUDIT-167: `js` is the headless web-JavaScript SDK leaf platform — the framework-agnostic SDK
|
|
112
|
+
// families that have no UI Kit and thus no `<framework>` platform (chat `core-v4` → the parked
|
|
113
|
+
// `cometchat-js-v4-sdk`; calls `calls-v5` → `cometchat-js-v5-calls`). NAMING.md line 9 lists it.
|
|
114
|
+
const PLATFORMS = ["react-native", "react", "angular", "android", "flutter", "ios", "js"];
|
|
115
|
+
|
|
116
|
+
// Previously: /^cometchat-([a-z]+)-(v\d+)-([a-z]+)$/ — which could not express React Native in
|
|
117
|
+
// THREE ways at once: [a-z]+ forbids the hyphen in `react-native`, the major was MANDATORY
|
|
118
|
+
// (NAMING.md: "included ONLY when two majors coexist for that platform"), and the slug forbade
|
|
119
|
+
// the hyphen in `expo-patterns` / `bare-patterns` (the two-patterns split SKILL-INVENTORY.md
|
|
120
|
+
// sanctions for react-native specifically). So every correctly-named RN skill was rejected.
|
|
121
|
+
// Same class as the six --family hardcodes (AUDIT-091/087): a rule written for one family.
|
|
122
|
+
function classify(dir) {
|
|
123
|
+
const c = dir.match(/^cometchat-([a-z0-9-]+)$/);
|
|
124
|
+
if (c && CROSS_FAMILY_SLUGS.has(c[1])) return { crossFamily: true, slug: c[1] };
|
|
125
|
+
const v = dir.match(COMPETITOR_MIGRATION);
|
|
126
|
+
if (v) return { crossFamily: true, slug: `migrate-from-${v[1]}` };
|
|
127
|
+
for (const p of PLATFORMS) {
|
|
128
|
+
let m = dir.match(new RegExp(`^cometchat-${p}-(v\\d+)-([a-z][a-z-]*)$`));
|
|
129
|
+
if (m) return { platform: p, major: m[1], slug: m[2] };
|
|
130
|
+
m = dir.match(new RegExp(`^cometchat-${p}-([a-z][a-z-]*)$`));
|
|
131
|
+
if (m && !/^v\d+$/.test(m[1])) return { platform: p, major: null, slug: m[1] };
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function lintSkill(dir, routed) {
|
|
137
|
+
const errs = [];
|
|
138
|
+
const file = join(SKILLS_DIR, dir, "SKILL.md");
|
|
139
|
+
if (!existsSync(file)) return [`${dir}: missing SKILL.md`];
|
|
140
|
+
const txt = readFileSync(file, "utf8");
|
|
141
|
+
|
|
142
|
+
const b = frontmatter(txt);
|
|
143
|
+
if (!b) return [`${dir}: missing YAML frontmatter block`];
|
|
144
|
+
|
|
145
|
+
const name = (topKey(b, "name") || "").trim();
|
|
146
|
+
if (!name) errs.push("frontmatter.name missing");
|
|
147
|
+
else if (name !== dir) errs.push(`frontmatter.name "${name}" != dir "${dir}"`);
|
|
148
|
+
if (!topKey(b, "description")) errs.push("frontmatter.description missing");
|
|
149
|
+
if (!topKey(b, "license")) errs.push("frontmatter.license missing");
|
|
150
|
+
if (!topKey(b, "compatibility")) errs.push("frontmatter.compatibility missing (version pins)");
|
|
151
|
+
if (!nestedKey(b, "version")) errs.push("frontmatter.metadata.version missing");
|
|
152
|
+
|
|
153
|
+
const cls = classify(dir);
|
|
154
|
+
if (!cls) {
|
|
155
|
+
errs.push(`name does not match the NAMING grammar cometchat-<platform>-<major>-<slug> (or a sanctioned bare cross-family/gate name)`);
|
|
156
|
+
} else if (cls.crossFamily) {
|
|
157
|
+
// Platform-NEUTRAL skill (a11y/i18n/onboarding): no per-platform Companion→core
|
|
158
|
+
// block (the section spine OMITS it for cross-family types — its companion is
|
|
159
|
+
// whichever family core the plan/context names). Routed via a neutral pointer
|
|
160
|
+
// (peers.yaml `gate:` / entry), not a peer's `skills:` list.
|
|
161
|
+
if (!routed.neutral.has(dir)) {
|
|
162
|
+
errs.push(`cross-family skill "${dir}" not routed in peers.yaml (add it as gate:, a neutral pointer, or a migrations: skill)`);
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
// Companion block — required on every non-core per-platform skill (BUILD-ORDER DRY rule).
|
|
166
|
+
// Exception: an `sdk` skill is the ENTRY/core of a HEADLESS family (self-contained init/login);
|
|
167
|
+
// it needs a Companion→core block ONLY if that platform ALSO ships a UI-Kit `core` skill. A
|
|
168
|
+
// standalone SDK family (e.g. cometchat-js-v4-sdk) has no core to reference — it IS the entry.
|
|
169
|
+
const coreExists = existsSync(join(SKILLS_DIR, `cometchat-${cls.platform}-${cls.major}-core`));
|
|
170
|
+
// `sdk` OR a `<artifact>-sdk` slug (calls-sdk): a platform can ship two headless entries — the
|
|
171
|
+
// chat SDK (`cometchat-android-v5-sdk`) AND the calls SDK (`cometchat-android-v5-calls-sdk`) —
|
|
172
|
+
// and the second cannot reuse the bare slug. Both are coreless entries (AUDIT: android-calls-v5).
|
|
173
|
+
const isSdkEntry = cls.slug === "sdk" || cls.slug.endsWith("-sdk");
|
|
174
|
+
const needsCompanion = cls.slug !== "core" && (!isSdkEntry || coreExists);
|
|
175
|
+
if (needsCompanion) {
|
|
176
|
+
// The core's name must be rebuilt from the parts that EXIST. Interpolating a null major
|
|
177
|
+
// produced "cometchat-react-native-null-core", which can never match — so every non-core
|
|
178
|
+
// skill in a single-major family failed this check no matter how correct its block was.
|
|
179
|
+
const coreName = `cometchat-${cls.platform}${cls.major ? `-${cls.major}` : ""}-core`;
|
|
180
|
+
const hasCompanion = /##\s*Companion skills/i.test(txt) && new RegExp(coreName).test(txt);
|
|
181
|
+
if (!hasCompanion) errs.push("non-core skill missing the Companion-skills → core block");
|
|
182
|
+
}
|
|
183
|
+
// Routing-as-data: the slug must be declared in peers.yaml.
|
|
184
|
+
if (!routed.slugs.has(cls.slug)) {
|
|
185
|
+
errs.push(`slug "${cls.slug}" not routed in peers.yaml (add to a peer's skills: [...])`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return errs.map((e) => `${dir}: ${e}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ── pack-level durability gates (retroactive hardening of prose-only AUDIT fixes) ──
|
|
192
|
+
|
|
193
|
+
// Recursively collect .md files under a dir (skills tree).
|
|
194
|
+
function mdFiles(dir, out = []) {
|
|
195
|
+
if (!existsSync(dir)) return out;
|
|
196
|
+
for (const name of readdirSync(dir)) {
|
|
197
|
+
const p = join(dir, name);
|
|
198
|
+
if (statSync(p).isDirectory()) mdFiles(p, out);
|
|
199
|
+
else if (name.endsWith(".md")) out.push(p);
|
|
200
|
+
}
|
|
201
|
+
return out;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Extract the bodies of ```css / ```scss fenced code blocks from a markdown string.
|
|
205
|
+
function cssFences(txt) {
|
|
206
|
+
const blocks = [];
|
|
207
|
+
const re = /```(?:css|scss)\b[^\n]*\n([\s\S]*?)```/gi;
|
|
208
|
+
let m;
|
|
209
|
+
while ((m = re.exec(txt))) blocks.push(m[1]);
|
|
210
|
+
return blocks;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// GATE A (AUDIT-025): a per-theme override written as the DESCENDANT selector
|
|
214
|
+
// `[data-theme=…] .cometchat {` (or `.cometchat,`) matches nothing — the provider
|
|
215
|
+
// puts data-theme + .cometchat on ONE element. `.cometchat[data-theme=…]` (same
|
|
216
|
+
// element) and `[data-theme=…] .cometchat-<bem>` (kit internal descendant) are FINE.
|
|
217
|
+
// FENCE-ONLY so anti-patterns.md / prose can still quote the bad form to warn about it.
|
|
218
|
+
const BAD_THEME_SELECTOR =
|
|
219
|
+
/\[data-theme\s*=\s*["']?[a-z]+["']?\s*\]\s+\.cometchat(?=[\s,{])/i;
|
|
220
|
+
function gateThemeSelector(files) {
|
|
221
|
+
const errs = [];
|
|
222
|
+
for (const f of files) {
|
|
223
|
+
for (const css of cssFences(readFileSync(f, "utf8"))) {
|
|
224
|
+
if (BAD_THEME_SELECTOR.test(css)) {
|
|
225
|
+
errs.push(
|
|
226
|
+
`${f.replace(ROOT + "/", "")}: broken per-theme selector in a css fence — ` +
|
|
227
|
+
`\`[data-theme=…] .cometchat {…}\` matches nothing (data-theme + .cometchat are the SAME div). ` +
|
|
228
|
+
`Use \`.cometchat[data-theme="dark"]\` (same element) or \`[data-theme="dark"] .cometchat-<bem>\` (kit internal). (AUDIT-025)`
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return errs;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// GATE B (AUDIT-018): the shipped RULES.md must be integration-only — it must NOT carry
|
|
237
|
+
// skill-authoring / CI artifacts (which read to a customer's agent as "test the app").
|
|
238
|
+
// Blocklist is artifact tokens that ONLY appear in authoring content — NOT generic words
|
|
239
|
+
// like "test"/"Playwright" (the shipped "do NOT test" rule legitimately uses those).
|
|
240
|
+
const RULES_FORBIDDEN = [
|
|
241
|
+
"predicate.mjs", "verify.yml", "verify:ci", "verify:predicate",
|
|
242
|
+
"smoke-runner", "run the gates", "CI-enforced", "CI-green",
|
|
243
|
+
"reviewer smoke", "reviewer's runtime smoke", "shippability",
|
|
244
|
+
];
|
|
245
|
+
function gateShippedRules() {
|
|
246
|
+
const errs = [];
|
|
247
|
+
const rulesPath = join(ROOT, "RULES.md");
|
|
248
|
+
if (!existsSync(rulesPath)) return [`RULES.md (shipped) missing`];
|
|
249
|
+
const txt = readFileSync(rulesPath, "utf8").toLowerCase();
|
|
250
|
+
for (const tok of RULES_FORBIDDEN) {
|
|
251
|
+
if (txt.includes(tok.toLowerCase())) {
|
|
252
|
+
errs.push(
|
|
253
|
+
`RULES.md: contains authoring/CI artifact "${tok}" — the shipped RULES must be ` +
|
|
254
|
+
`integration-only (no CI/authoring/reviewer content; a customer's agent must not be ` +
|
|
255
|
+
`nudged to test the app it built). Authoring rules live in the factory references/RULES.md. (AUDIT-018)`
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return errs;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// GATE C (AUDIT-013): no NEW `[v7-verify]` provenance marker ships. The known-intentional
|
|
263
|
+
// v6-carry-over markers are allowlisted by (file-relative-path : substring); any ADDITIONAL
|
|
264
|
+
// `[v7-verify]` in the shipped skills fails, so a regressed/unverified claim can't ship.
|
|
265
|
+
const V7VERIFY_ALLOW = [
|
|
266
|
+
// path suffix ⇒ how many [v7-verify] markers are sanctioned in that file
|
|
267
|
+
{ path: "skills/cometchat-react-v7-core/SKILL.md", count: 1 }, // the Ground-truth callout DEFINING the marker
|
|
268
|
+
{ path: "skills/cometchat-react-v7-core/references/setup-credentials.md", count: 1 }, // initFromSettings carry-over
|
|
269
|
+
];
|
|
270
|
+
function gateV7VerifyMarkers(files) {
|
|
271
|
+
const errs = [];
|
|
272
|
+
for (const f of files) {
|
|
273
|
+
const rel = f.replace(ROOT + "/", "");
|
|
274
|
+
const n = (readFileSync(f, "utf8").match(/\[v7-verify\]/g) || []).length;
|
|
275
|
+
if (n === 0) continue;
|
|
276
|
+
const allow = V7VERIFY_ALLOW.find((a) => rel.endsWith(a.path));
|
|
277
|
+
const allowed = allow ? allow.count : 0;
|
|
278
|
+
if (n > allowed) {
|
|
279
|
+
errs.push(
|
|
280
|
+
`${rel}: ${n} [v7-verify] marker(s) but only ${allowed} allowlisted — a NEW unverified ` +
|
|
281
|
+
`v6-carry-over marker cannot ship (verify it against v7 and remove the marker, or ` +
|
|
282
|
+
`allowlist it in lint-skills.mjs with a reason). (AUDIT-013)`
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return errs;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// GATE D (AUDIT-029): the follow-OS-theme RECIPE must stay TAUGHT. The kit has no
|
|
290
|
+
// theme="system" and never reads the OS (AUDIT-004), so following the OS light/dark
|
|
291
|
+
// setting is a host-side recipe: sync CometChatProvider's theme (→ the wrapper's
|
|
292
|
+
// data-theme) to window.matchMedia('(prefers-color-scheme: dark)') with a change
|
|
293
|
+
// listener. That how-to lives in core/references/theming.md as a standalone recipe.
|
|
294
|
+
// Because it's prose+a fenced hook (the AUDIT-020→026 "correct-but-ungated silently
|
|
295
|
+
// regresses" class), assert its load-bearing tokens are present so an edit can't quietly
|
|
296
|
+
// delete the recipe and leave only the "kit ignores the OS" pitfall with no fix.
|
|
297
|
+
// NOTE: this gates that the RECIPE is TAUGHT — it does NOT assert OS-follow is the
|
|
298
|
+
// product DEFAULT (that remains an OPEN product decision, AUDIT-004; see AUDIT.md).
|
|
299
|
+
const OS_THEME_RECIPE_FILE = "skills/cometchat-react-v7-core/references/theming.md";
|
|
300
|
+
const OS_THEME_REQUIRED = [
|
|
301
|
+
{ re: /matchMedia\s*[?.]*\s*\(\s*["']\(prefers-color-scheme:\s*dark\)["']\s*\)/i,
|
|
302
|
+
what: "matchMedia('(prefers-color-scheme: dark)') detection" },
|
|
303
|
+
{ re: /addEventListener\s*\(\s*["']change["']/i,
|
|
304
|
+
what: "a 'change' listener so it live-updates on OS theme flip" },
|
|
305
|
+
{ re: /data-theme|CometChatProvider[^\n]*theme=|theme=\{[^\n]*[Ss]ystem|useSystemTheme/i,
|
|
306
|
+
what: "syncing the result to CometChatProvider's theme prop / data-theme" },
|
|
307
|
+
];
|
|
308
|
+
function gateOsThemeRecipe() {
|
|
309
|
+
const p = join(ROOT, OS_THEME_RECIPE_FILE);
|
|
310
|
+
if (!existsSync(p)) {
|
|
311
|
+
return [`${OS_THEME_RECIPE_FILE}: missing — the follow-OS-theme recipe file is gone (AUDIT-029/004)`];
|
|
312
|
+
}
|
|
313
|
+
const txt = readFileSync(p, "utf8");
|
|
314
|
+
const missing = OS_THEME_REQUIRED.filter((r) => !r.re.test(txt)).map((r) => r.what);
|
|
315
|
+
if (missing.length) {
|
|
316
|
+
return [
|
|
317
|
+
`${OS_THEME_RECIPE_FILE}: the follow-OS-theme recipe lost — missing ${missing.map((m) => `[${m}]`).join(", ")}. ` +
|
|
318
|
+
`The kit has no theme="system" (AUDIT-004), so this host-side matchMedia→data-theme recipe is the only way to follow the OS ` +
|
|
319
|
+
`light/dark setting; it must stay taught. Restore the recipe (or update this gate if it moved). (AUDIT-029)`
|
|
320
|
+
];
|
|
321
|
+
}
|
|
322
|
+
return [];
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// GATE E (AUDIT-084): the core INIT recipe must use `initFromSettings` (telemetry — the UI Kit/SDK
|
|
326
|
+
// persist integrationSource="ai-agent" ONLY on that path; the classic builder init does not). The
|
|
327
|
+
// reviewer's per-feature emit never includes init (harness-provided), so NO G0–G7 gate sees it — this
|
|
328
|
+
// static check is the only guard against a regression back to the classic init(). Framework-agnostic:
|
|
329
|
+
// every platform's <core>/references/lifecycle.md (+ core SKILL.md) that ships an init recipe must
|
|
330
|
+
// reference initFromSettings, and the canonical lifecycle recipe must not use the classic init(.
|
|
331
|
+
function gateInitTelemetry() {
|
|
332
|
+
const errs = [];
|
|
333
|
+
const allDirs = readdirSync(SKILLS_DIR, { withFileTypes: true })
|
|
334
|
+
.filter((d) => d.isDirectory())
|
|
335
|
+
.map((d) => d.name);
|
|
336
|
+
// Skills that teach an init recipe: UI-Kit cores AND the headless / SDK-fallback skills (`-sdk`).
|
|
337
|
+
// Both must attribute via initFromSettings, never the classic builder init. The check was
|
|
338
|
+
// originally core-only, which let a `-sdk` fallback skill (cometchat-react-native-sdk) ship the
|
|
339
|
+
// classic `CometChat.init(APP_ID, settings)` in its golden path — a `-sdk` skill is exactly the
|
|
340
|
+
// SDK-fallback surface (e.g. rn-uikit-v5.sdkSkill), so it must obey the same rule. (AUDIT-084; ENG-39197.)
|
|
341
|
+
const initSkills = allDirs.filter((d) => d.endsWith("-core") || d.endsWith("-sdk"));
|
|
342
|
+
for (const dir of initSkills) {
|
|
343
|
+
for (const rel of [`${dir}/references/lifecycle.md`, `${dir}/SKILL.md`]) {
|
|
344
|
+
const p = join(SKILLS_DIR, rel);
|
|
345
|
+
if (!existsSync(p)) continue;
|
|
346
|
+
const txt = readFileSync(p, "utf8");
|
|
347
|
+
if (!/CometChatUIKit\.init|initCometChat|CometChat\.init\(/.test(txt)) continue; // only files that show an init recipe
|
|
348
|
+
if (!/initFromSettings/.test(txt)) {
|
|
349
|
+
errs.push(
|
|
350
|
+
`skills/${rel}: shows an init recipe but does NOT use initFromSettings — telemetry ` +
|
|
351
|
+
`attribution (integrationSource="ai-agent") requires CometChatUIKit.initFromSettings / ` +
|
|
352
|
+
`CometChat.initFromSettings, not the classic builder init. (AUDIT-084)`
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
// UI-Kit cores only: the baked lifecycle recipe must not default to the classic CometChatUIKit.init(.
|
|
358
|
+
const coreDirs = allDirs.filter((d) => d.endsWith("-core"));
|
|
359
|
+
for (const dir of coreDirs) {
|
|
360
|
+
const lc = join(SKILLS_DIR, `${dir}/references/lifecycle.md`);
|
|
361
|
+
if (existsSync(lc) && /CometChatUIKit\.init\(/.test(readFileSync(lc, "utf8"))) {
|
|
362
|
+
errs.push(
|
|
363
|
+
`skills/${dir}/references/lifecycle.md: the baked init recipe uses the classic ` +
|
|
364
|
+
`CometChatUIKit.init( — the default init must be initFromSettings (telemetry). (AUDIT-084)`
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return errs;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// GATE F: every reference to one of the PACK'S OWN generated artifacts must resolve.
|
|
372
|
+
//
|
|
373
|
+
// The iOS skills told the agent to verify each symbol against `catalogs/ios-v5.json` — a path
|
|
374
|
+
// that does not exist anywhere (the catalog ships at test-suite/catalogs/ios-v5.json). Eight
|
|
375
|
+
// references across six files, and the pack's own anti-hallucination control therefore pointed
|
|
376
|
+
// at nothing: an agent told to "check the catalog before emitting a symbol" would find no
|
|
377
|
+
// catalog and carry on emitting. A control that silently no-ops is worse than no control,
|
|
378
|
+
// because the skill reads as if it is guarded.
|
|
379
|
+
//
|
|
380
|
+
// Scoped DELIBERATELY NARROWLY to repo-root artifact roots. A general "every path in a skill
|
|
381
|
+
// must exist" rule is unworkable: skills legitimately name paths in the USER'S project
|
|
382
|
+
// (.cometchat/config.json, src/vite-env.d.ts, node_modules/...) which are absent here by
|
|
383
|
+
// definition, and many intra-skill links are written relative to the skill root rather than the
|
|
384
|
+
// containing file. Both produce noise, and noise in a gate is how a gate gets ignored. These
|
|
385
|
+
// roots are unambiguous: they exist in this repo or they are a defect.
|
|
386
|
+
// Root-level oracles (`features.ios-v5.json`, `contracts.ios-v5.json`, `peers.yaml`) are matched
|
|
387
|
+
// too. The first version of this gate only knew directory-rooted paths, so a bare filename slipped
|
|
388
|
+
// through — which is exactly how the iOS feature/contract oracles ended up referenced by a skill
|
|
389
|
+
// while missing from package.json `files`.
|
|
390
|
+
const ARTIFACT_REF = /`((?:test-suite|registry|catalogs|golden|\.claude)\/[A-Za-z0-9_.\-/]+\.\w+|(?:features|contracts|peers)[A-Za-z0-9_.\-]*\.(?:json|yaml))`/g;
|
|
391
|
+
|
|
392
|
+
// Is a repo-relative path covered by package.json "files"? Directory entries cover their subtree.
|
|
393
|
+
function shipsInPackage(ref) {
|
|
394
|
+
try {
|
|
395
|
+
const allow = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8")).files || [];
|
|
396
|
+
return allow.some((f) => ref === f || ref.startsWith(f.replace(/\/$/, "") + "/"));
|
|
397
|
+
} catch { return true; } // no package.json to judge against: do not invent a failure
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// GATE G: every SHIPPED platform must be discoverable in the distribution metadata.
|
|
401
|
+
//
|
|
402
|
+
// Three separate defects in one pass were the same thing — metadata still describing a React-only
|
|
403
|
+
// pack after iOS shipped: marketplace.json's description and keywords, package.json's description,
|
|
404
|
+
// and finally package.json's keywords. Each was found by a human reading a different file. A
|
|
405
|
+
// platform nobody can search for is invisible however good its skills are, so this is checked
|
|
406
|
+
// rather than remembered.
|
|
407
|
+
//
|
|
408
|
+
// Keyed off peers.yaml, so onboarding a platform makes this fail until its metadata is updated —
|
|
409
|
+
// which is the moment it matters, not months later. Parked peers are exempt: they ship no skills.
|
|
410
|
+
function gatePlatformMetadata() {
|
|
411
|
+
const errs = [];
|
|
412
|
+
let peers = "";
|
|
413
|
+
try { peers = readFileSync(PEERS, "utf8"); } catch { return []; }
|
|
414
|
+
|
|
415
|
+
// platform blocks, minus parked ones
|
|
416
|
+
const blocks = peers.split(/^\s*-\s*platform:/m).slice(1);
|
|
417
|
+
const shipped = [];
|
|
418
|
+
for (const b of blocks) {
|
|
419
|
+
if (/^\s*parked:\s*true/m.test(b)) continue;
|
|
420
|
+
const name = (b.match(/^\s*(\S+)/) || [])[1];
|
|
421
|
+
if (name) shipped.push(name.trim());
|
|
422
|
+
}
|
|
423
|
+
if (!shipped.length) return [];
|
|
424
|
+
|
|
425
|
+
const read = (rel) => { try { return JSON.parse(readFileSync(join(ROOT, rel), "utf8")); } catch { return null; } };
|
|
426
|
+
const pkg = read("package.json");
|
|
427
|
+
const mkt = read(".claude-plugin/marketplace.json");
|
|
428
|
+
|
|
429
|
+
const check = (label, keywords, text) => {
|
|
430
|
+
const hay = `${(keywords || []).join(" ")} ${text || ""}`.toLowerCase();
|
|
431
|
+
for (const p of shipped) {
|
|
432
|
+
if (!hay.includes(p.toLowerCase())) {
|
|
433
|
+
errs.push(`${label}: ships a '${p}' platform (peers.yaml) but never names it — a platform absent from the distribution metadata cannot be found by anyone searching for it`);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
if (pkg) check("package.json", pkg.keywords, pkg.description);
|
|
438
|
+
if (mkt && mkt.plugins && mkt.plugins[0]) {
|
|
439
|
+
const pl = mkt.plugins[0];
|
|
440
|
+
check(".claude-plugin/marketplace.json", pl.keywords, `${mkt.description || ""} ${pl.description || ""}`);
|
|
441
|
+
}
|
|
442
|
+
return errs;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function gateArtifactRefs(files) {
|
|
446
|
+
const errs = [];
|
|
447
|
+
for (const f of files) {
|
|
448
|
+
const txt = readFileSync(f, "utf8");
|
|
449
|
+
for (const m of txt.matchAll(ARTIFACT_REF)) {
|
|
450
|
+
const ref = m[1];
|
|
451
|
+
if (!existsSync(join(ROOT, ref))) {
|
|
452
|
+
errs.push(`${f.replace(ROOT + "/", "")}: references \`${ref}\`, which does not exist — ` +
|
|
453
|
+
`a skill pointing at a missing pack artifact silently no-ops the check it describes`);
|
|
454
|
+
} else if (!shipsInPackage(ref)) {
|
|
455
|
+
// EXISTING IS NOT ENOUGH — it has to reach the user. `features.ios-v5.json` and
|
|
456
|
+
// `contracts.ios-v5.json` were present in the repo and named by a skill as its oracle, but
|
|
457
|
+
// absent from package.json `files`, so an INSTALLED pack would not contain them: the skill
|
|
458
|
+
// would point at an oracle that exists only on our machines. That is the same silent no-op
|
|
459
|
+
// as a missing file, one step later in the pipeline.
|
|
460
|
+
errs.push(`${f.replace(ROOT + "/", "")}: references \`${ref}\`, which exists but is NOT in ` +
|
|
461
|
+
`package.json "files" — it would not ship, so the reference dies on an installed pack`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return errs;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// GATE F (AUDIT-085): SECTION-SPINE conformance — templates/_SECTION-SPINE.md fixes the
|
|
469
|
+
// heading set every shipped skill shares. Matched by PREFIX so a qualifying suffix stays
|
|
470
|
+
// legal (`## Common pitfalls (top 4 — …)`). `Prerequisites & install` is CONDITIONAL:
|
|
471
|
+
// required only when the skill ships its own `npm install` (restating core's is the DRY
|
|
472
|
+
// violation). `Common pitfalls` is n/a for troubleshooting, which is all symptom→fix.
|
|
473
|
+
function gateSectionSpine() {
|
|
474
|
+
const errs = [];
|
|
475
|
+
const dirs = readdirSync(SKILLS_DIR, { withFileTypes: true })
|
|
476
|
+
.filter((d) => d.isDirectory() && OWNED_SKILL(d.name) && d.name.startsWith("cometchat-"))
|
|
477
|
+
.map((d) => d.name);
|
|
478
|
+
for (const dir of dirs) {
|
|
479
|
+
const f = join(SKILLS_DIR, dir, "SKILL.md");
|
|
480
|
+
if (!existsSync(f)) continue;
|
|
481
|
+
const txt = readFileSync(f, "utf8");
|
|
482
|
+
const h2 = [...txt.matchAll(/^## (.+)$/gm)].map((m) => m[1].trim());
|
|
483
|
+
const has = (want) => h2.some((h) => h === want || h.startsWith(want + " ") || h.startsWith(want + "("));
|
|
484
|
+
const isCore = dir.endsWith("-core");
|
|
485
|
+
const isRouter = dir === "cometchat";
|
|
486
|
+
const isJourney = dir === "cometchat-onboarding";
|
|
487
|
+
if (isRouter || isJourney) continue; // router + onboarding have their own shapes
|
|
488
|
+
|
|
489
|
+
if (!isCore && !has("Companion skills")) errs.push(`${dir}: missing "## Companion skills" (spine; non-core)`);
|
|
490
|
+
if (!has("Use this skill when") && !has("Use when")) errs.push(`${dir}: missing "## Use this skill when" (spine)`);
|
|
491
|
+
if (!has("Verify it works")) errs.push(`${dir}: missing "## Verify it works" (spine)`);
|
|
492
|
+
if (!dir.endsWith("-troubleshooting") && !has("Common pitfalls")) {
|
|
493
|
+
errs.push(`${dir}: missing "## Common pitfalls" (spine; n/a only for troubleshooting)`);
|
|
494
|
+
}
|
|
495
|
+
// conditional: ships its own install command ⇒ must declare the section
|
|
496
|
+
if (/^```bash\n[^`]*npm install /m.test(txt) && !has("Prerequisites & install")) {
|
|
497
|
+
errs.push(`${dir}: ships an \`npm install\` but has no "## Prerequisites & install" (spine, conditional)`);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return errs;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
// GATE G: a preview-deploy DOCS_BASE is allowed (docs-maximal work happens against a
|
|
505
|
+
// Mintlify preview) but must carry a `SWAP-FOR-PROD` marker within 3 lines, so a release
|
|
506
|
+
// can grep for every one. Shipping an unlabelled preview URL 404s every fetch once the
|
|
507
|
+
// docs branch is deleted.
|
|
508
|
+
function gatePreviewDocsBase() {
|
|
509
|
+
const errs = [];
|
|
510
|
+
for (const f of mdFiles(SKILLS_DIR)) {
|
|
511
|
+
if (!OWNED_SKILL((f.slice(SKILLS_DIR.length + 1).split('/')[0]))) continue;
|
|
512
|
+
const lines = readFileSync(f, "utf8").split("\n");
|
|
513
|
+
lines.forEach((line, i) => {
|
|
514
|
+
if (!/mintlify\.site/.test(line)) return;
|
|
515
|
+
const near = lines.slice(Math.max(0, i - 1), i + 4).join("\n");
|
|
516
|
+
if (!/SWAP-FOR-PROD/.test(near)) {
|
|
517
|
+
errs.push(
|
|
518
|
+
`${f.replace(ROOT + "/", "")}:${i + 1}: preview docs base (mintlify.site) with no ` +
|
|
519
|
+
`SWAP-FOR-PROD marker within 3 lines — label it so it cannot ship unnoticed`
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
return errs;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
// GATE H (AUDIT-091): a features skill's description must cover its oracle's vocabulary.
|
|
529
|
+
// Under progressive disclosure the description is the only thing in context at routing
|
|
530
|
+
// time, so a feature with no lexical hook there is unreachable — the skill never loads and
|
|
531
|
+
// the agent hand-rolls what the product already ships. Lexical, not semantic: every feature
|
|
532
|
+
// id must contribute at least one word (>3 chars) present in the description.
|
|
533
|
+
function gateFeatureDescriptionCoverage() {
|
|
534
|
+
const errs = [];
|
|
535
|
+
for (const dir of readdirSync(SKILLS_DIR, { withFileTypes: true })
|
|
536
|
+
.filter((d) => d.isDirectory() && OWNED_SKILL(d.name) && /-features$/.test(d.name))
|
|
537
|
+
.map((d) => d.name)) {
|
|
538
|
+
const f = join(SKILLS_DIR, dir, "SKILL.md");
|
|
539
|
+
if (!existsSync(f)) continue;
|
|
540
|
+
const txt = readFileSync(f, "utf8");
|
|
541
|
+
const desc = ((txt.match(/^description:.*$/m) || [""])[0]).toLowerCase();
|
|
542
|
+
const m = dir.match(/^cometchat-(.+)-features$/);
|
|
543
|
+
if (!m) continue;
|
|
544
|
+
// features.<platform>-<major>.json, or the unsuffixed react default
|
|
545
|
+
const cand = [join(ROOT, `features.${m[1]}.json`), join(ROOT, "features.json")];
|
|
546
|
+
const oraclePath = cand.find((p) => existsSync(p));
|
|
547
|
+
if (!oraclePath) continue;
|
|
548
|
+
let ids = [];
|
|
549
|
+
try { ids = (JSON.parse(readFileSync(oraclePath, "utf8")).features || []).map((x) => x.id).filter(Boolean); }
|
|
550
|
+
catch { continue; }
|
|
551
|
+
const missing = ids.filter((id) => !id.split("-").filter((w) => w.length > 3).some((w) => desc.includes(w)));
|
|
552
|
+
if (missing.length) {
|
|
553
|
+
errs.push(
|
|
554
|
+
`${dir}: ${missing.length}/${ids.length} feature(s) in ${oraclePath.replace(ROOT + "/", "")} have no lexical hook ` +
|
|
555
|
+
`in the skill description, so the router cannot reach them: ${missing.slice(0, 8).join(", ")}` +
|
|
556
|
+
(missing.length > 8 ? ` …+${missing.length - 8}` : "")
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
return errs;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
// GATE I (AUDIT-092): every shipped family must be reachable through the shared skills.
|
|
565
|
+
// The router and onboarding are authored while one family is the only one that exists, so
|
|
566
|
+
// a second family can ship without ever being added to their routing prose — every other
|
|
567
|
+
// gate stays green because each skill is individually valid. A family is shipped if
|
|
568
|
+
// skills/cometchat-<family>-core exists; each shared skill must NAME that core. Structural
|
|
569
|
+
// on purpose: reading prose sentiment produced false positives.
|
|
570
|
+
function gateSharedSkillsRouteEveryFamily() {
|
|
571
|
+
const errs = [];
|
|
572
|
+
const shipped = readdirSync(SKILLS_DIR, { withFileTypes: true })
|
|
573
|
+
.filter((d) => d.isDirectory() && OWNED_SKILL(d.name))
|
|
574
|
+
.map((d) => (d.name.match(/^cometchat-(.+)-core$/) || [])[1])
|
|
575
|
+
.filter(Boolean);
|
|
576
|
+
if (shipped.length < 2) return errs; // single-family pack: nothing to orphan
|
|
577
|
+
for (const shared of ["cometchat", "cometchat-onboarding"]) {
|
|
578
|
+
const f = join(SKILLS_DIR, shared, "SKILL.md");
|
|
579
|
+
if (!existsSync(f)) continue;
|
|
580
|
+
const txt = readFileSync(f, "utf8");
|
|
581
|
+
for (const fam of shipped) {
|
|
582
|
+
if (!txt.includes(`cometchat-${fam}-core`)) {
|
|
583
|
+
errs.push(`${shared}: never names \`cometchat-${fam}-core\`, so the shipped ${fam} family is unreachable through it`);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return errs;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
// GATE J (AUDIT-093): field-proven facts that must not silently vanish. Each is one
|
|
592
|
+
// sentence a later edit could drop with every other gate still green — the recipe would
|
|
593
|
+
// still compile, pass the catalog, and fit the budget.
|
|
594
|
+
function gateFieldProvenFacts() {
|
|
595
|
+
const errs = [];
|
|
596
|
+
const MUST = [
|
|
597
|
+
{ file: "cometchat-angular-v5-features/references/custom-messages.md",
|
|
598
|
+
needle: /\{type\}_\{category\}/,
|
|
599
|
+
why: "the bubble key format — setBubbleView('location') is accepted silently, never matches, and renders \"This message type is not supported\"" },
|
|
600
|
+
{ file: "cometchat-angular-v5-placement/references/core-surface.md",
|
|
601
|
+
needle: /getConversationType\(\)/,
|
|
602
|
+
why: "(itemClick) emits a Conversation; duck-typing it sends every GROUP into the [user] slot -> 'getUid is not a function'" },
|
|
603
|
+
{ file: "cometchat-angular-v5-placement/SKILL.md",
|
|
604
|
+
needle: /getConversationType\(\)/,
|
|
605
|
+
why: "same discrimination bug, in the entry-level recipe" },
|
|
606
|
+
{ file: "cometchat-angular-v5-core/SKILL.md",
|
|
607
|
+
needle: /Which user should the app log in as/,
|
|
608
|
+
why: "the startup UID must be ASKED; a baked cometchat-uid-1 logs in nowhere on an app without seeded samples" },
|
|
609
|
+
{ file: "cometchat-angular-v5-core/SKILL.md",
|
|
610
|
+
needle: /getConversationType\(\)/,
|
|
611
|
+
why: "the GOLDEN PATH itself — the entry recipe an unscoped 'add chat' emits. It was guarded in placement/* but not here, so the one-slot [user] binding survived and shipped: 1:1 works, groups throw 'getUid is not a function' (contracts.angular-v5.json conversation-subject-discriminated)" },
|
|
612
|
+
{ file: "cometchat-angular-v5-components/SKILL.md",
|
|
613
|
+
needle: /cometchat-avatar/,
|
|
614
|
+
why: "the kit ships presentational atoms; without this the agent hand-rolls an <img> avatar" },
|
|
615
|
+
{ file: "cometchat-onboarding/references/calling-first.md",
|
|
616
|
+
needle: /VoIP push/,
|
|
617
|
+
why: "the ringing shape question must ALSO ask about delivery: 'one-on-one ringing' reads as WhatsApp, but the recipe is foreground-only — background/killed ringing is a separate VoIP-push build (FCM + ConnectionService + a real device). Asked after the plan, the limit cannot inform the choice (AUDIT-195)" },
|
|
618
|
+
{ file: "cometchat-onboarding/references/calling-first.md",
|
|
619
|
+
needle: /background|backgrounded/,
|
|
620
|
+
why: "the foreground-only limit must be stated at PLAN time; the foreground-only build passes every casual test (both apps open in dev) and fails first in the user's real world (AUDIT-195)" },
|
|
621
|
+
{ file: "cometchat-android-v5-calls-sdk/SKILL.md",
|
|
622
|
+
needle: /getLoggedInUser\(\)\?\.uid == uid/,
|
|
623
|
+
why: "the session guard must branch on IDENTITY: guarding login on `getLoggedInUser() != null` (the docs' own snippet) silently places calls as the PREVIOUS user on a shared/re-used device (AUDIT-194)" },
|
|
624
|
+
{ file: "cometchat-android-v5-calls-sdk/references/user-switch.md",
|
|
625
|
+
needle: /isInitialized/,
|
|
626
|
+
why: "logout leaves the Calls SDK de-initialized, so a user switch needs logout -> RE-INIT -> login; without the re-init generateToken fails with ERROR_COMETCHAT_CALLS_SDK_INIT, an error naming the wrong cause (AUDIT-194)" },
|
|
627
|
+
];
|
|
628
|
+
for (const { file, needle, why } of MUST) {
|
|
629
|
+
const f = join(SKILLS_DIR, file);
|
|
630
|
+
if (!existsSync(f)) { errs.push(`${file}: missing (GATE J expects it)`); continue; }
|
|
631
|
+
if (!needle.test(readFileSync(f, "utf8"))) {
|
|
632
|
+
errs.push(`${file}: lost a field-proven fact — ${why}`);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
return errs;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
// GATE K (AUDIT-094): every code-emitting skill must carry a docs route. A skill that
|
|
640
|
+
// emits code while naming no docs path leaves the installed bundle as the only place an
|
|
641
|
+
// agent can look — that is how node_modules excursions start. Satisfied by referencing
|
|
642
|
+
// docs-map.md / DOCS_BASE / a docs URL, or by declaring a labelled DOCS GAP.
|
|
643
|
+
function gateDocsRouteOrDeclaredGap() {
|
|
644
|
+
const errs = [];
|
|
645
|
+
const EXEMPT = new Set(["cometchat", "cometchat-onboarding"]); // router + journey: emit no symbols
|
|
646
|
+
for (const dir of readdirSync(SKILLS_DIR, { withFileTypes: true })
|
|
647
|
+
.filter((d) => d.isDirectory() && OWNED_SKILL(d.name) && !EXEMPT.has(d.name)).map((d) => d.name)) {
|
|
648
|
+
const f = join(SKILLS_DIR, dir, "SKILL.md");
|
|
649
|
+
if (!existsSync(f)) continue;
|
|
650
|
+
let txt = readFileSync(f, "utf8");
|
|
651
|
+
for (const ref of ["references"]) {
|
|
652
|
+
const rd = join(SKILLS_DIR, dir, ref);
|
|
653
|
+
if (existsSync(rd)) for (const r of readdirSync(rd)) txt += readFileSync(join(rd, r), "utf8");
|
|
654
|
+
}
|
|
655
|
+
const hasRoute = /docs-map|DOCS_BASE|docs_topic/.test(txt);
|
|
656
|
+
const declaresGap = /docs (?:have|do) no|not in the docs|no dedicated .{0,40}page|no testing page|DOCS GAP/i.test(txt);
|
|
657
|
+
if (!hasRoute && !declaresGap) {
|
|
658
|
+
errs.push(`${dir}: emits code but names no docs route (no docs-map / DOCS_BASE / docs_topic) and declares no DOCS GAP — the installed bundle becomes the only lookup path (RULES §20)`);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
return errs;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
// GATE L (AUDIT-097): the six kit list components size their inner scroll region off the
|
|
666
|
+
// HOST element's height and never size themselves (verified vs the compiled 5.1.0 bundle).
|
|
667
|
+
// Bare in a flex column they default to min-height:auto, so they grow past the shell and
|
|
668
|
+
// get clipped instead of scrolling. Every occurrence in a layout recipe must carry
|
|
669
|
+
// class="cc-fill" — or another class defined in the SAME skill with both `flex:` and
|
|
670
|
+
// `min-height: 0`.
|
|
671
|
+
function gateLeafListsAreSized() {
|
|
672
|
+
const errs = [];
|
|
673
|
+
const NEEDS_FILL = ["cometchat-conversations", "cometchat-users", "cometchat-groups",
|
|
674
|
+
"cometchat-call-logs", "cometchat-search", "cometchat-message-list"];
|
|
675
|
+
for (const dir of readdirSync(SKILLS_DIR, { withFileTypes: true })
|
|
676
|
+
.filter((d) => d.isDirectory() && OWNED_SKILL(d.name) && /^cometchat-angular-v5-/.test(d.name)).map((d) => d.name)) {
|
|
677
|
+
const files = [join(SKILLS_DIR, dir, "SKILL.md")];
|
|
678
|
+
const rd = join(SKILLS_DIR, dir, "references");
|
|
679
|
+
if (existsSync(rd)) for (const r of readdirSync(rd)) files.push(join(rd, r));
|
|
680
|
+
const texts = files.filter(existsSync).map((f) => readFileSync(f, "utf8"));
|
|
681
|
+
// classes defined ANYWHERE in this skill's files that carry both flex and
|
|
682
|
+
// min-height: 0 — a reference file legitimately reuses a class the companion
|
|
683
|
+
// SKILL.md or another reference defines ("Built on core-surface.md — the
|
|
684
|
+
// shell is identical" is exactly this pattern, not a missing definition).
|
|
685
|
+
const sizedClasses = new Set(
|
|
686
|
+
texts.flatMap((txt) =>
|
|
687
|
+
[...txt.matchAll(/\.([a-zA-Z0-9_-]+)\s*\{[^}]*\}/gs)]
|
|
688
|
+
.filter((m) => /flex\s*:/.test(m[0]) && /min-height\s*:\s*0/.test(m[0]))
|
|
689
|
+
.map((m) => m[1])
|
|
690
|
+
)
|
|
691
|
+
);
|
|
692
|
+
for (const f of files) {
|
|
693
|
+
if (!existsSync(f)) continue;
|
|
694
|
+
const txt = readFileSync(f, "utf8");
|
|
695
|
+
// Only check INSIDE an html fence that is itself a shell/column layout (contains
|
|
696
|
+
// cc-side/cc-main/cc-panel/cc-tabs). A snippet illustrating one prop in isolation —
|
|
697
|
+
// e.g. formatters.md's `<cometchat-message-list [textFormatters]="…">` — is not
|
|
698
|
+
// claiming to be a droppable layout and is correctly exempt.
|
|
699
|
+
for (const fence of txt.matchAll(/```html\n([\s\S]*?)```/g)) {
|
|
700
|
+
const block = fence[1];
|
|
701
|
+
if (!/\bcc-(side|main|panel|tabs)\b/.test(block)) continue;
|
|
702
|
+
// AUDIT-106: <cometchat-error-boundary> ships no :host rule (verified vs 5.1.0), so
|
|
703
|
+
// wrapping the shell in it inserts an auto-height flex item that breaks a
|
|
704
|
+
// `height: 100%` chain below it. Styled by ELEMENT here, not by class.
|
|
705
|
+
if (/<cometchat-error-boundary\b/.test(block)) {
|
|
706
|
+
const styled = texts.some((t) =>
|
|
707
|
+
/cometchat-error-boundary\s*\{[^}]*\}/s.test(t) &&
|
|
708
|
+
[...t.matchAll(/cometchat-error-boundary\s*\{([^}]*)\}/gs)]
|
|
709
|
+
.some((m) => /flex\s*:/.test(m[1]) && /min-height\s*:\s*0/.test(m[1]))
|
|
710
|
+
);
|
|
711
|
+
if (!styled) {
|
|
712
|
+
errs.push(`${dir}/${f.split("/").pop()}: <cometchat-error-boundary> wraps a shell layout but no \`cometchat-error-boundary { flex: …; min-height: 0 }\` rule is defined — the kit ships no :host sizing, so the height chain breaks under a routed \`height: 100%\` shell (AUDIT-106)`);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
for (const tag of NEEDS_FILL) {
|
|
716
|
+
const re = new RegExp(`<${tag}\\b([^>]*)>`, "g");
|
|
717
|
+
let m;
|
|
718
|
+
while ((m = re.exec(block))) {
|
|
719
|
+
const attrs = m[1];
|
|
720
|
+
const classMatch = attrs.match(/class="([^"]*)"/);
|
|
721
|
+
const classes = classMatch ? classMatch[1].split(/\s+/) : [];
|
|
722
|
+
if (!classes.some((c) => sizedClasses.has(c))) {
|
|
723
|
+
errs.push(`${dir}/${f.split("/").pop()}: <${tag}> inside a shell layout has no sized class (flex + min-height:0) — it will clip instead of scroll`);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return errs;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// GATE M (AUDIT-106): a skill must route to docs for its OWN subject, not merely name some
|
|
734
|
+
// docs path — GATE K is satisfied by any docs-map mention, which a migration skill passes
|
|
735
|
+
// while carrying no route to the migration guide at all. Add future subject-specific skills
|
|
736
|
+
// to SUBJECT_ROUTES.
|
|
737
|
+
const SUBJECT_ROUTES = [
|
|
738
|
+
{ suffix: "-migration", needle: /migration-guide|migration-overview|migration-property-changes|\/upgrade/i,
|
|
739
|
+
what: "a migration/upgrade docs path" },
|
|
740
|
+
];
|
|
741
|
+
function gateSubjectDocsRoute() {
|
|
742
|
+
const errs = [];
|
|
743
|
+
for (const dir of readdirSync(SKILLS_DIR, { withFileTypes: true })
|
|
744
|
+
.filter((d) => d.isDirectory() && OWNED_SKILL(d.name)).map((d) => d.name)) {
|
|
745
|
+
const rule = SUBJECT_ROUTES.find((r) => dir.endsWith(r.suffix));
|
|
746
|
+
if (!rule) continue;
|
|
747
|
+
const f = join(SKILLS_DIR, dir, "SKILL.md");
|
|
748
|
+
if (!existsSync(f)) continue;
|
|
749
|
+
// The route must be reachable from the skill itself OR from the docs-map it points at.
|
|
750
|
+
let txt = readFileSync(f, "utf8");
|
|
751
|
+
const rd = join(SKILLS_DIR, dir, "references");
|
|
752
|
+
if (existsSync(rd)) for (const r of readdirSync(rd)) txt += readFileSync(join(rd, r), "utf8");
|
|
753
|
+
const family = (dir.match(/^cometchat-(.+)-migration$/) || [])[1];
|
|
754
|
+
const dm = family && join(SKILLS_DIR, `cometchat-${family}-core`, "references", "docs-map.md");
|
|
755
|
+
const dmTxt = dm && existsSync(dm) ? readFileSync(dm, "utf8") : "";
|
|
756
|
+
const inSkill = rule.needle.test(txt);
|
|
757
|
+
const inDocsMap = rule.needle.test(dmTxt);
|
|
758
|
+
if (!inSkill || !inDocsMap) {
|
|
759
|
+
const where = !inSkill && !inDocsMap ? "its own files AND its family's docs-map.md"
|
|
760
|
+
: !inSkill ? "its own files (SKILL.md + references/)"
|
|
761
|
+
: "its family's core docs-map.md";
|
|
762
|
+
errs.push(`${dir}: no ${rule.what} in ${where} — a subject-specific skill must route to ITS OWN docs, not only to the component docs-map (RULES §Fetch discipline; AUDIT-106)`);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
return errs;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// GATE N (AUDIT-107): the app-level `chat-experience` oracle must list every list component
|
|
769
|
+
// its family's grow-target recipe actually renders — an oracle listing fewer components than
|
|
770
|
+
// the recipe emits makes the agent drop a tab. Checked against the RECIPE, not the sibling
|
|
771
|
+
// family: families may diverge, but a family must not disagree with itself.
|
|
772
|
+
function gateChatExperienceMatchesRecipe() {
|
|
773
|
+
const errs = [];
|
|
774
|
+
const FAMILIES = [
|
|
775
|
+
{ features: "features.angular-v5.json", recipe: "cometchat-angular-v5-placement/references/combined-app.md",
|
|
776
|
+
tags: { "cometchat-conversations": "CometChatConversationsComponent", "cometchat-users": "CometChatUsersComponent",
|
|
777
|
+
"cometchat-groups": "CometChatGroupsComponent", "cometchat-call-logs": "CometChatCallLogsComponent" } },
|
|
778
|
+
];
|
|
779
|
+
for (const fam of FAMILIES) {
|
|
780
|
+
const fp = join(SKILLS_DIR, "..", fam.features);
|
|
781
|
+
const rp = join(SKILLS_DIR, fam.recipe);
|
|
782
|
+
if (!existsSync(fp) || !existsSync(rp)) continue;
|
|
783
|
+
let feats;
|
|
784
|
+
try { feats = JSON.parse(readFileSync(fp, "utf8")); } catch { continue; }
|
|
785
|
+
const flat = Array.isArray(feats) ? feats : Object.values(feats).flat().filter((x) => x && x.id);
|
|
786
|
+
const ce = flat.find((x) => x.id === "chat-experience");
|
|
787
|
+
if (!ce) continue;
|
|
788
|
+
const declared = new Set(ce.ui_components || []);
|
|
789
|
+
const recipe = readFileSync(rp, "utf8");
|
|
790
|
+
for (const [tag, cls] of Object.entries(fam.tags)) {
|
|
791
|
+
// the recipe renders it (as an element) but the oracle omits it
|
|
792
|
+
if (new RegExp(`<${tag}\\b`).test(recipe) && !declared.has(cls)) {
|
|
793
|
+
errs.push(`${fam.features}: chat-experience omits ${cls}, but ${fam.recipe} renders <${tag}> — the app-level oracle must list every component its own grow-target recipe emits (AUDIT-107)`);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
return errs;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
// GATE Q (AUDIT-180): a skill may only tell an agent to run CLI commands that EXIST. The
|
|
802
|
+
// shipped dashboard CLI (@cometchat/skills-cli) has auth/provision/config/features — no
|
|
803
|
+
// `detect`. The skills pack CLI (@cometchat/skills) DOES ship `detect` (bin/cometchat-skills.mjs
|
|
804
|
+
// since v5.0.0, the authority for framework / UI Kit / version_conflict / existing_cometchat —
|
|
805
|
+
// RULES §3/§21), so its command set is DERIVED from the bin's dispatcher, never hard-coded: the
|
|
806
|
+
// hard-coded add/install list flagged the real `detect` as nonexistent. A bare `detect --json`
|
|
807
|
+
// that names no tool is still flagged (an agent can't tell which CLI to run). Applied pack-wide
|
|
808
|
+
// plus the shipped RULES.md (which the same agents read).
|
|
809
|
+
function packCliCommands() {
|
|
810
|
+
try {
|
|
811
|
+
const src = readFileSync(join(ROOT, "bin", "cometchat-skills.mjs"), "utf8");
|
|
812
|
+
const cmds = [...src.slice(src.lastIndexOf("switch (CMD)")).matchAll(/case "([a-z][a-z-]*)":/g)].map((m) => m[1]);
|
|
813
|
+
if (cmds.length) return new Set(cmds);
|
|
814
|
+
} catch { /* fall through */ }
|
|
815
|
+
return new Set(["add", "install", "detect"]);
|
|
816
|
+
}
|
|
817
|
+
function gateCliCommandsExist(files) {
|
|
818
|
+
const errs = [];
|
|
819
|
+
const CLI_OK = new Set(["auth", "provision", "config", "features"]);
|
|
820
|
+
const PACK_OK = packCliCommands();
|
|
821
|
+
const RE = /(@cometchat\/skills-cli(?:@\d+)?|@cometchat\/skills)\s+([a-z][a-z-]*)/g;
|
|
822
|
+
const targets = [...files];
|
|
823
|
+
const shippedRules = join(ROOT, "RULES.md");
|
|
824
|
+
if (existsSync(shippedRules)) targets.push(shippedRules);
|
|
825
|
+
for (const f of targets) {
|
|
826
|
+
const rel = f.startsWith(SKILLS_DIR) ? f.slice(SKILLS_DIR.length + 1) : f.slice(ROOT.length + 1);
|
|
827
|
+
readFileSync(f, "utf8").split("\n").forEach((line, i) => {
|
|
828
|
+
for (const m of line.matchAll(RE)) {
|
|
829
|
+
const isCli = m[1].startsWith("@cometchat/skills-cli");
|
|
830
|
+
const ok = isCli ? CLI_OK : PACK_OK;
|
|
831
|
+
if (!ok.has(m[2])) {
|
|
832
|
+
errs.push(`${rel}:${i + 1}: tells the agent to run \`${m[0]}\` — ${isCli ? "@cometchat/skills-cli" : "@cometchat/skills"} has no \`${m[2]}\` command (real: ${[...ok].join("/")}) (RULES §21)`);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
if (/\bdetect --json\b/.test(line) && !/@cometchat\/skills\s+detect --json\b/.test(line)) {
|
|
836
|
+
errs.push(`${rel}:${i + 1}: references \`detect --json\` without naming the tool — write \`npx @cometchat/skills detect --json\` (the skills pack CLI; the dashboard CLI has no detect) (RULES §21)`);
|
|
837
|
+
}
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
return errs;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// GATE R (AUDIT-181): the shared skills' PLATFORM COVERAGE must match the shipped families.
|
|
844
|
+
// GATE I checks a shipped core is NAMED; this checks the journey can actually run for it:
|
|
845
|
+
// platforms.md needs a column per shipped family (else beat 1 is improvised), and the
|
|
846
|
+
// "not-yet-built" lists must not name a family that ships (a literal agent declines the user —
|
|
847
|
+
// onboarding listed Android/RN/Flutter/Angular as not-built after all four had shipped).
|
|
848
|
+
function gateSharedSkillsCoverShippedFamilies() {
|
|
849
|
+
const errs = [];
|
|
850
|
+
const LABEL = { react: "React", angular: "Angular", "react-native": "React Native", android: "Android", flutter: "Flutter", ios: "iOS" };
|
|
851
|
+
const shipped = readdirSync(SKILLS_DIR, { withFileTypes: true })
|
|
852
|
+
.filter((d) => d.isDirectory())
|
|
853
|
+
.map((d) => (d.name.match(/^cometchat-(.+)-core$/) || [])[1])
|
|
854
|
+
.filter(Boolean)
|
|
855
|
+
.map((fam) => ({ fam, label: LABEL[fam.replace(/-v\d+$/, "")] }))
|
|
856
|
+
.filter((x) => x.label);
|
|
857
|
+
if (shipped.length < 2) return errs;
|
|
858
|
+
const onb = join(SKILLS_DIR, "cometchat-onboarding");
|
|
859
|
+
const plat = join(onb, "references", "platforms.md");
|
|
860
|
+
if (existsSync(plat)) {
|
|
861
|
+
const txt = readFileSync(plat, "utf8");
|
|
862
|
+
for (const { fam, label } of shipped) {
|
|
863
|
+
if (!new RegExp(`\\|[^|\\n]*\\b${label}\\b[^|\\n]*\\|`).test(txt)) {
|
|
864
|
+
errs.push(`cometchat-onboarding/references/platforms.md: no table column for the shipped ${fam} family ("${label}") — beat 1 gets improvised on that platform`);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
for (const f of [join(onb, "SKILL.md"), join(onb, "references", "not-in-this-pack.md")]) {
|
|
869
|
+
if (!existsSync(f)) continue;
|
|
870
|
+
const txt = readFileSync(f, "utf8");
|
|
871
|
+
for (const m of txt.matchAll(/Not-yet-built core \([^)]*?peer — (.*?) today/g)) {
|
|
872
|
+
for (const { fam, label } of shipped) {
|
|
873
|
+
if (new RegExp(`\\b${label}\\b`).test(m[1])) {
|
|
874
|
+
errs.push(`${f.slice(SKILLS_DIR.length + 1)}: lists the shipped ${fam} family ("${label}") as not-yet-built — a literal agent will decline that platform`);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
return errs;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
function main() {
|
|
883
|
+
if (!existsSync(SKILLS_DIR)) { console.error("no skills/ dir"); process.exit(2); }
|
|
884
|
+
const routed = routedSlugs();
|
|
885
|
+
const dirs = readdirSync(SKILLS_DIR, { withFileTypes: true })
|
|
886
|
+
.filter((d) => d.isDirectory())
|
|
887
|
+
.map((d) => d.name);
|
|
888
|
+
|
|
889
|
+
const routerOk = existsSync(join(SKILLS_DIR, "cometchat", "SKILL.md"));
|
|
890
|
+
const allErrs = [];
|
|
891
|
+
let checked = 0;
|
|
892
|
+
for (const dir of dirs) {
|
|
893
|
+
if (dir === "cometchat") continue; // the thin router, linted separately below
|
|
894
|
+
checked++;
|
|
895
|
+
allErrs.push(...lintSkill(dir, routed));
|
|
896
|
+
}
|
|
897
|
+
if (!routerOk) allErrs.push("cometchat/SKILL.md (thin router) missing");
|
|
898
|
+
|
|
899
|
+
// Pack-level durability gates (AUDIT-025 / AUDIT-018 / AUDIT-013 / AUDIT-029).
|
|
900
|
+
const allMd = mdFiles(SKILLS_DIR);
|
|
901
|
+
allErrs.push(...gateThemeSelector(allMd));
|
|
902
|
+
allErrs.push(...gateShippedRules());
|
|
903
|
+
allErrs.push(...gateV7VerifyMarkers(allMd));
|
|
904
|
+
allErrs.push(...gateOsThemeRecipe());
|
|
905
|
+
allErrs.push(...gateInitTelemetry());
|
|
906
|
+
allErrs.push(...gateSectionSpine());
|
|
907
|
+
allErrs.push(...gatePreviewDocsBase());
|
|
908
|
+
allErrs.push(...gateFeatureDescriptionCoverage());
|
|
909
|
+
allErrs.push(...gateSharedSkillsRouteEveryFamily());
|
|
910
|
+
allErrs.push(...gateFieldProvenFacts());
|
|
911
|
+
allErrs.push(...gateDocsRouteOrDeclaredGap());
|
|
912
|
+
allErrs.push(...gateLeafListsAreSized());
|
|
913
|
+
allErrs.push(...gateSubjectDocsRoute());
|
|
914
|
+
allErrs.push(...gateChatExperienceMatchesRecipe());
|
|
915
|
+
allErrs.push(...gateArtifactRefs(allMd));
|
|
916
|
+
allErrs.push(...gatePlatformMetadata());
|
|
917
|
+
allErrs.push(...gateCliCommandsExist(allMd));
|
|
918
|
+
allErrs.push(...gateSharedSkillsCoverShippedFamilies());
|
|
919
|
+
|
|
920
|
+
const ok = allErrs.length === 0;
|
|
921
|
+
if (JSON_OUT) {
|
|
922
|
+
console.log(JSON.stringify({ ok, checked, routerOk, errors: allErrs }, null, 0));
|
|
923
|
+
} else if (ok) {
|
|
924
|
+
console.log(`✅ lint-skills: ${checked} skill(s) valid; router present; ${routed.slugs.size} routed slug(s); pack durability gates green (theme-selector / shipped-RULES / v7-verify / os-theme-recipe / init-telemetry / artifact-refs / platform-metadata / section-spine / preview-docs-base / feature-description-coverage / shared-routes-every-family / field-proven-facts / docs-route / leaf-lists-sized / subject-docs-route / chat-experience-matches-recipe / cli-commands-exist / shared-skills-cover-shipped-families).`);
|
|
925
|
+
} else {
|
|
926
|
+
console.log(`❌ lint-skills: ${allErrs.length} problem(s) across ${checked} skill(s):`);
|
|
927
|
+
for (const e of allErrs) console.log(` - ${e}`);
|
|
928
|
+
}
|
|
929
|
+
process.exit(ok ? 0 : 1);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
main();
|