Skip to content

feat: Add api-schema-drift-sentinel kit - #341

Open
mohamad-shafeez wants to merge 7 commits into
Lamatic:mainfrom
mohamad-shafeez:main
Open

feat: Add api-schema-drift-sentinel kit#341
mohamad-shafeez wants to merge 7 commits into
Lamatic:mainfrom
mohamad-shafeez:main

Conversation

@mohamad-shafeez

@mohamad-shafeez mohamad-shafeez commented Aug 11, 2026

Copy link
Copy Markdown

Overview

API Schema Drift Sentinel detects breaking changes between OpenAPI specifications and produces grounded migration guidance.

Problem

API schema changes can silently break downstream clients when response fields are removed, parameter types change, or other incompatible changes are introduced.

Architecture

The kit uses a two-layer pipeline:

  • openapi-diff for deterministic structural comparison
  • A direct path-parameter comparison to supplement cases not consistently surfaced by the diff
  • A Lamatic workflow that receives confirmed change facts and generates executive impact analysis and migration guidance

Breaking-change counts and deployment risk are derived from the deterministic change classification rather than the LLM output.

Verification

The application was verified with:

  • Next.js production build
  • Type checking
  • Additive/non-breaking API schema test
  • Breaking-change test covering field removals and a path-parameter type change
  • Invalid OpenAPI input validation

Secrets such as .env.local, node_modules, and .next are excluded from the repository.

  • Added the API Schema Drift Sentinel kit and documentation.
  • Added configuration files for Lamatic, Next.js, TypeScript, Tailwind CSS, PostCSS, dependencies, environment variables, and ignored generated files.
  • Added deterministic OpenAPI comparison with openapi-diff.
  • Added direct path-parameter type comparison, normalized semantic changes, deduplication, severity classification, breaking-change counts, and deployment-risk calculation.
  • Added the POST /api/analyze-drift endpoint with input validation, size limits, workflow integration, response parsing, and deterministic fallback handling.
  • Added the analyzeSchemaDrift server action.
  • Added a Next.js dashboard for specification editing, drift analysis, risk metrics, detected changes, impact assessment, and migration guidance.
  • Added additive, breaking, and invalid-input verification scripts.
  • Added the Lamatic constitution and grounded system prompt for source-based impact analysis and migration guidance.
  • Added the Analyze Schema Drift flow with trigger, dynamic LLM, and response nodes. The trigger passes confirmed schema-drift facts to the LLM node. The response node returns the structured analysis.
  • Added the Gemini 3 Flash Preview model configuration for the LLM node.
  • The application calculates deterministic drift metrics before workflow execution. The workflow generates impact analysis and migration guidance from those confirmed facts.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@github-actions[bot], you've reached your PR review limit, so we couldn't start this review.

Next review available in: 59 minutes

Limit details: You’ve used all 2 included reviews currently available under your plan.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2572d17c-7d84-4d6d-b754-ba749bf5dccb

📥 Commits

Reviewing files that changed from the base of the PR and between 57aa74a and 2c204ec.

📒 Files selected for processing (2)
  • kits/api-schema-drift-sentinel/flows/analyze-schema-drift.ts
  • kits/api-schema-drift-sentinel/model-configs/analyze-schema-drift_llm-node_generative-model-name.ts

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 88205359-b9d1-4100-8384-013c2d1a5f5d

📥 Commits

Reviewing files that changed from the base of the PR and between 57aa74a and 7966cfd.

📒 Files selected for processing (2)
  • kits/api-schema-drift-sentinel/flows/analyze-schema-drift.ts
  • kits/api-schema-drift-sentinel/model-configs/analyze-schema-drift_llm-node_generative-model-name.ts

Included review availability: Your plan includes up to 2 reviews per rolling hour; 1 remains after this review.


Walkthrough

The kit adds deterministic OpenAPI schema comparison, normalized risk classification, Lamatic workflow analysis, a POST API endpoint, and a Next.js dashboard with setup and workflow test documentation.

Changes

Schema Drift Sentinel

