Skip to content

Commit e498128

Browse files
authored
Merge pull request #222 from objectstack-ai/copilot/improve-repo-scan-suggestions
2 parents 046fcc0 + 2bd0e99 commit e498128

26 files changed

Lines changed: 316 additions & 314 deletions

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ jobs:
3939
- name: Run linter
4040
run: pnpm run lint
4141

42+
- name: TypeScript type checking
43+
run: pnpm run typecheck
44+
4245
- name: Build project
4346
run: pnpm run build
4447

.github/workflows/code-quality.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ jobs:
4040

4141
- name: Check for console.log statements
4242
run: |
43-
if grep -r "console\.log" src/ --include="*.ts" --exclude-dir=node_modules; then
43+
if grep -r "console\.log" packages/ --include="*.ts" --exclude-dir=node_modules --exclude-dir=__tests__; then
4444
echo "Warning: console.log statements found in source code"
4545
echo "Please use proper logging instead"
4646
# This is just a warning, not failing the build
@@ -50,19 +50,19 @@ jobs:
5050
continue-on-error: true
5151

5252
- name: Check TypeScript compilation
53-
run: pnpm run build
53+
run: pnpm run typecheck
5454

5555
- name: Run ESLint
5656
run: pnpm run lint
5757

5858
- name: Check for TODO comments
5959
run: |
6060
echo "Checking for TODO/FIXME comments..."
61-
grep -r "TODO\|FIXME" src/ --include="*.ts" || echo "No TODO/FIXME found"
61+
grep -r "TODO\|FIXME" packages/ --include="*.ts" --exclude-dir=node_modules || echo "No TODO/FIXME found"
6262
continue-on-error: true
6363

6464
- name: Check file sizes
6565
run: |
6666
echo "Checking for large files..."
67-
find src/ -type f -size +100k -exec ls -lh {} \; || echo "No large files found"
67+
find packages/ -type f -size +100k -exec ls -lh {} \; || echo "No large files found"
6868
continue-on-error: true

