@cueloop/schema 0.1.0-alpha.64 → 0.1.0-alpha.66

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.
@@ -0,0 +1,85 @@
1
+ /**
2
+ * The linear view of a session's active path. Every client reads the artifact
3
+ * text, the working copy, and the comments straight off the record, so the
4
+ * record keeps them materialized; this module recomputes them from the path
5
+ * whenever a tip moves. Comments the path does not reach are shelved, never
6
+ * dropped, so a later move can show them again.
7
+ */
8
+
9
+ import { derivePath, followBranch, MAIN_BRANCH, pathOf, type SessionHistory } from "./history";
10
+ import type { Annotation, ReviewSession } from "./types";
11
+
12
+ export interface PathView {
13
+ /** The last agent revision on the path: what the artifact shows. */
14
+ content: string;
15
+ /** The reviewer's edits over it, when the path's head is a reviewer revision. */
16
+ workingCopy: string | undefined;
17
+ /** The comments open on the path, in path order. */
18
+ annotations: Annotation[];
19
+ /** Every other comment the session knows. */
20
+ shelvedAnnotations: Annotation[];
21
+ }
22
+
23
+ /**
24
+ * Fold the active path over every comment the session holds, on or off the
25
+ * path. Comments on the path whose objects are gone stay gone.
26
+ */
27
+ export function viewOfPath(history: SessionHistory, known: Annotation[]): PathView {
28
+ const derived = derivePath(history);
29
+ const agentHead = pathOf(history)
30
+ .filter((entry) => entry.type === "revision" && entry.by === "agent")
31
+ .at(-1);
32
+ const content = agentHead?.type === "revision" ? agentHead.content : derived.head.content;
33
+ const byId = new Map(known.map((annotation) => [annotation.id, annotation]));
34
+ const annotations: Annotation[] = [];
35
+
36
+ for (const id of derived.annotationIds) {
37
+ const annotation = byId.get(id);
38
+
39
+ if (annotation) {
40
+ annotations.push(annotation);
41
+ byId.delete(id);
42
+ }
43
+ }
44
+
45
+ return {
46
+ content,
47
+ workingCopy: derived.head.content === content ? undefined : derived.head.content,
48
+ annotations,
49
+ shelvedAnnotations: [...byId.values()],
50
+ };
51
+ }
52
+
53
+ /** Write a path view back into a record, in place; absent fields are removed. */
54
+ export function applyPathView(session: ReviewSession, view: PathView): void {
55
+ session.artifact = { ...session.artifact, content: view.content };
56
+ session.annotations = view.annotations;
57
+ if (view.workingCopy === undefined) delete session.workingCopy;
58
+ else session.workingCopy = view.workingCopy;
59
+ if (view.shelvedAnnotations.length === 0) delete session.shelvedAnnotations;
60
+ else session.shelvedAnnotations = view.shelvedAnnotations;
61
+ }
62
+
63
+ /**
64
+ * The record as a share carries it: the followed branch's path and what it
65
+ * shows, with no other branch, no shelved comment, and no working copy of the
66
+ * owner's. A followed branch that no longer exists falls back to main. A record
67
+ * without a history travels as it is.
68
+ */
69
+ export function viewFollowing(session: ReviewSession, branch?: string): ReviewSession {
70
+ const asked = branch ?? session.shareBranch ?? MAIN_BRANCH;
71
+ // a branch that no longer exists falls back to main, matching the daemon
72
+ const followed =
73
+ session.history && session.history.tips[asked] === undefined ? MAIN_BRANCH : asked;
74
+ const shared: ReviewSession = { ...session, shareBranch: followed };
75
+
76
+ if (!session.history) return shared;
77
+ const history = followBranch(session.history, followed);
78
+ const known = [...session.annotations, ...(session.shelvedAnnotations ?? [])];
79
+
80
+ shared.history = history;
81
+ applyPathView(shared, viewOfPath(history, known));
82
+ delete shared.shelvedAnnotations;
83
+
84
+ return shared;
85
+ }
package/src/types.ts CHANGED
@@ -1,15 +1,21 @@
1
1
  /**
2
2
  * The ReviewSession primitive. Everything in cueloop renders, annotates, or
3
3
  * resolves this one noun. This module is pure data shapes - no IO, no
4
- * dependencies.
4
+ * dependencies beyond the history shapes.
5
5
  */
6
6
 
7
+ import type { SessionHistory } from "./history";
8
+
7
9
  export const SCHEMA_VERSION = "1";
8
10
 
9
11
  /** A workspace is a repo/branch context holding review sessions. */