Layer / File(s) Summary
Kit and application foundation
kits/api-schema-drift-sentinel/.gitignore, kits/api-schema-drift-sentinel/apps/*, kits/api-schema-drift-sentinel/lamatic.config.ts, kits/api-schema-drift-sentinel/agent.md, kits/api-schema-drift-sentinel/constitutions/*, kits/api-schema-drift-sentinel/flows/*, kits/api-schema-drift-sentinel/model-configs/*
Adds kit metadata, flow resources, model configuration, application configuration, environment templates, Next.js setup, styling, and root layout.
OpenAPI diff normalization
kits/api-schema-drift-sentinel/apps/lib/sentinel.ts
Compares OpenAPI documents, detects parameter type changes, normalizes semantic changes, groups breaking and non-breaking changes, and calculates risk metrics.
Analysis workflow and API route
kits/api-schema-drift-sentinel/apps/actions/orchestrate.ts, kits/api-schema-drift-sentinel/apps/app/api/analyze-drift/route.ts, kits/api-schema-drift-sentinel/apps/lib/sentinel.ts, kits/api-schema-drift-sentinel/prompts/*
Validates requests, builds deterministic workflow payloads, invokes Lamatic through HTTP or GraphQL polling, parses responses, and returns drift and AI analysis data.
Dashboard analysis experience
kits/api-schema-drift-sentinel/apps/app/page.tsx, kits/api-schema-drift-sentinel/apps/app/layout.tsx, kits/api-schema-drift-sentinel/apps/app/globals.css
Adds editable specification inputs, analysis submission, result metrics, change details, AI summaries, migration guidance, and fallback states.
Workflow scenarios and documentation
kits/api-schema-drift-sentinel/apps/test-orchestrate.js, kits/api-schema-drift-sentinel/README.md
Adds additive and breaking schema scenarios, normalization checks, workflow polling tests, and setup and behavior documentation.

Suggested reviewers: amanintech, d-pamneja

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.27% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the addition of the API Schema Drift Sentinel kit.
Description check ✅ Passed The description covers the purpose, architecture, verification, and secret handling, but omits checklist selections and explicit CI or review status.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

:robot_face: AgentKit Structural Validation

New Contributions Detected

  • Kit: kits/api-schema-drift-sentinel

Check Results

Check Status
No edits to existing kits ✅ Pass
Required root files present ✅ Pass
Flow .ts files present ✅ Pass
lamatic.config.ts valid ✅ Pass
No changes outside kits/ ✅ Pass

🎉 All checks passed! This contribution follows the AgentKit structure.

@github-actions

Copy link
Copy Markdown
Contributor

Failure recorded at 2026-08-11T10:56:37Z UTC. If this PR is not fixed within 4 weeks it will be automatically closed.

@coderabbitai
coderabbitai Bot requested a review from d-pamneja August 11, 2026 10:57

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 23

🤖 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.

Inline comments:
In `@kits/api-schema-drift-sentinel/.gitignore`:
- Around line 3-5: Update the environment ignore patterns in .gitignore to
ignore all .env.* files, including development, production, and test variants,
while explicitly re-including the tracked apps/.env.example file. Preserve the
existing .env and .env.local coverage.

In `@kits/api-schema-drift-sentinel/apps/actions/orchestrate.ts`:
- Around line 13-22: Update the server action around normalizeDiff and the
payload construction: pass oldSpecContent and newSpecContent to normalizeDiff so
parameter type changes are detected consistently with the analyze-drift route,
and set changesCount from normalizedChanges.facts.totalBreaking rather than
normalizedChanges.allChanges.length to preserve the breaking-change count
contract.
- Line 3: Add an app-local configuration module under the apps deployment root
defining the analyze-schema-drift step, using LAMATIC_DRIFT_FLOW_ID as its flow
identifier. Update orchestrate.ts and related app wiring to consume this local
configuration instead of importing the parent lamatic.config.ts, while
preserving the existing sentinel imports and behavior.

In `@kits/api-schema-drift-sentinel/apps/app/api/analyze-drift/route.ts`:
- Around line 6-17: Validate specA and specB in the route handler before calling
runOpenApiDiff: require each value to be a string and enforce the intended
maximum size for each specification, returning the existing 400 response for
invalid or oversized inputs. Keep valid payloads flowing unchanged into
runOpenApiDiff.

In `@kits/api-schema-drift-sentinel/apps/app/globals.css`:
- Line 1: Remove the unused Google Fonts `@import` from globals.css, or integrate
Inter through next/font/google in the root layout and apply its generated class
or CSS variable to the body alongside the existing classes. Ensure the font is
either actually used or no longer downloaded.

In `@kits/api-schema-drift-sentinel/apps/app/layout.tsx`:
- Line 15: Remove suppressHydrationWarning from the root body element in the app
layout, or move it to the smallest specific dynamic child that has the known
hydration mismatch; keep the body’s existing classes and children rendering
unchanged.

In `@kits/api-schema-drift-sentinel/apps/app/page.tsx`:
- Around line 209-238: Remove suppressHydrationWarning from the controls and
textareas in the component, including the elements associated with loadExample,
specA, specB, and the analysis workflow around the referenced locations. Do not
replace it with another suppression unless a confirmed browser-extension
mismatch exists; if one does, document that reason with a short comment.
- Around line 230-238: Add accessible names to both spec textareas in the page
component by associating visually hidden labels or applying descriptive
aria-label values to their existing ids. Mark the analyze button’s loading state
with aria-busy={loading}, and add role="alert" to the error panel so failures
are announced.
- Around line 154-160: Normalize the untrusted AI response fields before
rendering in the page component: make recommendation support string
executiveSummary values as well as executiveSummary.recommendation, and only
accept string values for recommendation, detailedImpact, and migrationGuide,
falling back to safe defaults otherwise. Update the existing derived fields
around risk, recommendation, detailedImpact, and migrationGuide without changing
the valid-object behavior, and use the response interface instead of
useState<any> if the route’s declared shape is available.
- Around line 163-196: Update the page root container to consume a font CSS
variable defined in globals.css instead of hardcoding the font stack in its
inline style. Replace every raw inline SVG icon in the page, including the
header hex and GitHub icons, with appropriate lucide-react components while
preserving their existing appearance, sizing, colors, and accessibility labels.

In `@kits/api-schema-drift-sentinel/apps/lib/sentinel.ts`:
- Around line 354-386: The polling loop in the function containing the status
check can exceed the serverless runtime limit before its timeout error is
returned. Either export an appropriate maxDuration from the calling route to
cover the full polling window, or reduce the attempts and delay; prefer bounded
exponential backoff if adjusting polling so early completions return sooner
while preserving successful and error status handling.
- Around line 372-374: Update the status-response parsing in the polling flow
around rawResult and parsedData so malformed string payloads cannot throw out of
the loop. Guard JSON.parse, preserve valid parsed data, and continue polling
when parsing fails instead of aborting the workflow.
- Around line 300-307: Update the REST request flow around the res.ok check and
catch block to log the failed response status before falling back to GraphQL,
and log the caught fetch error before fallback when fetch throws. Preserve the
existing successful response handling and GraphQL fallback behavior.
- Around line 329-344: Set an explicit Axios timeout on both outbound requests
in the execute call and the status-polling call, using the same bounded duration
for each. Update the Axios configuration near the visible POST request and its
corresponding status request without changing the polling or request behavior.
- Around line 169-184: Update the classification ladder in the change-mapping
logic to handle response.body.scope.remove and request.body.scope.add explicitly
before generic remove/add checks. Ensure response-side removals map to the
correct non-breaking or breaking classification based on isBreaking, and
breaking request-side scope additions map to REQUIRED_FIELD_ADDED with action
"add"; prevent the generic scope.add branch from overriding this behavior.
- Around line 76-84: Update the parameter comparison around v1Op.parameters and
v2Op.parameters to include each operation’s path-item parameters, merging
path-level and operation-level entries by name and location with operation-level
entries taking precedence. Use the merged parameter sets for the existing type
comparison so shared path-level parameter changes are detected.

In `@kits/api-schema-drift-sentinel/apps/package.json`:
- Around line 14-22: Update the dependency declarations in the apps package
manifest so react, react-dom, `@types/react`, and `@types/react-dom` use compatible
React 18 releases, while leaving the Next.js and unrelated dependencies
unchanged.
- Line 9: Update the lint script in the package scripts to stop invoking the
deprecated next lint wrapper. Add a project-owned ESLint or Biome dependency and
configuration, then invoke that tool directly; alternatively remove the lint
script if linting is not supported for this app.

In `@kits/api-schema-drift-sentinel/apps/test-orchestrate.js`:
- Around line 164-231: Update runMatrixTests to add deterministic assertions for
each normalized payload, validating the expected additive and breaking change
counts and normalized change contents. After each triggerWorkflowAndPoll call,
assert that the returned workflow result is present; throw or otherwise fail
explicitly when it is null or absent. Do not assert workflow-generated or LLM
prose.
- Around line 89-104: Update the request flow in test-orchestrate.js to create
one Axios client with a finite timeout, such as 15 seconds, and replace direct
axios calls for both the execution and status requests with that client.
Preserve the existing request methods, URLs, headers, and polling limit.

In
`@kits/api-schema-drift-sentinel/prompts/analyze-schema-drift_llm-node_system.md`:
- Around line 18-22: Update the “Risk classification” section in the
schema-drift analysis prompt to define only the deterministic HIGH/LOW mapping:
use HIGH when one or more breaking changes are present and LOW otherwise. Remove
the allowance for MEDIUM and CRITICAL so the prompt matches the HIGH/LOW values
produced through facts.calculatedRisk.
- Line 41: Update the prompt rule for breakingChangesCount to explicitly define
that facts with Severity: CRITICAL are breaking changes, and require the count
to equal the number of such supplied facts. Keep the existing fact-line format
and ensure the mapping is stated near the breakingChangesCount requirement.

In `@kits/api-schema-drift-sentinel/README.md`:
- Around line 186-194: Align the Test A scenario in the README with the harness
behavior in apps/test-orchestrate.js: either document the existing full_name
addition to GET /users/{id} or update the fixture to add POST /users. Ensure the
expected changesCount matches factsAdditive.totalBreaking, using 0 for this
additive case, while keeping breakingChangesCount and deploymentRisk consistent.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 053d2769-e2fc-44b9-8303-2f9495816410

📥 Commits

Reviewing files that changed from the base of the PR and between 6d631f1 and ad34c60.

⛔ Files ignored due to path filters (1)
  • kits/api-schema-drift-sentinel/apps/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (17)
  • kits/api-schema-drift-sentinel/.gitignore
  • kits/api-schema-drift-sentinel/README.md
  • kits/api-schema-drift-sentinel/apps/.env.example
  • kits/api-schema-drift-sentinel/apps/actions/orchestrate.ts
  • kits/api-schema-drift-sentinel/apps/app/api/analyze-drift/route.ts
  • kits/api-schema-drift-sentinel/apps/app/globals.css
  • kits/api-schema-drift-sentinel/apps/app/layout.tsx
  • kits/api-schema-drift-sentinel/apps/app/page.tsx
  • kits/api-schema-drift-sentinel/apps/lib/sentinel.ts
  • kits/api-schema-drift-sentinel/apps/next-env.d.ts
  • kits/api-schema-drift-sentinel/apps/package.json
  • kits/api-schema-drift-sentinel/apps/postcss.config.js
  • kits/api-schema-drift-sentinel/apps/tailwind.config.js
  • kits/api-schema-drift-sentinel/apps/test-orchestrate.js
  • kits/api-schema-drift-sentinel/apps/tsconfig.json
  • kits/api-schema-drift-sentinel/lamatic.config.ts
  • kits/api-schema-drift-sentinel/prompts/analyze-schema-drift_llm-node_system.md

Comment thread kits/api-schema-drift-sentinel/.gitignore Outdated
@@ -0,0 +1,29 @@
"use server";

import { runOpenApiDiff, normalizeDiff, triggerLamaticWorkflow } from '../lib/sentinel';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check the kit deploy root and the flow-ID env key alignment.
set -euo pipefail

fd -t f 'lamatic.config.ts' kits/api-schema-drift-sentinel --exec cat {}
fd -t f '.env.example' kits/api-schema-drift-sentinel --exec cat {}
rg -n 'LAMATIC_DRIFT_FLOW_ID|root-directory|links' kits/api-schema-drift-sentinel

Repository: Lamatic/AgentKit

Length of output: 2352


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- orchestrate.ts ---'
cat -n kits/api-schema-drift-sentinel/apps/actions/orchestrate.ts

printf '%s\n' '--- app files related to configuration ---'
fd -t f . kits/api-schema-drift-sentinel/apps | sort | rg '(^|/)(lamatic|config|orchestrate)|package.json|next.config'

printf '%s\n' '--- configuration references ---'
rg -n 'lamatic\.config|LAMATIC_DRIFT_FLOW_ID|envKey|steps|workflowId' kits/api-schema-drift-sentinel/apps kits/api-schema-drift-sentinel/lamatic.config.ts

Repository: Lamatic/AgentKit

Length of output: 2317


Add app-local step configuration.

The deployment root is kits/api-schema-drift-sentinel/apps. Do not import the parent lamatic.config.ts. The app currently has no local configuration module. Add one for the analyze-schema-drift step and use LAMATIC_DRIFT_FLOW_ID, which already matches sentinel.ts and the parent configuration.

🤖 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 `@kits/api-schema-drift-sentinel/apps/actions/orchestrate.ts` at line 3, Add an
app-local configuration module under the apps deployment root defining the
analyze-schema-drift step, using LAMATIC_DRIFT_FLOW_ID as its flow identifier.
Update orchestrate.ts and related app wiring to consume this local configuration
instead of importing the parent lamatic.config.ts, while preserving the existing
sentinel imports and behavior.

Sources: Coding guidelines, Learnings

Comment on lines +13 to +22
const rawDiff = await runOpenApiDiff(oldSpecContent, newSpecContent);
const normalizedChanges = normalizeDiff(rawDiff);

const payload = {
apiName,
oldVersion,
newVersion,
changesCount: normalizedChanges.allChanges.length,
changes: normalizedChanges.allChanges
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Two payload discrepancies against the rest of the mission.

  1. Line 14 calls normalizeDiff(rawDiff) without the specs. normalizeDiff runs detectParameterTypeChanges only when both specA and specB are present (kits/api-schema-drift-sentinel/apps/lib/sentinel.ts Line 246). This server action therefore reports zero parameter type changes, while kits/api-schema-drift-sentinel/apps/app/api/analyze-drift/route.ts Line 21 passes the specs and reports them. The same input produces different results on the two entry points.

  2. Line 20 sets changesCount to allChanges.length. kits/api-schema-drift-sentinel/apps/test-orchestrate.js sets the same field to facts.totalBreaking, and Rule 1 of kits/api-schema-drift-sentinel/prompts/analyze-schema-drift_llm-node_system.md requires breakingChangesCount to equal the number of breaking changes supplied. Passing the total change count invites the model to report an inflated breaking-change count.

🎯 Proposed fix to align the payload with the route and the prompt contract
     const rawDiff = await runOpenApiDiff(oldSpecContent, newSpecContent);
-    const normalizedChanges = normalizeDiff(rawDiff);
+    const normalizedChanges = normalizeDiff(rawDiff, oldSpecContent, newSpecContent);
 
     const payload = {
       apiName,
       oldVersion,
       newVersion,
-      changesCount: normalizedChanges.allChanges.length,
+      changesCount: normalizedChanges.totalBreaking,
       changes: normalizedChanges.allChanges
     };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const rawDiff = await runOpenApiDiff(oldSpecContent, newSpecContent);
const normalizedChanges = normalizeDiff(rawDiff);
const payload = {
apiName,
oldVersion,
newVersion,
changesCount: normalizedChanges.allChanges.length,
changes: normalizedChanges.allChanges
};
const rawDiff = await runOpenApiDiff(oldSpecContent, newSpecContent);
const normalizedChanges = normalizeDiff(rawDiff, oldSpecContent, newSpecContent);
const payload = {
apiName,
oldVersion,
newVersion,
changesCount: normalizedChanges.totalBreaking,
changes: normalizedChanges.allChanges
};
🤖 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 `@kits/api-schema-drift-sentinel/apps/actions/orchestrate.ts` around lines 13 -
22, Update the server action around normalizeDiff and the payload construction:
pass oldSpecContent and newSpecContent to normalizeDiff so parameter type
changes are detected consistently with the analyze-drift route, and set
changesCount from normalizedChanges.facts.totalBreaking rather than
normalizedChanges.allChanges.length to preserve the breaking-change count
contract.

Comment thread kits/api-schema-drift-sentinel/apps/app/api/analyze-drift/route.ts
Comment thread kits/api-schema-drift-sentinel/apps/app/globals.css Outdated
Comment thread kits/api-schema-drift-sentinel/apps/test-orchestrate.js Outdated
Comment on lines +164 to +231
async function runMatrixTests() {
console.log("==========================================");
console.log("STEP 1: Verify Production Normalization via sentinel.ts");
console.log("==========================================");

const mockBreakingDiff = {
breakingDifferences: [
{
code: "response.body.scope.add",
entity: "response.body.scope",
sourceSpecEntityDetails: [{ location: "paths./users/{id}.get" }],
details: {
differenceSchema: {
anyOf: [
{ required: ["name"] },
{ required: ["email"] }
]
}
}
}
]
};

const normalizedMock = normalizeDiff(mockBreakingDiff);
console.log("Verified Mock Breaking Changes Normalization Output:");
console.log(JSON.stringify(normalizedMock, null, 2));

console.log("\n==========================================");
console.log("STEP 2: Matrix Execution");
console.log("==========================================");

// Test Case A: Additive (Non-breaking Baseline)
console.log("\n--- TEST CASE A: Additive (Non-Breaking) ---");
const diffAdditive = await runOpenApiDiff(v1, v2Additive);
const factsAdditive = normalizeDiff(diffAdditive, v1, v2Additive);
const payloadAdditive = {
apiName: "User Service API",
oldVersion: "1.0.0",
newVersion: "2.0.0",
changesCount: factsAdditive.totalBreaking,
changes: factsAdditive.allChanges
};
console.log("Additive Normalized Payload:", JSON.stringify(payloadAdditive, null, 2));
console.log("Triggering Lamatic Workflow for Additive Test Case...");
const resultAdditive = await triggerWorkflowAndPoll(payloadAdditive);
console.log("Additive Test Result Output:", JSON.stringify(resultAdditive, null, 2));

// Test Case B: Breaking Removal & Type Change
console.log("\n--- TEST CASE B: Breaking Removal & Type Change ---");
const diffBreaking = await runOpenApiDiff(v1, v2Breaking);
const factsBreaking = normalizeDiff(diffBreaking, v1, v2Breaking);

console.log(
"FULL OPENAPI DIFF:",
JSON.stringify(diffBreaking, null, 2)
);

const payloadBreaking = {
apiName: "User Service API",
oldVersion: "1.0.0",
newVersion: "2.0.0",
changesCount: factsBreaking.totalBreaking,
changes: factsBreaking.allChanges
};
console.log("Breaking Normalized Payload:", JSON.stringify(payloadBreaking, null, 2));
console.log("Triggering Lamatic Workflow for Breaking Test Case...");
const resultBreaking = await triggerWorkflowAndPoll(payloadBreaking);
console.log("Breaking Test Result Output:", JSON.stringify(resultBreaking, null, 2));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Mission directive: Make the matrix fail on incorrect results.

runMatrixTests only writes results to stdout. Incorrect normalized changes and null workflow results still complete without an explicit failure.

Add deterministic assertions for each normalized payload. Fail when a workflow result is absent. Do not assert LLM prose because it is not deterministic.

Proposed fix
+const assert = require('node:assert/strict');
+
   const factsAdditive = normalizeDiff(diffAdditive, v1, v2Additive);
+  assert.equal(factsAdditive.totalBreaking, 0);
+  assert.ok(
+    factsAdditive.nonBreakingChanges.some(
+      (change) => change.field === 'full_name' && change.action === 'add'
+    )
+  );
 ...
   const resultAdditive = await triggerWorkflowAndPoll(payloadAdditive);
+  assert.ok(resultAdditive, 'Additive workflow returned no result');
 ...
   const factsBreaking = normalizeDiff(diffBreaking, v1, v2Breaking);
+  assert.equal(factsBreaking.totalBreaking, 3);
 ...
   const resultBreaking = await triggerWorkflowAndPoll(payloadBreaking);
+  assert.ok(resultBreaking, 'Breaking workflow returned no result');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function runMatrixTests() {
console.log("==========================================");
console.log("STEP 1: Verify Production Normalization via sentinel.ts");
console.log("==========================================");
const mockBreakingDiff = {
breakingDifferences: [
{
code: "response.body.scope.add",
entity: "response.body.scope",
sourceSpecEntityDetails: [{ location: "paths./users/{id}.get" }],
details: {
differenceSchema: {
anyOf: [
{ required: ["name"] },
{ required: ["email"] }
]
}
}
}
]
};
const normalizedMock = normalizeDiff(mockBreakingDiff);
console.log("Verified Mock Breaking Changes Normalization Output:");
console.log(JSON.stringify(normalizedMock, null, 2));
console.log("\n==========================================");
console.log("STEP 2: Matrix Execution");
console.log("==========================================");
// Test Case A: Additive (Non-breaking Baseline)
console.log("\n--- TEST CASE A: Additive (Non-Breaking) ---");
const diffAdditive = await runOpenApiDiff(v1, v2Additive);
const factsAdditive = normalizeDiff(diffAdditive, v1, v2Additive);
const payloadAdditive = {
apiName: "User Service API",
oldVersion: "1.0.0",
newVersion: "2.0.0",
changesCount: factsAdditive.totalBreaking,
changes: factsAdditive.allChanges
};
console.log("Additive Normalized Payload:", JSON.stringify(payloadAdditive, null, 2));
console.log("Triggering Lamatic Workflow for Additive Test Case...");
const resultAdditive = await triggerWorkflowAndPoll(payloadAdditive);
console.log("Additive Test Result Output:", JSON.stringify(resultAdditive, null, 2));
// Test Case B: Breaking Removal & Type Change
console.log("\n--- TEST CASE B: Breaking Removal & Type Change ---");
const diffBreaking = await runOpenApiDiff(v1, v2Breaking);
const factsBreaking = normalizeDiff(diffBreaking, v1, v2Breaking);
console.log(
"FULL OPENAPI DIFF:",
JSON.stringify(diffBreaking, null, 2)
);
const payloadBreaking = {
apiName: "User Service API",
oldVersion: "1.0.0",
newVersion: "2.0.0",
changesCount: factsBreaking.totalBreaking,
changes: factsBreaking.allChanges
};
console.log("Breaking Normalized Payload:", JSON.stringify(payloadBreaking, null, 2));
console.log("Triggering Lamatic Workflow for Breaking Test Case...");
const resultBreaking = await triggerWorkflowAndPoll(payloadBreaking);
console.log("Breaking Test Result Output:", JSON.stringify(resultBreaking, null, 2));
const assert = require('node:assert/strict');
async function runMatrixTests() {
console.log("==========================================");
console.log("STEP 1: Verify Production Normalization via sentinel.ts");
console.log("==========================================");
const mockBreakingDiff = {
breakingDifferences: [
{
code: "response.body.scope.add",
entity: "response.body.scope",
sourceSpecEntityDetails: [{ location: "paths./users/{id}.get" }],
details: {
differenceSchema: {
anyOf: [
{ required: ["name"] },
{ required: ["email"] }
]
}
}
}
]
};
const normalizedMock = normalizeDiff(mockBreakingDiff);
console.log("Verified Mock Breaking Changes Normalization Output:");
console.log(JSON.stringify(normalizedMock, null, 2));
console.log("\n==========================================");
console.log("STEP 2: Matrix Execution");
console.log("==========================================");
// Test Case A: Additive (Non-breaking Baseline)
console.log("\n--- TEST CASE A: Additive (Non-Breaking) ---");
const diffAdditive = await runOpenApiDiff(v1, v2Additive);
const factsAdditive = normalizeDiff(diffAdditive, v1, v2Additive);
assert.equal(factsAdditive.totalBreaking, 0);
assert.ok(
factsAdditive.nonBreakingChanges.some(
(change) => change.field === 'full_name' && change.action === 'add'
)
);
const payloadAdditive = {
apiName: "User Service API",
oldVersion: "1.0.0",
newVersion: "2.0.0",
changesCount: factsAdditive.totalBreaking,
changes: factsAdditive.allChanges
};
console.log("Additive Normalized Payload:", JSON.stringify(payloadAdditive, null, 2));
console.log("Triggering Lamatic Workflow for Additive Test Case...");
const resultAdditive = await triggerWorkflowAndPoll(payloadAdditive);
assert.ok(resultAdditive, 'Additive workflow returned no result');
console.log("Additive Test Result Output:", JSON.stringify(resultAdditive, null, 2));
// Test Case B: Breaking Removal & Type Change
console.log("\n--- TEST CASE B: Breaking Removal & Type Change ---");
const diffBreaking = await runOpenApiDiff(v1, v2Breaking);
const factsBreaking = normalizeDiff(diffBreaking, v1, v2Breaking);
assert.equal(factsBreaking.totalBreaking, 3);
console.log(
"FULL OPENAPI DIFF:",
JSON.stringify(diffBreaking, null, 2)
);
const payloadBreaking = {
apiName: "User Service API",
oldVersion: "1.0.0",
newVersion: "2.0.0",
changesCount: factsBreaking.totalBreaking,
changes: factsBreaking.allChanges
};
console.log("Breaking Normalized Payload:", JSON.stringify(payloadBreaking, null, 2));
console.log("Triggering Lamatic Workflow for Breaking Test Case...");
const resultBreaking = await triggerWorkflowAndPoll(payloadBreaking);
assert.ok(resultBreaking, 'Breaking workflow returned no result');
console.log("Breaking Test Result Output:", JSON.stringify(resultBreaking, null, 2));
🤖 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 `@kits/api-schema-drift-sentinel/apps/test-orchestrate.js` around lines 164 -
231, Update runMatrixTests to add deterministic assertions for each normalized
payload, validating the expected additive and breaking change counts and
normalized change contents. After each triggerWorkflowAndPoll call, assert that
the returned workflow result is present; throw or otherwise fail explicitly when
it is null or absent. Do not assert workflow-generated or LLM prose.

Comment thread kits/api-schema-drift-sentinel/prompts/analyze-schema-drift_llm-node_system.md Outdated
Comment thread kits/api-schema-drift-sentinel/prompts/analyze-schema-drift_llm-node_system.md Outdated
Comment on lines +186 to +194
### Test A — Additive (non-breaking)

**Input:** Base spec has `GET /users`, target spec adds `POST /users`.

**Expected result:**
- `changesCount: 1`
- `breakingChangesCount: 0`
- `deploymentRisk: LOW`
- One non-breaking change: `POST /users` added

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Mission directive: Align Test A with the harness.

The README says Test A adds POST /users. apps/test-orchestrate.js lines 27-45 instead add full_name to GET /users/{id}.

The documented changesCount: 1 also conflicts with the harness payload, which assigns changesCount from factsAdditive.totalBreaking. That value should be 0 for an additive case.

Choose one scenario. Update the fixture or this expected-result section consistently.

🤖 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 `@kits/api-schema-drift-sentinel/README.md` around lines 186 - 194, Align the
Test A scenario in the README with the harness behavior in
apps/test-orchestrate.js: either document the existing full_name addition to GET
/users/{id} or update the fixture to add POST /users. Ensure the expected
changesCount matches factsAdditive.totalBreaking, using 0 for this additive
case, while keeping breakingChangesCount and deploymentRisk consistent.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 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.

Inline comments:
In `@kits/api-schema-drift-sentinel/.env.example`:
- Around line 2-4: Update the variable ordering in .env.example so
LAMATIC_API_URL and LAMATIC_DRIFT_FLOW_ID precede LAMATIC_PROJECT_ID, then
ensure the file ends with a final newline.

In `@kits/api-schema-drift-sentinel/agent.md`:
- Around line 3-10: Add blank lines before and after each of the four reported
Markdown headings in kits/api-schema-drift-sentinel/agent.md#L3-L10. Apply the
same heading-spacing correction in the template/source at
kits/api-schema-drift-sentinel/constitutions/default.md#L3-L15, then regenerate
that file so the generated output matches the source.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: be904b5d-5c86-4da1-8280-ef4cef986642

📥 Commits

Reviewing files that changed from the base of the PR and between ad34c60 and 19ce46e.

📒 Files selected for processing (4)
  • kits/api-schema-drift-sentinel/.env.example
  • kits/api-schema-drift-sentinel/agent.md
  • kits/api-schema-drift-sentinel/constitutions/default.md
  • kits/api-schema-drift-sentinel/flows/analyze-schema-drift.ts

Comment on lines +2 to +4
LAMATIC_PROJECT_ID=your_project_id_here
LAMATIC_API_URL=https://api.lamatic.ai
LAMATIC_DRIFT_FLOW_ID=your_id No newline at end of file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Mission requirement: clear the environment-file lint findings.

dotenv-linter reports that LAMATIC_API_URL and LAMATIC_DRIFT_FLOW_ID must appear before LAMATIC_PROJECT_ID. Move LAMATIC_PROJECT_ID to the end and add the missing final newline.

Proposed ordering
 LAMATIC_API_KEY=your_lamatic_api_key_here
-LAMATIC_PROJECT_ID=your_project_id_here
 LAMATIC_API_URL=https://api.lamatic.ai
 LAMATIC_DRIFT_FLOW_ID=your_id
+LAMATIC_PROJECT_ID=your_project_id_here
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
LAMATIC_PROJECT_ID=your_project_id_here
LAMATIC_API_URL=https://api.lamatic.ai
LAMATIC_DRIFT_FLOW_ID=your_id
LAMATIC_API_KEY=your_lamatic_api_key_here
LAMATIC_API_URL=https://api.lamatic.ai
LAMATIC_DRIFT_FLOW_ID=your_id
LAMATIC_PROJECT_ID=your_project_id_here
🧰 Tools
🪛 dotenv-linter (4.0.0)

[warning] 3-3: [UnorderedKey] The LAMATIC_API_URL key should go before the LAMATIC_PROJECT_ID key

(UnorderedKey)


[warning] 4-4: [EndingBlankLine] No blank line at the end of the file

(EndingBlankLine)


[warning] 4-4: [UnorderedKey] The LAMATIC_DRIFT_FLOW_ID key should go before the LAMATIC_PROJECT_ID key

(UnorderedKey)

🤖 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 `@kits/api-schema-drift-sentinel/.env.example` around lines 2 - 4, Update the
variable ordering in .env.example so LAMATIC_API_URL and LAMATIC_DRIFT_FLOW_ID
precede LAMATIC_PROJECT_ID, then ensure the file ends with a final newline.

Source: Linters/SAST tools

Comment on lines +3 to +10
## Overview
API Schema Drift Sentinel detects breaking changes between OpenAPI specifications and produces grounded migration guidance.

## Purpose
The goal of this kit is to prevent breaking API drift by combining deterministic AST diffing with an AI reasoning layer.

## Flows
### 1. Analyze Schema Drift

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Clear the shared Markdown heading-spacing findings at the correct source.

Both files place headings directly next to following content. Add blank lines around the headings in kits/api-schema-drift-sentinel/agent.md. Update the template/source for kits/api-schema-drift-sentinel/constitutions/default.md, then regenerate it.

  • kits/api-schema-drift-sentinel/agent.md#L3-L10: add blank lines around the four reported headings.
  • kits/api-schema-drift-sentinel/constitutions/default.md#L3-L15: apply the spacing fix in the template/source and regenerate the file.

Based on learnings: kits/*/constitutions/default.md is a templated/auto-generated file.