CONTRIBUTING.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,12 @@ Only the compiled `dist/` folder is included in published packages (controlled b
143143
## 🔄 Pull Request Process
144144

145145
1. **Before Creating a PR**
146-
- Ensure all tests pass
147-
- Run linting and fix any issues
146+
- Ensure all tests pass (`pnpm test`)
147+
- Run type checking (`pnpm typecheck`)
148+
- Run linting and fix any issues (`pnpm lint`)
148149
- Update documentation if needed
149150
- Add tests for new features
151+
- Update ROADMAP.md if the change completes a roadmap item
150152
- **Add a changeset** if your changes affect package behavior
151153

152154
2. **Creating a PR**

DEVELOPMENT_WORKFLOW.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,16 +86,18 @@ export default ObjectSchema.create({
8686

8787
```typescript
8888
// packages/{pkg}/src/my_entity.hook.ts
89-
export function beforeInsert(context: any) {
90-
const { doc, broker } = context;
89+
import type { HookContext } from '@objectstack/spec/data';
90+
91+
export function beforeInsert(context: HookContext) {
92+
const doc = context.input.doc as Record<string, any>;
9193
// Validation and business logic
9294
if (!doc.name) {
9395
throw new Error('Name is required');
9496
}
9597
}
9698

97-
export function afterInsert(context: any) {
98-
const { doc, broker } = context;
99+
export function afterInsert(context: HookContext) {
100+
const doc = context.result as Record<string, any>;
99101
// Post-creation side effects (notifications, related records, etc.)
100102
}
101103
```
@@ -204,8 +206,10 @@ If the field needs validation or side effects, add logic to the hook file:
204206

205207
```typescript
206208
// packages/crm/src/account.hook.ts
207-
export function beforeInsert(context: any) {
208-
const { doc } = context;
209+
import type { HookContext } from '@objectstack/spec/data';
210+
211+
export function beforeInsert(context: HookContext) {
212+
const doc = context.input.doc as Record<string, any>;
209213
// Validate URL format if provided
210214
if (doc.website_url && !doc.website_url.startsWith('http')) {
211215
doc.website_url = 'https://' + doc.website_url;

ROADMAP.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# HotCRM Development Roadmap
22

33
> Comprehensive development plan for HotCRM — the world's first AI-Native CRM.
4-
> Protocol: @objectstack/spec v3.0.8 | Last Updated: February 21, 2027
4+
> Protocol: @objectstack/spec v3.0.8 | Last Updated: February 2026
55
66
## Strategic Direction
77

@@ -15,6 +15,7 @@
1515
2027 Q1-Q2 ████████████████████████████████ Phase 13: Module Optimization & Seed Data ✅ COMPLETE
1616
2027 Q2-Q3 ████████████████████████████████ Phase 14: v3.0.8 Feed & Interface Builder Adoption ✅ COMPLETE
1717
2027+ ████████████████████████████████ Phase 12E: Advanced AI & Enterprise Features ✅ COMPLETE
18+
2026 Q1 ████████████████████████████████ Phase 15: Repository Quality & DX Improvements 🔄 IN PROGRESS
1819
```
1920

2021
## Current State Summary
@@ -171,10 +172,39 @@ These items were deferred during Phase 13 and have been completed:
171172

172173
---
173174

175+
### Phase 15: Repository Quality & DX Improvements (2026 Q1)
176+
177+
> Goal: Improve code quality, CI/CD, documentation accuracy, and developer experience.
178+
179+
#### High Priority
180+
181+
- [x] **DEVELOPMENT_WORKFLOW.md test count** — Remove hardcoded test count; use dynamic description
182+
- [x] **Hook `any` type migration** — Replace `ctx: any` with `ctx: HookContext` in 18 helper functions across hook files
183+
- [x] **pnpm-workspace.yaml documentation** — Add comment explaining core/server exclusion reason
184+
- [x] **CI workflow typecheck** — Add `pnpm typecheck` step to CI workflow
185+
- [x] **Code quality workflow paths** — Fix `src/` references to `packages/` in code-quality workflow
186+
187+
#### Medium Priority
188+
189+
- [x] **ESLint version alignment** — Align `@typescript-eslint/parser` (^6 → ^7) with `@typescript-eslint/eslint-plugin` (^7)
190+
- [x] **CONTRIBUTING.md checklist** — Add pre-submission development checklist (typecheck, test, ROADMAP)
191+
- [x] **Documentation hook examples** — Update DEVELOPMENT_WORKFLOW.md code examples to use `HookContext` instead of `any`
192+
- [x] **ROADMAP.md date sync** — Update last-updated date and version timeline to match current state
193+
194+
#### Deferred / Future
195+
196+
- [ ] **Structured logging migration** — Replace `console.log` with pino logger across hook files
197+
- [ ] **Legacy API cleanup** — Remove `db.ts` references and migrate to broker/ObjectQL
198+
- [ ] **E2E test coverage** — Build API-layer end-to-end tests for Feed API / MCP API
199+
- [ ] **FormView `as any` cleanup** — Remove `as any` casts once `@objectstack/spec` aligns FormView column types
200+
201+
---
202+
174203
## Version Upgrade History
175204

176205
| Date | From | To | Breaking Changes | Tests |
177206
|------|------|----|-----------------|-------|
207+
| 2026-02-21 | v3.0.8 | v3.0.8 | None (Phase 15: Repository quality improvements — hook `any` type migration, CI typecheck, ESLint version alignment, documentation updates, code-quality workflow fixes) | 3799 ✅ |
178208
| 2027-02-21 | v3.0.8 | v3.0.8 | None (Phase 14 complete: Activity Feed/Chatter across 6 clouds, 4 Interface Builder blank pages, Dashboard headers & global filters on 6 clouds, ViewTabs on 18 views, Feed API 8 endpoints, Feed service contract, System metadata, Studio builder configs, Navigation areas, 327 new tests) | 3707 ✅ |
179209
| 2026-02-21 | v3.0.6 | v3.0.8 | None (New: Activity Feed/Chatter system, Interface Builder/Blank Pages, Dashboard headers & global filters, Feed API contracts, Studio builder configs, Oclif CLI plugin, Package conventions; Phase 14 roadmap added) | 3318 ✅ |
180210
| 2026-02-16 | v3.0.0 | v3.0.0 | None (Phase 13 roadmap: Module-by-module deep optimization, seed data foundation, vertical package UI enhancement, core cloud metadata equalization) | 3318 ✅ |

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
"@objectstack/studio": "^3.0.8",
6868
"@types/node": "^25.2.3",
6969
"@typescript-eslint/eslint-plugin": "^7.18.0",
70-
"@typescript-eslint/parser": "^6.21.0",
70+
"@typescript-eslint/parser": "^7.18.0",
7171
"@vitest/coverage-v8": "^1.6.1",
7272
"@vitest/ui": "^1.6.1",
7373
"eslint": "^8.57.1",

packages/crm/src/hooks/account.hook.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ const AccountHealthScoreTrigger: Hook = {
4141
/**
4242
* Calculate account health score (0-100)
4343
*/
44-
async function calculateHealthScore(account: Record<string, any>, ctx: any): Promise<number> {
44+
async function calculateHealthScore(account: Record<string, any>, ctx: HookContext): Promise<number> {
4545
let score = 0;
4646

4747
// Base score for active customer status (20 points)
@@ -125,7 +125,7 @@ const AccountHierarchyTrigger: Hook = {
125125
/**
126126
* Update parent account metrics by aggregating child account data
127127
*/
128-
async function updateParentAccountMetrics(parentId: string, ctx: any): Promise<void> {
128+
async function updateParentAccountMetrics(parentId: string, ctx: HookContext): Promise<void> {
129129
console.log(`🔄 Updating parent account metrics: ${parentId}`);
130130

131131
// In real implementation, would query all child accounts and aggregate
@@ -146,7 +146,7 @@ async function updateParentAccountMetrics(parentId: string, ctx: any): Promise<v
146146
/**
147147
* Cascade ownership changes to child accounts
148148
*/
149-
async function cascadeOwnershipChange(accountId: string, newOwnerId: string, ctx: any): Promise<void> {
149+
async function cascadeOwnershipChange(accountId: string, newOwnerId: string, ctx: HookContext): Promise<void> {
150150
console.log(`🔄 Cascading ownership change to child accounts of ${accountId}`);
151151

152152
// In real implementation, would query and update all child accounts

packages/crm/src/hooks/activity.hook.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ const ActivityRelatedObjectUpdatesTrigger: Hook = {
8181
/**
8282
* Update Contact's last activity date
8383
*/
84-
async function updateContactLastActivityDate(whoId: string, activityDate: string, ctx: any): Promise<void> {
84+
async function updateContactLastActivityDate(whoId: string, activityDate: string, ctx: HookContext): Promise<void> {
8585
console.log(`🔄 Updating LastContactDate for contact: ${whoId}`);
8686

8787
// In real implementation:
@@ -98,7 +98,7 @@ async function updateContactLastActivityDate(whoId: string, activityDate: string
9898
/**
9999
* Update related object's last activity date
100100
*/
101-
async function updateWhatObjectLastActivityDate(whatId: string, activityDate: string, ctx: any): Promise<void> {
101+
async function updateWhatObjectLastActivityDate(whatId: string, activityDate: string, ctx: HookContext): Promise<void> {
102102
console.log(`🔄 Updating LastActivityDate for related object: ${whatId}`);
103103

104104
// In real implementation, would need to determine object type from WhatId
@@ -154,7 +154,7 @@ const ActivityCompletionTrigger: Hook = {
154154
/**
155155
* Create next recurrence of a recurring activity
156156
*/
157-
async function createNextRecurrence(activity: Record<string, any>, ctx: any): Promise<void> {
157+
async function createNextRecurrence(activity: Record<string, any>, ctx: HookContext): Promise<void> {
158158
console.log(`🔄 Creating next recurrence for: ${activity.Subject}`);
159159

160160
// Calculate next occurrence date based on pattern

packages/crm/src/hooks/opportunity.hook.ts

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,9 @@ const OpportunityStageChange: Hook = {
8282
/**
8383
* Handle Closed Won automation
8484
*/
85-
async function handleClosedWon(ctx: any): Promise<void> {
85+
async function handleClosedWon(ctx: HookContext): Promise<void> {
8686
console.log('✅ Processing Closed Won automation...');
87-
const opportunity = ctx.result;
87+
const opportunity = ctx.result as Record<string, any>;
8888

8989
if (!opportunity.AccountId) {
9090
console.error('❌ Cannot process: Opportunity has no AccountId');
@@ -96,7 +96,7 @@ async function handleClosedWon(ctx: any): Promise<void> {
9696
// 1. Create Contract
9797
let contractId;
9898
try {
99-
const contract = await ctx.ql.doc.create('contract', {
99+
const contract = await (ctx.ql as any).doc.create('contract', {
100100
AccountId: opportunity.AccountId,
101101
OpportunityId: opportunity.Id,
102102
Status: 'draft',
@@ -118,7 +118,7 @@ async function handleClosedWon(ctx: any): Promise<void> {
118118

119119
// 2. Update Account Status
120120
try {
121-
await ctx.ql.doc.update('account', opportunity.AccountId, {
121+
await (ctx.ql as any).doc.update('account', opportunity.AccountId, {
122122
CustomerStatus: 'active_customer'
123123
});
124124
console.log('✅ Account status updated to Active Customer');
@@ -129,7 +129,7 @@ async function handleClosedWon(ctx: any): Promise<void> {
129129

130130
// 3. Log activity
131131
try {
132-
await ctx.ql.doc.create('activity', {
132+
await (ctx.ql as any).doc.create('activity', {
133133
Subject: `Deal Won: ${opportunity.Name}`,
134134
Type: 'Milestone',
135135
Status: 'completed',
@@ -160,17 +160,17 @@ async function handleClosedWon(ctx: any): Promise<void> {
160160
}
161161
}
162162

163-
async function handleClosedLost(ctx: any): Promise<void> {
163+
async function handleClosedLost(ctx: HookContext): Promise<void> {
164164
console.log('❌ Processing Closed Lost automation...');
165-
const opportunity = ctx.result;
165+
const opportunity = ctx.result as Record<string, any>;
166166

167167
if (!opportunity.AccountId) {
168168
return;
169169
}
170170

171171
// Log activity for lost opportunity
172172
try {
173-
await ctx.ql.doc.create('activity', {
173+
await (ctx.ql as any).doc.create('activity', {
174174
Subject: `Deal Lost: ${opportunity.Name}`,
175175
Type: 'Milestone',
176176
Status: 'completed',
@@ -192,20 +192,20 @@ async function handleClosedLost(ctx: any): Promise<void> {
192192
/**
193193
* Log activity when stage changes
194194
*/
195-
async function logStageChange(ctx: any): Promise<void> {
195+
async function logStageChange(ctx: HookContext): Promise<void> {
196196
try {
197-
const opportunity = ctx.result;
198-
const oldStage = ctx.previous?.Stage || 'unknown';
199-
await ctx.ql.doc.create('activity', {
200-
Subject: `Opportunity Stage Change: ${oldStage}${ctx.result.Stage}`,
197+
const opportunity = ctx.result as Record<string, any>;
198+
const oldStage = (ctx.previous as Record<string, any>)?.Stage || 'unknown';
199+
await (ctx.ql as any).doc.create('activity', {
200+
Subject: `Opportunity Stage Change: ${oldStage}${(ctx.result as Record<string, any>).Stage}`,
201201
Type: 'Stage Change',
202202
Status: 'completed',
203203
Priority: 'normal',
204204
AccountId: opportunity.AccountId,
205205
WhatId: opportunity.Id,
206206
OwnerId: ctx.user.id,
207207
ActivityDate: new Date().toISOString().split('T')[0],
208-
Description: `Opportunity stage changed from "${oldStage}" to "${ctx.result.Stage}"`
208+
Description: `Opportunity stage changed from "${oldStage}" to "${(ctx.result as Record<string, any>).Stage}"`
209209
});
210210
} catch (error) {
211211
console.error('❌ Failed to log stage change activity:', error);
@@ -215,8 +215,8 @@ async function logStageChange(ctx: any): Promise<void> {
215215
/**
216216
* Validate required fields for advanced stages
217217
*/
218-
async function validateStageRequirements(ctx: any): Promise<void> {
219-
const opportunity = ctx.result;
218+
async function validateStageRequirements(ctx: HookContext): Promise<void> {
219+
const opportunity = ctx.result as Record<string, any>;
220220
const stage = opportunity.Stage;
221221
const warnings: string[] = [];
222222

@@ -252,13 +252,13 @@ async function validateStageRequirements(ctx: any): Promise<void> {
252252
/**
253253
* Helper: Count related quotes
254254
*/
255-
async function countRelatedQuotes(ctx: any, opportunityId: string): Promise<number> {
255+
async function countRelatedQuotes(ctx: HookContext, opportunityId: string): Promise<number> {
256256
// Check if quote object exists first (it's in products package)
257257
try {
258258
// In a real monorepo with strict boundaries, we might use a decoupled service.
259259
// Here we assume the broker can find 'quote' across packages.
260260
// Mocking for now since we don't have the full runtime
261-
const quotes = await ctx.ql.find('quote', {
261+
const quotes = await (ctx.ql as any).find('quote', {
262262
filters: [['opportunity', '=', opportunityId]]
263263
});
264264
return quotes.length;

packages/hr/src/hooks/application.hook.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ const ApplicationStatusWorkflowTrigger: Hook = {
6464
/**
6565
* Create an interview record when application moves to interview stage
6666
*/
67-
async function createInterviewRecord(application: any, ctx: any): Promise<void> {
67+
async function createInterviewRecord(application: Record<string, any>, ctx: HookContext): Promise<void> {
6868
try {
6969
const scheduledDate = new Date();
7070
scheduledDate.setDate(scheduledDate.getDate() + 3); // Default: 3 days from now
@@ -91,7 +91,7 @@ async function createInterviewRecord(application: any, ctx: any): Promise<void>
9191
/**
9292
* Update candidate status to 'hired' when application is hired
9393
*/
94-
async function updateCandidateToHired(application: any, ctx: any): Promise<void> {
94+
async function updateCandidateToHired(application: Record<string, any>, ctx: HookContext): Promise<void> {
9595
try {
9696
if (!application.candidate_id) {
9797
console.warn('⚠️ No candidate linked to application, skipping candidate update');
@@ -112,10 +112,10 @@ async function updateCandidateToHired(application: any, ctx: any): Promise<void>
112112
* Log application status change
113113
*/
114114
async function logApplicationStatusChange(
115-
application: any,
115+
application: Record<string, any>,
116116
oldStatus: string,
117117
newStatus: string,
118-
ctx: any
118+
ctx: HookContext
119119
): Promise<void> {
120120
try {
121121
console.log(`📝 Logging application status change: ${oldStatus}${newStatus} for ${application.application_number}`);
@@ -179,7 +179,7 @@ const ApplicationScreeningTrigger: Hook = {
179179
/**
180180
* Fetch candidate record
181181
*/
182-
async function fetchCandidate(candidateId: string, ctx: any): Promise<any> {
182+
async function fetchCandidate(candidateId: string, ctx: HookContext): Promise<any> {
183183
try {
184184
const candidates = await (ctx.ql as any).find('candidate', {
185185
filters: [['id', '=', candidateId]],
@@ -195,7 +195,7 @@ async function fetchCandidate(candidateId: string, ctx: any): Promise<any> {
195195
/**
196196
* Fetch recruitment record
197197
*/
198-
async function fetchRecruitment(recruitmentId: string, ctx: any): Promise<any> {
198+
async function fetchRecruitment(recruitmentId: string, ctx: HookContext): Promise<any> {
199199
try {
200200
if (!recruitmentId) return null;
201201
const recruitments = await (ctx.ql as any).find('recruitment', {
@@ -212,7 +212,7 @@ async function fetchRecruitment(recruitmentId: string, ctx: any): Promise<any> {
212212
/**
213213
* Calculate screening score based on candidate qualifications
214214
*/
215-
function calculateScreeningScore(candidate: any, recruitment: any): number {
215+
function calculateScreeningScore(candidate: Record<string, any>, recruitment: Record<string, any>): number {
216216
let score = 0;
217217

218218
// Experience score (30 points)

0 commit comments

Comments
 (0)