From 75d9c5ddaf1e26889e8bc4fe52db71958c09cce8 Mon Sep 17 00:00:00 2001 From: Tofik Hasanov Date: Fri, 12 Jun 2026 11:59:09 -0400 Subject: [PATCH 1/2] feat(framework-editor): raise requirement description limit to 10,000 chars Requirement descriptions were capped at 5,000 characters by the API DTOs, too short for detailed NIST-style requirement text. Raise the @MaxLength on the description field to 10,000 across create, update, and batch-update requirement DTOs. Adds DTO validation specs proving the 10,000-char boundary (10k passes, 10,001 rejected) for all three DTOs. Closes FRAME-2 Co-Authored-By: Claude Opus 4.8 --- .../dto/batch-update-requirements.dto.spec.ts | 49 ++++++++++++++++++ .../dto/batch-update-requirements.dto.ts | 2 +- .../dto/create-requirement.dto.spec.ts | 50 +++++++++++++++++++ .../requirement/dto/create-requirement.dto.ts | 2 +- .../dto/update-requirement.dto.spec.ts | 44 ++++++++++++++++ .../requirement/dto/update-requirement.dto.ts | 2 +- 6 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 apps/api/src/framework-editor/requirement/dto/batch-update-requirements.dto.spec.ts create mode 100644 apps/api/src/framework-editor/requirement/dto/create-requirement.dto.spec.ts create mode 100644 apps/api/src/framework-editor/requirement/dto/update-requirement.dto.spec.ts diff --git a/apps/api/src/framework-editor/requirement/dto/batch-update-requirements.dto.spec.ts b/apps/api/src/framework-editor/requirement/dto/batch-update-requirements.dto.spec.ts new file mode 100644 index 0000000000..668a183cc5 --- /dev/null +++ b/apps/api/src/framework-editor/requirement/dto/batch-update-requirements.dto.spec.ts @@ -0,0 +1,49 @@ +import { plainToInstance } from 'class-transformer'; +import { validate } from 'class-validator'; +import { BatchUpdateRequirementsDto } from './batch-update-requirements.dto'; + +/** + * Mirrors the global ValidationPipe config from main.ts: + * whitelist: true, transform: true, enableImplicitConversion: true + */ +function toDto(plain: Record): BatchUpdateRequirementsDto { + return plainToInstance(BatchUpdateRequirementsDto, plain, { + enableImplicitConversion: true, + }); +} + +describe('BatchUpdateRequirementsDto', () => { + it('accepts a valid batch payload', async () => { + const dto = toDto({ + updates: [{ id: 'frq_1', description: 'Updated description' }], + }); + const errors = await validate(dto, { + whitelist: true, + forbidNonWhitelisted: true, + }); + expect(errors).toHaveLength(0); + }); + + // ── nested description length (FRAME-2: limit raised 5,000 → 10,000) ─ + it('accepts a nested description at the 10,000-char limit', async () => { + const dto = toDto({ + updates: [{ id: 'frq_1', description: 'x'.repeat(10_000) }], + }); + const errors = await validate(dto, { + whitelist: true, + forbidNonWhitelisted: true, + }); + expect(errors).toHaveLength(0); + }); + + it('rejects a nested description longer than 10,000 chars', async () => { + const dto = toDto({ + updates: [{ id: 'frq_1', description: 'x'.repeat(10_001) }], + }); + const errors = await validate(dto, { + whitelist: true, + forbidNonWhitelisted: true, + }); + expect(errors.length).toBeGreaterThan(0); + }); +}); diff --git a/apps/api/src/framework-editor/requirement/dto/batch-update-requirements.dto.ts b/apps/api/src/framework-editor/requirement/dto/batch-update-requirements.dto.ts index e0a8530105..250da0f694 100644 --- a/apps/api/src/framework-editor/requirement/dto/batch-update-requirements.dto.ts +++ b/apps/api/src/framework-editor/requirement/dto/batch-update-requirements.dto.ts @@ -30,7 +30,7 @@ class BatchUpdateRequirementItem { @ApiProperty() @IsString() @IsOptional() - @MaxLength(5000) + @MaxLength(10000) description?: string; @ApiProperty() diff --git a/apps/api/src/framework-editor/requirement/dto/create-requirement.dto.spec.ts b/apps/api/src/framework-editor/requirement/dto/create-requirement.dto.spec.ts new file mode 100644 index 0000000000..693976958b --- /dev/null +++ b/apps/api/src/framework-editor/requirement/dto/create-requirement.dto.spec.ts @@ -0,0 +1,50 @@ +import { plainToInstance } from 'class-transformer'; +import { validate } from 'class-validator'; +import { CreateRequirementDto } from './create-requirement.dto'; + +/** + * Mirrors the global ValidationPipe config from main.ts: + * whitelist: true, transform: true, enableImplicitConversion: true + */ +function toDto(plain: Record): CreateRequirementDto { + return plainToInstance(CreateRequirementDto, plain, { + enableImplicitConversion: true, + }); +} + +const VALID_BASE = { + frameworkId: 'frk_abc123', + name: 'AC-1', + description: 'Access control policy and procedures', +}; + +describe('CreateRequirementDto', () => { + it('accepts a valid payload', async () => { + const dto = toDto(VALID_BASE); + const errors = await validate(dto, { + whitelist: true, + forbidNonWhitelisted: true, + }); + expect(errors).toHaveLength(0); + }); + + // ── description length (FRAME-2: limit raised 5,000 → 10,000) ────── + it('accepts a description at the 10,000-char limit', async () => { + const dto = toDto({ ...VALID_BASE, description: 'x'.repeat(10_000) }); + const errors = await validate(dto, { + whitelist: true, + forbidNonWhitelisted: true, + }); + expect(errors).toHaveLength(0); + }); + + it('rejects a description longer than 10,000 chars', async () => { + const dto = toDto({ ...VALID_BASE, description: 'x'.repeat(10_001) }); + const errors = await validate(dto, { + whitelist: true, + forbidNonWhitelisted: true, + }); + expect(errors.length).toBeGreaterThan(0); + expect(errors[0].property).toBe('description'); + }); +}); diff --git a/apps/api/src/framework-editor/requirement/dto/create-requirement.dto.ts b/apps/api/src/framework-editor/requirement/dto/create-requirement.dto.ts index 87181ac569..8c21671c00 100644 --- a/apps/api/src/framework-editor/requirement/dto/create-requirement.dto.ts +++ b/apps/api/src/framework-editor/requirement/dto/create-requirement.dto.ts @@ -22,7 +22,7 @@ export class CreateRequirementDto { @ApiProperty({ example: 'Control environment requirements' }) @IsString() - @MaxLength(5000) + @MaxLength(10000) description: string; @ApiPropertyOptional({ example: 'Access Control' }) diff --git a/apps/api/src/framework-editor/requirement/dto/update-requirement.dto.spec.ts b/apps/api/src/framework-editor/requirement/dto/update-requirement.dto.spec.ts new file mode 100644 index 0000000000..83246f17ed --- /dev/null +++ b/apps/api/src/framework-editor/requirement/dto/update-requirement.dto.spec.ts @@ -0,0 +1,44 @@ +import { plainToInstance } from 'class-transformer'; +import { validate } from 'class-validator'; +import { UpdateRequirementDto } from './update-requirement.dto'; + +/** + * Mirrors the global ValidationPipe config from main.ts: + * whitelist: true, transform: true, enableImplicitConversion: true + */ +function toDto(plain: Record): UpdateRequirementDto { + return plainToInstance(UpdateRequirementDto, plain, { + enableImplicitConversion: true, + }); +} + +describe('UpdateRequirementDto', () => { + it('accepts a partial update (single field)', async () => { + const dto = toDto({ name: 'AC-2' }); + const errors = await validate(dto, { + whitelist: true, + forbidNonWhitelisted: true, + }); + expect(errors).toHaveLength(0); + }); + + // ── description length (FRAME-2: limit raised 5,000 → 10,000) ────── + it('accepts a description at the 10,000-char limit', async () => { + const dto = toDto({ description: 'x'.repeat(10_000) }); + const errors = await validate(dto, { + whitelist: true, + forbidNonWhitelisted: true, + }); + expect(errors).toHaveLength(0); + }); + + it('rejects a description longer than 10,000 chars', async () => { + const dto = toDto({ description: 'x'.repeat(10_001) }); + const errors = await validate(dto, { + whitelist: true, + forbidNonWhitelisted: true, + }); + expect(errors.length).toBeGreaterThan(0); + expect(errors[0].property).toBe('description'); + }); +}); diff --git a/apps/api/src/framework-editor/requirement/dto/update-requirement.dto.ts b/apps/api/src/framework-editor/requirement/dto/update-requirement.dto.ts index 9a85ebc715..705707934d 100644 --- a/apps/api/src/framework-editor/requirement/dto/update-requirement.dto.ts +++ b/apps/api/src/framework-editor/requirement/dto/update-requirement.dto.ts @@ -17,7 +17,7 @@ export class UpdateRequirementDto { @ApiPropertyOptional() @IsString() @IsOptional() - @MaxLength(5000) + @MaxLength(10000) description?: string; @ApiPropertyOptional() From a9b24cc14b17c9afbef18c2f062cda9081a9416c Mon Sep 17 00:00:00 2001 From: Tofik Hasanov Date: Fri, 12 Jun 2026 12:22:30 -0400 Subject: [PATCH 2/2] docs(mcp): one-click "Download for Claude Desktop" button The Claude Desktop setup step linked to the generic GitHub Releases page, where the .mcpb is buried under the frequent product releases and customers (and CX) couldn't find it. Point it at the new stable endpoint instead, which always resolves to the latest MCP release's bundle: https://api.trycomp.ai/mcp/download/claude-desktop Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/docs/mcp-server.mdx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/docs/mcp-server.mdx b/packages/docs/mcp-server.mdx index 03ecbcbf16..da77161a3b 100644 --- a/packages/docs/mcp-server.mdx +++ b/packages/docs/mcp-server.mdx @@ -45,8 +45,12 @@ The `npx -y @trycompai/mcp-server` command you'll see throughout is what handles **Easiest path — drag-and-drop install:** -1. Download the latest [`mcp-server.mcpb`](https://github.com/trycompai/comp/releases) bundle from our GitHub Releases page. -2. Drag the `.mcpb` file directly onto the Claude Desktop window. + + Always serves the latest Comp AI MCP bundle (`.mcpb`) — no version hunting. + + +1. Click **Download for Claude Desktop** above to get the latest `mcp-server.mcpb` bundle. +2. Drag the downloaded `.mcpb` file directly onto the Claude Desktop window. 3. Claude Desktop shows an install prompt — confirm, then paste your Comp AI API key in the extension settings. 4. Restart any open chats. Comp AI tools are now available.