🧰 Tools
🪛 markdownlint-cli2 (0.23.2)

[warning] 3-3: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 6-6: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 9-9: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 10-10: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Above

(MD022, blanks-around-headings)


[warning] 10-10: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)

📍 Affects 2 files
  • kits/api-schema-drift-sentinel/agent.md#L3-L10 (this comment)
  • kits/api-schema-drift-sentinel/constitutions/default.md#L3-L15
🤖 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 `@kits/api-schema-drift-sentinel/agent.md` around lines 3 - 10, Add blank lines
before and after each of the four reported Markdown headings in
kits/api-schema-drift-sentinel/agent.md#L3-L10. Apply the same heading-spacing
correction in the template/source at
kits/api-schema-drift-sentinel/constitutions/default.md#L3-L15, then regenerate
that file so the generated output matches the source.

Sources: Learnings, Linters/SAST tools

@akshatvirmani

Copy link
Copy Markdown
Contributor

/validate

@github-actions

Copy link
Copy Markdown
Contributor

📡 Running Studio validation — results will appear here shortly.

@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Studio Runtime Validation (Phase 2)

Studio validation passed. The kit loaded successfully in Lamatic Studio.

This PR is ready for final review and merge.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 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.

Inline comments:
In `@kits/api-schema-drift-sentinel/apps/app/api/analyze-drift/route.ts`:
- Around line 26-35: Update the size check near MAX_SIZE to measure strA and
strB as UTF-8 byte lengths using TextEncoder rather than String.length. Compare
the encoded byte counts against the existing 2 MiB limit and preserve the
current 400 response for oversized payloads.
- Around line 19-24: Update the type validation in the analyze-drift route to
accept strings or only non-null, non-array objects for specA and specB. Reject
arrays and other invalid object-like values with the existing 400 response
before they enter the diff pipeline.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 217144e0-52a1-4d20-aa36-cc405809d37f

