דלגו לתוכן

מחזור חיי כרטיס

סטטוס מחרוזת אחסון תיאור
backlog backlog נלכד, אך טרם עבר טריאז’ אל התור הפעיל
open open עבר טריאז’ ומוכן לעבודה, אך טרם התחיל
inProgress inProgress בעבודה פעילה
blocked blocked התחיל אך חסום בגלל משהו חיצוני
inReview inReview העבודה הושלמה, ממתין לסקירה
done done הסתיים בהצלחה (מצב סיום)
failed failed הסתיים בשגיאה (מצב סיום)
cancelled cancelled נזנח (מצב סיום)

ערכי סטטוס נשמרים ב-camelCase (inProgress, inReview), ולעולם לא in_progress. ערך null שמור מפוענח כ-open; ערך לא מוכר זורק שגיאה במקום לכפות המרה.

tryParseLoose מקבל כינויים שמספקים סוכנים, תוך התעלמות מאותיות רישיות ומהתווים - / _ / רווח:

סטטוס כינויים מקובלים
open todo, ready, pending, new
inProgress doing, started, active, wip
blocked stuck
inReview review, reviewing
done closed, complete, completed, finished
failed error, errored
cancelled canceled, abandoned, dropped
backlog ──→ open ──→ inProgress ──→ inReview ──→ done
│ │ │
│ └──→ failed ┘
└──→ blocked ──→ inProgress
Any non-terminal status ──→ cancelled; inReview ──→ failed

הגרף הזה מחייב סוכנים ואוטומציה בלבד. TicketWorkflowService.transitionStatus בודק אותו כאשר force הוא false — כלי MCP ומנגנוני היישור. מעבר לא חוקי שם נרשם ביומן ומתעלמים ממנו. כל שינוי סטטוס שנעשה בידי משתמש בממשק עובר עם force: true ועוקף את הגרף לחלוטין, כך שאדם רשאי להעביר כרטיס לכל סטטוס שהבורר מציג (כל סטטוס מלבד failed), כולל פתיחה מחדש של כרטיס במצב סיום. סוכנים מסמנים failed עם fail_ticket.

סטטוס רשאי תמיד לעבור לעצמו. למצבי סיום אין קצוות יוצאים.

מ- יעדים מותרים
backlog open, cancelled
open inProgress, blocked, cancelled
inProgress blocked, inReview, done, failed, cancelled
blocked inProgress, cancelled
inReview inProgress, done, failed, cancelled
done — (מצב סיום)
failed — (מצב סיום)
cancelled — (מצב סיום)

נשמרת כמספר שלם לפי הסקאלה המקורית של Linear, 0–4. ערך לא מוכר מפוענח כ-none.

עדיפות נשמר כ-
none 0
urgent 1
high 2
medium 3
low 4

ערך שמור לא מוכר או null מפוענח כ-local.

ספק סטטוס
local נשמר כולו במסד הנתונים של סביבת העבודה; ברירת המחדל
linear בבעלות מרוחקת, משוקף מקומית

מתאמי סנכרון יושבים תחת packages/cc_infra/lib/src/tickets/sync/ עבור Linear, GitHub Issues, Jira Cloud ו-ClickUp. GitHub Issues הוא ספק של מתאם סנכרון (vendorId: github) בלבד — הוא אינו ערך של TicketProvider, ולכן סוגיה שנמשכה מ-GitHub נשמרת עם originKind: externalSync וברירת המחדל provider: local. מיפוי הספקים יושב ב-ticket_sync_links.

createTicket כותב manual. משיכה מספק כותבת externalSync. ילדים מואצלים נשארים manual.

ערך משמעות
manual ברירת המחדל ב-createTicket (שורות של אדם, סוכן וילדים מואצלים)
externalSync נקבע בידי TicketSyncEngine במשיכה מספק

האגרגט משתרע על שני תחומים בשורה אחת. השיקוף (ספק, מפתחות, כותרת, תיאור, עדיפות, תוויות, סטטוסים, חותמות זמן) הוא מטמון לספק מרוחק ונכתב מחדש כולו ברענון. השכבת־על (הקצאה, האצלה, מרחב, הורה, פרויקט, בקשות משיכה מקושרות) קיימת רק ב-Control Center, ורענון לעולם אינו נוגע בה.

