Skip to content

Commit 957c2ad

Browse files
committed
feat(tickets): finalize normalization core flow
1 parent d166be1 commit 957c2ad

7 files changed

Lines changed: 1956 additions & 1227 deletions

File tree

docs/guides/tickets.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,8 @@ In your project repo:
144144
Portability rules:
145145

146146
- Only the journal under `.hack/tickets/events/` is portable ticket state.
147+
- The hidden ref stores only the journal tree; it does not include `projection.sqlite` or other local cache files.
148+
- After `hack x tickets sync`, the checked-out tickets worktree materializes that journal under `.hack/tickets/git/worktree/.hack/tickets/events/`.
147149
- The SQLite projection is local-only and can be deleted safely.
148150
- After sync or clone, peers rebuild `.hack/tickets/projection.sqlite` from the journal on first read.
149151

src/control-plane/extensions/linear/commands.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2907,9 +2907,15 @@ type LinearDeliverySummary = {
29072907

29082908
function resolveTicketAuthority(input: {
29092909
readonly ticket: TicketSummary;
2910-
}): "hack" | "linear" {
2910+
}): "hack" | "linear" | "review_required" {
29112911
const owner = input.ticket.owner.trim().toLowerCase();
29122912
const source = input.ticket.source.trim().toLowerCase();
2913+
if (
2914+
(owner === "linear" && source !== "linear") ||
2915+
(source === "linear" && owner !== "linear")
2916+
) {
2917+
return "review_required";
2918+
}
29132919
if (owner === "linear" || source === "linear") {
29142920
return "linear";
29152921
}
@@ -2947,7 +2953,7 @@ function normalizeProjectValue(input: {
29472953
}
29482954

29492955
function detectAuthoritativeFieldConflicts(input: {
2950-
readonly authority: "hack" | "linear";
2956+
readonly authority: "hack" | "linear" | "review_required";
29512957
readonly ticket: TicketSummary;
29522958
readonly issue: LinearIssue;
29532959
readonly remoteProjection: Pick<
@@ -2956,13 +2962,15 @@ function detectAuthoritativeFieldConflicts(input: {
29562962
>;
29572963
}): readonly RecordedSyncConflict[] {
29582964
const conflicts: RecordedSyncConflict[] = [];
2965+
const authorityLabel =
2966+
input.authority === "review_required" ? "review-required" : input.authority;
29592967
const localTitle = input.ticket.title.trim();
29602968
const remoteTitle = input.issue.title.trim();
29612969
if (localTitle !== remoteTitle) {
29622970
conflicts.push({
29632971
field: "title",
29642972
authority: input.authority,
2965-
summary: `Authoritative ${input.authority} title diverged from the other side.`,
2973+
summary: `Authoritative ${authorityLabel} title diverged from the other side.`,
29662974
localValue: localTitle,
29672975
remoteValue: remoteTitle,
29682976
});
@@ -2978,7 +2986,7 @@ function detectAuthoritativeFieldConflicts(input: {
29782986
conflicts.push({
29792987
field: "body",
29802988
authority: input.authority,
2981-
summary: `Authoritative ${input.authority} body diverged from the other side.`,
2989+
summary: `Authoritative ${authorityLabel} body diverged from the other side.`,
29822990
localValue: localBody,
29832991
remoteValue: remoteBody,
29842992
});
@@ -2988,7 +2996,7 @@ function detectAuthoritativeFieldConflicts(input: {
29882996
conflicts.push({
29892997
field: "status",
29902998
authority: input.authority,
2991-
summary: `Authoritative ${input.authority} status diverged from the other side.`,
2999+
summary: `Authoritative ${authorityLabel} status diverged from the other side.`,
29923000
localValue: input.ticket.status,
29933001
remoteValue: input.remoteProjection.status,
29943002
});
@@ -3009,7 +3017,7 @@ function detectAuthoritativeFieldConflicts(input: {
30093017
conflicts.push({
30103018
field: "project",
30113019
authority: input.authority,
3012-
summary: `Authoritative ${input.authority} project routing diverged from the other side.`,
3020+
summary: `Authoritative ${authorityLabel} project routing diverged from the other side.`,
30133021
...(localProject !== undefined ? { localValue: localProject } : {}),
30143022
...(remoteProject !== undefined ? { remoteValue: remoteProject } : {}),
30153023
});
@@ -4792,7 +4800,7 @@ async function applyLinearIssueToExistingTicket(input: {
47924800
readonly existingTicket: TicketSummary;
47934801
readonly projection: TicketProjectionFromLinearIssue;
47944802
readonly syncToggles: SyncToggles;
4795-
readonly authority: "hack" | "linear";
4803+
readonly authority: "hack" | "linear" | "review_required";
47964804
readonly conflictsRecorded: number;
47974805
}): Promise<
47984806
SyncTicketFromLinearSuccess | { readonly ok: false; readonly error: string }

0 commit comments

Comments
 (0)