📥 Commits

Reviewing files that changed from the base of the PR and between 19ce46e and 2ae66d7.

📒 Files selected for processing (6)
  • kits/api-schema-drift-sentinel/.gitignore
  • kits/api-schema-drift-sentinel/apps/actions/orchestrate.ts
  • kits/api-schema-drift-sentinel/apps/app/api/analyze-drift/route.ts
  • kits/api-schema-drift-sentinel/apps/app/globals.css
  • kits/api-schema-drift-sentinel/apps/test-orchestrate.js
  • kits/api-schema-drift-sentinel/prompts/analyze-schema-drift_llm-node_system.md
💤 Files with no reviewable changes (1)
  • kits/api-schema-drift-sentinel/apps/app/globals.css

Comment thread kits/api-schema-drift-sentinel/apps/app/api/analyze-drift/route.ts Outdated
Comment thread kits/api-schema-drift-sentinel/apps/app/api/analyze-drift/route.ts
@mohamad-shafeez

Copy link
Copy Markdown
Author

/validate

@github-actions

Copy link
Copy Markdown
Contributor

📡 Running Studio validation — results will appear here shortly.

@github-actions

Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor
⚠️ Action not completed

Already reviewed.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions

Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor
⚠️ Action not completed

Already reviewed.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@akshatvirmani