10
12
  export interface WorkspaceKey {
11
13
  repoRoot: string;
12
14
  branch: string;
15
+ /** Earliest root commit SHA; the project key that survives moving or re-cloning the repo. Absent for a standalone thread. */
16
+ rootCommit?: string;
17
+ /** Origin remote URL when present; for display and repair only, never the project key. */
18
+ remote?: string;
13
19
  }
14
20
 
15
21
  /**
@@ -61,6 +67,17 @@ export interface ArtifactMeta {
61
67
  /** How a file changed, so curation emits the right create/delete headers. */
62
68
  export type DiffFileStatus = "added" | "modified" | "deleted";
63
69
 
70
+ /**
71
+ * One reject decision of a diff review: a whole hunk, or one change block of
72
+ * it when `changeIndex` is set. The daemon curates the working copy from the
73
+ * full set, so every client sees the same patch.
74
+ */
75
+ export interface HunkRejection {
76
+ path: string;
77
+ hunkIndex: number;
78
+ changeIndex?: number;
79
+ }
80
+
64
81
  export interface DiffFileContents {
65
82
  path: string;
66
83
  oldContents: string;
@@ -91,9 +108,11 @@ export interface Anchor {
91
108
  quote: string;
92
109
  prefix: string;
93
110
  suffix: string;
94
- /** Index of the block the anchor was made in (hint). */
111
+ /** Index of the block the anchor starts in (hint). */
95
112
  blockIndex?: number;
96
- /** Character offsets within that block's text (hint). */
113
+ /** Last block of a quote that spans blocks (hint); absent for one block. */
114
+ endBlockIndex?: number;
115
+ /** Character offsets: `start` within the first block, `end` within the last (hint). */
97
116
  start?: number;
98
117
  end?: number;
99
118
  /** Prototype anchors: the CSS selector of the annotated element (authority). */
@@ -127,6 +146,11 @@ export interface Annotation {
127
146
  * planner can tell whose note is whose and never overwrite a collaborator's.
128
147
  */
129
148
  author?: string;
149
+ /**
150
+ * The root comment this one replies to. Absent on a root. A reply shares its
151
+ * root's anchor, so a discussion stays one conversation when the text moves.
152
+ */
153
+ replyTo?: string;
130
154
  /**
131
155
  * Set when a revision addressed this annotation: the agent reported the id
132
156
  * on resubmit ("agent"), or the quoted text disappeared from the revised
@@ -190,6 +214,15 @@ export interface ReviewSession {
190
214
  /** Revision history; artifact.content always equals the latest revision. */
191
215
  revisions: Revision[];
192
216
  annotations: Annotation[];
217
+ /**
218
+ * The session's history as a tree of entries with named branches; the
219
+ * artifact text and the open comments derive from the active path. Absent
220
+ * only on records written before histories existed; the store migrates
221
+ * those on read.
222
+ */
223
+ history?: SessionHistory;
224
+ /** A diff review's reject decisions; the working copy is the patch they leave. */
225
+ curation?: HunkRejection[];
193
226
  /**
194
227
  * The reviewer's working copy of the artifact source (plan edits).
195
228
  * Serializes as ONE unified diff against the submitted revision.
@@ -205,8 +238,18 @@ export interface ReviewSession {
205
238
  verdict: Verdict | null;
206
239
  status: SessionStatus;
207
240
  createdAt: string;
241
+ /**
242
+ * Comments off the active path - removed, or made on a segment a tip moved
243
+ * away from. Nothing is deleted: a navigate or switch that brings their
244
+ * entries back onto the path shows them again.
245
+ */
246
+ shelvedAnnotations?: Annotation[];
247
+ /** The session this one was forked from. */
248
+ parentSessionId?: string;
208
249
  /** Share id once published; lets the planner pull collaborator notes back. */
209
250
  shareId?: string;
251
+ /** The branch the share follows and shows collaborators; `main` when absent. */
252
+ shareBranch?: string;
210
253
  /** SSH fingerprint that created the share; the gateway stamps it to gate pulls. */
211
254
  owner?: string;
212
255
  /**
@@ -41,6 +41,20 @@ function signature(text: string): string {
41
41
  .join("\0");
42
42
  }
43
43
 
44
+ /**
45
+ * Whether a base block is missing from the working source: the working copy
46
+ * holds fewer blocks with its kind and text than the base does. Only such a
47
+ * block can be restored without duplicating content.
48
+ */
49
+ export function isBlockCut(base: string, working: string, block: Block): boolean {
50
+ const matches = (text: string) =>
51
+ parseBlocks(text).filter(
52
+ (candidate) => candidate.kind === block.kind && candidate.text === block.text,
53
+ ).length;
54
+
55
+ return matches(working) < matches(base);
56
+ }
57
+
44
58
  /**
45
59
  * Re-insert a cut base block into the working source before `beforeLine`.
46
60
  * Returns undefined when the block structure round-trips to the base content