Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ node_modules
.pnp
.pnp.js

.docusaurus

.env.development

# Local env files
Expand Down
3 changes: 0 additions & 3 deletions docs/src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import Layout from '@theme/Layout';
import { useHistory } from "@docusaurus/router"



export default function Home(): ReactNode {
const { siteConfig } = useDocusaurusContext();
const history = useHistory();
Expand All @@ -14,7 +12,6 @@ export default function Home(): ReactNode {
history.push("/prototype/docs/Getting Started/getting-started")
}, [])

useHistory
return (
<Layout
title={`Hello from ${siteConfig.title}`}
Expand Down
2 changes: 0 additions & 2 deletions mappings/.gitignore

This file was deleted.

3 changes: 2 additions & 1 deletion platforms/dreamsync-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"typeorm": "typeorm-ts-node-commonjs",
"migration:generate": "typeorm-ts-node-commonjs migration:generate -d src/database/data-source.ts",
"migration:run": "typeorm-ts-node-commonjs migration:run -d src/database/data-source.ts",
"migration:revert": "typeorm-ts-node-commonjs migration:revert -d src/database/data-source.ts"
"migration:revert": "typeorm-ts-node-commonjs migration:revert -d src/database/data-source.ts",
"migrate-summaries": "ts-node --project tsconfig.json scripts/migrate-summaries.ts"
},
"dependencies": {
"axios": "^1.6.7",
Expand Down
93 changes: 93 additions & 0 deletions platforms/dreamsync-api/scripts/migrate-summaries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import "reflect-metadata";
import path from "node:path";
import { config } from "dotenv";
import { AppDataSource } from "../src/database/data-source";
import { WishlistSummaryService } from "../src/services/WishlistSummaryService";
import { Wishlist } from "../src/database/entities/Wishlist";

// Load environment variables
config({ path: path.resolve(__dirname, "../../../.env") });

async function migrateSummaries() {
console.log("Starting migration: Converting text summaries to AI-generated arrays...\n");

try {
// Initialize database connection
await AppDataSource.initialize();
console.log("Database connection established\n");

const wishlistRepository = AppDataSource.getRepository(Wishlist);
const summaryService = WishlistSummaryService.getInstance();

// Get all wishlists that need migration (have text summaries or no summaries)
const wishlists = await wishlistRepository.find({
where: { isActive: true },
relations: ["user"],
order: { updatedAt: "DESC" },
});

console.log(`Found ${wishlists.length} wishlists to process\n`);

let processedCount = 0;
let skippedCount = 0;
let errorCount = 0;

for (const wishlist of wishlists) {
try {
// Check if already has array summaries
const hasArraySummaries =
Array.isArray(wishlist.summaryWants) && wishlist.summaryWants.length > 0 &&
Array.isArray(wishlist.summaryOffers) && wishlist.summaryOffers.length > 0;

if (hasArraySummaries) {
skippedCount++;
continue;
}

console.log(`Processing wishlist ${wishlist.id}...`);
console.log(` Raw content: ${wishlist.content.substring(0, 200)}${wishlist.content.length > 200 ? '...' : ''}`);

// Use the summary service to generate arrays from the raw content
const summary = await summaryService.summarizeWishlistContent(wishlist.content);

console.log(` Generated Wants: ${JSON.stringify(summary.summaryWants)}`);
console.log(` Generated Offers: ${JSON.stringify(summary.summaryOffers)}`);

// Save the arrays to the database
wishlist.summaryWants = summary.summaryWants;
wishlist.summaryOffers = summary.summaryOffers;
await wishlistRepository.save(wishlist);

console.log(` Saved successfully\n`);
processedCount++;

} catch (error) {
console.error(` Error processing wishlist ${wishlist.id}:`, error);
errorCount++;
}
}

console.log("Migration completed:");
console.log(` Processed: ${processedCount}`);
console.log(` Skipped (already has arrays): ${skippedCount}`);
console.log(` Errors: ${errorCount}\n`);

} catch (error) {
console.error("Migration failed:", error);
process.exit(1);
} finally {
await AppDataSource.destroy();
console.log("Database connection closed");
}
}

// Run migration
migrateSummaries()
.then(() => {
console.log("Migration script completed");
process.exit(0);
})
.catch((error) => {
console.error("Migration script failed:", error);
process.exit(1);
});
8 changes: 4 additions & 4 deletions platforms/dreamsync-api/src/database/entities/Wishlist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ export class Wishlist {
@Column({ type: "text" })
content: string; // Markdown content

@Column({ type: "text", nullable: true })
summaryWants: string | null;
@Column({ type: "jsonb", nullable: true })
summaryWants: string[] | null;

@Column({ type: "text", nullable: true })
summaryOffers: string | null;
@Column({ type: "jsonb", nullable: true })
summaryOffers: string[] | null;

@Column({ type: "boolean", default: true })
isActive: boolean;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { MigrationInterface, QueryRunner } from "typeorm";

export class Migration1768904445609 implements MigrationInterface {
name = 'Migration1768904445609'

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "wishlists" DROP COLUMN "summaryWants"`);
await queryRunner.query(`ALTER TABLE "wishlists" ADD "summaryWants" jsonb`);
await queryRunner.query(`ALTER TABLE "wishlists" DROP COLUMN "summaryOffers"`);
await queryRunner.query(`ALTER TABLE "wishlists" ADD "summaryOffers" jsonb`);
}
Comment on lines +6 to +11
Copy link
Contributor

@coderabbitai coderabbitai bot Jan 20, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Data loss risk: Dropping columns destroys existing summaries.

The migration drops summaryWants and summaryOffers columns before re-adding them as jsonb. Any existing text summaries in production will be permanently lost. The migrate-summaries.ts script cannot help because it runs after the migration has already deleted the data.

Consider using ALTER COLUMN ... TYPE jsonb USING to preserve and convert existing data:

🐛 Proposed fix to preserve existing data
     public async up(queryRunner: QueryRunner): Promise<void> {
-        await queryRunner.query(`ALTER TABLE "wishlists" DROP COLUMN "summaryWants"`);
-        await queryRunner.query(`ALTER TABLE "wishlists" ADD "summaryWants" jsonb`);
-        await queryRunner.query(`ALTER TABLE "wishlists" DROP COLUMN "summaryOffers"`);
-        await queryRunner.query(`ALTER TABLE "wishlists" ADD "summaryOffers" jsonb`);
+        // Convert existing text to jsonb arrays, wrapping single strings in arrays
+        await queryRunner.query(`
+            ALTER TABLE "wishlists" 
+            ALTER COLUMN "summaryWants" TYPE jsonb 
+            USING CASE 
+                WHEN "summaryWants" IS NULL THEN NULL
+                ELSE to_jsonb(ARRAY["summaryWants"])
+            END
+        `);
+        await queryRunner.query(`
+            ALTER TABLE "wishlists" 
+            ALTER COLUMN "summaryOffers" TYPE jsonb 
+            USING CASE 
+                WHEN "summaryOffers" IS NULL THEN NULL
+                ELSE to_jsonb(ARRAY["summaryOffers"])
+            END
+        `);
     }

Alternatively, if re-generating all summaries is intentional, document this clearly and ensure the migration script runs immediately after on deployment.

🤖 Prompt for AI Agents
In `@platforms/dreamsync-api/src/database/migrations/1768904445609-migration.ts`
around lines 6 - 11, The migration currently drops and re-adds summaryWants and
summaryOffers which destroys existing data; update the up(queryRunner:
QueryRunner) implementation to convert in-place instead of DROP/ADD by using
ALTER TABLE "wishlists" ALTER COLUMN "<column>" TYPE jsonb USING
to_json("<column>")::jsonb for both summaryWants and summaryOffers (remove the
DROP and ADD statements), so existing text summaries are preserved and cast to
jsonb; if deletion was intentional, add a clear comment and ensure the
migrate-summaries.ts runs immediately after deployment instead of changing the
migration.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

nack this data is essentially platform level logic which can be discarded and is not important, the only truly important thing is keeping the actual wishlist safe

Copy link
Contributor

Choose a reason for hiding this comment

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

@coodos, understood! Since summaryWants and summaryOffers are derived from the wishlist content and can be regenerated via the migrate-summaries.ts script, dropping them is safe. The source data remains intact. Thanks for the clarification.


✏️ Learnings added
Learnt from: coodos
Repo: MetaState-Prototype-Project/prototype PR: 691
File: platforms/dreamsync-api/src/database/migrations/1768904445609-migration.ts:6-11
Timestamp: 2026-01-20T10:41:46.622Z
Learning: In the DreamSync platform (platforms/dreamsync-api), the `summaryWants` and `summaryOffers` fields in the Wishlist entity are derived/computed data that can be safely discarded and regenerated from the wishlist content. Only the actual wishlist content is considered critical data that must be preserved.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "wishlists" DROP COLUMN "summaryOffers"`);
await queryRunner.query(`ALTER TABLE "wishlists" ADD "summaryOffers" text`);
await queryRunner.query(`ALTER TABLE "wishlists" DROP COLUMN "summaryWants"`);
await queryRunner.query(`ALTER TABLE "wishlists" ADD "summaryWants" text`);
}

}
15 changes: 8 additions & 7 deletions platforms/dreamsync-api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,23 +32,24 @@ AppDataSource.initialize()
const exists = await platformService.checkPlatformEVaultExists();