Copy link
Copy Markdown
Contributor

/validate

@github-actions

Copy link
Copy Markdown
Contributor

📡 Running Studio validation — results will appear here shortly.

@akshatvirmani

Copy link
Copy Markdown
Contributor

@mohamad-shafeez phase 2 is failing
https://github.com/Lamatic/AgentKit/actions/runs/31806862908

also there are lots of coderabbit comments in PR

@mohamad-shafeez

Copy link
Copy Markdown
Author

/validate

@github-actions

Copy link
Copy Markdown
Contributor

📡 Running Studio validation — results will appear here shortly.

@mohamad-shafeez

Copy link
Copy Markdown
Author

/validate

@github-actions

Copy link
Copy Markdown
Contributor

📡 Running Studio validation — results will appear here shortly.

@mohamad-shafeez

Copy link
Copy Markdown
Author

/validate

@github-actions

Copy link
Copy Markdown
Contributor

📡 Running Studio validation — results will appear here shortly.

@github-actions

Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@akshatvirmani

Copy link
Copy Markdown
Contributor

/validate

@github-actions

Copy link
Copy Markdown
Contributor

📡 Running Studio validation — results will appear here shortly.

@akshatvirmani

Copy link
Copy Markdown
Contributor

@mohamad-shafeez there are some comments left by coderabbit please resolve them then we can merge

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants