Skip to content
Merged
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: 1 addition & 1 deletion .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"hooks": [
{
"type": "command",
"command": "file=\"$CLAUDE_FILE_PATH\"; if echo \"$file\" | grep -qE '\\.(ts|tsx)$' && echo \"$file\" | grep -qE '^(apps/api|apps/app)/'; then echo 'TypeScript file modified — remember to run typecheck before committing'; fi"
"command": "file=\"$CLAUDE_FILE_PATH\"; if echo \"$file\" | grep -qE '\\.(ts|tsx)$' && echo \"$file\" | grep -qE '^(apps/|packages/)'; then echo 'TypeScript file modified — run the cleanup skill and typecheck before committing'; fi"
}
]
},
Expand Down
102 changes: 102 additions & 0 deletions .claude/skills/cleanup/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
---
name: cleanup
description: "MUST run after writing or modifying code — reviews changed files for verbose patterns, inconsistencies, and readability issues before considering work done"
---

# Post-Implementation Cleanup

**This skill is mandatory.** After writing or modifying code, you MUST review all changed files before reporting the task as complete. Code must be readable at a glance.

## When to Run

- After completing any implementation work
- After fixing bugs
- After refactoring
- Before committing

## Checklist

For every file you changed, verify:

### 1. No Verbose Defensive Checks

Extract repeated patterns into typed helpers.

```tsx
// ❌ Verbose and repeated
const perms = typeof role.permissions === 'string'
? JSON.parse(role.permissions) : role.permissions;
if (perms && typeof perms === 'object' && Array.isArray(perms.portal) && perms.portal.length > 0) {

// ✅ Typed helper
const perms = parseRolePermissions(role.permissions);
if (perms?.portal?.length) {
```

### 2. Consistent Idioms Across Files

The same check must use the same pattern everywhere.

```tsx
// ❌ Inconsistent
file1: perms?.portal?.length > 0
file2: perms?.portal?.length

// ✅ Pick one
perms?.portal?.length
```

### 3. No Redundant Type Casts

If you need a cast to satisfy TypeScript, extract a helper function instead.

```tsx
// ❌ Verbose cast repeated in every file
const restrictedRoles: readonly string[] = RESTRICTED_ROLES;
restrictedRoles.includes(role);

// ✅ Helper in shared package
export function isRestrictedRole(role: string): boolean {
return (RESTRICTED_ROLES as readonly string[]).includes(role);
}
```

### 4. Error Handling on Boundaries

`JSON.parse`, external API calls, and DB queries at system boundaries need error handling.

```tsx
// ❌ Unguarded parse
const parsed = JSON.parse(value);

// ✅ Safe parse
try {
return JSON.parse(value);
} catch {
return null;
}
```

### 5. Shared Patterns Belong in Shared Packages

If the same logic appears in 2+ apps (api, app, portal), extract it to a shared package (`packages/auth`, `packages/db`, etc.).

### 6. No Dead Code

- Remove unused imports
- Remove unused variables
- Remove unused function parameters
- Remove props that are always null/false

### 7. Readable at a Glance

- Function and variable names should convey intent without reading the implementation
- One-liner expressions over multi-line when equally clear
- No nested ternaries

## How to Run

1. List all files you modified: `git diff --name-only`
2. Read each file and check against this checklist
3. Fix any issues found
4. Typecheck after fixes: `npx tsc --noEmit`
17 changes: 15 additions & 2 deletions apps/api/src/controls/controls.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,9 @@ export class ControlsController {
async findOne(
@OrganizationId() organizationId: string,
@Param('id') id: string,
@Query('frameworkInstanceId') frameworkInstanceId?: string,
) {
return this.controlsService.findOne(id, organizationId);
return this.controlsService.findOne(id, organizationId, frameworkInstanceId);
}

@Post()
Expand All @@ -105,11 +106,13 @@ export class ControlsController {
@OrganizationId() organizationId: string,
@Param('id') id: string,
@Body() dto: LinkPoliciesDto,
@Query('frameworkInstanceId') frameworkInstanceId?: string,
) {
return this.controlsService.linkPolicies(
id,
organizationId,
dto.policyIds,
frameworkInstanceId,
);
}

Expand All @@ -120,8 +123,14 @@ export class ControlsController {
@OrganizationId() organizationId: string,
@Param('id') id: string,
@Body() dto: LinkTasksDto,
@Query('frameworkInstanceId') frameworkInstanceId?: string,
) {
return this.controlsService.linkTasks(id, organizationId, dto.taskIds);
return this.controlsService.linkTasks(
id,
organizationId,
dto.taskIds,
frameworkInstanceId,
);
}

@Post(':id/requirements/link')
Expand All @@ -146,11 +155,13 @@ export class ControlsController {
@OrganizationId() organizationId: string,
@Param('id') id: string,
@Body() dto: LinkDocumentTypesDto,
@Query('frameworkInstanceId') frameworkInstanceId?: string,
) {
return this.controlsService.linkDocumentTypes(
id,
organizationId,
dto.formTypes,
frameworkInstanceId,
);
}

Expand All @@ -162,11 +173,13 @@ export class ControlsController {
@Param('id') id: string,
@Param('formType', new ParseEnumPipe(EvidenceFormType))
formType: EvidenceFormType,
@Query('frameworkInstanceId') frameworkInstanceId?: string,
) {
return this.controlsService.unlinkDocumentType(
id,
organizationId,
formType,
frameworkInstanceId,
);
}

Expand Down
Loading
Loading