@sparkleideas/claims 3.5.2-patch.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +261 -0
- package/package.json +84 -0
- package/src/api/cli-commands.ts +1459 -0
- package/src/api/cli-types.ts +154 -0
- package/src/api/index.ts +24 -0
- package/src/api/mcp-tools.ts +1977 -0
- package/src/application/claim-service.ts +753 -0
- package/src/application/index.ts +46 -0
- package/src/application/load-balancer.ts +840 -0
- package/src/application/work-stealing-service.ts +807 -0
- package/src/domain/events.ts +779 -0
- package/src/domain/index.ts +214 -0
- package/src/domain/repositories.ts +239 -0
- package/src/domain/rules.ts +526 -0
- package/src/domain/types.ts +826 -0
- package/src/index.ts +79 -0
- package/src/infrastructure/claim-repository.ts +358 -0
- package/src/infrastructure/event-store.ts +297 -0
- package/src/infrastructure/index.ts +21 -0
|
@@ -0,0 +1,826 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claims Domain Types
|
|
3
|
+
*
|
|
4
|
+
* Core type definitions for the issue claiming system.
|
|
5
|
+
* Supports both human and agent claimants with handoff capabilities.
|
|
6
|
+
*
|
|
7
|
+
* @module v3/claims/domain/types
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// Core Types
|
|
12
|
+
// =============================================================================
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Unique identifier for claims
|
|
16
|
+
*/
|
|
17
|
+
export type ClaimId = `claim-${string}`;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Unique identifier for issues
|
|
21
|
+
*/
|
|
22
|
+
export type IssueId = string;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Claimant type - human or agent
|
|
26
|
+
*/
|
|
27
|
+
export type ClaimantType = 'human' | 'agent';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Claim status lifecycle
|
|
31
|
+
*/
|
|
32
|
+
export type ClaimStatus =
|
|
33
|
+
| 'active' // Claim is active and work is in progress
|
|
34
|
+
| 'pending_handoff' // Handoff has been requested but not accepted
|
|
35
|
+
| 'in_review' // Work is complete, awaiting review
|
|
36
|
+
| 'completed' // Claim is completed successfully
|
|
37
|
+
| 'released' // Claim was released voluntarily
|
|
38
|
+
| 'expired' // Claim expired due to inactivity
|
|
39
|
+
| 'paused' // Work is temporarily paused
|
|
40
|
+
| 'blocked' // Work is blocked by external dependency
|
|
41
|
+
| 'stealable'; // Claim can be stolen by another agent
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Issue labels/tags
|
|
45
|
+
*/
|
|
46
|
+
export type IssueLabel = string;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Issue priority levels
|
|
50
|
+
*/
|
|
51
|
+
export type IssuePriority = 'critical' | 'high' | 'medium' | 'low';
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Issue complexity levels
|
|
55
|
+
*/
|
|
56
|
+
export type IssueComplexity = 'trivial' | 'simple' | 'moderate' | 'complex' | 'epic';
|
|
57
|
+
|
|
58
|
+
// =============================================================================
|
|
59
|
+
// Value Objects
|
|
60
|
+
// =============================================================================
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Duration value object for time-based operations
|
|
64
|
+
*/
|
|
65
|
+
export interface Duration {
|
|
66
|
+
value: number;
|
|
67
|
+
unit: 'ms' | 'seconds' | 'minutes' | 'hours' | 'days';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Convert duration to milliseconds
|
|
72
|
+
*/
|
|
73
|
+
export function durationToMs(duration: Duration): number {
|
|
74
|
+
const multipliers: Record<Duration['unit'], number> = {
|
|
75
|
+
ms: 1,
|
|
76
|
+
seconds: 1000,
|
|
77
|
+
minutes: 60 * 1000,
|
|
78
|
+
hours: 60 * 60 * 1000,
|
|
79
|
+
days: 24 * 60 * 60 * 1000,
|
|
80
|
+
};
|
|
81
|
+
return duration.value * multipliers[duration.unit];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// =============================================================================
|
|
85
|
+
// Entity Interfaces
|
|
86
|
+
// =============================================================================
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Claimant - a human or agent that can claim issues
|
|
90
|
+
*/
|
|
91
|
+
export interface Claimant {
|
|
92
|
+
id: string;
|
|
93
|
+
type: ClaimantType;
|
|
94
|
+
name: string;
|
|
95
|
+
capabilities?: string[];
|
|
96
|
+
specializations?: string[];
|
|
97
|
+
currentWorkload?: number;
|
|
98
|
+
maxConcurrentClaims?: number;
|
|
99
|
+
metadata?: Record<string, unknown>;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Issue - a work item that can be claimed
|
|
104
|
+
*/
|
|
105
|
+
export interface Issue {
|
|
106
|
+
id: IssueId;
|
|
107
|
+
title: string;
|
|
108
|
+
description: string;
|
|
109
|
+
labels: IssueLabel[];
|
|
110
|
+
priority: IssuePriority;
|
|
111
|
+
complexity: IssueComplexity;
|
|
112
|
+
requiredCapabilities?: string[];
|
|
113
|
+
estimatedDuration?: Duration;
|
|
114
|
+
repositoryId?: string;
|
|
115
|
+
url?: string;
|
|
116
|
+
createdAt: Date;
|
|
117
|
+
updatedAt: Date;
|
|
118
|
+
metadata?: Record<string, unknown>;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Issue claim - represents an active claim on an issue
|
|
123
|
+
*/
|
|
124
|
+
export interface IssueClaim {
|
|
125
|
+
id: ClaimId;
|
|
126
|
+
issueId: IssueId;
|
|
127
|
+
claimant: Claimant;
|
|
128
|
+
status: ClaimStatus;
|
|
129
|
+
claimedAt: Date;
|
|
130
|
+
lastActivityAt: Date;
|
|
131
|
+
expiresAt?: Date;
|
|
132
|
+
notes?: string[];
|
|
133
|
+
handoffChain?: HandoffRecord[];
|
|
134
|
+
reviewers?: Claimant[];
|
|
135
|
+
metadata?: Record<string, unknown>;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Handoff record - tracks handoff history
|
|
140
|
+
*/
|
|
141
|
+
export interface HandoffRecord {
|
|
142
|
+
id: string;
|
|
143
|
+
from: Claimant;
|
|
144
|
+
to: Claimant;
|
|
145
|
+
reason: string;
|
|
146
|
+
status: 'pending' | 'accepted' | 'rejected';
|
|
147
|
+
requestedAt: Date;
|
|
148
|
+
resolvedAt?: Date;
|
|
149
|
+
rejectionReason?: string;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Issue with claim information
|
|
154
|
+
*/
|
|
155
|
+
export interface IssueWithClaim {
|
|
156
|
+
issue: Issue;
|
|
157
|
+
claim: IssueClaim | null;
|
|
158
|
+
pendingHandoffs: HandoffRecord[];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Claim result - returned when claiming an issue
|
|
163
|
+
*/
|
|
164
|
+
export interface ClaimResult {
|
|
165
|
+
success: boolean;
|
|
166
|
+
claim?: IssueClaim;
|
|
167
|
+
error?: ClaimError;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// =============================================================================
|
|
171
|
+
// Filter/Query Types
|
|
172
|
+
// =============================================================================
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Filters for querying issues
|
|
176
|
+
*/
|
|
177
|
+
export interface IssueFilters {
|
|
178
|
+
labels?: IssueLabel[];
|
|
179
|
+
priority?: IssuePriority[];
|
|
180
|
+
complexity?: IssueComplexity[];
|
|
181
|
+
requiredCapabilities?: string[];
|
|
182
|
+
excludeClaimed?: boolean;
|
|
183
|
+
repositoryId?: string;
|
|
184
|
+
limit?: number;
|
|
185
|
+
offset?: number;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// =============================================================================
|
|
189
|
+
// Error Types
|
|
190
|
+
// =============================================================================
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Claim error codes
|
|
194
|
+
*/
|
|
195
|
+
export type ClaimErrorCode =
|
|
196
|
+
| 'ALREADY_CLAIMED'
|
|
197
|
+
| 'NOT_CLAIMED'
|
|
198
|
+
| 'CLAIM_NOT_FOUND'
|
|
199
|
+
| 'ISSUE_NOT_FOUND'
|
|
200
|
+
| 'CLAIMANT_NOT_FOUND'
|
|
201
|
+
| 'INVALID_CLAIMANT'
|
|
202
|
+
| 'HANDOFF_PENDING'
|
|
203
|
+
| 'HANDOFF_NOT_FOUND'
|
|
204
|
+
| 'UNAUTHORIZED'
|
|
205
|
+
| 'MAX_CLAIMS_EXCEEDED'
|
|
206
|
+
| 'CAPABILITY_MISMATCH'
|
|
207
|
+
| 'INVALID_STATUS_TRANSITION'
|
|
208
|
+
| 'VALIDATION_ERROR';
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Claim error with details
|
|
212
|
+
*/
|
|
213
|
+
export interface ClaimError {
|
|
214
|
+
code: ClaimErrorCode;
|
|
215
|
+
message: string;
|
|
216
|
+
details?: Record<string, unknown>;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Custom error class for claim operations
|
|
221
|
+
*/
|
|
222
|
+
export class ClaimOperationError extends Error {
|
|
223
|
+
constructor(
|
|
224
|
+
public readonly code: ClaimErrorCode,
|
|
225
|
+
message: string,
|
|
226
|
+
public readonly details?: Record<string, unknown>
|
|
227
|
+
) {
|
|
228
|
+
super(message);
|
|
229
|
+
this.name = 'ClaimOperationError';
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
toClaimError(): ClaimError {
|
|
233
|
+
return {
|
|
234
|
+
code: this.code,
|
|
235
|
+
message: this.message,
|
|
236
|
+
details: this.details,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// =============================================================================
|
|
242
|
+
// Work Stealing Types
|
|
243
|
+
// =============================================================================
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Agent type for cross-type steal rules
|
|
247
|
+
*/
|
|
248
|
+
export type AgentType =
|
|
249
|
+
| 'coder'
|
|
250
|
+
| 'debugger'
|
|
251
|
+
| 'tester'
|
|
252
|
+
| 'reviewer'
|
|
253
|
+
| 'researcher'
|
|
254
|
+
| 'planner'
|
|
255
|
+
| 'architect'
|
|
256
|
+
| 'coordinator';
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Reason an issue became stealable
|
|
260
|
+
*/
|
|
261
|
+
export type StealableReason =
|
|
262
|
+
| 'stale' // No progress for staleThresholdMinutes
|
|
263
|
+
| 'blocked' // Blocked for blockedThresholdMinutes
|
|
264
|
+
| 'overloaded' // Agent has too many claims
|
|
265
|
+
| 'manual' // Manually marked as stealable
|
|
266
|
+
| 'timeout'; // Task timed out
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Information about why an issue is stealable
|
|
270
|
+
*/
|
|
271
|
+
export interface StealableInfo {
|
|
272
|
+
reason: StealableReason;
|
|
273
|
+
markedAt: Date;
|
|
274
|
+
expiresAt?: Date;
|
|
275
|
+
originalProgress: number;
|
|
276
|
+
allowedStealerTypes?: AgentType[];
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Error codes specific to steal operations
|
|
281
|
+
*/
|
|
282
|
+
export type StealErrorCode =
|
|
283
|
+
| 'NOT_STEALABLE'
|
|
284
|
+
| 'ALREADY_CLAIMED'
|
|
285
|
+
| 'CROSS_TYPE_NOT_ALLOWED'
|
|
286
|
+
| 'IN_GRACE_PERIOD'
|
|
287
|
+
| 'PROTECTED_BY_PROGRESS'
|
|
288
|
+
| 'STEALER_OVERLOADED'
|
|
289
|
+
| 'ISSUE_NOT_FOUND'
|
|
290
|
+
| 'CONTEST_PENDING';
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Result of a steal operation
|
|
294
|
+
*/
|
|
295
|
+
export interface StealResult {
|
|
296
|
+
success: boolean;
|
|
297
|
+
claim?: IssueClaim;
|
|
298
|
+
previousClaimant?: Claimant;
|
|
299
|
+
contestWindowEndsAt?: Date;
|
|
300
|
+
error?: string;
|
|
301
|
+
errorCode?: StealErrorCode;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Information about a contested steal
|
|
306
|
+
*/
|
|
307
|
+
export interface ContestInfo {
|
|
308
|
+
contestedAt: Date;
|
|
309
|
+
contestedBy: Claimant;
|
|
310
|
+
stolenBy: Claimant;
|
|
311
|
+
reason: string;
|
|
312
|
+
windowEndsAt: Date;
|
|
313
|
+
resolution?: ContestResolution;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Resolution of a contest
|
|
318
|
+
*/
|
|
319
|
+
export interface ContestResolution {
|
|
320
|
+
resolvedAt: Date;
|
|
321
|
+
winner: Claimant;
|
|
322
|
+
resolvedBy: 'queen' | 'human' | 'timeout';
|
|
323
|
+
reason: string;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Work stealing configuration
|
|
328
|
+
*/
|
|
329
|
+
export interface WorkStealingConfig {
|
|
330
|
+
/** Minutes without progress before issue becomes stealable */
|
|
331
|
+
staleThresholdMinutes: number;
|
|
332
|
+
|
|
333
|
+
/** Minutes blocked before issue becomes stealable */
|
|
334
|
+
blockedThresholdMinutes: number;
|
|
335
|
+
|
|
336
|
+
/** Max claims per agent before lowest priority becomes stealable */
|
|
337
|
+
overloadThreshold: number;
|
|
338
|
+
|
|
339
|
+
/** Minutes after claiming before work can be stolen */
|
|
340
|
+
gracePeriodMinutes: number;
|
|
341
|
+
|
|
342
|
+
/** Progress percentage that protects from stealing (0-100) */
|
|
343
|
+
minProgressToProtect: number;
|
|
344
|
+
|
|
345
|
+
/** Minutes to contest a steal */
|
|
346
|
+
contestWindowMinutes: number;
|
|
347
|
+
|
|
348
|
+
/** Enable cross-type stealing */
|
|
349
|
+
allowCrossTypeSteal: boolean;
|
|
350
|
+
|
|
351
|
+
/** Agent type pairs that can steal from each other */
|
|
352
|
+
crossTypeStealRules: [AgentType, AgentType][];
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Default work stealing configuration
|
|
357
|
+
*/
|
|
358
|
+
export const DEFAULT_WORK_STEALING_CONFIG: WorkStealingConfig = {
|
|
359
|
+
staleThresholdMinutes: 30,
|
|
360
|
+
blockedThresholdMinutes: 60,
|
|
361
|
+
overloadThreshold: 5,
|
|
362
|
+
gracePeriodMinutes: 10,
|
|
363
|
+
minProgressToProtect: 75,
|
|
364
|
+
contestWindowMinutes: 5,
|
|
365
|
+
allowCrossTypeSteal: true,
|
|
366
|
+
crossTypeStealRules: [
|
|
367
|
+
['coder', 'debugger'],
|
|
368
|
+
['tester', 'reviewer'],
|
|
369
|
+
],
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Extended IssueClaim with work stealing properties
|
|
374
|
+
*/
|
|
375
|
+
export interface IssueClaimWithStealing extends IssueClaim {
|
|
376
|
+
progress: number; // 0-100 percentage
|
|
377
|
+
blockedReason?: string;
|
|
378
|
+
blockedAt?: Date;
|
|
379
|
+
stealableAt?: Date;
|
|
380
|
+
stealInfo?: StealableInfo;
|
|
381
|
+
contestInfo?: ContestInfo;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// =============================================================================
|
|
385
|
+
// Work Stealing Events
|
|
386
|
+
// =============================================================================
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Event types for work stealing
|
|
390
|
+
*/
|
|
391
|
+
export type WorkStealingEventType =
|
|
392
|
+
| 'IssueMarkedStealable'
|
|
393
|
+
| 'IssueStolen'
|
|
394
|
+
| 'StealContested'
|
|
395
|
+
| 'StealContestResolved';
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Base event interface for work stealing
|
|
399
|
+
*/
|
|
400
|
+
export interface WorkStealingEvent {
|
|
401
|
+
id: string;
|
|
402
|
+
type: WorkStealingEventType;
|
|
403
|
+
timestamp: Date;
|
|
404
|
+
issueId: IssueId;
|
|
405
|
+
claimId: ClaimId;
|
|
406
|
+
payload: unknown;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Event: Issue marked as stealable
|
|
411
|
+
*/
|
|
412
|
+
export interface IssueMarkedStealableEvent extends WorkStealingEvent {
|
|
413
|
+
type: 'IssueMarkedStealable';
|
|
414
|
+
payload: {
|
|
415
|
+
info: StealableInfo;
|
|
416
|
+
currentClaimant: Claimant;
|
|
417
|
+
claim: IssueClaimWithStealing;
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Event: Issue stolen
|
|
423
|
+
*/
|
|
424
|
+
export interface IssueStolenEvent extends WorkStealingEvent {
|
|
425
|
+
type: 'IssueStolen';
|
|
426
|
+
payload: {
|
|
427
|
+
previousClaimant: Claimant;
|
|
428
|
+
newClaimant: Claimant;
|
|
429
|
+
stealableInfo: StealableInfo;
|
|
430
|
+
contestWindowEndsAt: Date;
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Event: Steal contested
|
|
436
|
+
*/
|
|
437
|
+
export interface StealContestedEvent extends WorkStealingEvent {
|
|
438
|
+
type: 'StealContested';
|
|
439
|
+
payload: {
|
|
440
|
+
contestInfo: ContestInfo;
|
|
441
|
+
claim: IssueClaimWithStealing;
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Event: Contest resolved
|
|
447
|
+
*/
|
|
448
|
+
export interface StealContestResolvedEvent extends WorkStealingEvent {
|
|
449
|
+
type: 'StealContestResolved';
|
|
450
|
+
payload: {
|
|
451
|
+
contestInfo: ContestInfo;
|
|
452
|
+
resolution: ContestResolution;
|
|
453
|
+
winnerClaim: IssueClaimWithStealing;
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// =============================================================================
|
|
458
|
+
// Repository Interface for Work Stealing
|
|
459
|
+
// =============================================================================
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Repository interface for issue claims with work stealing support
|
|
463
|
+
*/
|
|
464
|
+
export interface IIssueClaimRepository {
|
|
465
|
+
findById(id: ClaimId): Promise<IssueClaimWithStealing | null>;
|
|
466
|
+
findByIssueId(issueId: IssueId): Promise<IssueClaimWithStealing | null>;
|
|
467
|
+
findByAgentId(agentId: string): Promise<IssueClaimWithStealing[]>;
|
|
468
|
+
findStealable(agentType?: AgentType): Promise<IssueClaimWithStealing[]>;
|
|
469
|
+
findContested(): Promise<IssueClaimWithStealing[]>;
|
|
470
|
+
findAll(): Promise<IssueClaimWithStealing[]>;
|
|
471
|
+
save(claim: IssueClaimWithStealing): Promise<void>;
|
|
472
|
+
update(claim: IssueClaimWithStealing): Promise<void>;
|
|
473
|
+
delete(id: ClaimId): Promise<void>;
|
|
474
|
+
countByAgentId(agentId: string): Promise<number>;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// =============================================================================
|
|
478
|
+
// Event Bus Interface for Work Stealing
|
|
479
|
+
// =============================================================================
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Event bus interface for work stealing events
|
|
483
|
+
*/
|
|
484
|
+
export interface IWorkStealingEventBus {
|
|
485
|
+
emit(event: WorkStealingEvent): Promise<void>;
|
|
486
|
+
subscribe(
|
|
487
|
+
eventType: WorkStealingEventType,
|
|
488
|
+
handler: (event: WorkStealingEvent) => void | Promise<void>
|
|
489
|
+
): () => void;
|
|
490
|
+
subscribeAll(handler: (event: WorkStealingEvent) => void | Promise<void>): () => void;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// =============================================================================
|
|
494
|
+
// ADR-016 Extended Types
|
|
495
|
+
// =============================================================================
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Extended claim status per ADR-016
|
|
499
|
+
* Includes all lifecycle states for human/agent claiming
|
|
500
|
+
*/
|
|
501
|
+
export type ExtendedClaimStatus =
|
|
502
|
+
| 'active' // Claim is being actively worked on
|
|
503
|
+
| 'paused' // Work is temporarily paused
|
|
504
|
+
| 'handoff-pending' // Handoff has been requested, awaiting acceptance
|
|
505
|
+
| 'review-requested' // Claimant is requesting a review
|
|
506
|
+
| 'blocked' // Work is blocked by external dependency
|
|
507
|
+
| 'stealable' // Claim can be stolen by another agent
|
|
508
|
+
| 'completed'; // Work is finished
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Agent identifier type
|
|
512
|
+
*/
|
|
513
|
+
export type AgentId = `agent-${string}` | string;
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Human user identifier
|
|
517
|
+
*/
|
|
518
|
+
export type UserId = string;
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Reasons why a claim might be blocked
|
|
522
|
+
*/
|
|
523
|
+
export type BlockedReason =
|
|
524
|
+
| 'dependency' // Waiting on another issue/PR
|
|
525
|
+
| 'external' // Waiting on external resource
|
|
526
|
+
| 'clarification' // Waiting for clarification from maintainer
|
|
527
|
+
| 'resource' // Waiting for computational resource
|
|
528
|
+
| 'approval' // Waiting for approval
|
|
529
|
+
| 'other'; // Other reason
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Information about a blocked claim
|
|
533
|
+
*/
|
|
534
|
+
export interface BlockedInfo {
|
|
535
|
+
readonly reason: BlockedReason;
|
|
536
|
+
readonly description: string;
|
|
537
|
+
readonly relatedIssues: readonly IssueId[];
|
|
538
|
+
readonly blockedAt: number;
|
|
539
|
+
readonly estimatedUnblockTime?: number;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Reason for work stealing per ADR-016
|
|
544
|
+
*/
|
|
545
|
+
export type StealReason =
|
|
546
|
+
| 'timeout' // Original claimant timed out
|
|
547
|
+
| 'overloaded' // Original claimant is overloaded
|
|
548
|
+
| 'blocked' // Claim has been blocked too long
|
|
549
|
+
| 'voluntary' // Original claimant voluntarily released
|
|
550
|
+
| 'rebalancing' // System is rebalancing load
|
|
551
|
+
| 'abandoned' // Claimant appears to have abandoned work
|
|
552
|
+
| 'priority-change'; // Higher priority claimant needs this issue
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Extended StealableInfo per ADR-016
|
|
556
|
+
*/
|
|
557
|
+
export interface ExtendedStealableInfo {
|
|
558
|
+
readonly reason: StealReason;
|
|
559
|
+
readonly markedAt: number;
|
|
560
|
+
readonly originalClaimant: Claimant;
|
|
561
|
+
readonly minPriorityToSteal: IssuePriority;
|
|
562
|
+
readonly requiresContest: boolean;
|
|
563
|
+
readonly gracePeriodMs: number;
|
|
564
|
+
readonly gracePeriodEndsAt: number;
|
|
565
|
+
readonly previousSteals: number;
|
|
566
|
+
readonly interestedAgents: readonly AgentId[];
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Handoff reason per ADR-016
|
|
571
|
+
*/
|
|
572
|
+
export type HandoffReason =
|
|
573
|
+
| 'capacity' // Current claimant at capacity
|
|
574
|
+
| 'expertise' // Target has better expertise
|
|
575
|
+
| 'shift-change' // Scheduled handoff
|
|
576
|
+
| 'escalation' // Issue needs escalation
|
|
577
|
+
| 'voluntary' // Voluntary transfer
|
|
578
|
+
| 'rebalancing'; // System rebalancing
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Extended handoff info per ADR-016
|
|
582
|
+
*/
|
|
583
|
+
export interface ExtendedHandoffInfo {
|
|
584
|
+
readonly initiatedBy: AgentId | UserId;
|
|
585
|
+
readonly targetClaimant: Claimant;
|
|
586
|
+
readonly requestedAt: number;
|
|
587
|
+
readonly expiresAt: number;
|
|
588
|
+
readonly reason: HandoffReason;
|
|
589
|
+
readonly notes?: string;
|
|
590
|
+
readonly contextSummary?: string;
|
|
591
|
+
readonly artifacts: readonly string[];
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Claimant workload metrics per ADR-016
|
|
596
|
+
*/
|
|
597
|
+
export interface ClaimantWorkload {
|
|
598
|
+
readonly activeClaims: number;
|
|
599
|
+
readonly pausedClaims: number;
|
|
600
|
+
readonly pendingHandoffs: number;
|
|
601
|
+
readonly completedClaims: number;
|
|
602
|
+
readonly averageCompletionTime: number;
|
|
603
|
+
readonly loadPercentage: number;
|
|
604
|
+
readonly availableCapacity: number;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Extended Claimant with ADR-016 properties
|
|
609
|
+
*/
|
|
610
|
+
export interface ExtendedClaimant extends Claimant {
|
|
611
|
+
readonly workload: ClaimantWorkload;
|
|
612
|
+
readonly priority: number;
|
|
613
|
+
readonly registeredAt: number;
|
|
614
|
+
readonly lastActivityAt: number;
|
|
615
|
+
readonly isAvailable: boolean;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Agent load information per ADR-016
|
|
620
|
+
*/
|
|
621
|
+
export interface AgentLoadInfo {
|
|
622
|
+
readonly agentId: AgentId;
|
|
623
|
+
readonly name: string;
|
|
624
|
+
readonly activeClaims: number;
|
|
625
|
+
readonly maxClaims: number;
|
|
626
|
+
readonly loadPercentage: number;
|
|
627
|
+
readonly isOverloaded: boolean;
|
|
628
|
+
readonly isUnderloaded: boolean;
|
|
629
|
+
readonly capabilities: readonly string[];
|
|
630
|
+
readonly processingRate: number;
|
|
631
|
+
readonly avgClaimDuration: number;
|
|
632
|
+
readonly queueDepth: number;
|
|
633
|
+
readonly healthScore: number;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Claim move during rebalancing
|
|
638
|
+
*/
|
|
639
|
+
export interface ClaimMove {
|
|
640
|
+
readonly claimId: ClaimId;
|
|
641
|
+
readonly fromAgent: AgentId;
|
|
642
|
+
readonly toAgent: AgentId;
|
|
643
|
+
readonly reason: string;
|
|
644
|
+
readonly success: boolean;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* Rebalancing error
|
|
649
|
+
*/
|
|
650
|
+
export interface RebalanceError {
|
|
651
|
+
readonly claimId: ClaimId;
|
|
652
|
+
readonly error: string;
|
|
653
|
+
readonly recoverable: boolean;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Rebalance result per ADR-016
|
|
658
|
+
*/
|
|
659
|
+
export interface ExtendedRebalanceResult {
|
|
660
|
+
readonly success: boolean;
|
|
661
|
+
readonly claimsMoved: number;
|
|
662
|
+
readonly moves: readonly ClaimMove[];
|
|
663
|
+
readonly overloadedAgents: readonly AgentId[];
|
|
664
|
+
readonly underloadedAgents: readonly AgentId[];
|
|
665
|
+
readonly loadBefore: readonly AgentLoadInfo[];
|
|
666
|
+
readonly loadAfter: readonly AgentLoadInfo[];
|
|
667
|
+
readonly durationMs: number;
|
|
668
|
+
readonly errors: readonly RebalanceError[];
|
|
669
|
+
readonly timestamp: number;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Rebalance strategy
|
|
674
|
+
*/
|
|
675
|
+
export type RebalanceStrategy =
|
|
676
|
+
| 'oldest-first' // Move oldest claims first
|
|
677
|
+
| 'newest-first' // Move newest claims first
|
|
678
|
+
| 'lowest-priority' // Move lowest priority claims first
|
|
679
|
+
| 'least-progress' // Move claims with least progress first
|
|
680
|
+
| 'capability-match'; // Move to best capability match
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* Load balancing configuration per ADR-016
|
|
684
|
+
*/
|
|
685
|
+
export interface LoadBalancingConfig {
|
|
686
|
+
readonly enabled: boolean;
|
|
687
|
+
readonly checkIntervalMs: number;
|
|
688
|
+
readonly overloadThreshold: number;
|
|
689
|
+
readonly underloadThreshold: number;
|
|
690
|
+
readonly rebalanceThreshold: number;
|
|
691
|
+
readonly maxMovesPerRebalance: number;
|
|
692
|
+
readonly selectionStrategy: RebalanceStrategy;
|
|
693
|
+
readonly respectCapabilities: boolean;
|
|
694
|
+
readonly cooldownMs: number;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* Default load balancing config
|
|
699
|
+
*/
|
|
700
|
+
export const DEFAULT_LOAD_BALANCING_CONFIG: LoadBalancingConfig = {
|
|
701
|
+
enabled: true,
|
|
702
|
+
checkIntervalMs: 5 * 60 * 1000, // 5 minutes
|
|
703
|
+
overloadThreshold: 90,
|
|
704
|
+
underloadThreshold: 30,
|
|
705
|
+
rebalanceThreshold: 40,
|
|
706
|
+
maxMovesPerRebalance: 5,
|
|
707
|
+
selectionStrategy: 'capability-match',
|
|
708
|
+
respectCapabilities: true,
|
|
709
|
+
cooldownMs: 10 * 60 * 1000, // 10 minutes
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Extended IssueClaim with all ADR-016 properties
|
|
714
|
+
*/
|
|
715
|
+
export interface ExtendedIssueClaim {
|
|
716
|
+
readonly id: ClaimId;
|
|
717
|
+
readonly issueId: IssueId;
|
|
718
|
+
readonly repository: string;
|
|
719
|
+
readonly claimant: ExtendedClaimant;
|
|
720
|
+
readonly status: ExtendedClaimStatus;
|
|
721
|
+
readonly claimedAt: number;
|
|
722
|
+
readonly updatedAt: number;
|
|
723
|
+
readonly startedAt?: number;
|
|
724
|
+
readonly completedAt?: number;
|
|
725
|
+
readonly expiresAt?: number;
|
|
726
|
+
readonly priority: IssuePriority;
|
|
727
|
+
readonly tags: readonly string[];
|
|
728
|
+
readonly progress: number;
|
|
729
|
+
readonly blockedInfo?: BlockedInfo;
|
|
730
|
+
readonly stealableInfo?: ExtendedStealableInfo;
|
|
731
|
+
readonly handoffInfo?: ExtendedHandoffInfo;
|
|
732
|
+
readonly pullRequestId?: number;
|
|
733
|
+
readonly notes: readonly ClaimNote[];
|
|
734
|
+
readonly statusHistory: readonly StatusChange[];
|
|
735
|
+
readonly metadata: Record<string, unknown>;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Claim note
|
|
740
|
+
*/
|
|
741
|
+
export interface ClaimNote {
|
|
742
|
+
readonly id: string;
|
|
743
|
+
readonly content: string;
|
|
744
|
+
readonly authorId: AgentId | UserId;
|
|
745
|
+
readonly createdAt: number;
|
|
746
|
+
readonly type: 'progress' | 'question' | 'blocker' | 'general';
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* Status change record
|
|
751
|
+
*/
|
|
752
|
+
export interface StatusChange {
|
|
753
|
+
readonly fromStatus: ExtendedClaimStatus;
|
|
754
|
+
readonly toStatus: ExtendedClaimStatus;
|
|
755
|
+
readonly changedAt: number;
|
|
756
|
+
readonly changedBy: AgentId | UserId;
|
|
757
|
+
readonly reason?: string;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* Query options for claims
|
|
762
|
+
*/
|
|
763
|
+
export interface ClaimQueryOptions {
|
|
764
|
+
readonly claimantId?: AgentId | UserId;
|
|
765
|
+
readonly claimantType?: ClaimantType;
|
|
766
|
+
readonly status?: ExtendedClaimStatus | readonly ExtendedClaimStatus[];
|
|
767
|
+
readonly repository?: string;
|
|
768
|
+
readonly issueId?: IssueId;
|
|
769
|
+
readonly priority?: IssuePriority | readonly IssuePriority[];
|
|
770
|
+
readonly tags?: readonly string[];
|
|
771
|
+
readonly createdAfter?: number;
|
|
772
|
+
readonly createdBefore?: number;
|
|
773
|
+
readonly updatedAfter?: number;
|
|
774
|
+
readonly stealableOnly?: boolean;
|
|
775
|
+
readonly blockedOnly?: boolean;
|
|
776
|
+
readonly limit?: number;
|
|
777
|
+
readonly offset?: number;
|
|
778
|
+
readonly sortBy?: 'claimedAt' | 'updatedAt' | 'priority' | 'progress';
|
|
779
|
+
readonly sortDirection?: 'asc' | 'desc';
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/**
|
|
783
|
+
* Claim statistics
|
|
784
|
+
*/
|
|
785
|
+
export interface ClaimStatistics {
|
|
786
|
+
readonly totalClaims: number;
|
|
787
|
+
readonly byStatus: Record<ExtendedClaimStatus, number>;
|
|
788
|
+
readonly byPriority: Record<IssuePriority, number>;
|
|
789
|
+
readonly byClaimantType: Record<ClaimantType, number>;
|
|
790
|
+
readonly avgDurationMs: number;
|
|
791
|
+
readonly avgProgress: number;
|
|
792
|
+
readonly activeSteals: number;
|
|
793
|
+
readonly pendingHandoffs: number;
|
|
794
|
+
readonly completedLast24h: number;
|
|
795
|
+
readonly byRepository: Record<string, number>;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* Helper function to generate unique claim IDs
|
|
800
|
+
*/
|
|
801
|
+
export function generateClaimId(): ClaimId {
|
|
802
|
+
return `claim-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* Check if a status is an active claim status
|
|
807
|
+
*/
|
|
808
|
+
export function isActiveClaimStatus(status: ExtendedClaimStatus): boolean {
|
|
809
|
+
return ['active', 'paused', 'blocked', 'handoff-pending', 'review-requested'].includes(status);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* Get valid status transitions per ADR-016
|
|
814
|
+
*/
|
|
815
|
+
export function getValidStatusTransitions(currentStatus: ExtendedClaimStatus): readonly ExtendedClaimStatus[] {
|
|
816
|
+
const transitions: Record<ExtendedClaimStatus, readonly ExtendedClaimStatus[]> = {
|
|
817
|
+
'active': ['paused', 'blocked', 'handoff-pending', 'review-requested', 'stealable', 'completed'],
|
|
818
|
+
'paused': ['active', 'blocked', 'handoff-pending', 'stealable', 'completed'],
|
|
819
|
+
'blocked': ['active', 'paused', 'stealable', 'completed'],
|
|
820
|
+
'handoff-pending': ['active', 'completed'],
|
|
821
|
+
'review-requested': ['active', 'completed', 'blocked'],
|
|
822
|
+
'stealable': ['active', 'completed'],
|
|
823
|
+
'completed': [],
|
|
824
|
+
};
|
|
825
|
+
return transitions[currentStatus];
|
|
826
|
+
}
|