if (!exists) {
console.log("🔧 Creating platform eVault for DreamSync...");
console.log("Creating platform eVault for DreamSync...");
const result = await platformService.createPlatformEVault();
console.log(`Platform eVault created successfully: ${result.w3id}`);
console.log(`Platform eVault created successfully: ${result.w3id}`);
} else {
console.log("Platform eVault already exists for DreamSync");
console.log("Platform eVault already exists for DreamSync");
}
} catch (error) {
console.error("Failed to initialize platform eVault:", error);
console.error("Failed to initialize platform eVault:", error);
// Don't exit the process, just log the error
}

// Backfill wishlist summaries for existing records
// Summarize all wishlists on platform start
try {
const wishlistSummaryService = WishlistSummaryService.getInstance();
await wishlistSummaryService.backfillMissingSummaries();
await wishlistSummaryService.summarizeAllWishlists();
} catch (error) {
console.error("❌ Failed to backfill wishlist summaries:", error);
console.error("Failed to summarize wishlists:", error);
// Don't exit the process, just log the error
}

// Start AI matching job (disabled automatic startup)
Expand Down
61 changes: 43 additions & 18 deletions platforms/dreamsync-api/src/services/AIMatchingService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,26 +38,38 @@ export class AIMatchingService {

return withOperationContext('AIMatchingService', operationId, async () => {
const wishlists = await this.getWishlistsForMatching();
console.log(`📋 Found ${wishlists.length} wishlists to analyze`);
console.log(`📋 Found ${wishlists.length} wishlists to analyze (after filtering blank wishlists)`);

if (wishlists.length === 0) {
console.log("⚠️ No valid wishlists to match, skipping matching process");
return;
}

await this.ensureWishlistSummaries(wishlists);

// Get existing groups for context
const existingGroups = await this.getExistingGroups();
console.log(`🏠 Found ${existingGroups.length} existing groups to consider`);

// Convert to shared service format
const wishlistData: WishlistData[] = wishlists.map(wishlist => ({
id: wishlist.id,
content: wishlist.content,
summaryWants: wishlist.summaryWants || "",
summaryOffers: wishlist.summaryOffers || "",
userId: wishlist.userId,
user: {
id: wishlist.user.id,
name: wishlist.user.name || wishlist.user.ename,
ename: wishlist.user.ename
}
}));
// Convert to shared service format, filtering out wishlists without summaries
const wishlistData: WishlistData[] = wishlists
.filter(wishlist => {
// Only include wishlists with valid summary arrays
return wishlist.summaryWants && wishlist.summaryWants.length > 0 &&
wishlist.summaryOffers && wishlist.summaryOffers.length > 0;
})
.map(wishlist => ({
id: wishlist.id,
content: wishlist.content,
summaryWants: wishlist.summaryWants || [],
summaryOffers: wishlist.summaryOffers || [],
userId: wishlist.userId,
user: {
id: wishlist.user.id,
name: wishlist.user.name || wishlist.user.ename,
ename: wishlist.user.ename
}
}));

// Use matching service for parallel processing
const matchResults = await this.matchingService.findMatches(wishlistData, existingGroups);
Expand Down Expand Up @@ -224,9 +236,19 @@ export class AIMatchingService {

private async ensureWishlistSummaries(wishlists: Wishlist[]): Promise<void> {
for (const wishlist of wishlists) {
if (!wishlist.summaryWants || !wishlist.summaryOffers) {
// Ensure summaries exist and are arrays
if (!wishlist.summaryWants || wishlist.summaryWants.length === 0 ||
!wishlist.summaryOffers || wishlist.summaryOffers.length === 0) {
try {
await this.wishlistSummaryService.ensureSummaries(wishlist);
// Reload wishlist to get updated summaries
const updated = await this.wishlistRepository.findOne({
where: { id: wishlist.id },
relations: ["user"]
});
if (updated) {
Object.assign(wishlist, updated);
}
} catch (error) {
console.error(`Failed to ensure summary for wishlist ${wishlist.id}`, error);
}
Expand Down Expand Up @@ -383,10 +405,12 @@ Content: ${wishlistA.content}
Title: ${wishlistB.title}
Content: ${wishlistB.content}

IMPORTANT: Only return a JSON response if there's a meaningful connection (confidence > 0.7). If there's no meaningful connection, return confidence: 0 and matchType: "private" (this will be filtered out).
IMPORTANT: Only return a JSON response if there's a meaningful connection (confidence > 0.85). If there's no meaningful connection, return confidence: 0 and matchType: "private" (this will be filtered out).

CRITICAL: If either wishlist is blank, templated with minimal content, or contains insufficient information, return confidence: 0. A blank/templated wishlist has the template structure (## What I Want / ## What I Can Do) but with very few items (2 or fewer meaningful items) or very short/placeholder content. Do NOT generate matches based on generic or placeholder content.

Return JSON with:
1. "confidence": number between 0-1 indicating match strength (0 if no meaningful connection)
1. "confidence": number between 0-1 indicating match strength (0 if no meaningful connection or if wishlists are too sparse)
2. "matchType": "private" or "group" (use "private" if no connection)
3. "reason": brief explanation of why they match (or why no match)
4. "matchedWants": array of what User A wants that User B can offer
Expand All @@ -399,8 +423,9 @@ Consider:
- Complementary needs and offerings
- Potential for meaningful collaboration
- Whether this could be a private connection or group activity
- Whether the wishlists contain sufficient meaningful content (not just template placeholders)

Only suggest matches with confidence > 0.7 for meaningful connections.
Only suggest matches with confidence > 0.85 for meaningful connections based on substantial content.
`.trim();
}

Expand Down
Loading