[MSDK-3955] Expose ConsentOrPay settings in React Native bridge for P…#212
[MSDK-3955] Expose ConsentOrPay settings in React Native bridge for P…#212asadraza-usercentrics wants to merge 1 commit into
Conversation
|
CodeAnt AI is reviewing your PR. Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
📝 WalkthroughWalkthroughThis PR adds support for a "consent or pay" configuration feature across the TCF2 settings model. A new ChangesTCF2 Consent-or-Pay Configuration
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
src/models/TCF2Settings.tsxOops! Something went wrong! :( ESLint: 8.57.1 TypeError: prettier.resolveConfig.sync is not a function Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Review Summary by QodoExpose ConsentOrPay settings in React Native bridge
WalkthroughsDescription• Expose ConsentOrPay settings in React Native bridge • Add serialization for ConsentOrPaySettings on Android • Add dictionary conversion for ConsentOrPaySettings on iOS • Create TypeScript model for TCF2ConsentOrPaySettings Diagramflowchart LR
A["ConsentOrPaySettings<br/>Native Models"] -->|"Serialize"| B["Android<br/>WritableMap"]
A -->|"Convert"| C["iOS<br/>Dictionary"]
B -->|"Bridge"| D["React Native<br/>TypeScript Models"]
C -->|"Bridge"| D
D -->|"Expose"| E["TCF2ConsentOrPaySettings<br/>Class"]
File Changes1. android/src/main/java/com/usercentrics/reactnative/extensions/UsercentricsCMPDataExtensions.kt
|
Code Review by Qodo
1. Unbridged consentOrPay maps
|
|
PR Summary: Add ConsentOrPay settings to the React Native bridge so JS can read them from CMP settings.
Impact and notes
|
There was a problem hiding this comment.
🧹 Nitpick comments (1)
ios/Extensions/UsercentricsCMPData+Dict.swift (1)
244-253: 💤 Low valueConsider using
NSDictionaryas return type for consistency.Most other
toDictionary()extensions in this file returnNSDictionary(e.g.,TCF2Settings.toDictionary()at line 177,CCPASettings.toDictionary()at line 153). This extension returns[String: Any], which is functionally equivalent since it's cast toAnyat the call site (line 239), but the inconsistency might confuse future maintainers.♻️ Optional refactor for consistency
extension ConsentOrPaySettings { - func toDictionary() -> [String: Any] { + func toDictionary() -> NSDictionary { return [ "enableConsentOrPay": self.enableConsentOrPay, "showTogglesForVendors": self.showTogglesForVendors, "publisherRestrictions": self.publisherRestrictions, "specialFeatures": self.specialFeatures ] } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@ios/Extensions/UsercentricsCMPData`+Dict.swift around lines 244 - 253, Change ConsentOrPaySettings.toDictionary() to return NSDictionary for consistency with other extensions; update the signature to func toDictionary() -> NSDictionary and return an NSDictionary instance (either by constructing NSDictionary with the same key/value pairs or by casting the literal to NSDictionary) so callers and other toDictionary() implementations (e.g., TCF2Settings.toDictionary, CCPASettings.toDictionary) use the same type.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@ios/Extensions/UsercentricsCMPData`+Dict.swift:
- Around line 244-253: Change ConsentOrPaySettings.toDictionary() to return
NSDictionary for consistency with other extensions; update the signature to func
toDictionary() -> NSDictionary and return an NSDictionary instance (either by
constructing NSDictionary with the same key/value pairs or by casting the
literal to NSDictionary) so callers and other toDictionary() implementations
(e.g., TCF2Settings.toDictionary, CCPASettings.toDictionary) use the same type.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 299aeff8-a7db-4fef-952c-52eee10a0768
📒 Files selected for processing (3)
android/src/main/java/com/usercentrics/reactnative/extensions/UsercentricsCMPDataExtensions.ktios/Extensions/UsercentricsCMPData+Dict.swiftsrc/models/TCF2Settings.tsx
| "changedPurposes" to changedPurposes?.serialize(), | ||
| "acmV2Enabled" to acmV2Enabled, | ||
| "selectedATPIds" to selectedATPIds, | ||
| "consentOrPay" to consentOrPay?.serialize(), |
There was a problem hiding this comment.
[VALIDATION] You added "consentOrPay" to the TCF2Settings map (new line 250). Ensure the nested ConsentOrPaySettings data is serialized into a shape accepted by toWritableMap() (and ultimately the RN bridge) — specifically: 1) handle nulls safely (use consentOrPay?.let { ... } ), 2) normalize key types for nested maps (publisherRestrictions/specialFeatures) to string keys (JS requires string keys), and 3) convert nested structures to WritableMap/Array where applicable. Also update related Android unit-test expected maps so they include the new "consentOrPay" entry (see android/src/androidTest/java/com/usercentrics/reactnative/mock/GetCMPDataMock.kt expectedTCF2Settings around lines 448-509).
private fun TCF2Settings.serialize(): WritableMap {
return mapOf(
// ...existing fields...
"selectedATPIds" to selectedATPIds,
"consentOrPay" to consentOrPay?.serialize(),
).toWritableMap()
}
private fun ConsentOrPaySettings.serialize(): WritableMap = mapOf(
"enableConsentOrPay" to enableConsentOrPay,
"showTogglesForVendors" to showTogglesForVendors,
// ensure keys are strings for JS
"publisherRestrictions" to publisherRestrictions
?.mapKeys { it.key.toString() }
?.toMap(),
"specialFeatures" to specialFeatures
?.mapKeys { it.key.toString() }
?.toMap(),
).toWritableMap()| private fun ConsentOrPaySettings.serialize(): Map<String, Any?> = mapOf( | ||
| "enableConsentOrPay" to enableConsentOrPay, | ||
| "showTogglesForVendors" to showTogglesForVendors, | ||
| "publisherRestrictions" to publisherRestrictions, | ||
| "specialFeatures" to specialFeatures | ||
| ) |
There was a problem hiding this comment.
[REFACTORING] The new private fun ConsentOrPaySettings.serialize() (lines 254-259) currently returns a Kotlin Map<String,Any?> while other serializer helpers in this file return WritableMap (or rely on .toWritableMap()). For consistency and to avoid subtle nested conversion bugs, make this return a WritableMap (e.g. build a map and call .toWritableMap()). Also explicitly convert publisherRestrictions and specialFeatures into JS-friendly structures (Map keys -> String, values -> primitives). Example: publisherRestrictions?.mapKeys { it.key.toString() }?.toMap() and then include .toWritableMap(). This reduces runtime surprises across Android RN bridge conversions.
private fun ConsentOrPaySettings.serialize(): WritableMap = mapOf(
"enableConsentOrPay" to enableConsentOrPay,
"showTogglesForVendors" to showTogglesForVendors,
"publisherRestrictions" to publisherRestrictions
?.mapKeys { it.key.toString() }
?.toMap(),
"specialFeatures" to specialFeatures
?.mapKeys { it.key.toString() }
?.toMap(),
).toWritableMap()| "acmV2Enabled": self.acmV2Enabled, | ||
| "selectedATPIds": self.selectedATPIds, | ||
| "resurfacePeriod": self.resurfacePeriod, | ||
| "consentOrPay": self.consentOrPay?.toDictionary() as Any, |
There was a problem hiding this comment.
[VALIDATION] You added mapping "consentOrPay": self.consentOrPay?.toDictionary() (line 239). Ensure the toDictionary() produces JS-bridge-safe types: convert KotlinBoolean to Bool (.boolValue) where needed and convert any Kotlin map/dictionary keys to Swift String keys before returning. If consentOrPay fields can be nil, keep the optional handling (as you already do) but confirm callers expect null vs empty object.
extension TCF2Settings {
func toDictionary() -> NSDictionary {
return [
// ...existing fields...
"selectedATPIds": self.selectedATPIds,
"resurfacePeriod": self.resurfacePeriod,
"consentOrPay": self.consentOrPay?.toDictionary() as Any,
]
}
}
extension ConsentOrPaySettings {
func toDictionary() -> [String: Any] {
return [
"enableConsentOrPay": self.enableConsentOrPay.boolValue,
"showTogglesForVendors": self.showTogglesForVendors.boolValue,
"publisherRestrictions": self.publisherRestrictions as [String: Any],
"specialFeatures": self.specialFeatures as [String: Any],
]
}
}| extension ConsentOrPaySettings { | ||
| func toDictionary() -> [String: Any] { | ||
| return [ | ||
| "enableConsentOrPay": self.enableConsentOrPay, | ||
| "showTogglesForVendors": self.showTogglesForVendors, | ||
| "publisherRestrictions": self.publisherRestrictions, | ||
| "specialFeatures": self.specialFeatures | ||
| ] | ||
| } | ||
| } |
There was a problem hiding this comment.
[CRITICAL_BUG] The new extension ConsentOrPaySettings.toDictionary() returns fields directly (lines 244-253). This may cause compile/runtime issues: Kotlin booleans from the KMP bindings are often KotlinBoolean and need to be converted with .boolValue (see pattern in ios/Extensions/TCFData+Dict.swift lines ~85-115 where consent?.boolValue is used). Also publisherRestrictions and specialFeatures may be Kotlin map types — convert them to native [String: Any] (map keys to String) and ensure values are JS-serializable. Update to mirror existing conversion patterns (use .boolValue for KotlinBoolean and explicit dictionary transforms) to avoid crashes or incorrect values in JS.
extension ConsentOrPaySettings {
func toDictionary() -> [String: Any] {
return [
"enableConsentOrPay": self.enableConsentOrPay.boolValue,
"showTogglesForVendors": self.showTogglesForVendors.boolValue,
// Ensure keys are Strings and values are JSON-serializable
"publisherRestrictions": self.publisherRestrictions.reduce(into: [String: String]()) { result, entry in
if let key = entry.key as? String, let value = entry.value as? String {
result[key] = value
}
},
"specialFeatures": self.specialFeatures.reduce(into: [String: String]()) { result, entry in
if let key = entry.key as? String, let value = entry.value as? String {
result[key] = value
}
},
]
}
}|
Reviewed up to commit:20c2e3ea766c19069cef61bc60e50fb7bb62bb37 Additional Suggestionsrc/models/TCF2Settings.tsx, line:214-234Consider tightening the types for publisherRestrictions and specialFeatures in TCF2ConsentOrPaySettings (lines 214-234). Right now they're Record. If the value can only be the literal 'flexible' (per comment), use a more precise type like Record or an enum/union. That improves type-safety across the codebase and makes platform serializers clearer about permitted values.export class TCF2ConsentOrPaySettings {
enableConsentOrPay: boolean
showTogglesForVendors: boolean
/** Maps TCF Purpose ID (as string) to "flexible". Absent entries are mandatory. */
publisherRestrictions: Record<string, "flexible">
/** Maps Special Feature ID (as string) to "flexible". Absent entries are mandatory. */
specialFeatures: Record<string, "flexible">
constructor(
enableConsentOrPay: boolean,
showTogglesForVendors: boolean,
publisherRestrictions: Record<string, "flexible">,
specialFeatures: Record<string, "flexible">,
) {
this.enableConsentOrPay = enableConsentOrPay
this.showTogglesForVendors = showTogglesForVendors
this.publisherRestrictions = publisherRestrictions
this.specialFeatures = specialFeatures
}
}Others- Update Android unit tests/mocks that construct expected TCF2Settings objects and TCF data (e.g. GetCMPDataMock.kt expectedTCF2Settings around lines 448-509 and any TCF mock builders) to include the new consentOrPay structure (or cover null case). Without updating tests, CI will fail or the new field will be unexpectedly missing from assertions.// android/src/androidTest/java/com/usercentrics/reactnative/mock/GetCMPDataMock.kt
private val expectedTCF2Settings = hashMapOf(
// ...existing expectations...
"scope" to 1,
"changedPurposes" to mapOf(
"purposes" to listOf(1, 2, 3),
"legIntPurposes" to listOf(1, 2, 3),
),
// new field: consentOrPay
"consentOrPay" to mapOf(
"enableConsentOrPay" to false,
"showTogglesForVendors" to false,
"publisherRestrictions" to emptyMap<String, String>(),
"specialFeatures" to emptyMap<String, String>(),
),
)
extension TCF2Settings {
static func mock() -> TCF2Settings {
let consentOrPay = ConsentOrPaySettings(
enableConsentOrPay: KotlinBoolean(bool: true),
showTogglesForVendors: KotlinBoolean(bool: false),
publisherRestrictions: ["1": "flexible", "2": "flexible"],
specialFeatures: ["1": "flexible"]
)
return .init(
firstLayerTitle: "firstLayerTitle",
secondLayerTitle: "secondLayerTitle",
tabsPurposeLabel: "tabsPurposeLabel",
tabsVendorsLabel: "tabsVendorsLabel",
labelsFeatures: "labelsFeatures",
labelsIabVendors: "labelsIabVendors",
labelsNonIabPurposes: "labelsNonIabPurposes",
labelsNonIabVendors: "labelsNonIabVendors",
labelsPurposes: "labelsPurposes",
vendorFeatures: "vendorFeatures",
vendorLegitimateInterestPurposes: "vendorLegitimateInterestPurposes",
vendorPurpose: "vendorPurpose",
vendorSpecialFeatures: "vendorSpecialFeatures",
vendorSpecialPurposes: "vendorSpecialPurposes",
togglesConsentToggleLabel: "togglesConsentToggleLabel",
togglesLegIntToggleLabel: "togglesLegIntToggleLabel",
buttonsAcceptAllLabel: "buttonsAcceptAllLabel",
buttonsDenyAllLabel: "buttonsDenyAllLabel",
buttonsSaveLabel: "buttonsSaveLabel",
linksManageSettingsLabel: "linksManageSettingsLabel",
linksVendorListLinkLabel: "linksVendorListLinkLabel",
togglesSpecialFeaturesToggleOn: "togglesSpecialFeaturesToggleOn",
togglesSpecialFeaturesToggleOff: "togglesSpecialFeaturesToggleOff",
firstLayerMobileVariant: .full,
firstLayerHideToggles: true,
secondLayerHideToggles: true,
hideLegitimateInterestToggles: true,
categoriesOfDataLabel: "categoriesOfDataLabel",
dataRetentionPeriodLabel: "dataRetentionPeriodLabel",
legitimateInterestLabel: "legitimateInterestLabel",
version: "version",
examplesLabel: "examplesLabel",
cmpId: 123,
cmpVersion: 123,
showDataSharedOutsideEUText: true,
dataSharedOutsideEUText: "dataSharedOutsideEUText",
vendorIdsOutsideEUList: [1,2,3],
firstLayerHideButtonDeny: true,
hideButtonManageSettings: false,
secondLayerHideButtonDeny: true,
publisherCountryCode: "publisherCountryCode",
purposeOneTreatment: true,
selectedVendorIds: [1,2,3],
gdprApplies: true,
selectedStacks: [1,2,3],
scope: .global,
disabledSpecialFeatures: [1,2,3],
firstLayerShowDescriptions: true,
hideNonIabOnFirstLayer: true,
resurfacePeriod: 1,
resurfacePurposeChanged: true,
resurfaceVendorAdded: true,
firstLayerDescription: "firstLayerDescription",
firstLayerAdditionalInfo: "firstLayerAdditionalInfo",
secondLayerDescription: "secondLayerDescription",
appLayerNoteResurface: "appLayerNoteResurface",
firstLayerNoteResurface: "firstLayerNoteResurface",
changedPurposes: .mock(),
acmV2Enabled: true,
selectedATPIds: [43,46,55],
resurfaceATPListChanged: false,
atpListTitle: "Google Providers",
consentOrPay: consentOrPay
)
}
} |
CI Feedback 🧐A test triggered by this PR failed. Here is an AI-generated analysis of the failure:
|
| "publisherRestrictions" to publisherRestrictions, | ||
| "specialFeatures" to specialFeatures |
There was a problem hiding this comment.
Suggestion: publisherRestrictions and specialFeatures are forwarded as raw nested maps, but the React Native serializer in this module only safely handles nested maps with string keys and primitive JS-compatible values. These Consent-or-Pay maps are keyed by IDs and can contain non-string/non-primitive values from the SDK, which can cause runtime cast/serialization failures or silently dropped entries. Normalize both maps before exporting (stringify keys and map values to bridge-safe primitives). [type error]
Severity Level: Major ⚠️
- ❌ Android `getCMPData` can crash when Consent-or-Pay maps complex.
- ⚠️ JS cannot reliably read Consent-or-Pay restrictions on Android.Steps of Reproduction ✅
1. From JS, call `Usercentrics.getCMPData()` as in
`sample/src/screens/CustomUI.tsx:12-15`, which invokes the exported React Native method to
fetch CMP data.
2. On Android, this is bridged to `getCMPData` in
`android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt:85-87`, which
executes `usercentricsProxy.instance.getCMPData().serialize()` and returns the result to
JS.
3. `UsercentricsCMPData.serialize()` in
`android/src/main/java/com/usercentrics/reactnative/extensions/UsercentricsCMPDataExtensions.kt:27-36`
calls `UsercentricsSettings.serialize()` and then `TCF2Settings.serialize()` (lines
189-251), which includes `"consentOrPay" to consentOrPay?.serialize()` (line 250) and,
inside `ConsentOrPaySettings.serialize()` (lines 254-259), forwards
`"publisherRestrictions" to publisherRestrictions` and `"specialFeatures" to
specialFeatures` without any normalization.
4. The resulting `Map<String, Any?>` is converted to a React Native `WritableMap` via
`Map<String, Any?>.toWritableMap()` in
`android/src/main/java/com/usercentrics/reactnative/extensions/ReadableMapExtensions.kt:15-56`,
where nested maps are handled by `is Map<*, *>` and cast to `Map<String, Any>` (line
36-38) and only primitive / JS-safe value types are supported in the `when` branches. If
`ConsentOrPaySettings.publisherRestrictions` or `specialFeatures` (coming from the native
SDK) use non-String keys or non-primitive values (e.g. enums or other objects), this cast
or value handling will either throw at runtime (ClassCastException on keys) or silently
drop entries, resulting in broken or incomplete `consentOrPay` data returned by
`getCMPData()` to JS.Fix in Cursor | Fix in VSCode Claude
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** android/src/main/java/com/usercentrics/reactnative/extensions/UsercentricsCMPDataExtensions.kt
**Line:** 257:258
**Comment:**
*Type Error: `publisherRestrictions` and `specialFeatures` are forwarded as raw nested maps, but the React Native serializer in this module only safely handles nested maps with string keys and primitive JS-compatible values. These Consent-or-Pay maps are keyed by IDs and can contain non-string/non-primitive values from the SDK, which can cause runtime cast/serialization failures or silently dropped entries. Normalize both maps before exporting (stringify keys and map values to bridge-safe primitives).
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| "publisherRestrictions": self.publisherRestrictions, | ||
| "specialFeatures": self.specialFeatures |
There was a problem hiding this comment.
Suggestion: The Consent-or-Pay dictionaries are exposed directly to JS without converting keys/values to RN-safe JSON primitives. If these SDK maps use numeric keys or non-primitive value types, bridging to JavaScript can fail or produce unusable objects. Convert them into [String: String] (or another explicit JS-safe shape) before putting them into the exported dictionary. [api mismatch]
Severity Level: Major ⚠️
- ❌ iOS bridge may fail when Consent-or-Pay maps non-primitive.
- ⚠️ JS clients may see malformed Consent-or-Pay data on iOS.Steps of Reproduction ✅
1. From JS, call `Usercentrics.getCMPData()` as demonstrated in
`sample/src/screens/CustomUI.tsx:12-15`; on iOS this is bridged to the native module's
`getCMPData` (verified in
`sample/ios/sampleTests/RNUsercentricsModuleTests.swift:529-533`, which expects an
`NSDictionary` result).
2. The native module builds that `NSDictionary` via `UsercentricsCMPData.toDictionary()`
in `ios/Extensions/UsercentricsCMPData+Dict.swift:4-13`, which embeds
`self.settings.toDictionary()`; `UsercentricsSettings.toDictionary()` (lines 17-50) then
includes `"tcf2": self.tcf2?.toDictionary() as Any` (line 26).
3. `TCF2Settings.toDictionary()` in
`ios/Extensions/UsercentricsCMPData+Dict.swift:176-241` includes `"consentOrPay":
self.consentOrPay?.toDictionary() as Any` (line 239), which calls
`ConsentOrPaySettings.toDictionary()` defined at lines 244-252.
4. `ConsentOrPaySettings.toDictionary()` currently forwards `"publisherRestrictions":
self.publisherRestrictions` and `"specialFeatures": self.specialFeatures` directly (lines
247-250). If the underlying `ConsentOrPaySettings` in the Usercentrics iOS SDK represents
these as dictionaries with non-String keys or non-primitive values (e.g. enums/objects),
the React Native bridge—which only safely transports
NSString/NSNumber/NSArray/NSDictionary/NSNull—can fail to marshal them correctly, leading
to runtime bridging errors or JS receiving unusable structures where TypeScript expects
`Record<string, string>` (`src/models/TCF2Settings.tsx:15-22`).Fix in Cursor | Fix in VSCode Claude
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** ios/Extensions/UsercentricsCMPData+Dict.swift
**Line:** 249:250
**Comment:**
*Api Mismatch: The Consent-or-Pay dictionaries are exposed directly to JS without converting keys/values to RN-safe JSON primitives. If these SDK maps use numeric keys or non-primitive value types, bridging to JavaScript can fail or produce unusable objects. Convert them into `[String: String]` (or another explicit JS-safe shape) before putting them into the exported dictionary.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix|
CodeAnt AI finished reviewing your PR. |
User description
Summary by CodeRabbit
CodeAnt-AI Description
Expose Consent or Pay settings in React Native TCF2 data
What Changed
Impact
✅ Consistent Consent or Pay setup across iOS and Android✅ Fewer missing CMP settings in React Native apps✅ Clearer control over vendor and special feature display💡 Usage Guide
Checking Your Pull Request
Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.
Talking to CodeAnt AI
Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.
Example
Preserve Org Learnings with CodeAnt
You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:
This helps CodeAnt AI learn and adapt to your team's coding style and standards.
Example
Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
Check Your Repository Health
To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.