שדה סוג תיאור
id String UUID v4
workspaceId String סביבת העבודה שאליה הכרטיס שייך
provider TicketProvider הבקאנד שמחזיק בנתונים הקנוניים (ברירת מחדל local)
externalKey String? מפתח מקורי של הספק (למשל LIN-123). createTicket מקומי כותב כאן UUID נפרד, ולא את ה-id של השורה
url String? כתובת URL באינטרנט אצל הספק המרוחק
title String תקציר קצר; אסור שיהיה ריק
description String? גוף Markdown ארוך יותר
priority TicketPriority ברירת מחדל none
labels List<String> תוויות חופשיות
status TicketStatus סטטוס קנוני ומנורמל
rawStatus String? שם המצב המקורי של הספק המרוחק, נשמר לתצוגה ללא אובדן
externalSyncedAt DateTime? הסנכרון הרב-ספקי האחרון; null עד לסנכרון הראשון. מיפוי לפי ספק יושב ב-ticket_sync_links
parentTicketId String? ההורה בעץ ההאצלה / הפירוק
projectId String? הפרויקט שאליו הכרטיס שייך; לעולם אינו נדחף למרוחק
assignedAgentId String? ה-id של הישות הראשית שהוקצתה (מטא-נתונים בלבד — הקצאה אינה משגרת דבר)
assigneeType PrincipalType איזה סוג של ישות ראשית assignedAgentId מציין — user או agent (ברירת מחדל agent)
createdByType PrincipalType? סוג הישות הראשית שיצרה את הכרטיס (user או agent); null בשורות מדור קודם או מערכת
createdById String? ה-id של הישות הראשית שיצרה אותו
assignedTeamId String? הצוות שהוקצה (מטא-נתונים בלבד)
delegatedByAgentId String? הסוכן שהאציל את הכרטיס הזה
delegationDepth int העומק בשרשרת ההאצלה; השורש הוא 0, ונבדק מול תקרת העומק המרבית
delegationRootTicketId String? השורש של שרשרת ההאצלה; null בכרטיס שורש
spaceId String? השיחה שהכרטיס נוצר ממנה, או המרחב של הסוכן המאציל
errorMessage String? נקבע כאשר status הוא failed
linkedPrIds List<String> מזהי הצמתים של בקשות המשיכה שאליהן הכרטיס מקושר
metadata Map<String, dynamic> שק חופשי
createdAt DateTime זמן היצירה
startedAt DateTime? מתי העבודה התחילה
blockedAt DateTime? מתי הכרטיס נחסם
cancelledAt DateTime? מתי הכרטיס בוטל
completedAt DateTime? מתי הכרטיס הושלם בהצלחה
finishedAt DateTime? מתי הכרטיס הגיע למצב סיום כלשהו
updatedAt DateTime השינוי האחרון (רענון שיקוף או שינוי בשכבת־על)
version int מונה מקביליות אופטימיסטית, שמתקדם בכל שינוי
originKind TicketOriginKind איך הכרטיס נוצר (ברירת מחדל manual)
collaborators List<TicketCollaborator> נטען בידי המאגר לפי דרישה

נגזרים: isTerminal, isRemote (provider != local), isAssignedToUser ו-displayKey (externalKey ?? id — כרטיסים מקומיים מציגים אפוא את ה-UUID הנפרד מזמן היצירה, ולא את ה-id של השורה).

הקצוות השמורים הם שורות כיווניות ב-ticket_links:

סוג מחרוזת אחסון משמעות
blocks blocks המקור חוסם את היעד
relatesTo relates_to קשר סימטרי
duplicateOf duplicate_of המקור הוא כפיל של היעד

אוצר המילים שבהם מדברים הממשק והכלי ticket_relation הוא TicketRelationKind, שנגזר מקצה שמור ומהצד שבו יושב הכרטיס הנדון:

סוג נגזר מ-
blockedBy שורת blocks שבה הכרטיס הנדון הוא היעד
blocking שורת blocks שבה הכרטיס הנדון הוא המקור
relatedTo שורת relates_to (סימטרי)
duplicateOf שורת duplicate_of שבה הכרטיס הנדון הוא המקור
duplicatedBy שורת duplicate_of שבה הכרטיס הנדון הוא היעד
subIssueOf tickets.parent_ticket_id של הכרטיס הנדון
parentOf tickets.parent_ticket_id של הכרטיס האחר

קשרי הורה ותת־סוגיה אינם שורות ב-ticket_links — הם יושבים על tickets.parent_ticket_id.

שמונת המחלקות האלה יושבות ב-packages/cc_domain/lib/core/domain/events/ticketing_events.dart. אין TicketStarted, TicketDelegated, TicketCollaboratorAdded או ExternalTicketWebhookReceived.

אירוע נפלט כאשר
TicketCreated כרטיס נוצר
TicketAssigned כרטיס מוקצה לישות ראשית או לצוות
TicketCompleted העבודה מסתיימת בהצלחה
TicketFailed העבודה נכשלת
TicketCancelled כרטיס מבוטל
TicketStatusChanged כל שינוי סטטוס
TicketReassigned כרטיס מוקצה מחדש
TicketDetailsUpdated כותרת, תיאור, עדיפות או תוויות משתנים

TicketAssigned הוא אות ביקורת והתראה וגם טריגר לפייפליין. הקצאה לסוכן אינה מפעילה שום ריצה — אין משגר כרטיסים שצופה ב-assignedAgentId. הקצאה לצוות היא החריג: TeamRoutingService משגר את מוביל הצוות. הוספת משתף פעולה כותבת שורה ואינה מפרסמת דבר. webhook מספק מיושם דרך TicketSyncEngine בלי אירוע תחום.

נקודת החנק של השינויים

קטע בשם “נקודת החנק של השינויים”

TicketWorkflowService מחזיק בכל שינוי.

מנגנון פירוט
נקודת חנק אחת כל השינויים עוברים דרך _mutate, שטוען את השורה וקורא ל-_assertWorkspace לפני היישום
בידוד סביבות עבודה _assertWorkspace זורק WorkspaceMismatchException כאשר ה-workspaceId של השורה שנטענה אינו תואם לזה של הקורא
מקביליות אופטימיסטית _mutate כותב עם expectedVersion שמוגדר ל-version שהוא קרא זה עתה, ומנסה שוב בעת ConcurrencyConflictException על ידי קריאה מחדש של מצב עדכני
תפיסה בלעדית tryCheckout / releaseCheckout; סוכן שני שתופס כרטיס תפוס מקבל CheckoutConflictException
שומר מעברים canTransitionTo, מיושם רק כאשר force הוא false